fix: QTH field is wider to see the full city names

This commit is contained in:
2026-07-16 22:47:24 +02:00
parent 7cf2dfeaf9
commit dcf006905c
3 changed files with 36 additions and 12 deletions
+10 -6
View File
@@ -2866,12 +2866,12 @@ export default function App() {
</div> </div>
</> </>
) : ( ) : (
<div className="flex flex-col flex-[0.55] min-w-[70px]"><Label className="mb-1 h-3.5">{t('field.qth')}</Label> <div className="flex flex-col flex-1 min-w-[130px]"><Label className="mb-1 h-3.5">{t('field.qth')}</Label>
<Input value={qth} onChange={(e) => { setQth(e.target.value); markEdited('qth'); }} /> <Input value={qth} onChange={(e) => { setQth(e.target.value); markEdited('qth'); }} />
</div> </div>
); );
const gridBlock = ( const gridBlock = (
<div className="flex flex-col w-28 shrink-0"><Label className="mb-1 h-3.5">{t('field.grid')}</Label> <div className="flex flex-col w-[76px] shrink-0"><Label className="mb-1 h-3.5">{t('field.grid')}</Label>
<Input value={grid} placeholder="JN05" className="font-mono" onChange={(e) => { setGrid(e.target.value); markEdited('grid'); }} /> <Input value={grid} placeholder="JN05" className="font-mono" onChange={(e) => { setGrid(e.target.value); markEdited('grid'); }} />
</div> </div>
); );
@@ -2886,7 +2886,7 @@ export default function App() {
: 'border-danger text-danger bg-danger/15'; : 'border-danger text-danger bg-danger/15';
return ( return (
<div className="self-end shrink-0" title={t('lotw.userTip', { date: lotwInfo.last_upload || '?', days: d })}> <div className="self-end shrink-0" title={t('lotw.userTip', { date: lotwInfo.last_upload || '?', days: d })}>
<span className={cn('inline-flex items-center rounded-md border px-1.5 py-1.5 text-[10px] font-extrabold tracking-wide leading-none', cls)}> <span className={cn('inline-flex items-center rounded-md border px-1.5 py-1 text-[9px] font-extrabold tracking-wide leading-none', cls)}>
LoTW LoTW
</span> </span>
</div> </div>
@@ -3910,9 +3910,13 @@ export default function App() {
</div> </div>
{qthBlock} {qthBlock}
{gridBlock} {gridBlock}
{lotwBlock} {/* LoTW badge, offline outbox and the spot-alert LED grouped at the
{offlineBlock} right edge, so the alert always has a clear spot. */}
{alertLedBlock} <div className="ml-auto flex items-end gap-2 shrink-0">
{lotwBlock}
{offlineBlock}
{alertLedBlock}
</div>
</div> </div>
{/* Row 3: tight left detail column (Band/Mode/Country) and {/* Row 3: tight left detail column (Band/Mode/Country) and
@@ -225,6 +225,7 @@ export function StationControlPanel({ centerLat, centerLon, bearing }: RotatorPr
try { const v = JSON.parse(localStorage.getItem('opslog.stationOrder') || '[]'); return Array.isArray(v) ? v : []; } catch { return []; } try { const v = JSON.parse(localStorage.getItem('opslog.stationOrder') || '[]'); return Array.isArray(v) ? v : []; } catch { return []; }
}); });
const dragId = useRef<string | null>(null); const dragId = useRef<string | null>(null);
const [cols, setCols] = useState<string>(() => localStorage.getItem('opslog.stationCols') || 'auto');
const loadDevices = useCallback(async () => { const loadDevices = useCallback(async () => {
try { setDevices(((await GetStationDevices()) ?? []) as Device[]); } catch { /* db not ready */ } try { setDevices(((await GetStationDevices()) ?? []) as Device[]); } catch { /* db not ready */ }
@@ -353,13 +354,32 @@ export function StationControlPanel({ centerLat, centerLon, bearing }: RotatorPr
const noDevices = devices.length === 0 && !rot.enabled && !ant.enabled; const noDevices = devices.length === 0 && !rot.enabled && !ant.enabled;
// Column count controls the layout: with 4 widgets, choosing "2" lays them out
// 2×2 — which linear drag-reorder alone can't do, since the grid auto-flows to
// fill however many columns are available.
const gridCols: Record<string, string> = {
auto: 'sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4',
'1': 'grid-cols-1', '2': 'grid-cols-2', '3': 'grid-cols-3', '4': 'grid-cols-4',
};
return ( return (
<div className="flex-1 min-h-0 overflow-auto p-4"> <div className="flex-1 min-h-0 overflow-auto p-4">
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
<h2 className="text-sm font-bold uppercase tracking-wider text-muted-foreground">{t('station.title')}</h2> <h2 className="text-sm font-bold uppercase tracking-wider text-muted-foreground">{t('station.title')}</h2>
<Button size="sm" variant="outline" onClick={() => setEditing(blankDevice())}> <div className="flex items-center gap-2">
<Plus className="size-3.5 mr-1" /> {t('station.addDevice')} <div className="flex items-center rounded-md border border-border overflow-hidden text-[11px]">
</Button> {(['auto', '2', '3', '4'] as const).map((c) => (
<button key={c} type="button"
onClick={() => { setCols(c); writeUiPref('opslog.stationCols', c); }}
className={cn('px-2 py-1 font-medium', cols === c ? 'bg-primary text-primary-foreground' : 'text-muted-foreground hover:bg-muted')}>
{c === 'auto' ? t('station.colsAuto') : c}
</button>
))}
</div>
<Button size="sm" variant="outline" onClick={() => setEditing(blankDevice())}>
<Plus className="size-3.5 mr-1" /> {t('station.addDevice')}
</Button>
</div>
</div> </div>
{noDevices && !editing && ( {noDevices && !editing && (
@@ -368,7 +388,7 @@ export function StationControlPanel({ centerLat, centerLon, bearing }: RotatorPr
</div> </div>
)} )}
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 items-start"> <div className={cn('grid gap-3 items-start', gridCols[cols] ?? gridCols.auto)}>
{ordered.map((w) => ( {ordered.map((w) => (
<div key={w.id} draggable <div key={w.id} draggable
onDragStart={(e) => { dragId.current = w.id; e.dataTransfer.effectAllowed = 'move'; }} onDragStart={(e) => { dragId.current = w.id; e.dataTransfer.effectAllowed = 'move'; }}
+2 -2
View File
@@ -89,7 +89,7 @@ const en: Dict = {
'sec.confirmations': 'Confirmations', 'sec.external': 'External services', 'sec.confirmations': 'Confirmations', 'sec.external': 'External services',
'sec.general': 'General', 'sec.email': 'E-mail (SMTP)', 'sec.lookup': 'Callsign Lookup', 'sec.general': 'General', 'sec.email': 'E-mail (SMTP)', 'sec.lookup': 'Callsign Lookup',
'sec.bands': 'Bands', 'sec.modes': 'Modes & default RST', 'sec.cluster': 'DX Cluster', 'sec.bands': 'Bands', 'sec.modes': 'Modes & default RST', 'sec.cluster': 'DX Cluster',
'sec.udp': 'UDP integrations', 'sec.database': 'Database', 'sec.autostart': 'Autostart', 'sec.backup': 'Database backup', 'station.title': 'Station Control', 'station.rotator': 'Rotator', 'station.rotatorNoRead': 'No heading read', 'station.pattern': 'Pattern', 'station.bi': 'Bi', 'station.retract': 'Retract elements', 'station.moving': 'MOVING', 'station.elements': 'Elements (mm)', 'station.read': 'Read', 'station.readLengths': 'Read current element lengths from the controller', 'station.noLengths': 'Lengths unknown — click Read to fetch them from the controller.', 'station.element': 'Element', 'station.reflector': 'Reflector', 'station.driven': 'Driven', 'station.director': 'Dir', 'station.set': 'Set', 'station.elementsHint': 'Each press lengthens/shortens the element by 2 mm (like the physical console). Verify which element responds on your antenna.', 'station.go': 'Go', 'station.stop': 'Stop', 'station.dragHint': 'Drag a widget by its card to reorder.', 'station.addDevice': 'Add device', 'station.editDevice': 'Edit device', 'station.empty': 'No relay boards yet. Add a WebSwitch 1216H or a KMTronic 8-relay board to control your station power and accessories.', 'station.online': 'Online', 'station.offline': 'Offline', 'station.edit': 'Edit', 'station.delete': 'Delete', 'station.relay': 'Relay', 'station.on': 'ON', 'station.off': 'OFF', 'station.type': 'Device type', 'station.name': 'Name', 'station.host': 'Host / IP', 'station.user': 'Username', 'station.pass': 'Password', 'station.optional': 'optional', 'station.labels': 'Relay labels', 'station.cancel': 'Cancel', 'station.save': 'Save', 'sec.udp': 'UDP integrations', 'sec.database': 'Database', 'sec.autostart': 'Autostart', 'sec.backup': 'Database backup', 'station.title': 'Station Control', 'station.rotator': 'Rotator', 'station.rotatorNoRead': 'No heading read', 'station.pattern': 'Pattern', 'station.bi': 'Bi', 'station.retract': 'Retract elements', 'station.moving': 'MOVING', 'station.elements': 'Elements (mm)', 'station.read': 'Read', 'station.readLengths': 'Read current element lengths from the controller', 'station.noLengths': 'Lengths unknown — click Read to fetch them from the controller.', 'station.element': 'Element', 'station.reflector': 'Reflector', 'station.driven': 'Driven', 'station.director': 'Dir', 'station.set': 'Set', 'station.elementsHint': 'Each press lengthens/shortens the element by 2 mm (like the physical console). Verify which element responds on your antenna.', 'station.go': 'Go', 'station.stop': 'Stop', 'station.dragHint': 'Drag a widget by its card to reorder. Pick a column count to lay them out in a grid.', 'station.colsAuto': 'Auto', 'station.addDevice': 'Add device', 'station.editDevice': 'Edit device', 'station.empty': 'No relay boards yet. Add a WebSwitch 1216H or a KMTronic 8-relay board to control your station power and accessories.', 'station.online': 'Online', 'station.offline': 'Offline', 'station.edit': 'Edit', 'station.delete': 'Delete', 'station.relay': 'Relay', 'station.on': 'ON', 'station.off': 'OFF', 'station.type': 'Device type', 'station.name': 'Name', 'station.host': 'Host / IP', 'station.user': 'Username', 'station.pass': 'Password', 'station.optional': 'optional', 'station.labels': 'Relay labels', 'station.cancel': 'Cancel', 'station.save': 'Save',
'sec.awards': 'Awards', 'sec.cat': 'CAT interface', 'sec.rotator': 'Rotator', 'sec.winkeyer': 'CW Keyer', 'sec.awards': 'Awards', 'sec.cat': 'CAT interface', 'sec.rotator': 'Rotator', 'sec.winkeyer': 'CW Keyer',
'sec.antenna': 'Ultrabeam / Steppir', 'sec.antgenius': 'Antenna Genius', 'sec.pgxl': 'Power Genius', 'sec.flex': 'FlexRadio', 'sec.audio': 'Audio devices', 'sec.antenna': 'Ultrabeam / Steppir', 'sec.antgenius': 'Antenna Genius', 'sec.pgxl': 'Power Genius', 'sec.flex': 'FlexRadio', 'sec.audio': 'Audio devices',
// General panel // General panel
@@ -353,7 +353,7 @@ const fr: Dict = {
'sec.confirmations': 'Confirmations', 'sec.external': 'Services externes', 'sec.confirmations': 'Confirmations', 'sec.external': 'Services externes',
'sec.general': 'Général', 'sec.email': 'E-mail (SMTP)', 'sec.lookup': "Recherche d'indicatif", 'sec.general': 'Général', 'sec.email': 'E-mail (SMTP)', 'sec.lookup': "Recherche d'indicatif",
'sec.bands': 'Bandes', 'sec.modes': 'Modes & RST par défaut', 'sec.cluster': 'DX Cluster', 'sec.bands': 'Bandes', 'sec.modes': 'Modes & RST par défaut', 'sec.cluster': 'DX Cluster',
'sec.udp': 'Intégrations UDP', 'sec.database': 'Base de données', 'sec.autostart': 'Démarrage auto', 'sec.backup': 'Sauvegarde base', 'station.title': 'Contrôle station', 'station.rotator': 'Rotator', 'station.rotatorNoRead': 'Azimut non lu', 'station.pattern': 'Diagramme', 'station.bi': 'Bi', 'station.retract': 'Rétracter les éléments', 'station.moving': 'EN MOUVEMENT', 'station.elements': 'Éléments (mm)', 'station.read': 'Lire', 'station.readLengths': 'Lire les longueurs actuelles depuis le contrôleur', 'station.noLengths': 'Longueurs inconnues — clique sur Lire pour les récupérer depuis le contrôleur.', 'station.element': 'Élément', 'station.reflector': 'Réflecteur', 'station.driven': 'Radiateur', 'station.director': 'Dir', 'station.set': 'Régler', 'station.elementsHint': "Chaque appui allonge/raccourcit l'élément de 2 mm (comme le pupitre). Vérifie quel élément répond sur ton antenne.", 'station.go': 'Aller', 'station.stop': 'Stop', 'station.dragHint': 'Glisse une carte pour réordonner les widgets.', 'station.addDevice': 'Ajouter un appareil', 'station.editDevice': "Modifier l'appareil", 'station.empty': "Aucune carte relais. Ajoute un WebSwitch 1216H ou une carte KMTronic 8 relais pour piloter l'alimentation et les accessoires de ta station.", 'station.online': 'En ligne', 'station.offline': 'Hors ligne', 'station.edit': 'Modifier', 'station.delete': 'Supprimer', 'station.relay': 'Relais', 'station.on': 'ON', 'station.off': 'OFF', 'station.type': "Type d'appareil", 'station.name': 'Nom', 'station.host': 'Hôte / IP', 'station.user': "Nom d'utilisateur", 'station.pass': 'Mot de passe', 'station.optional': 'optionnel', 'station.labels': 'Libellés des relais', 'station.cancel': 'Annuler', 'station.save': 'Enregistrer', 'sec.udp': 'Intégrations UDP', 'sec.database': 'Base de données', 'sec.autostart': 'Démarrage auto', 'sec.backup': 'Sauvegarde base', 'station.title': 'Contrôle station', 'station.rotator': 'Rotator', 'station.rotatorNoRead': 'Azimut non lu', 'station.pattern': 'Diagramme', 'station.bi': 'Bi', 'station.retract': 'Rétracter les éléments', 'station.moving': 'EN MOUVEMENT', 'station.elements': 'Éléments (mm)', 'station.read': 'Lire', 'station.readLengths': 'Lire les longueurs actuelles depuis le contrôleur', 'station.noLengths': 'Longueurs inconnues — clique sur Lire pour les récupérer depuis le contrôleur.', 'station.element': 'Élément', 'station.reflector': 'Réflecteur', 'station.driven': 'Radiateur', 'station.director': 'Dir', 'station.set': 'Régler', 'station.elementsHint': "Chaque appui allonge/raccourcit l'élément de 2 mm (comme le pupitre). Vérifie quel élément répond sur ton antenne.", 'station.go': 'Aller', 'station.stop': 'Stop', 'station.dragHint': 'Glisse une carte pour réordonner. Choisis un nombre de colonnes pour la disposition.', 'station.colsAuto': 'Auto', 'station.addDevice': 'Ajouter un appareil', 'station.editDevice': "Modifier l'appareil", 'station.empty': "Aucune carte relais. Ajoute un WebSwitch 1216H ou une carte KMTronic 8 relais pour piloter l'alimentation et les accessoires de ta station.", 'station.online': 'En ligne', 'station.offline': 'Hors ligne', 'station.edit': 'Modifier', 'station.delete': 'Supprimer', 'station.relay': 'Relais', 'station.on': 'ON', 'station.off': 'OFF', 'station.type': "Type d'appareil", 'station.name': 'Nom', 'station.host': 'Hôte / IP', 'station.user': "Nom d'utilisateur", 'station.pass': 'Mot de passe', 'station.optional': 'optionnel', 'station.labels': 'Libellés des relais', 'station.cancel': 'Annuler', 'station.save': 'Enregistrer',
'sec.awards': 'Diplômes', 'sec.cat': 'Interface CAT', 'sec.rotator': 'Rotator', 'sec.winkeyer': 'Manipulateur CW', 'sec.awards': 'Diplômes', 'sec.cat': 'Interface CAT', 'sec.rotator': 'Rotator', 'sec.winkeyer': 'Manipulateur CW',
'sec.antenna': 'Antenne motorisée', 'sec.antgenius': 'Antenna Genius', 'sec.pgxl': 'Power Genius', 'sec.flex': 'FlexRadio', 'sec.audio': 'Périphériques audio', 'sec.antenna': 'Antenne motorisée', 'sec.antgenius': 'Antenna Genius', 'sec.pgxl': 'Power Genius', 'sec.flex': 'FlexRadio', 'sec.audio': 'Périphériques audio',
'gen.hint': 'Comportement de l\'application (enregistré immédiatement).', 'gen.hint': 'Comportement de l\'application (enregistré immédiatement).',