Files
NightSpire/web-next/components/ArmoryRaids.tsx
T
Inna beba7301a1 Armería: PvP campos de batalla, logros por categoría, bandas/mazmorras y página de hermandad
- PvP: pestaña Campos de batalla (pvpstats) con resultado, stats por partida y fecha/hora.
- Logros: rejilla por categoría con anillos de progreso + detalle por categoría
  (datos de Achievement.db2 + Achievement_Category.db2, iconos vía wowhead).
- Bandas: progreso por dificultad (10/25/heroico) con jefes reales de DungeonEncounter.db2
  y tooltip de jefes; agrupado por expansión (Clásico/TBC/WotLK).
- Mazmorras: mismo sistema, Normal + Heroico, Clásico/TBC/WotLK.
- Página de hermandad estilo armería: cabecera (emblema, puntos = unión de logros de
  miembros sin duplicar, nº miembros, fundada), pestañas Hermandad/Logros, roster con
  iconos de clase/raza, columna Función (rol por spec), filtros clase/función,
  orden por rango/puntos/nivel de objeto/nivel/nombre, paginación.
- Barra lateral: mensaje diario con login (iron-session, gated por pertenencia),
  logros recientes y desgloses de clases y funciones.
- Cabecera de personaje: enlace a la hermandad + nombre del reino.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 01:18:30 +00:00

121 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export type RaidBossState = { name: string; killed: boolean }
export type RaidDifficulty = { diff: number; label: string; killed: number; total: number; bosses: RaidBossState[] }
export type RaidProgressItem = { map: number; name: string; exp: number; total: number; difficulties: RaidDifficulty[] }
/** Gradiente de cabecera por banda (sin arte del cliente). */
const RAID_THEME: Record<number, string> = {
// Classic
409: 'linear-gradient(135deg, #8a3a1a, #2e1208)',
469: 'linear-gradient(135deg, #7a2a2a, #250d0d)',
531: 'linear-gradient(135deg, #8a7a2a, #2a2408)',
509: 'linear-gradient(135deg, #6b7a2a, #202808)',
309: 'linear-gradient(135deg, #2f7a5a, #0d281c)',
// TBC
532: 'linear-gradient(135deg, #4a2f6b, #1a1028)',
565: 'linear-gradient(135deg, #6b3a2f, #281410)',
544: 'linear-gradient(135deg, #7a2f4a, #2a1018)',
548: 'linear-gradient(135deg, #2a6b6b, #0d2828)',
550: 'linear-gradient(135deg, #3a5c8a, #101f2f)',
534: 'linear-gradient(135deg, #2f6b46, #0d281a)',
564: 'linear-gradient(135deg, #5a2f6b, #1c1028)',
580: 'linear-gradient(135deg, #8a7a2f, #2a2410)',
568: 'linear-gradient(135deg, #6b5a2a, #282008)',
// WotLK
249: 'linear-gradient(135deg, #7a2d1a, #3a1710)',
533: 'linear-gradient(135deg, #2f6b46, #10281a)',
616: 'linear-gradient(135deg, #2a5c8a, #10202f)',
615: 'linear-gradient(135deg, #4a2f6b, #1a1028)',
624: 'linear-gradient(135deg, #6b5a2f, #282010)',
603: 'linear-gradient(135deg, #35566b, #101c28)',
649: 'linear-gradient(135deg, #6b4a2f, #281a10)',
631: 'linear-gradient(135deg, #3a6b8a, #0f2230)',
724: 'linear-gradient(135deg, #7a1f2f, #2a0f14)',
}
function barClass(killed: number, total: number) {
if (total > 0 && killed >= total) return 'full'
if (killed > 0) return 'partial'
return 'empty'
}
/** Gradiente determinista por mapId cuando no hay tema fijo (mazmorras). */
function themeFor(map: number) {
if (RAID_THEME[map]) return RAID_THEME[map]
const h = (map * 47) % 360
return `linear-gradient(135deg, hsl(${h} 42% 30%), hsl(${h} 48% 11%))`
}
export function ArmoryRaids({
locale,
raids,
labels,
}: {
locale: string
raids: RaidProgressItem[]
labels: { bosses: string; empty: string; classic: string; tbc: string; wotlk: string }
}) {
if (raids.length === 0) {
return (
<div className="armory-panel armory-raids">
<p className="armory-hint">{labels.empty}</p>
</div>
)
}
const groups: { exp: number; title: string; items: RaidProgressItem[] }[] = [
{ exp: 2, title: labels.wotlk, items: raids.filter((r) => r.exp === 2) },
{ exp: 1, title: labels.tbc, items: raids.filter((r) => r.exp === 1) },
{ exp: 0, title: labels.classic, items: raids.filter((r) => r.exp === 0) },
].filter((g) => g.items.length > 0)
return (
<div className="armory-panel armory-raids">
{groups.map((g) => (
<section className="armory-raid-exp" key={g.exp}>
<h3 className="armory-raid-exp-title">{g.title}</h3>
<div className="armory-raid-grid">
{g.items.map((r) => (
<div className="armory-raid-card" key={r.map}>
<div className="armory-raid-banner" style={{ background: themeFor(r.map) }}>
<span className="armory-raid-name">{r.name}</span>
<span className="armory-raid-count">
{r.total} {labels.bosses}
</span>
</div>
<div className="armory-raid-diffs">
{r.difficulties.map((d) => (
<div className="armory-raid-row" key={d.diff}>
<span className="armory-raid-diff-lbl">{d.label}</span>
<div className={`armory-raid-bar ${barClass(d.killed, d.total)}`}>
<div
className="armory-raid-bar-fill"
style={{ width: `${d.total ? Math.round((d.killed / d.total) * 100) : 0}%` }}
/>
<span className="armory-raid-bar-txt">
{d.killed}/{d.total}
</span>
</div>
<div className="armory-raid-tip" role="tooltip">
<div className="armory-raid-tip-head">
{r.name} · {d.label}
</div>
<ul>
{d.bosses.map((b, i) => (
<li key={i} className={b.killed ? 'killed' : 'alive'}>
<span className="armory-raid-tip-x">{b.killed ? '1' : '0'} ×</span> {b.name}
</li>
))}
</ul>
</div>
</div>
))}
</div>
</div>
))}
</div>
</section>
))}
</div>
)
}