feat: added versionning & About window

This commit is contained in:
2026-06-16 19:36:56 +02:00
parent 33af122964
commit 69d0780bac
16 changed files with 1398 additions and 56 deletions
+47 -1
View File
@@ -31,11 +31,17 @@ export type AwardDef = {
url?: string; download_url?: string; ref_url?: string; valid_from?: string; valid_to?: string; alias?: string;
type?: string; field: string; match_by?: string; exact_match?: boolean; pattern: string;
leading_str?: string; trailing_str?: string; multi?: boolean; dynamic?: boolean; add_prefixes?: string[];
or_rules?: AwardOrRule[];
dxcc_filter: number[] | null; valid_bands?: string[]; valid_modes?: string[]; emission?: string[];
confirm: string[] | null; validate?: string[] | null; grant_codes?: string; export_credit_granted?: boolean;
total: number; builtin?: boolean;
};
type AwardOrRule = {
field: string; match_by?: string; exact_match?: boolean; pattern?: string;
leading_str?: string; trailing_str?: string; prefix?: string;
};
type AwardRef = {
code: string; name: string; dxcc: number; group: string; subgrp: string;
dxcc_list?: number[]; pattern?: string; valid: boolean; valid_from?: string; valid_to?: string;
@@ -162,7 +168,7 @@ export function AwardEditor({ open, onClose, onSaved }: Props) {
if (!open) return;
setErr('');
Promise.all([GetAwardDefs(), AwardFields(), GetAwardPresets(), ListCountries()])
.then(([d, f, p, c]) => { setDefs((d ?? []) as any); setFields((f ?? []) as any); setPresets((p ?? []) as any); setCountries((c ?? []) as any); })
.then(([d, f, p, c]) => { setDefs((d ?? []) as any); setFields(((f ?? []) as string[]).slice().sort((a, b) => a.localeCompare(b))); setPresets((p ?? []) as any); setCountries((c ?? []) as any); })
.catch((e) => setErr(String(e?.message ?? e)));
loadMeta();
}, [open]);
@@ -344,6 +350,46 @@ export function AwardEditor({ open, onClose, onSaved }: Props) {
<Field2 label="Leading string"><Input className="h-8 font-mono text-xs" value={cur.leading_str ?? ''} onChange={(e) => patch({ leading_str: e.target.value })} /></Field2>
<Field2 label="Trailing string"><Input className="h-8 font-mono text-xs" value={cur.trailing_str ?? ''} onChange={(e) => patch({ trailing_str: e.target.value })} /></Field2>
</div>
{/* Additional OR searches: a QSO earns a reference if the
primary rule OR any of these match. */}
<div className="border-t pt-2.5 space-y-2">
<div className="flex items-center justify-between">
<p className="text-[11px] text-muted-foreground">Additional searches <span className="font-semibold">(OR)</span> also match the reference if any of these hit</p>
<Button size="sm" variant="outline" className="h-7"
onClick={() => patch({ or_rules: [...(cur.or_rules ?? []), { field: cur.field || 'note', match_by: 'pattern', pattern: '', prefix: '' }] })}>
<Plus className="size-3.5" /> Add OR
</Button>
</div>
{(cur.or_rules ?? []).map((r, ri) => {
const upd = (p: Partial<AwardOrRule>) => patch({ or_rules: (cur.or_rules ?? []).map((x, j) => (j === ri ? { ...x, ...p } : x)) });
const del = () => patch({ or_rules: (cur.or_rules ?? []).filter((_, j) => j !== ri) });
return (
<div key={ri} className="rounded-md border border-border bg-muted/20 p-2 space-y-2">
<div className="flex items-center gap-2">
<span className="text-[11px] text-muted-foreground">OR search in</span>
<Select value={r.field} onValueChange={(v) => upd({ field: v })}>
<SelectTrigger className="h-7 text-xs w-44"><SelectValue /></SelectTrigger>
<SelectContent className="max-h-72">{fields.map((f) => <SelectItem key={f} value={f}>{f}</SelectItem>)}</SelectContent>
</Select>
<div className="flex items-center gap-2 text-[11px]">
{['code', 'description', 'pattern'].map((m) => (
<label key={m} className="flex items-center gap-1 cursor-pointer">
<input type="radio" name={`orby-${ri}`} checked={(r.match_by || 'code') === m} onChange={() => upd({ match_by: m })} className="accent-primary" /> {m}
</label>
))}
</div>
<label className="flex items-center gap-1.5 text-[11px] cursor-pointer"><Checkbox checked={!!r.exact_match} onCheckedChange={(c) => upd({ exact_match: !!c })} /> exact</label>
<button className="ml-auto text-destructive hover:opacity-70" onClick={del} title="Remove this OR search"><Trash2 className="size-4" /></button>
</div>
<div className="grid grid-cols-[1fr_120px] gap-2">
<Input className="h-7 font-mono text-xs" value={r.pattern ?? ''} onChange={(e) => upd({ pattern: e.target.value })} placeholder="regex — group 1 = reference (e.g. \b(\d{2})\d{3}\b for postal → dept)" />
<Input className="h-7 font-mono text-xs" value={r.prefix ?? ''} onChange={(e) => upd({ prefix: e.target.value })} placeholder="prefix (D)" title="Prepended to each found reference, e.g. 74 → D74" />
</div>
</div>
);
})}
</div>
</div>
</TabsContent>