import { useMemo, useState } from 'react'; import { Loader2 } from 'lucide-react'; import { BulkUpdateField } from '../../wailsjs/go/main/App'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, } from '@/components/ui/select'; import { useI18n } from '@/lib/i18n'; type FieldKind = 'status' | 'text'; type FieldDef = { id: string; label: string; group: string; kind: FieldKind; upper?: boolean }; // Fields a bulk edit may target. status → Y/N/R/I dropdown; text → free input. // upper:true uppercases code-like values (callsign, grid, refs). The id matches // the backend BulkUpdateField whitelist. // label holds an i18n key (resolved with t() at render time). const FIELDS: FieldDef[] = [ // QSL / upload status { id: 'lotw_sent', label: 'bulk.fLotwSent', group: 'QSL / upload', kind: 'status' }, { id: 'lotw_rcvd', label: 'bulk.fLotwRcvd', group: 'QSL / upload', kind: 'status' }, { id: 'eqsl_sent', label: 'bulk.fEqslSent', group: 'QSL / upload', kind: 'status' }, { id: 'eqsl_rcvd', label: 'bulk.fEqslRcvd', group: 'QSL / upload', kind: 'status' }, { id: 'qsl_sent', label: 'bulk.fQslSent', group: 'QSL / upload', kind: 'status' }, { id: 'qsl_rcvd', label: 'bulk.fQslRcvd', group: 'QSL / upload', kind: 'status' }, { id: 'qrz_upload', label: 'bulk.fQrzUpload', group: 'QSL / upload', kind: 'status' }, { id: 'clublog_upload', label: 'bulk.fClublogUpload', group: 'QSL / upload', kind: 'status' }, { id: 'hrdlog_upload', label: 'bulk.fHrdlogUpload', group: 'QSL / upload', kind: 'status' }, { id: 'qsl_via', label: 'bulk.fQslVia', group: 'QSL / upload', kind: 'text' }, // My station / operator { id: 'station_callsign', label: 'bulk.fStationCall', group: 'My station', kind: 'text', upper: true }, { id: 'operator', label: 'bulk.fOperator', group: 'My station', kind: 'text', upper: true }, { id: 'my_grid', label: 'bulk.fMyGrid', group: 'My station', kind: 'text', upper: true }, { id: 'my_antenna', label: 'bulk.fMyAntenna', group: 'My station', kind: 'text' }, { id: 'my_rig', label: 'bulk.fMyRig', group: 'My station', kind: 'text' }, { id: 'my_street', label: 'bulk.fMyStreet', group: 'My station', kind: 'text' }, { id: 'my_city', label: 'bulk.fMyCity', group: 'My station', kind: 'text' }, { id: 'my_postal_code', label: 'bulk.fMyPostal', group: 'My station', kind: 'text' }, { id: 'my_country', label: 'bulk.fMyCountry', group: 'My station', kind: 'text' }, { id: 'my_state', label: 'bulk.fMyState', group: 'My station', kind: 'text', upper: true }, { id: 'my_cnty', label: 'bulk.fMyCounty', group: 'My station', kind: 'text' }, { id: 'my_iota', label: 'bulk.fMyIota', group: 'My station', kind: 'text', upper: true }, { id: 'my_sota_ref', label: 'bulk.fMySota', group: 'My station', kind: 'text', upper: true }, { id: 'my_pota_ref', label: 'bulk.fMyPota', group: 'My station', kind: 'text', upper: true }, { id: 'my_wwff_ref', label: 'bulk.fMyWwff', group: 'My station', kind: 'text', upper: true }, { id: 'my_sig', label: 'bulk.fMySig', group: 'My station', kind: 'text' }, { id: 'my_sig_info', label: 'bulk.fMySigInfo', group: 'My station', kind: 'text' }, { id: 'my_name', label: 'bulk.fMyName', group: 'My station', kind: 'text' }, { id: 'my_arrl_sect', label: 'bulk.fMyArrlSect', group: 'My station', kind: 'text', upper: true }, { id: 'my_darc_dok', label: 'bulk.fMyDarcDok', group: 'My station', kind: 'text', upper: true }, { id: 'my_vucc_grids', label: 'bulk.fMyVuccGrids', group: 'My station', kind: 'text', upper: true }, // Contest { id: 'contest_id', label: 'bulk.fContestId', group: 'Contest', kind: 'text', upper: true }, { id: 'srx_string', label: 'bulk.fSrxString', group: 'Contest', kind: 'text' }, { id: 'stx_string', label: 'bulk.fStxString', group: 'Contest', kind: 'text' }, { id: 'arrl_sect', label: 'bulk.fArrlSect', group: 'Contest', kind: 'text', upper: true }, { id: 'precedence', label: 'bulk.fPrecedence', group: 'Contest', kind: 'text', upper: true }, { id: 'class', label: 'bulk.fClass', group: 'Contest', kind: 'text', upper: true }, // Propagation / satellite { id: 'prop_mode', label: 'bulk.fPropMode', group: 'Propagation', kind: 'text', upper: true }, { id: 'sat_name', label: 'bulk.fSatName', group: 'Propagation', kind: 'text', upper: true }, { id: 'sat_mode', label: 'bulk.fSatMode', group: 'Propagation', kind: 'text', upper: true }, // Contacted station (activation refs / SIG) { id: 'pota_ref', label: 'bulk.fPotaRef', group: 'Contacted station', kind: 'text', upper: true }, { id: 'sota_ref', label: 'bulk.fSotaRef', group: 'Contacted station', kind: 'text', upper: true }, { id: 'wwff_ref', label: 'bulk.fWwffRef', group: 'Contacted station', kind: 'text', upper: true }, { id: 'iota', label: 'bulk.fIota', group: 'Contacted station', kind: 'text', upper: true }, { id: 'sig', label: 'bulk.fSig', group: 'Contacted station', kind: 'text' }, { id: 'sig_info', label: 'bulk.fSigInfo', group: 'Contacted station', kind: 'text' }, // Misc { id: 'comment', label: 'bulk.fComment', group: 'Misc', kind: 'text' }, { id: 'notes', label: 'bulk.fNotes', group: 'Misc', kind: 'text' }, { id: 'rig', label: 'bulk.fRig', group: 'Misc', kind: 'text' }, { id: 'ant', label: 'bulk.fAnt', group: 'Misc', kind: 'text' }, ]; // label holds an i18n key. const STATUS_VALUES: { v: string; label: string }[] = [ { v: 'Y', label: 'bulk.statusY' }, { v: 'N', label: 'bulk.statusN' }, { v: 'R', label: 'bulk.statusR' }, { v: 'I', label: 'bulk.statusI' }, { v: '_', label: 'bulk.statusBlank' }, ]; const GROUPS = ['QSL / upload', 'My station', 'Contacted station', 'Contest', 'Propagation', 'Misc']; // Maps the internal group key → its i18n label key. const GROUP_LABELS: Record = { 'QSL / upload': 'bulk.groupQsl', 'My station': 'bulk.groupMyStation', 'Contacted station': 'bulk.groupContacted', 'Contest': 'bulk.groupContest', 'Propagation': 'bulk.groupPropagation', 'Misc': 'bulk.groupMisc', }; type Props = { open: boolean; ids: number[]; onClose: () => void; onApplied: (n: number) => void; }; // BulkEditModal sets one QSL/upload status field to a chosen value across the // selected QSOs — e.g. flip a filtered batch of imported contacts from N to R // so they become eligible for upload. export function BulkEditModal({ open, ids, onClose, onApplied }: Props) { const { t } = useI18n(); const [field, setField] = useState('hrdlog_upload'); const [statusValue, setStatusValue] = useState('R'); const [textValue, setTextValue] = useState(''); const [busy, setBusy] = useState(false); const [error, setError] = useState(''); const def = useMemo(() => FIELDS.find((f) => f.id === field) ?? FIELDS[0], [field]); const isStatus = def.kind === 'status'; const effectiveValue = isStatus ? (statusValue === '_' ? '' : statusValue) : textValue.trim(); async function apply() { setBusy(true); setError(''); try { const n = await BulkUpdateField(ids as any, field, effectiveValue); onApplied(Number(n) || 0); onClose(); } catch (e: any) { setError(String(e?.message ?? e)); } finally { setBusy(false); } } return ( { if (!o) onClose(); }}> {t('bulk.title')} {t('bulk.desc', { n: ids.length })}
{isStatus ? ( ) : ( setTextValue(def.upper ? e.target.value.toUpperCase() : e.target.value)} /> )}
{t('bulk.willSet')} {t(def.label)} ={' '} {effectiveValue === '' ? t('bulk.blank') : effectiveValue} {t('bulk.onQsos', { n: ids.length })}
{error &&
{error}
}
); }