award
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Trash2, Search, Loader2 } from 'lucide-react';
|
||||
import { LookupCallsign, DXCCForCountry } from '../../wailsjs/go/main/App';
|
||||
import { LookupCallsign, DXCCForCountry, GetAwardDefs, ComputeQSOAwardRefs } from '../../wailsjs/go/main/App';
|
||||
import { AwardRefSelector } from '@/components/AwardRefSelector';
|
||||
import { applyAwardRefs, buildAwardRefs } from '@/lib/awardRefs';
|
||||
import {
|
||||
Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription,
|
||||
} from '@/components/ui/dialog';
|
||||
@@ -166,6 +168,47 @@ export function QSOEditModal({ qso, onSave, onDelete, onClose, countries = [] }:
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [looking, setLooking] = useState(false);
|
||||
|
||||
// === Award references (Log4OM-style tab) ===
|
||||
// Manual refs are edited as a "CODE@REF;…" string; computed refs (DXCC, WAZ,
|
||||
// WPX, …) are derived from the QSO by the backend and shown read-only.
|
||||
const awardFieldRef = useRef<Record<string, string>>({});
|
||||
const [awardRefs, setAwardRefs] = useState('');
|
||||
const [computedRefs, setComputedRefs] = useState<Array<{ code: string; ref: string; name?: string }>>([]);
|
||||
|
||||
// Load award definitions once, then seed the editable manual refs from the QSO.
|
||||
useEffect(() => {
|
||||
GetAwardDefs()
|
||||
.then(async (defs) => {
|
||||
const list = (defs ?? []) as any[];
|
||||
const fieldOf: Record<string, string> = {};
|
||||
for (const d of list) fieldOf[String(d.code).toUpperCase()] = String(d.field || '').toLowerCase();
|
||||
awardFieldRef.current = fieldOf;
|
||||
// Which awards are reference-list (manual) ones? Ask the backend, which
|
||||
// also tells us pickable vs computed for the current QSO.
|
||||
try {
|
||||
const all = (await ComputeQSOAwardRefs(draft as any)) ?? [];
|
||||
const pickableCodes = new Set(all.filter((r: any) => r.pickable).map((r: any) => String(r.code).toUpperCase()));
|
||||
const pickable = list
|
||||
.filter((d) => pickableCodes.has(String(d.code).toUpperCase()))
|
||||
.map((d) => ({ code: String(d.code), field: String(d.field || '').toLowerCase() }));
|
||||
setAwardRefs(buildAwardRefs(draft, pickable));
|
||||
} catch { /* leave manual refs empty on failure */ }
|
||||
})
|
||||
.catch(() => {});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
// Recompute the read-only computed refs whenever a source field changes.
|
||||
useEffect(() => {
|
||||
const t = window.setTimeout(async () => {
|
||||
try {
|
||||
const all = (await ComputeQSOAwardRefs(draft as any)) ?? [];
|
||||
setComputedRefs(all.filter((r: any) => !r.pickable).map((r: any) => ({ code: r.code, ref: r.ref, name: r.name })));
|
||||
} catch { setComputedRefs([]); }
|
||||
}, 250);
|
||||
return () => window.clearTimeout(t);
|
||||
}, [draft.dxcc, draft.cqz, draft.ituz, draft.cont, draft.state, draft.callsign, draft.notes, draft.band]);
|
||||
|
||||
function set<K extends keyof QSO>(key: K, value: QSO[K]) {
|
||||
setDraft((d) => ({ ...d, [key]: value }));
|
||||
}
|
||||
@@ -228,9 +271,7 @@ export function QSOEditModal({ qso, onSave, onDelete, onClose, countries = [] }:
|
||||
operator: (draft.operator ?? '').trim().toUpperCase(),
|
||||
my_grid: (draft.my_grid ?? '').trim().toUpperCase(),
|
||||
my_gridsquare_ext: (draft.my_gridsquare_ext ?? '').trim().toUpperCase(),
|
||||
iota: (draft.iota ?? '').trim().toUpperCase(),
|
||||
sota_ref: (draft.sota_ref ?? '').trim().toUpperCase(),
|
||||
pota_ref: (draft.pota_ref ?? '').trim().toUpperCase(),
|
||||
// iota / sota_ref / pota_ref are set below from the Award Refs tab.
|
||||
my_iota: (draft.my_iota ?? '').trim().toUpperCase(),
|
||||
my_sota_ref: (draft.my_sota_ref ?? '').trim().toUpperCase(),
|
||||
my_pota_ref: (draft.my_pota_ref ?? '').trim().toUpperCase(),
|
||||
@@ -253,6 +294,11 @@ export function QSOEditModal({ qso, onSave, onDelete, onClose, countries = [] }:
|
||||
tx_pwr: numOrUndef(draft.tx_pwr),
|
||||
extras: parseExtras(extrasText),
|
||||
};
|
||||
// The Award Refs tab is authoritative for the reference-list awards. Reset
|
||||
// the dedicated columns, then route the picked refs back onto the payload
|
||||
// (POTA/SOTA/IOTA → columns, WWFF/custom → extras).
|
||||
out.iota = ''; out.sota_ref = ''; out.pota_ref = '';
|
||||
applyAwardRefs(out, awardRefs, awardFieldRef.current);
|
||||
onSave(out);
|
||||
}
|
||||
|
||||
@@ -283,6 +329,7 @@ export function QSOEditModal({ qso, onSave, onDelete, onClose, countries = [] }:
|
||||
<TabsList className="px-3 overflow-x-auto">
|
||||
<TabsTrigger value="qsoinfo">QSO Info</TabsTrigger>
|
||||
<TabsTrigger value="contact">Contact's details</TabsTrigger>
|
||||
<TabsTrigger value="awards">Award Refs</TabsTrigger>
|
||||
<TabsTrigger value="qsl">QSL Info</TabsTrigger>
|
||||
<TabsTrigger value="contest">Contest</TabsTrigger>
|
||||
<TabsTrigger value="sat">Sat / Prop</TabsTrigger>
|
||||
@@ -411,6 +458,35 @@ export function QSOEditModal({ qso, onSave, onDelete, onClose, countries = [] }:
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="awards" className="mt-0">
|
||||
<div className="grid grid-cols-[1fr_240px] gap-5">
|
||||
{/* Left: pick reference-list awards (POTA/SOTA/IOTA/WWFF/…) */}
|
||||
<div>
|
||||
<AwardRefSelector dxcc={draft.dxcc} value={awardRefs} onChange={setAwardRefs} />
|
||||
</div>
|
||||
|
||||
{/* Right: computed awards (read-only) derived from this QSO */}
|
||||
<div className="flex flex-col gap-1.5 min-w-0">
|
||||
<span className="text-xs font-semibold">Computed (automatic)</span>
|
||||
<p className="text-[11px] text-muted-foreground leading-snug">
|
||||
Derived from this QSO's fields (DXCC, zones, prefix, notes…). Not editable here.
|
||||
</p>
|
||||
<div className="flex-1 overflow-auto border rounded-md text-xs min-h-[160px] max-h-[210px]">
|
||||
{computedRefs.length === 0 ? (
|
||||
<div className="px-2 py-1.5 text-[11px] text-muted-foreground">None yet.</div>
|
||||
) : (
|
||||
computedRefs.map((r) => (
|
||||
<div key={`${r.code}@${r.ref}`} className="px-2 py-1 border-b border-border/30 last:border-0">
|
||||
<span className="font-mono font-semibold">{r.code}@{r.ref}</span>
|
||||
{r.name && <span className="text-[10px] text-muted-foreground ml-1.5 truncate">{r.name}</span>}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="qsl" className="mt-0">
|
||||
{(() => {
|
||||
const def = CONFIRMATIONS.find((c) => c.key === confSel) ?? CONFIRMATIONS[0];
|
||||
|
||||
Reference in New Issue
Block a user