// Shared helpers for per-QSO award references. // // In the UI a QSO's manually-assigned award references are edited as a single // semicolon-delimited string of "CODE@REF" entries, e.g. // "POTA@FR-11553;IOTA@EU-064" // On save each entry is routed to the QSO field its award actually reads from // (see internal/award/award.go): POTA/SOTA/IOTA have dedicated columns; WWFF // and custom awards live in uppercase ADIF extras keys. // parseAwardRefs turns "POTA@FR-11553;IOTA@EU-064" into // { POTA: "FR-11553", IOTA: "EU-064" }. Repeated codes join with commas. export function parseAwardRefs(v: string): Record { const out: Record = {}; for (const entry of (v ?? '').split(';').filter(Boolean)) { const at = entry.indexOf('@'); if (at <= 0) continue; const code = entry.slice(0, at).toUpperCase(); const ref = entry.slice(at + 1).trim().toUpperCase(); if (!ref) continue; out[code] = out[code] ? `${out[code]},${ref}` : ref; } return out; } // appendTokens adds space-separated tokens (a "A,B" ref string) to a text field, // skipping any already present, so re-picking is idempotent. function appendTokens(existing: string | undefined, refs: string): string { let out = (existing ?? '').trim(); for (const tok of refs.split(',').map((s) => s.trim()).filter(Boolean)) { const re = new RegExp(`(^|\\s)${tok.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(\\s|$)`, 'i'); if (!re.test(out)) out = out ? `${out} ${tok}` : tok; } return out; } // applyAwardRefs writes picked references onto a QSO payload using each award's // scanned field. fieldOf maps an award CODE (uppercase) to its field name. export function applyAwardRefs(payload: any, awardRefs: string, fieldOf: Record) { const byCode = parseAwardRefs(awardRefs); const extras: Record = { ...(payload.extras ?? {}) }; for (const [code, ref] of Object.entries(byCode)) { const field = fieldOf[code] || code.toLowerCase(); switch (field) { case 'iota': payload.iota = ref; break; case 'sota_ref': payload.sota_ref = ref; break; case 'pota_ref': payload.pota_ref = ref; break; // Predefined-list awards on a QSO field (WAS/RAC/WAJA on state, JCC on // county): picking a reference writes it straight into that column. case 'state': payload.state = ref; break; case 'cnty': payload.cnty = ref; break; case 'wwff': extras['WWFF_REF'] = ref; extras['SIG'] = 'WWFF'; extras['SIG_INFO'] = ref; break; // QSOFIELDS awards read their reference from a free-text field (e.g. DDFM // scans the note for "D06"). Picking such a reference appends its code(s) // to that field so the matcher finds it. case 'note': case 'notes': payload.notes = appendTokens(payload.notes, ref); break; case 'comment': payload.comment = appendTokens(payload.comment, ref); break; default: extras[field.toUpperCase()] = ref; break; } } if (Object.keys(extras).length > 0) payload.extras = extras; } // awardRefValue reads a single award's stored reference from a QSO, inverse of // applyAwardRefs. Used to seed the editor when opening an existing QSO. export function awardRefValue(qso: any, code: string, field: string): string { switch (field) { case 'iota': return (qso.iota ?? '').toUpperCase(); case 'sota_ref': return (qso.sota_ref ?? '').toUpperCase(); case 'pota_ref': return (qso.pota_ref ?? '').toUpperCase(); case 'state': return (qso.state ?? '').toUpperCase(); case 'cnty': return (qso.cnty ?? '').toUpperCase(); case 'wwff': { const ex = qso.extras ?? {}; if (ex['WWFF_REF']) return String(ex['WWFF_REF']).toUpperCase(); if (String(ex['SIG'] ?? '').toUpperCase() === 'WWFF') return String(ex['SIG_INFO'] ?? '').toUpperCase(); return ''; } default: { const ex = qso.extras ?? {}; return String(ex[field.toUpperCase()] ?? '').toUpperCase(); } } } // buildAwardRefs reconstructs the "CODE@REF;…" editor string from a QSO for the // given pickable awards (code → field). Only awards with a stored value appear. export function buildAwardRefs(qso: any, pickable: Array<{ code: string; field: string }>): string { const out: string[] = []; for (const { code, field } of pickable) { const v = awardRefValue(qso, code, field); // A multi-reference field (n-fer POTA "US-6544,US-0680") becomes one // editor entry per reference, so each shows on its own removable line. for (const ref of v.split(/[,;]/).map((s) => s.trim()).filter(Boolean)) { out.push(`${code.toUpperCase()}@${ref}`); } } return out.join(';'); }