feat: Themes added, 4 themes available (3 dark, 1 light)

This commit is contained in:
2026-07-06 09:08:47 +02:00
parent fafa0c22ab
commit 06183bd5d4
43 changed files with 982 additions and 457 deletions
+20 -20
View File
@@ -36,9 +36,9 @@ function cellStatus(r: AwardRef, band: string): CellStatus {
return 'none';
}
const CELL_STYLE: Record<CellStatus, string> = {
validated: 'bg-emerald-500 text-white',
confirmed: 'bg-amber-400 text-amber-950',
worked: 'bg-stone-400 text-white',
validated: 'bg-success text-success-foreground',
confirmed: 'bg-warning text-warning-foreground',
worked: 'bg-muted-foreground text-background',
none: '',
};
const CELL_LABEL: Record<CellStatus, string> = { validated: 'V', confirmed: 'C', worked: 'W', none: '' };
@@ -53,8 +53,8 @@ function ProgressBar({ worked, confirmed, total }: { worked: number; confirmed:
if (total <= 0) return null;
return (
<div className="h-1.5 w-full rounded-full bg-muted overflow-hidden flex">
<div className="bg-emerald-500" style={{ width: `${pct(confirmed, total)}%` }} />
<div className="bg-amber-400/70" style={{ width: `${pct(worked - confirmed, total)}%` }} />
<div className="bg-success" style={{ width: `${pct(confirmed, total)}%` }} />
<div className="bg-warning/70" style={{ width: `${pct(worked - confirmed, total)}%` }} />
</div>
);
}
@@ -255,7 +255,7 @@ export function AwardsPanel({ onEditQSO }: { onEditQSO?: (id: number) => void }
<span className="font-semibold text-sm">{a.code}</span>
{r ? (
<span className="text-[11px] font-mono text-muted-foreground">
<span className="text-emerald-600">{r.confirmed}</span>
<span className="text-success">{r.confirmed}</span>
/<span className="text-foreground">{r.worked}</span>
{r.total > 0 && <span className="text-muted-foreground/70"> {t('awp.of')} {r.total}</span>}
</span>
@@ -286,8 +286,8 @@ export function AwardsPanel({ onEditQSO }: { onEditQSO?: (id: number) => void }
</div>
<div className="mt-1 flex items-center gap-4 text-sm">
<span><span className="font-bold text-foreground">{current.worked}</span> <span className="text-muted-foreground">{t('awp.worked')}</span></span>
<span><span className="font-bold text-emerald-600">{current.confirmed}</span> <span className="text-muted-foreground">{t('awp.confirmed')}</span></span>
<span><span className="font-bold text-sky-600">{current.validated}</span> <span className="text-muted-foreground">{t('awp.validated')}</span></span>
<span><span className="font-bold text-success">{current.confirmed}</span> <span className="text-muted-foreground">{t('awp.confirmed')}</span></span>
<span><span className="font-bold text-info">{current.validated}</span> <span className="text-muted-foreground">{t('awp.validated')}</span></span>
{current.total > 0 && (
<span className="text-muted-foreground">{t('awp.ofConfirmed', { total: current.total, pct: pct(current.confirmed, current.total) })}</span>
)}
@@ -303,7 +303,7 @@ export function AwardsPanel({ onEditQSO }: { onEditQSO?: (id: number) => void }
{(current.bands ?? []).map((b) => (
<div key={b.band} className="rounded-md border border-border bg-card px-2.5 py-1 text-sm">
<span className="font-mono font-semibold">{b.band}</span>{' '}
<span className="text-emerald-600 font-mono">{b.confirmed}</span>
<span className="text-success font-mono">{b.confirmed}</span>
<span className="text-muted-foreground font-mono">/{b.worked}</span>
</div>
))}
@@ -328,7 +328,7 @@ export function AwardsPanel({ onEditQSO }: { onEditQSO?: (id: number) => void }
<span className="text-xs text-muted-foreground">{filteredRefs.length} {t('awp.refs')}</span>
<button
onClick={() => setShowMissing(true)}
className="flex items-center gap-1 text-xs text-amber-700 hover:text-amber-900 border border-amber-300 bg-amber-50 rounded px-2 py-1"
className="flex items-center gap-1 text-xs text-warning-muted-foreground hover:text-warning border border-warning-border bg-warning-muted rounded px-2 py-1"
title={t('awp.missingRefsTitle')}
>
<AlertTriangle className="size-3" /> {t('awp.missingRefs')}
@@ -336,9 +336,9 @@ export function AwardsPanel({ onEditQSO }: { onEditQSO?: (id: number) => void }
<div className="flex-1" />
{/* Legend */}
<div className="flex items-center gap-2 text-[10px] text-muted-foreground">
<span className="inline-flex items-center gap-1"><span className="size-3 rounded-sm bg-stone-400" />W</span>
<span className="inline-flex items-center gap-1"><span className="size-3 rounded-sm bg-amber-400" />C</span>
<span className="inline-flex items-center gap-1"><span className="size-3 rounded-sm bg-emerald-500" />V</span>
<span className="inline-flex items-center gap-1"><span className="size-3 rounded-sm bg-muted-foreground" />W</span>
<span className="inline-flex items-center gap-1"><span className="size-3 rounded-sm bg-warning" />C</span>
<span className="inline-flex items-center gap-1"><span className="size-3 rounded-sm bg-success" />V</span>
</div>
<div className="flex items-center rounded-md border border-border overflow-hidden">
<button className={cn('px-1.5 py-1', view === 'grid' ? 'bg-accent' : 'hover:bg-accent/50')} title={t('awp.gridView')} onClick={() => setView('grid')}><Grid3x3 className="size-3.5" /></button>
@@ -370,7 +370,7 @@ export function AwardsPanel({ onEditQSO }: { onEditQSO?: (id: number) => void }
<td className={cn('sticky left-0 bg-card py-1 pr-3 border-b border-border/30 whitespace-nowrap',
isGroupStart ? 'font-semibold text-foreground' : 'text-muted-foreground')}>{row.label}</td>
{statsBandIdx.map((i) => { const c = row.cells[i] ?? 0; return (
<td key={i} className={cn('text-center py-1 border-b border-l border-border/20 font-mono', c > 0 ? 'bg-emerald-500/15 text-emerald-700' : 'text-muted-foreground/30')}>{c || ''}</td>
<td key={i} className={cn('text-center py-1 border-b border-l border-border/20 font-mono', c > 0 ? 'bg-success/15 text-success' : 'text-muted-foreground/30')}>{c || ''}</td>
); })}
<td className="text-center py-1 px-2 border-b border-l border-border font-mono font-semibold">{row.total || ''}</td>
<td className="text-center py-1 px-2 border-b border-l border-border/20 font-mono text-muted-foreground">{row.grand_total || ''}</td>
@@ -439,13 +439,13 @@ export function AwardsPanel({ onEditQSO }: { onEditQSO?: (id: number) => void }
<td className="py-1 pr-2 text-muted-foreground truncate max-w-[200px]">{r.group}</td>
<td className="py-1 pr-2">
{!r.worked ? <span className="text-muted-foreground/70">{t('awp.missing')}</span>
: r.validated ? <span className="text-emerald-600">{t('awp.validated')}</span>
: r.confirmed ? <span className="text-amber-600">{t('awp.confirmed')}</span>
: <span className="text-stone-500">{t('awp.worked')}</span>}
: r.validated ? <span className="text-success">{t('awp.validated')}</span>
: r.confirmed ? <span className="text-warning">{t('awp.confirmed')}</span>
: <span className="text-muted-foreground">{t('awp.worked')}</span>}
</td>
<td className="py-1 font-mono text-muted-foreground">
{r.bands.map((b) => (
<span key={b} className={cn('mr-1', r.validated_bands.includes(b) ? 'text-emerald-600 font-semibold' : r.confirmed_bands.includes(b) && 'text-amber-600 font-semibold')}>{b}</span>
<span key={b} className={cn('mr-1', r.validated_bands.includes(b) ? 'text-success font-semibold' : r.confirmed_bands.includes(b) && 'text-warning font-semibold')}>{b}</span>
))}
</td>
</tr>
@@ -558,7 +558,7 @@ function MissingQSOModal({ code, name, onClose, onEditQSO }: { code: string; nam
<div className="fixed inset-0 z-50 bg-black/40 grid place-items-center" onClick={onClose}>
<div className="bg-card border border-border rounded-lg shadow-xl w-[860px] max-h-[80vh] flex flex-col" onClick={(e) => e.stopPropagation()}>
<div className="flex items-center gap-2 px-4 py-2.5 border-b">
<AlertTriangle className="size-4 text-amber-600" />
<AlertTriangle className="size-4 text-warning" />
<span className="font-semibold text-sm">{code} {t('awp.contactsMissingRef')}</span>
{name && <span className="text-xs text-muted-foreground truncate">{name}</span>}
<div className="flex-1" />
@@ -589,7 +589,7 @@ function MissingQSOModal({ code, name, onClose, onEditQSO }: { code: string; nam
<Button size="sm" disabled={!assignRef || sel.size === 0 || busy} onClick={applyAssign}>
{busy ? <Loader2 className="size-3.5 animate-spin" /> : null} {t('awp.assignToSelected', { n: sel.size })}
</Button>
{msg && <span className="text-[11px] text-emerald-700">{msg}</span>}
{msg && <span className="text-[11px] text-success">{msg}</span>}
</div>
<div className="flex-1 overflow-auto">