/** * Property On — Register «Aufgaben» des Personalblatts (§8). * * Die Liste zeigt jede Aufgabe des gewählten Mitarbeiters einzeln schaltbar. * Geändert wird zuerst nur ein lokaler Entwurf: Zuschalten und Pausieren * verändert, was ein digitaler Mitarbeiter im Alltag der Bewirtschaftung tut — * das darf nicht beiläufig im Hintergrund passieren, sondern erst mit einer * bewussten Freigabe über «Änderungen speichern» (§8.2). */ import { memo, useCallback, useMemo, useState } from 'react' import { Box, Button, Switch, Typography } from '@mui/material' import { Clock, Plug, RotateCcw, Save, ShieldCheck } from 'lucide-react' import type { AgentTask, TeamAgent } from '../../domain/teamAgent' import { AGENT_CHANNEL_LABELS, AGENT_SYSTEM_LABELS } from '../../lib/constants' import { DS_BG, DS_BORDER, DS_SHADOW, DS_SURFACE, DS_TEXT } from '../../lib/ds' import { GenericBadge } from '../shared/GenericBadge' import { useSaveAgentTasks } from '../../hooks/useTeamAgents' // ── Eine Aufgabenzeile ──────────────────────────────────────────────────────── interface TaskRowProps { task: AgentTask busy: boolean onToggle: (id: string, enabled: boolean) => void } /** * `React.memo` ist Pflicht: ein Mitarbeiter führt bis zu zwölf Aufgaben, und * jeder Schalterklick verändert den Entwurf der ganzen Liste (CLAUDE.md §10.2). */ const TaskRow = memo(function TaskRow({ task, busy, onToggle }: TaskRowProps) { const dependencies = useMemo(() => { const out: string[] = [] if (task.dependsOnChannel) { out.push(`Kanal: ${AGENT_CHANNEL_LABELS[task.dependsOnChannel] ?? task.dependsOnChannel}`) } if (task.dependsOnSystem) { out.push(`Systemzugang: ${AGENT_SYSTEM_LABELS[task.dependsOnSystem] ?? task.dependsOnSystem}`) } return out }, [task.dependsOnChannel, task.dependsOnSystem]) const handleChange = useCallback( (_event: unknown, checked: boolean) => onToggle(task.id, checked), [onToggle, task.id], ) return ( {/* Schalter mit Zustand als Text — Farbe allein trägt keinen Status (§18) */} {task.enabled ? 'Aktiv' : 'Pausiert'} {task.title} {task.description} Zeitplan: {task.schedule} {dependencies.map((dependency) => ( Setzt voraus — {dependency} ))} {task.requiresApproval && ( } tooltip="Das Ergebnis dieser Aufgabe wird erst nach Ihrer Freigabe wirksam." /> )} ) }) // ── Register «Aufgaben» ─────────────────────────────────────────────────────── export function AgentTasksTab({ agent }: { agent: TeamAgent }) { const [draft, setDraft] = useState(agent.tasks) const saveTasks = useSaveAgentTasks() /** * Beim Wechsel des Mitarbeiters wird der Entwurf neu aufgesetzt — sonst stünde * der Entwurf des zuvor gewählten Mitarbeiters in einem fremden Personalblatt. * Bewusst nur an `agent.id` gebunden: liefe der Effekt auch bei jeder neuen * Referenz von `agent.tasks`, würde ein Hintergrundabgleich unbestätigte * Änderungen still verwerfen. */ // Kein Effekt nötig: `Personalverwaltung` gibt diesem Reiter die Mitarbeiter-ID // als `key` mit, er wird beim Wechsel des Dossiers ohnehin neu aufgebaut. /** Ein Durchlauf für beide Ableitungen — Anzahl aktiver Aufgaben und Abweichung (§10.4). */ const { activeCount, dirty } = useMemo(() => { const saved = new Map(agent.tasks.map((task) => [task.id, task.enabled])) let active = 0 let changed = draft.length !== agent.tasks.length for (const task of draft) { if (task.enabled) active += 1 if (saved.get(task.id) !== task.enabled) changed = true } return { activeCount: active, dirty: changed } }, [draft, agent.tasks]) const handleToggle = useCallback((id: string, enabled: boolean) => { setDraft((prev) => prev.map((task) => (task.id === id ? { ...task, enabled } : task))) }, []) const handleReset = useCallback(() => setDraft(agent.tasks), [agent.tasks]) const handleSave = useCallback(() => { saveTasks.mutate({ id: agent.id, tasks: draft }) }, [saveTasks, agent.id, draft]) const busy = saveTasks.isPending const focusRing = { '&:focus-visible': { outline: `2px solid ${DS_TEXT.brand}`, outlineOffset: 2 } } return ( {activeCount} von {draft.length} Aufgaben aktiv Jede Aufgabe lässt sich einzeln zuschalten oder pausieren. Änderungen werden erst mit dem Speichern für {agent.name} wirksam. {dirty && ( Nicht gespeicherte Änderungen )} {draft.length === 0 ? ( Für {agent.name} sind noch keine Aufgaben hinterlegt. ) : ( {draft.map((task) => ( ))} )} ) }