beba7301a1
- 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>
119 lines
3.6 KiB
TypeScript
119 lines
3.6 KiB
TypeScript
'use client'
|
||
|
||
import { useState } from 'react'
|
||
|
||
export type ReputationEntry = {
|
||
faction: number
|
||
name: string
|
||
standing: number
|
||
rank: string
|
||
rankIndex: number
|
||
color: string
|
||
cur: number
|
||
max: number
|
||
}
|
||
|
||
/** Pestaña de Reputaciones: búsqueda en tiempo real + paginación + barra de rango/progreso. */
|
||
export function ArmoryReputations({
|
||
reputations,
|
||
pageSize,
|
||
labels,
|
||
}: {
|
||
reputations: ReputationEntry[]
|
||
pageSize: number
|
||
labels: { title: string; search: string; empty: string }
|
||
}) {
|
||
const [q, setQ] = useState('')
|
||
const [page, setPage] = useState(1)
|
||
const query = q.trim().toLowerCase()
|
||
const filtered = query ? reputations.filter((r) => r.name.toLowerCase().includes(query)) : reputations
|
||
const pages = Math.max(1, Math.ceil(filtered.length / pageSize))
|
||
const p = Math.min(page, pages)
|
||
const pageItems = filtered.slice((p - 1) * pageSize, (p - 1) * pageSize + pageSize)
|
||
|
||
const win = 2
|
||
const from = Math.max(1, p - win)
|
||
const to = Math.min(pages, p + win)
|
||
const nums: number[] = []
|
||
for (let i = from; i <= to; i++) nums.push(i)
|
||
|
||
return (
|
||
<div className="armory-panel armory-reps-panel">
|
||
<div className="armory-quest-title">
|
||
<i className="fas fa-handshake" /> {labels.title}{' '}
|
||
<span className="armory-quest-title-count">({reputations.length})</span>
|
||
</div>
|
||
|
||
<input
|
||
className="armory-ach-search"
|
||
value={q}
|
||
onChange={(e) => {
|
||
setQ(e.target.value)
|
||
setPage(1)
|
||
}}
|
||
placeholder={labels.search}
|
||
/>
|
||
|
||
{filtered.length === 0 ? (
|
||
<p className="armory-hint">{labels.empty}</p>
|
||
) : (
|
||
<div className="armory-reps">
|
||
{pageItems.map((r) => (
|
||
<div className="armory-rep" key={r.faction}>
|
||
<div className="armory-rep-head">
|
||
<span className="armory-rep-name">{r.name}</span>
|
||
<span className="armory-rep-standing">
|
||
<span className="armory-rep-rank" style={{ color: r.color }}>
|
||
{r.rank}
|
||
</span>
|
||
{r.rankIndex < 7 ? (
|
||
<span className="armory-rep-nums">
|
||
{' '}
|
||
{r.cur.toLocaleString()} / {r.max.toLocaleString()}
|
||
</span>
|
||
) : null}
|
||
</span>
|
||
</div>
|
||
<div className="armory-rep-bar">
|
||
<span style={{ width: `${Math.round((r.cur / Math.max(1, r.max)) * 100)}%`, background: r.color }} />
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
{pages > 1 && (
|
||
<div className="armory-pager">
|
||
<button onClick={() => setPage(p - 1)} disabled={p <= 1} type="button" aria-label="Anterior">
|
||
‹
|
||
</button>
|
||
{from > 1 && (
|
||
<>
|
||
<button onClick={() => setPage(1)} type="button">
|
||
1
|
||
</button>
|
||
{from > 2 && <span className="armory-pager-dots">…</span>}
|
||
</>
|
||
)}
|
||
{nums.map((n) => (
|
||
<button key={n} className={n === p ? 'active' : ''} onClick={() => setPage(n)} type="button">
|
||
{n}
|
||
</button>
|
||
))}
|
||
{to < pages && (
|
||
<>
|
||
{to < pages - 1 && <span className="armory-pager-dots">…</span>}
|
||
<button onClick={() => setPage(pages)} type="button">
|
||
{pages}
|
||
</button>
|
||
</>
|
||
)}
|
||
<button onClick={() => setPage(p + 1)} disabled={p >= pages} type="button" aria-label="Siguiente">
|
||
›
|
||
</button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|