feat(team): Datenschicht für Property On
Property On wird als Funktionsbereich "Teamübersicht" in Property Match eingebettet — sieben digitale Mitarbeiter, die ausführen, während der Mensch freigibt, anpasst oder zurückweist. Durchgehend Provider → Service → Hook, kein übersprungener Layer: - domain/: teamAgent, agentWorkItem, agentProtocol, agentConnection, agentFilters - mock-data/: 7 Personalblätter aus dem Agenten-Katalog (Aufgabenzahlen exakt: Ferdi 9, Sina 7, Lea 11, Bruno 9, Reto 10, Livia 11, Nora 8), 14 Vorgänge, 38 Protokolleinträge, 9 Verbindungen - provider/: vier Mockup-Provider mit versionierter localStorage-Persistenz - services/: Fachlogik inkl. Freigabe, Zurückweisung, Kennzahlen, Demo-Reset - hooks/: React Query, Mutationen entwerten Vorgänge, Kennzahlen und Protokoll gemeinsam — sonst zeigt die Oberfläche widersprüchliche Zahlen Namensgebung: Der Entitätstyp heisst TeamAgent, nicht Agent. Im Repo existiert bereits PowerOn als KI-Backend-Proxy, und Claude Code legt Arbeitskopien unter agent-* ab; ein nackter Typ Agent wäre in Suchen nicht auffindbar. Alle Satellitentypen sind Agent*-präfixiert, weil domain/index.ts per export * bündelt und generische Namen dort kollidieren. Eigener Protokolltyp statt ActivityEvent: den Namen gibt es im Repo dreifach und gegenseitig inkompatibel (domain/activityEvent.ts, governanceService.ts, PropertyActivityLogPanel.tsx). Ein Anschluss hätte den Konflikt zementiert. Demo-Uhr in lib/teamClock.ts: die Mockdaten sind auf den 20.05.2026 verankert. Gegen die Systemzeit gerechnet wären die Filter "heute / diese Woche / dieser Monat" an jedem anderen Kalendertag leer und die Auswertung sähe kaputt aus, obwohl sie korrekt rechnet. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
36ce548169
commit
52dc7de25e
38 files changed
+9055
No files matched your search
@@ -0,0 +1,128 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { agentWorkItemService } from '../services/agentWorkItemService'
|
||||
import type { AgentWorkItemFilters } from '../provider/IAgentWorkItemProvider'
|
||||
import type { AgentEditableField } from '../domain/agentWorkItem'
|
||||
import { useToastStore } from '../stores/toastStore'
|
||||
import { STALE_AGENT_WORK_ITEMS } from '../lib/constants'
|
||||
import { QK_AGENT_PROTOCOL } from './useAgentProtocol'
|
||||
|
||||
export const QK_AGENT_WORK_ITEMS = 'agentWorkItems'
|
||||
export const QK_AGENT_KPIS = 'agentKpis'
|
||||
|
||||
export function useAgentWorkItems(filters?: AgentWorkItemFilters) {
|
||||
return useQuery({
|
||||
queryKey: [QK_AGENT_WORK_ITEMS, filters ?? {}],
|
||||
queryFn: () => agentWorkItemService.getAll(filters),
|
||||
staleTime: STALE_AGENT_WORK_ITEMS,
|
||||
select: (res) => res.data ?? [],
|
||||
})
|
||||
}
|
||||
|
||||
export function useAgentWorkItem(id: string | null) {
|
||||
return useQuery({
|
||||
queryKey: [QK_AGENT_WORK_ITEMS, 'item', id],
|
||||
queryFn: () => agentWorkItemService.getById(id!),
|
||||
staleTime: STALE_AGENT_WORK_ITEMS,
|
||||
enabled: !!id,
|
||||
select: (res) => res.data ?? null,
|
||||
})
|
||||
}
|
||||
|
||||
/** Die drei Kennzahlen der Auswertung — reagieren auf dieselben Filter wie der Verlauf. */
|
||||
export function useAgentKpis(filters?: AgentWorkItemFilters) {
|
||||
return useQuery({
|
||||
queryKey: [QK_AGENT_KPIS, filters ?? {}],
|
||||
queryFn: () => agentWorkItemService.getKpis(filters),
|
||||
staleTime: STALE_AGENT_WORK_ITEMS,
|
||||
select: (res) => res.data,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Jede Entscheidung verschiebt einen Vorgang zwischen den Reitern, verändert die
|
||||
* Kennzahlen und schreibt ins Protokoll — alle drei Bestände müssen zusammen
|
||||
* entwertet werden, sonst zeigt die Oberfläche widersprüchliche Zahlen.
|
||||
*/
|
||||
function useWorkItemInvalidation() {
|
||||
const queryClient = useQueryClient()
|
||||
return () => {
|
||||
queryClient.invalidateQueries({ queryKey: [QK_AGENT_WORK_ITEMS] })
|
||||
queryClient.invalidateQueries({ queryKey: [QK_AGENT_KPIS] })
|
||||
queryClient.invalidateQueries({ queryKey: [QK_AGENT_PROTOCOL] })
|
||||
}
|
||||
}
|
||||
|
||||
export function useApproveWorkItem() {
|
||||
const invalidate = useWorkItemInvalidation()
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => agentWorkItemService.approve(id),
|
||||
onSuccess: () => {
|
||||
invalidate()
|
||||
useToastStore.getState().showToast('Vorgang freigegeben.', 'success')
|
||||
},
|
||||
onError: () => {
|
||||
useToastStore.getState().showToast('Freigabe fehlgeschlagen.', 'error')
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useRejectWorkItem() {
|
||||
const invalidate = useWorkItemInvalidation()
|
||||
return useMutation({
|
||||
mutationFn: ({ id, reason }: { id: string; reason?: string }) =>
|
||||
agentWorkItemService.reject(id, reason),
|
||||
onSuccess: () => {
|
||||
invalidate()
|
||||
useToastStore.getState().showToast('Vorgang zurückgewiesen.', 'warning')
|
||||
},
|
||||
onError: () => {
|
||||
useToastStore.getState().showToast('Zurückweisung fehlgeschlagen.', 'error')
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useSaveWorkItemEdit() {
|
||||
const invalidate = useWorkItemInvalidation()
|
||||
return useMutation({
|
||||
mutationFn: ({ id, fields }: { id: string; fields: AgentEditableField[] }) =>
|
||||
agentWorkItemService.saveEdit(id, fields),
|
||||
onSuccess: () => {
|
||||
invalidate()
|
||||
useToastStore
|
||||
.getState()
|
||||
.showToast('Änderungen gespeichert. Der Vorgang wartet weiterhin auf die Entscheidung.', 'info')
|
||||
},
|
||||
onError: () => {
|
||||
useToastStore.getState().showToast('Änderungen konnten nicht gespeichert werden.', 'error')
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useAnswerWorkItemQuery() {
|
||||
const invalidate = useWorkItemInvalidation()
|
||||
return useMutation({
|
||||
mutationFn: ({ id, answer }: { id: string; answer: string }) =>
|
||||
agentWorkItemService.answerQuery(id, answer),
|
||||
onSuccess: () => {
|
||||
invalidate()
|
||||
useToastStore.getState().showToast('Rückfrage beantwortet.', 'success')
|
||||
},
|
||||
onError: () => {
|
||||
useToastStore.getState().showToast('Antwort konnte nicht gespeichert werden.', 'error')
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useMarkWorkItemDone() {
|
||||
const invalidate = useWorkItemInvalidation()
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => agentWorkItemService.markDone(id),
|
||||
onSuccess: () => {
|
||||
invalidate()
|
||||
useToastStore.getState().showToast('Vorgang als erledigt markiert.', 'success')
|
||||
},
|
||||
onError: () => {
|
||||
useToastStore.getState().showToast('Vorgang konnte nicht abgeschlossen werden.', 'error')
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user