122 lines
6.3 KiB
TypeScript
122 lines
6.3 KiB
TypeScript
import { Star } from 'lucide-react';
|
||
import { Badge } from '@/components/ui/badge';
|
||
import { useI18n } from '@/lib/i18n';
|
||
import type { WorkedBeforeView } from '@/types';
|
||
|
||
type WorkedBefore = WorkedBeforeView;
|
||
|
||
interface Props {
|
||
wb: WorkedBefore | null;
|
||
busy: boolean;
|
||
currentCall: string;
|
||
}
|
||
|
||
function fmtDate(s: any): string {
|
||
if (!s) return '';
|
||
const d = new Date(s);
|
||
if (isNaN(d.getTime())) return '';
|
||
const p = (n: number) => String(n).padStart(2, '0');
|
||
return `${d.getUTCFullYear()}-${p(d.getUTCMonth() + 1)}-${p(d.getUTCDate())}`;
|
||
}
|
||
function fmtDateTime(s: any): string {
|
||
if (!s) return '';
|
||
const d = new Date(s);
|
||
if (isNaN(d.getTime())) return '';
|
||
const p = (n: number) => String(n).padStart(2, '0');
|
||
return `${d.getUTCFullYear()}-${p(d.getUTCMonth() + 1)}-${p(d.getUTCDate())} ${p(d.getUTCHours())}:${p(d.getUTCMinutes())}`;
|
||
}
|
||
|
||
export function CallHistoryPanel({ wb, busy, currentCall }: Props) {
|
||
const { t } = useI18n();
|
||
const hasCall = currentCall.trim() !== '';
|
||
const count = wb?.count ?? 0;
|
||
const entries = wb?.entries ?? [];
|
||
|
||
return (
|
||
<aside className="flex flex-col bg-card border border-border rounded-lg shadow-sm min-h-0 overflow-hidden">
|
||
<header className="flex items-center justify-between px-3 py-2 border-b border-border bg-muted/40">
|
||
<div className="flex items-baseline gap-2">
|
||
<span className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">
|
||
{t('chp.workedBefore')}
|
||
</span>
|
||
{hasCall && (
|
||
<span className="font-mono text-sm font-bold text-primary tracking-wider">
|
||
{currentCall.toUpperCase()}
|
||
</span>
|
||
)}
|
||
</div>
|
||
{hasCall && count > 0 && (
|
||
<Badge variant="accent" className="font-mono text-[11px] tracking-wider">
|
||
{count}×
|
||
</Badge>
|
||
)}
|
||
</header>
|
||
|
||
{!hasCall ? (
|
||
<div className="flex-1 flex flex-col items-center justify-center gap-2 p-6 text-center text-xs text-muted-foreground">
|
||
{t('chp.typeCallsign')}
|
||
</div>
|
||
) : busy && count === 0 ? (
|
||
<div className="flex-1 flex items-center justify-center text-xs text-muted-foreground italic">
|
||
{t('chp.checking')}
|
||
</div>
|
||
) : count === 0 ? (
|
||
<div className="flex-1 flex flex-col items-center justify-center gap-2 p-6 text-center text-xs text-muted-foreground">
|
||
<Star className="size-6 text-primary fill-current" />
|
||
<div className="text-lg font-bold text-primary tracking-wider">{t('chp.new')}</div>
|
||
<div>{t('chp.noPriorQso')}</div>
|
||
</div>
|
||
) : (
|
||
<>
|
||
<div className="px-3 py-1.5 text-[11px] text-muted-foreground bg-muted/40 border-b border-border">
|
||
{t('chp.first')}: <strong className="text-foreground font-semibold">{fmtDate(wb?.first)}</strong> ·{' '}
|
||
{t('chp.last')}: <strong className="text-foreground font-semibold">{fmtDate(wb?.last)}</strong>
|
||
</div>
|
||
<div className="flex-1 overflow-y-auto">
|
||
<table className="w-full text-[11px] border-collapse">
|
||
<thead>
|
||
<tr>
|
||
<th className="sticky top-0 bg-stone-200 text-left px-2 py-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground border-b border-border">{t('chp.dateUtc')}</th>
|
||
<th className="sticky top-0 bg-stone-200 text-left px-2 py-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground border-b border-border">{t('chp.band')}</th>
|
||
<th className="sticky top-0 bg-stone-200 text-left px-2 py-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground border-b border-border">{t('chp.mode')}</th>
|
||
<th className="sticky top-0 bg-stone-200 text-left px-2 py-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground border-b border-border">RST tx</th>
|
||
<th className="sticky top-0 bg-stone-200 text-left px-2 py-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground border-b border-border">RST rx</th>
|
||
<th className="sticky top-0 bg-stone-200 text-left px-2 py-1 text-[10px] font-semibold uppercase tracking-wide text-muted-foreground border-b border-border">QSL</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{entries.map((e) => (
|
||
<tr key={e.id} className="hover:bg-stone-50">
|
||
<td className="px-2 py-1 font-mono border-b border-border/40 whitespace-nowrap">{fmtDateTime(e.qso_date)}</td>
|
||
<td className="px-2 py-1 border-b border-border/40 whitespace-nowrap">
|
||
<span className="inline-block px-1.5 py-0.5 rounded font-mono text-[10px] font-semibold bg-accent text-accent-foreground">{e.band}</span>
|
||
</td>
|
||
<td className="px-2 py-1 border-b border-border/40 whitespace-nowrap">
|
||
<span className="inline-block px-1.5 py-0.5 rounded font-mono text-[10px] font-semibold bg-emerald-100 text-emerald-700">{e.mode}</span>
|
||
</td>
|
||
<td className="px-2 py-1 font-mono border-b border-border/40 whitespace-nowrap">{e.rst_sent ?? ''}</td>
|
||
<td className="px-2 py-1 font-mono border-b border-border/40 whitespace-nowrap">{e.rst_rcvd ?? ''}</td>
|
||
<td className="px-2 py-1 border-b border-border/40 whitespace-nowrap text-muted-foreground">
|
||
{e.lotw_rcvd === 'Y' && (
|
||
<span className="inline-block w-[14px] h-[14px] rounded text-center leading-[14px] text-[9px] font-bold text-white bg-blue-600 mr-0.5" title={t('chp.lotwRcvd')}>L</span>
|
||
)}
|
||
{e.qsl_rcvd === 'Y' && (
|
||
<span className="inline-block w-[14px] h-[14px] rounded text-center leading-[14px] text-[9px] font-bold text-white bg-emerald-600 mr-0.5" title={t('chp.bureauRcvd')}>B</span>
|
||
)}
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
{count > entries.length && (
|
||
<div className="text-center py-1.5 text-[11px] italic text-muted-foreground">
|
||
{t('chp.olderQsos', { n: count - entries.length })}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</>
|
||
)}
|
||
</aside>
|
||
);
|
||
}
|