update
This commit is contained in:
@@ -25,9 +25,13 @@ interface Props {
|
||||
// Semicolon-delimited "AWARD@REF" entries, e.g. "POTA@FR-11553;IOTA@EU-064"
|
||||
value: string;
|
||||
onChange: (v: string) => void;
|
||||
// Current QSO field values (state, cnty, …). When a predefined award reads one
|
||||
// of these and it's already filled (e.g. VE9CF → state NB), the award counts
|
||||
// automatically — we surface that so the operator needn't pick it by hand.
|
||||
fieldValues?: Record<string, string>;
|
||||
}
|
||||
|
||||
export function AwardRefSelector({ dxcc, value, onChange }: Props) {
|
||||
export function AwardRefSelector({ dxcc, value, onChange, fieldValues }: Props) {
|
||||
const [defs, setDefs] = useState<AwardDef[]>([]);
|
||||
const [metas, setMetas] = useState<Record<string, Meta>>({});
|
||||
const [awardCode, setAwardCode] = useState('POTA');
|
||||
@@ -67,7 +71,8 @@ export function AwardRefSelector({ dxcc, value, onChange }: Props) {
|
||||
const scope = d.dxcc_filter ?? [];
|
||||
if (scope.length > 0 && (!dxcc || !scope.includes(dxcc))) return false;
|
||||
return true;
|
||||
}).map((d) => ({ code: d.code, name: d.name }));
|
||||
}).map((d) => ({ code: d.code, name: d.name, field: String(d.field ?? '').toLowerCase() }))
|
||||
.sort((a, b) => a.code.localeCompare(b.code));
|
||||
}, [defs, metas, dxcc]);
|
||||
|
||||
// Keep the selected award valid as the offered list changes with the call.
|
||||
@@ -86,6 +91,12 @@ export function AwardRefSelector({ dxcc, value, onChange }: Props) {
|
||||
// For dynamic lists, restrict to the contacted entity; otherwise load all.
|
||||
const refDxcc = isDynamic ? (dxcc ?? 0) : 0;
|
||||
|
||||
// The field the selected award reads (state / cnty / note / …).
|
||||
const selField = useMemo(
|
||||
() => String(defs.find((d) => d.code === awardCode)?.field ?? '').toLowerCase(),
|
||||
[defs, awardCode],
|
||||
);
|
||||
|
||||
// Search helper with a DXCC fallback: try the entity-scoped query first, but
|
||||
// if it finds nothing AND we were filtering by DXCC, retry unfiltered. This
|
||||
// fixes awards whose references carry no per-ref DXCC (e.g. SOTA summits,
|
||||
@@ -130,6 +141,20 @@ export function AwardRefSelector({ dxcc, value, onChange }: Props) {
|
||||
// When q is empty/short: show auto-results (if ≤ AUTO_SHOW_MAX); when typing: show search results.
|
||||
const results: AwardRef[] = q.length >= 2 ? searchResults : (tooManyAuto ? [] : autoResults);
|
||||
|
||||
// Auto-match: when the selected award reads a QSO field that's already filled
|
||||
// (e.g. RAC reads `state`, and the call resolved to state NB), find the matching
|
||||
// reference so the operator can add it in one click — the award already counts
|
||||
// it from the field, but this makes it explicit and confirms it.
|
||||
const autoMatch = useMemo(() => {
|
||||
if (!selField || !fieldValues) return null;
|
||||
const v = String(fieldValues[selField] ?? '').trim().toUpperCase();
|
||||
if (!v) return null;
|
||||
const pool = (autoResults.length ? autoResults : searchResults);
|
||||
const m = pool.find((r) => r.code.toUpperCase() === v);
|
||||
return m ? { code: m.code, name: m.name } : null;
|
||||
}, [selField, fieldValues, autoResults, searchResults]);
|
||||
const autoAlreadyAdded = autoMatch ? entries.includes(`${awardCode}@${autoMatch.code}`) : false;
|
||||
|
||||
function addRef(ref: AwardRef) {
|
||||
const entry = `${awardCode}@${ref.code}`;
|
||||
if (!entries.includes(entry)) {
|
||||
@@ -230,6 +255,25 @@ export function AwardRefSelector({ dxcc, value, onChange }: Props) {
|
||||
{/* Right panel: reference search */}
|
||||
<div className="w-[172px] shrink-0 flex flex-col gap-1.5 border-l pl-2 min-w-0">
|
||||
<span className="text-xs font-semibold">References</span>
|
||||
{/* Auto-match from the QSO field (e.g. State NB → RAC@NB). */}
|
||||
{autoMatch && (
|
||||
<button
|
||||
type="button"
|
||||
disabled={autoAlreadyAdded}
|
||||
onClick={() => addRef({ code: autoMatch.code, name: autoMatch.name } as AwardRef)}
|
||||
className={`text-left rounded border px-1.5 py-1 text-[11px] leading-tight ${
|
||||
autoAlreadyAdded
|
||||
? 'border-emerald-300 bg-emerald-50 text-emerald-800 cursor-default'
|
||||
: 'border-emerald-300 bg-emerald-50/60 text-emerald-800 hover:bg-emerald-100'
|
||||
}`}
|
||||
title={`The ${selField.toUpperCase()} field is ${autoMatch.code} — this award counts it automatically`}
|
||||
>
|
||||
{autoAlreadyAdded ? '✓ ' : '+ '}
|
||||
<span className="font-mono font-semibold">{autoMatch.code}</span>
|
||||
<span className="text-emerald-700"> from {selField}</span>
|
||||
{!autoAlreadyAdded && <span className="block text-[10px] text-emerald-700/80">auto — click to add</span>}
|
||||
</button>
|
||||
)}
|
||||
<input
|
||||
className="h-6 w-full rounded border border-input bg-background px-2 text-xs focus:outline-none focus:ring-1 focus:ring-ring"
|
||||
placeholder="Search…"
|
||||
|
||||
Reference in New Issue
Block a user