import { useEffect, useState } from "preact/hooks"; import { Trash2, Check } from "lucide-preact"; import { useBool } from "@common/hooks/useBool"; import { useInputState } from "@common/hooks/useInputState"; import { Modal } from "@common/components/Modal"; import clsx from "clsx"; import styles from "../../assets/manage-workers-modal.module.css"; import ui from "@common/assets/ui.module.css"; import { useHordeState } from "../../contexts/state"; import { deleteWorker, fetchWorker, updateWorker, type WorkerData } from "../../utils/api"; interface Props { open: boolean; onClose: () => void; } interface WorkerEdit { name: string; info: string; maintenance_mode: boolean; } interface WorkerCard { data: WorkerData; edit: WorkerEdit; saving: boolean; saved: boolean; deleting: boolean; confirmDelete: boolean; } export const ManageWorkersModal = ({ open, onClose }: Props) => { const { state, dispatch } = useHordeState(); const { user, apiKey } = state; const [workers, setWorkers] = useState([]); const [error, setError] = useState(null); const loading = useBool(false); useEffect(() => { if (!open || !user || !apiKey) return; let cancelled = false; loading.setTrue(); setError(null); Promise.all(user.worker_ids.map(id => fetchWorker(id, apiKey))) .then(details => { if (cancelled) return; const sorted = details.sort((a, b) => { if (a.online !== b.online) return a.online ? -1 : 1; return a.name.localeCompare(b.name); }); setWorkers(sorted.map(w => ({ data: w, edit: { name: w.name, info: w.info ?? '', maintenance_mode: w.maintenance_mode }, saving: false, saved: false, deleting: false, confirmDelete: false, }))); }) .catch(() => { if (!cancelled) setError('Failed to load workers'); }) .finally(() => { if (!cancelled) loading.setFalse(); }); return () => { cancelled = true; }; }, [open, user?.worker_ids.join(',')]); const setEdit = (id: string, patch: Partial) => { setWorkers(prev => prev.map(w => w.data.id === id ? { ...w, edit: { ...w.edit, ...patch }, saved: false } : w )); }; const save = async (id: string) => { if (!apiKey) return; setWorkers(prev => prev.map(w => w.data.id === id ? { ...w, saving: true } : w)); try { const worker = workers.find(w => w.data.id === id)!; const updated = await updateWorker(id, apiKey, worker.edit); setWorkers(prev => prev.map(w => w.data.id === id ? { ...w, data: updated, saving: false, saved: true } : w )); setTimeout(() => { setWorkers(prev => prev.map(w => w.data.id === id ? { ...w, saved: false } : w )); }, 1500); } catch { setError('Failed to save worker'); setWorkers(prev => prev.map(w => w.data.id === id ? { ...w, saving: false } : w )); } }; const startDelete = (id: string) => { setWorkers(prev => prev.map(w => w.data.id === id ? { ...w, confirmDelete: true } : w )); }; const cancelDelete = (id: string) => { setWorkers(prev => prev.map(w => w.data.id === id ? { ...w, confirmDelete: false } : w )); }; const confirmDelete = async (id: string) => { if (!apiKey) return; setWorkers(prev => prev.map(w => w.data.id === id ? { ...w, deleting: true, confirmDelete: false } : w )); try { await deleteWorker(id, apiKey); setWorkers(prev => prev.filter(w => w.data.id !== id)); dispatch({ type: 'SET_USER', user: { ...user!, worker_ids: user!.worker_ids.filter(i => i !== id) } }); } catch { setError('Failed to delete worker'); setWorkers(prev => prev.map(w => w.data.id === id ? { ...w, deleting: false } : w )); } }; const hasChanges = workers.some(w => w.edit.name !== w.data.name || w.edit.info !== (w.data.info ?? '') || w.edit.maintenance_mode !== w.data.maintenance_mode ); const footer = (
{workers.length} worker{workers.length !== 1 ? 's' : ''} {hasChanges && ' · unsaved changes'} {hasChanges && (
)}
); const hasChangesForWorker = (w: WorkerCard) => w.edit.name !== w.data.name || w.edit.info !== (w.data.info ?? '') || w.edit.maintenance_mode !== w.data.maintenance_mode; return ( {loading.value &&

Loading workers…

} {error &&

{error}

} {!loading.value && !error && workers.length === 0 && (

No workers found.

)} {!loading.value && !error && workers.length > 0 && (
{workers.map(w => (
{w.data.name}
{w.data.models.slice(0, 3).join(', ')} {w.data.models.length > 3 && ` +${w.data.models.length - 3}`}
{w.data.online ? 'online' : 'offline'} {w.data.maintenance_mode && ( maint )}
setEdit(w.data.id, { name: (e.target as HTMLInputElement).value })} />