awards
This commit is contained in:
@@ -44,7 +44,11 @@ type AwardRef = {
|
||||
|
||||
type Preset = { key: string; name: string; field: string; dxcc: number; refs: AwardRef[] };
|
||||
|
||||
const AWARD_TYPES = ['QSOFIELDS', 'REFERENCE', 'DXCC', 'GRID'];
|
||||
// Award types mirror Log4OM: REFERENCE (we supply a list), QSOFIELDS (scan any
|
||||
// QSO field — handles state/grid4/zones/etc.), CALLSIGN. The type is
|
||||
// organizational only; matching is driven by the field/pattern/dynamic options,
|
||||
// so there's no need for separate GRID/DXCC types (use QSOFIELDS + the field).
|
||||
const AWARD_TYPES = ['REFERENCE', 'QSOFIELDS', 'CALLSIGN'];
|
||||
const CONFIRM_SRC = [
|
||||
{ id: 'lotw', label: 'LoTW' }, { id: 'qsl', label: 'QSL' }, { id: 'eqsl', label: 'eQSL' },
|
||||
{ id: 'qrzcom', label: 'QRZ.com' }, { id: 'custom', label: 'Custom' },
|
||||
@@ -141,6 +145,15 @@ export function AwardEditor({ open, onClose, onSaved }: Props) {
|
||||
const [updating, setUpdating] = useState<string | null>(null);
|
||||
const [err, setErr] = useState('');
|
||||
|
||||
// The err banner doubles as a success/notice area (export path, import counts,
|
||||
// "populated N refs"). Auto-dismiss it after a few seconds so it doesn't stay
|
||||
// forever; the longer text (export path) gets a bit more time.
|
||||
useEffect(() => {
|
||||
if (!err) return;
|
||||
const t = window.setTimeout(() => setErr(''), 8000);
|
||||
return () => window.clearTimeout(t);
|
||||
}, [err]);
|
||||
|
||||
const loadMeta = () => GetAwardReferenceMeta()
|
||||
.then((m) => setMeta(Object.fromEntries(((m ?? []) as RefMeta[]).map((x) => [x.code.toUpperCase(), x]))))
|
||||
.catch(() => {});
|
||||
@@ -214,7 +227,11 @@ export function AwardEditor({ open, onClose, onSaved }: Props) {
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const q = search.trim().toUpperCase();
|
||||
return defs.map((d, i) => ({ d, i })).filter(({ d }) => !q || d.code.toUpperCase().includes(q) || d.name.toUpperCase().includes(q));
|
||||
// Keep the original index `i` (used for selection/patch) but display the
|
||||
// list sorted alphabetically by code — scales as the catalogue grows.
|
||||
return defs.map((d, i) => ({ d, i }))
|
||||
.filter(({ d }) => !q || d.code.toUpperCase().includes(q) || d.name.toUpperCase().includes(q))
|
||||
.sort((a, b) => a.d.code.localeCompare(b.d.code));
|
||||
}, [defs, search]);
|
||||
|
||||
return (
|
||||
@@ -251,7 +268,7 @@ export function AwardEditor({ open, onClose, onSaved }: Props) {
|
||||
|
||||
{/* Right: tabbed editor for selected award */}
|
||||
<div className="flex flex-col min-h-0 overflow-hidden">
|
||||
{err && <div className="mx-4 mt-3 text-xs text-destructive bg-destructive/10 border border-destructive/30 rounded px-3 py-1.5 whitespace-pre-line break-all">{err}</div>}
|
||||
{err && <div onClick={() => setErr('')} title="Click to dismiss" className="mx-4 mt-3 text-xs text-destructive bg-destructive/10 border border-destructive/30 rounded px-3 py-1.5 whitespace-pre-line break-all cursor-pointer">{err}</div>}
|
||||
{!cur ? (
|
||||
<div className="flex-1 grid place-items-center text-sm text-muted-foreground">Select or create an award.</div>
|
||||
) : (
|
||||
@@ -293,7 +310,12 @@ export function AwardEditor({ open, onClose, onSaved }: Props) {
|
||||
<Field2 label="Award type">
|
||||
<Select value={cur.type || 'QSOFIELDS'} onValueChange={(v) => patch({ type: v })}>
|
||||
<SelectTrigger className="h-8 text-xs w-48"><SelectValue /></SelectTrigger>
|
||||
<SelectContent>{AWARD_TYPES.map((t) => <SelectItem key={t} value={t}>{t}</SelectItem>)}</SelectContent>
|
||||
<SelectContent>{
|
||||
// Keep a legacy type (DXCC/GRID) selectable if an existing
|
||||
// award still uses it, so it isn't silently changed.
|
||||
(cur.type && !AWARD_TYPES.includes(cur.type) ? [cur.type, ...AWARD_TYPES] : AWARD_TYPES)
|
||||
.map((t) => <SelectItem key={t} value={t}>{t}</SelectItem>)
|
||||
}</SelectContent>
|
||||
</Select>
|
||||
</Field2>
|
||||
<label className="flex items-center gap-2 text-xs cursor-pointer"><Checkbox checked={!!cur.multi} onCheckedChange={(c) => patch({ multi: !!c })} /> Allow multiple references on a single QSO</label>
|
||||
@@ -530,9 +552,18 @@ function ReferencesPanel({ code, presets, meta, onUpdateOnline, updating, onChan
|
||||
<Field2 label="DXCC"><Input type="number" className="h-8 w-32 font-mono" value={sel.dxcc || ''} onChange={(e) => patchSel({ dxcc: parseInt(e.target.value, 10) || 0 })} /></Field2>
|
||||
<Field2 label="Pattern (regex)"><Input className="h-8 font-mono text-xs" value={sel.pattern ?? ''} onChange={(e) => patchSel({ pattern: e.target.value })} placeholder="optional per-reference regex" /></Field2>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<Field2 label="Score"><Input type="number" className="h-8 font-mono" value={sel.score ?? 0} onChange={(e) => patchSel({ score: parseInt(e.target.value, 10) || 0 })} /></Field2>
|
||||
<Field2 label="Bonus"><Input type="number" className="h-8 font-mono" value={sel.bonus ?? 0} onChange={(e) => patchSel({ bonus: parseInt(e.target.value, 10) || 0 })} /></Field2>
|
||||
<Field2 label="Grid"><Input className="h-8 font-mono" value={sel.gridsquare ?? ''} onChange={(e) => patchSel({ gridsquare: e.target.value })} /></Field2>
|
||||
<div className="flex flex-col gap-1 min-w-0">
|
||||
<Label className="text-xs text-muted-foreground">Score</Label>
|
||||
<Input type="number" className="h-8 font-mono w-full" value={sel.score ?? 0} onChange={(e) => patchSel({ score: parseInt(e.target.value, 10) || 0 })} />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 min-w-0">
|
||||
<Label className="text-xs text-muted-foreground">Bonus</Label>
|
||||
<Input type="number" className="h-8 font-mono w-full" value={sel.bonus ?? 0} onChange={(e) => patchSel({ bonus: parseInt(e.target.value, 10) || 0 })} />
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 min-w-0">
|
||||
<Label className="text-xs text-muted-foreground">Grid</Label>
|
||||
<Input className="h-8 font-mono w-full" value={sel.gridsquare ?? ''} onChange={(e) => patchSel({ gridsquare: e.target.value })} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end pt-1"><Button size="sm" className="h-7" onClick={() => sel && saveRef(sel)}><Save className="size-3.5 mr-1" /> Save reference</Button></div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user