diff --git a/client/src/components/Dashboard/TimezoneWidget.tsx b/client/src/components/Dashboard/TimezoneWidget.tsx index 5ee97e51..087937a7 100644 --- a/client/src/components/Dashboard/TimezoneWidget.tsx +++ b/client/src/components/Dashboard/TimezoneWidget.tsx @@ -51,6 +51,9 @@ export default function TimezoneWidget() { }) const [now, setNow] = useState(Date.now()) const [showAdd, setShowAdd] = useState(false) + const [customLabel, setCustomLabel] = useState('') + const [customTz, setCustomTz] = useState('') + const [customError, setCustomError] = useState('') useEffect(() => { const i = setInterval(() => setNow(Date.now()), 10000) @@ -61,6 +64,20 @@ export default function TimezoneWidget() { localStorage.setItem('dashboard_timezones', JSON.stringify(zones)) }, [zones]) + const isValidTz = (tz: string) => { + try { Intl.DateTimeFormat('en-US', { timeZone: tz }).format(new Date()); return true } catch { return false } + } + + const addCustomZone = () => { + const tz = customTz.trim() + if (!tz) { setCustomError(t('dashboard.timezoneCustomErrorEmpty')); return } + if (!isValidTz(tz)) { setCustomError(t('dashboard.timezoneCustomErrorInvalid')); return } + if (zones.find(z => z.tz === tz)) { setCustomError(t('dashboard.timezoneCustomErrorDuplicate')); return } + const label = customLabel.trim() || tz.split('/').pop()?.replace(/_/g, ' ') || tz + setZones([...zones, { label, tz }]) + setCustomLabel(''); setCustomTz(''); setCustomError(''); setShowAdd(false) + } + const addZone = (zone) => { if (!zones.find(z => z.tz === zone.tz)) { setZones([...zones, zone]) @@ -108,7 +125,29 @@ export default function TimezoneWidget() { {/* Add zone dropdown */} {showAdd && ( -
+
+ {/* Custom timezone */} +
+

{t('dashboard.timezoneCustomTitle')}

+
+ setCustomLabel(e.target.value)} + placeholder={t('dashboard.timezoneCustomLabelPlaceholder')} + className="w-full px-2 py-1.5 rounded-lg text-xs outline-none" + style={{ background: 'var(--bg-secondary)', color: 'var(--text-primary)', border: '1px solid var(--border-secondary)' }} /> + { setCustomTz(e.target.value); setCustomError('') }} + placeholder={t('dashboard.timezoneCustomTzPlaceholder')} + className="w-full px-2 py-1.5 rounded-lg text-xs outline-none" + style={{ background: 'var(--bg-secondary)', color: 'var(--text-primary)', border: `1px solid ${customError ? '#ef4444' : 'var(--border-secondary)'}` }} + onKeyDown={e => { if (e.key === 'Enter') addCustomZone() }} /> + {customError &&

{customError}

} + +
+
+ {/* Popular zones */} {POPULAR_ZONES.filter(z => !zones.find(existing => existing.tz === z.tz)).map(z => (