From ef5b381f8ea8da125f27eb0eb7cfb3d9edf38ba8 Mon Sep 17 00:00:00 2001 From: Maurice Date: Wed, 1 Apr 2026 15:30:59 +0200 Subject: [PATCH] feat: collapse days hides map markers, Immich test-before-save (#216) Map markers: - Collapsing a day in the sidebar hides its places from the map - Places assigned to multiple days only hide when all days collapsed - Unplanned places always stay visible Immich settings: - New POST /integrations/immich/test endpoint validates credentials without saving them - Save button disabled until test connection passes - Changing URL or API key resets test status - i18n: testFirst key for all 12 languages --- .../src/components/Planner/DayPlanSidebar.tsx | 3 ++ client/src/i18n/translations/ar.ts | 1 + client/src/i18n/translations/br.ts | 1 + client/src/i18n/translations/cs.ts | 1 + client/src/i18n/translations/de.ts | 1 + client/src/i18n/translations/en.ts | 1 + client/src/i18n/translations/es.ts | 1 + client/src/i18n/translations/fr.ts | 1 + client/src/i18n/translations/hu.ts | 1 + client/src/i18n/translations/it.ts | 1 + client/src/i18n/translations/nl.ts | 1 + client/src/i18n/translations/ru.ts | 1 + client/src/i18n/translations/zh.ts | 1 + client/src/pages/SettingsPage.tsx | 19 +++++++------ client/src/pages/TripPlannerPage.tsx | 28 +++++++++++++++++-- server/src/routes/immich.ts | 18 ++++++++++++ 16 files changed, 70 insertions(+), 10 deletions(-) diff --git a/client/src/components/Planner/DayPlanSidebar.tsx b/client/src/components/Planner/DayPlanSidebar.tsx index 2c85a4f..1b062c7 100644 --- a/client/src/components/Planner/DayPlanSidebar.tsx +++ b/client/src/components/Planner/DayPlanSidebar.tsx @@ -79,6 +79,7 @@ interface DayPlanSidebarProps { reservations?: Reservation[] onAddReservation: () => void onNavigateToFiles?: () => void + onExpandedDaysChange?: (expandedDayIds: Set) => void } const DayPlanSidebar = React.memo(function DayPlanSidebar({ @@ -91,6 +92,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar({ reservations = [], onAddReservation, onNavigateToFiles, + onExpandedDaysChange, }: DayPlanSidebarProps) { const toast = useToast() const { t, language, locale } = useTranslation() @@ -109,6 +111,7 @@ const DayPlanSidebar = React.memo(function DayPlanSidebar({ } catch {} return new Set(days.map(d => d.id)) }) + useEffect(() => { onExpandedDaysChange?.(expandedDays) }, [expandedDays]) const [editingDayId, setEditingDayId] = useState(null) const [editTitle, setEditTitle] = useState('') const [isCalculating, setIsCalculating] = useState(false) diff --git a/client/src/i18n/translations/ar.ts b/client/src/i18n/translations/ar.ts index e0587a9..5cac990 100644 --- a/client/src/i18n/translations/ar.ts +++ b/client/src/i18n/translations/ar.ts @@ -1341,6 +1341,7 @@ const ar: Record = { 'memories.immichUrl': 'عنوان خادم Immich', 'memories.immichApiKey': 'مفتاح API', 'memories.testConnection': 'اختبار الاتصال', + 'memories.testFirst': 'اختبر الاتصال أولاً', 'memories.connected': 'متصل', 'memories.disconnected': 'غير متصل', 'memories.connectionSuccess': 'تم الاتصال بـ Immich', diff --git a/client/src/i18n/translations/br.ts b/client/src/i18n/translations/br.ts index 9ea1d24..32bed17 100644 --- a/client/src/i18n/translations/br.ts +++ b/client/src/i18n/translations/br.ts @@ -1392,6 +1392,7 @@ const br: Record = { 'memories.immichUrl': 'URL do servidor Immich', 'memories.immichApiKey': 'Chave da API', 'memories.testConnection': 'Testar conexão', + 'memories.testFirst': 'Teste a conexão primeiro', 'memories.connected': 'Conectado', 'memories.disconnected': 'Não conectado', 'memories.connectionSuccess': 'Conectado ao Immich', diff --git a/client/src/i18n/translations/cs.ts b/client/src/i18n/translations/cs.ts index 09c625b..326ab00 100644 --- a/client/src/i18n/translations/cs.ts +++ b/client/src/i18n/translations/cs.ts @@ -1341,6 +1341,7 @@ const cs: Record = { 'memories.immichUrl': 'URL serveru Immich', 'memories.immichApiKey': 'API klíč', 'memories.testConnection': 'Otestovat připojení', + 'memories.testFirst': 'Nejprve otestujte připojení', 'memories.connected': 'Připojeno', 'memories.disconnected': 'Nepřipojeno', 'memories.connectionSuccess': 'Připojeno k Immich', diff --git a/client/src/i18n/translations/de.ts b/client/src/i18n/translations/de.ts index f7ba5da..7010c61 100644 --- a/client/src/i18n/translations/de.ts +++ b/client/src/i18n/translations/de.ts @@ -1338,6 +1338,7 @@ const de: Record = { 'memories.immichUrl': 'Immich Server URL', 'memories.immichApiKey': 'API-Schlüssel', 'memories.testConnection': 'Verbindung testen', + 'memories.testFirst': 'Verbindung zuerst testen', 'memories.connected': 'Verbunden', 'memories.disconnected': 'Nicht verbunden', 'memories.connectionSuccess': 'Verbindung zu Immich hergestellt', diff --git a/client/src/i18n/translations/en.ts b/client/src/i18n/translations/en.ts index 3a6cef8..ef12cda 100644 --- a/client/src/i18n/translations/en.ts +++ b/client/src/i18n/translations/en.ts @@ -1335,6 +1335,7 @@ const en: Record = { 'memories.immichUrl': 'Immich Server URL', 'memories.immichApiKey': 'API Key', 'memories.testConnection': 'Test connection', + 'memories.testFirst': 'Test connection first', 'memories.connected': 'Connected', 'memories.disconnected': 'Not connected', 'memories.connectionSuccess': 'Connected to Immich', diff --git a/client/src/i18n/translations/es.ts b/client/src/i18n/translations/es.ts index 82d2bd6..8c29be0 100644 --- a/client/src/i18n/translations/es.ts +++ b/client/src/i18n/translations/es.ts @@ -1291,6 +1291,7 @@ const es: Record = { 'memories.immichUrl': 'URL del servidor Immich', 'memories.immichApiKey': 'Clave API', 'memories.testConnection': 'Probar conexión', + 'memories.testFirst': 'Probar conexión primero', 'memories.connected': 'Conectado', 'memories.disconnected': 'No conectado', 'memories.connectionSuccess': 'Conectado a Immich', diff --git a/client/src/i18n/translations/fr.ts b/client/src/i18n/translations/fr.ts index 2abd843..fc011a7 100644 --- a/client/src/i18n/translations/fr.ts +++ b/client/src/i18n/translations/fr.ts @@ -1337,6 +1337,7 @@ const fr: Record = { 'memories.immichUrl': 'URL du serveur Immich', 'memories.immichApiKey': 'Clé API', 'memories.testConnection': 'Tester la connexion', + 'memories.testFirst': 'Testez la connexion avant de sauvegarder', 'memories.connected': 'Connecté', 'memories.disconnected': 'Non connecté', 'memories.connectionSuccess': 'Connecté à Immich', diff --git a/client/src/i18n/translations/hu.ts b/client/src/i18n/translations/hu.ts index 9f0c608..c1c5630 100644 --- a/client/src/i18n/translations/hu.ts +++ b/client/src/i18n/translations/hu.ts @@ -1408,6 +1408,7 @@ const hu: Record = { 'memories.immichUrl': 'Immich szerver URL', 'memories.immichApiKey': 'API kulcs', 'memories.testConnection': 'Kapcsolat tesztelése', + 'memories.testFirst': 'Először teszteld a kapcsolatot', 'memories.connected': 'Csatlakoztatva', 'memories.disconnected': 'Nincs csatlakoztatva', 'memories.connectionSuccess': 'Csatlakozva az Immichhez', diff --git a/client/src/i18n/translations/it.ts b/client/src/i18n/translations/it.ts index 5c3ee54..0cb96ea 100644 --- a/client/src/i18n/translations/it.ts +++ b/client/src/i18n/translations/it.ts @@ -1338,6 +1338,7 @@ const it: Record = { 'memories.immichUrl': 'URL Server Immich', 'memories.immichApiKey': 'Chiave API', 'memories.testConnection': 'Test connessione', + 'memories.testFirst': 'Testa prima la connessione', 'memories.connected': 'Connesso', 'memories.disconnected': 'Non connesso', 'memories.connectionSuccess': 'Connesso a Immich', diff --git a/client/src/i18n/translations/nl.ts b/client/src/i18n/translations/nl.ts index b47f198..ee8b6ba 100644 --- a/client/src/i18n/translations/nl.ts +++ b/client/src/i18n/translations/nl.ts @@ -1337,6 +1337,7 @@ const nl: Record = { 'memories.immichUrl': 'Immich Server URL', 'memories.immichApiKey': 'API-sleutel', 'memories.testConnection': 'Verbinding testen', + 'memories.testFirst': 'Test eerst de verbinding', 'memories.connected': 'Verbonden', 'memories.disconnected': 'Niet verbonden', 'memories.connectionSuccess': 'Verbonden met Immich', diff --git a/client/src/i18n/translations/ru.ts b/client/src/i18n/translations/ru.ts index 9f74102..5264729 100644 --- a/client/src/i18n/translations/ru.ts +++ b/client/src/i18n/translations/ru.ts @@ -1337,6 +1337,7 @@ const ru: Record = { 'memories.immichUrl': 'URL сервера Immich', 'memories.immichApiKey': 'API-ключ', 'memories.testConnection': 'Проверить подключение', + 'memories.testFirst': 'Сначала проверьте подключение', 'memories.connected': 'Подключено', 'memories.disconnected': 'Не подключено', 'memories.connectionSuccess': 'Подключение к Immich установлено', diff --git a/client/src/i18n/translations/zh.ts b/client/src/i18n/translations/zh.ts index b376baf..b78b196 100644 --- a/client/src/i18n/translations/zh.ts +++ b/client/src/i18n/translations/zh.ts @@ -1337,6 +1337,7 @@ const zh: Record = { 'memories.immichUrl': 'Immich 服务器地址', 'memories.immichApiKey': 'API 密钥', 'memories.testConnection': '测试连接', + 'memories.testFirst': '请先测试连接', 'memories.connected': '已连接', 'memories.disconnected': '未连接', 'memories.connectionSuccess': '已连接到 Immich', diff --git a/client/src/pages/SettingsPage.tsx b/client/src/pages/SettingsPage.tsx index 36d29a1..94f46d9 100644 --- a/client/src/pages/SettingsPage.tsx +++ b/client/src/pages/SettingsPage.tsx @@ -142,14 +142,16 @@ export default function SettingsPage(): React.ReactElement { } }, [memoriesEnabled]) + const [immichTestPassed, setImmichTestPassed] = useState(false) + const handleSaveImmich = async () => { setSaving(s => ({ ...s, immich: true })) try { await apiClient.put('/integrations/immich/settings', { immich_url: immichUrl, immich_api_key: immichApiKey || undefined }) toast.success(t('memories.saved')) - // Test connection const res = await apiClient.get('/integrations/immich/status') setImmichConnected(res.data.connected) + setImmichTestPassed(false) } catch { toast.error(t('memories.connectionError')) } finally { @@ -160,13 +162,13 @@ export default function SettingsPage(): React.ReactElement { const handleTestImmich = async () => { setImmichTesting(true) try { - const res = await apiClient.get('/integrations/immich/status') + const res = await apiClient.post('/integrations/immich/test', { immich_url: immichUrl, immich_api_key: immichApiKey }) if (res.data.connected) { toast.success(`${t('memories.connectionSuccess')} — ${res.data.user?.name || ''}`) - setImmichConnected(true) + setImmichTestPassed(true) } else { toast.error(`${t('memories.connectionError')}: ${res.data.error}`) - setImmichConnected(false) + setImmichTestPassed(false) } } catch { toast.error(t('memories.connectionError')) @@ -676,19 +678,20 @@ export default function SettingsPage(): React.ReactElement {
- setImmichUrl(e.target.value)} + { setImmichUrl(e.target.value); setImmichTestPassed(false) }} placeholder="https://immich.example.com" className="w-full px-3 py-2.5 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-slate-300" />
- setImmichApiKey(e.target.value)} + { setImmichApiKey(e.target.value); setImmichTestPassed(false) }} placeholder={immichConnected ? '••••••••' : 'API Key'} className="w-full px-3 py-2.5 border border-slate-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-slate-300" />
-