"use client"; import { CheckCircle2, CircleDashed, XCircle } from "lucide-react"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { FieldLabel, SettingsCard, SettingsSection, } from "@/components/settings/settings-parts"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Switch } from "@/components/ui/switch"; import { type IntegrationConfig, type IntegrationType, listIntegrations, saveIntegration, testIntegration, } from "@/lib/integrations"; import { notify } from "@/lib/toast"; const TYPES: IntegrationType[] = ["fhir", "eprescribe", "claims"]; function StatusBadge({ config }: { config: IntegrationConfig }) { const { t } = useTranslation(); if (config.status === "connected") { return ( {t("settings.integrations.status.connected")} ); } if (config.status === "error") { return ( {t("settings.integrations.status.error")} ); } return ( {t("settings.integrations.status.unconfigured")} ); } function IntegrationCard({ type, initial, }: { type: IntegrationType; initial: IntegrationConfig; }) { const { t } = useTranslation(); const [endpoint, setEndpoint] = useState(initial.endpoint); const [enabled, setEnabled] = useState(initial.enabled); // Empty = leave the stored secret untouched; typing replaces it. const [credentials, setCredentials] = useState(""); const [config, setConfig] = useState(initial); const [saving, setSaving] = useState(false); const [testing, setTesting] = useState(false); const dirty = endpoint !== config.endpoint || enabled !== config.enabled || credentials.length > 0; const save = async () => { setSaving(true); try { const saved = await saveIntegration(type, { endpoint, enabled, ...(credentials ? { credentials } : {}), }); setConfig(saved); setEndpoint(saved.endpoint); setEnabled(saved.enabled); setCredentials(""); notify.success( t("settings.integrations.savedTitle"), t(`settings.integrations.${type}.title`), ); } catch { notify.error( t("settings.integrations.saveFailedTitle"), t("settings.integrations.saveFailedBody"), ); } finally { setSaving(false); } }; const test = async () => { setTesting(true); try { const result = await testIntegration(type); if (result.ok) { notify.success(t("settings.integrations.testOk"), result.message); } else { notify.error(t("settings.integrations.testFailed"), result.message); } } catch { notify.error( t("settings.integrations.testFailed"), t("settings.integrations.testError"), ); } finally { setTesting(false); } }; return ( } description={t(`settings.integrations.${type}.description`)} title={t(`settings.integrations.${type}.title`)} >
{t("settings.integrations.endpoint")} setEndpoint(e.target.value)} placeholder={t(`settings.integrations.${type}.endpointPlaceholder`)} value={endpoint} />
{t("settings.integrations.credentials")} setCredentials(e.target.value)} placeholder={ config.hasCredentials ? t("settings.integrations.credentialsSet") : t(`settings.integrations.${type}.credentialsPlaceholder`) } type="password" value={credentials} />

{t(`settings.integrations.${type}.credentialsHint`)}

{config.lastSyncAt ? ( {t("settings.integrations.lastSync", { when: new Date(config.lastSyncAt).toLocaleString(), })} ) : null}
); } export function IntegrationsPanel() { const { t } = useTranslation(); const [configs, setConfigs] = useState(null); useEffect(() => { let active = true; listIntegrations() .then((rows) => active && setConfigs(rows)) .catch(() => active && setConfigs([])); return () => { active = false; }; }, []); if (configs === null) { return (

{t("settings.integrations.loading")}

); } return (

{t("settings.integrations.intro")}

{TYPES.map((type) => { const initial = configs.find((c) => c.type === type) ?? ({ type, endpoint: "", enabled: false, status: "unconfigured", hasCredentials: false, lastSyncAt: null, } satisfies IntegrationConfig); return ; })}
); }