import { BatteryCharging, Bot, Car, Check, Home, Shield, Thermometer, Wrench, X, type LucideIcon, } from 'lucide-react' import axios from 'axios' import { useCallback, useMemo, useState } from 'react' import { toast } from 'sonner' import { postSiteMode } from '../api/backend' export type OperatingModeCode = 'AUTO' | 'SELF_SUSTAIN' | 'CHARGE_CHEAP' | 'PRESERVE' | 'MANUAL' type ModeDef = { code: OperatingModeCode title: string description: string ev: boolean hp: boolean Icon: LucideIcon } const MODES: ModeDef[] = [ { code: 'AUTO', title: 'AUTO', description: 'EMS řídí FVE, baterii, EV a TČ podle plánu a cen.', ev: true, hp: true, Icon: Bot, }, { code: 'SELF_SUSTAIN', title: 'SELF_SUSTAIN', description: 'Autonomní domácí režim bez exportu; EV a TČ zastaveny.', ev: false, hp: false, Icon: Home, }, { code: 'CHARGE_CHEAP', title: 'CHARGE_CHEAP', description: 'Max. nabíjení baterie; EV a TČ vypnuty.', ev: false, hp: false, Icon: BatteryCharging, }, { code: 'PRESERVE', title: 'PRESERVE', description: 'Držení SoC; EV a TČ zastaveny (dovolená / servis).', ev: false, hp: false, Icon: Shield, }, { code: 'MANUAL', title: 'MANUAL', description: 'Servisní režim; žádné řízení z EMS.', ev: false, hp: false, Icon: Wrench, }, ] function modeBadgeRing(code: string): string { const c = code.toUpperCase() if (c === 'AUTO') return 'ring-emerald-500/50' if (c === 'SELF_SUSTAIN') return 'ring-cyan-500/50' if (c === 'CHARGE_CHEAP') return 'ring-violet-500/50' if (c === 'PRESERVE') return 'ring-amber-500/50' if (c === 'MANUAL') return 'ring-slate-500/50' return 'ring-slate-600' } type Props = { siteId: number | null currentMode: string | null | undefined onModeApplied?: () => void } export function ModeSelector({ siteId, currentMode, onModeApplied }: Props) { const [pending, setPending] = useState(null) const [notes, setNotes] = useState('') const [validUntilLocal, setValidUntilLocal] = useState('') const [optimisticMode, setOptimisticMode] = useState(null) const [submitting, setSubmitting] = useState(false) const displayMode = optimisticMode ?? currentMode ?? null const normalizedCurrent = (displayMode ?? '').toUpperCase() const closeModal = useCallback(() => { setPending(null) setNotes('') setValidUntilLocal('') }, []) const confirmSwitch = useCallback(async () => { if (siteId == null || pending == null) return const modeCode = pending const notePayload = notes.trim() === '' ? null : notes.trim() const valid_until = validUntilLocal.trim() === '' ? null : new Date(validUntilLocal).toISOString() setSubmitting(true) setOptimisticMode(modeCode) closeModal() try { await postSiteMode(siteId, { mode: modeCode, notes: notePayload, valid_until, }) setOptimisticMode(null) onModeApplied?.() toast.success(`Režim ${modeCode} byl aktivován.`) } catch (e: unknown) { setOptimisticMode(null) let msg = String(e) if (axios.isAxiosError(e)) { const d = e.response?.data as { detail?: unknown } | undefined if (d?.detail != null) { msg = Array.isArray(d.detail) ? d.detail.map((x) => JSON.stringify(x)).join('; ') : String(d.detail) } else if (e.message) { msg = e.message } } toast.error('Přepnutí režimu se nezdařilo', { description: msg }) } finally { setSubmitting(false) } }, [siteId, pending, notes, validUntilLocal, closeModal, onModeApplied]) const openConfirm = useCallback( (code: OperatingModeCode) => { if (siteId == null) { toast.error('Chybí lokalita (site_id).') return } if (code === normalizedCurrent) return setPending(code) setNotes('') setValidUntilLocal('') }, [siteId, normalizedCurrent], ) const modalTitle = useMemo(() => { if (!pending) return '' const m = MODES.find((x) => x.code === pending) return m?.title ?? pending }, [pending]) return (
{MODES.map(({ code, title, description, ev, hp, Icon }) => { const active = normalizedCurrent === code return ( ) })}
{pending ? (
{ if (ev.target === ev.currentTarget) closeModal() }} >

Přepnout na {modalTitle}?

Změna se zapíše do DB a odešle se signál do Loxone (je-li endpoint).