docs(agenten): Fallakte Livia und Agenten-Briefing sichern
`LIVIA_PROFIL_UND_PROMPT.md` hält Livia als Exposé Master fest, wie sie vor Runde 8 war: Personalblatt, Aufgabenkatalog, Kanäle und Systeme, das Exposé-Dossier mit elf Bereichen und neun Pflichtfeldern, die `missingFacts`-Regel der Textmaschine — dazu ein System-Prompt, mit dem sich diese Fassung ausserhalb des Repositories rekonstruieren lässt. `archive/livia/` ergänzt die Beschreibung um die Wiederherstellung: die 23 Dateien der Exposé-Funktion als wörtliche Kopie im Original-Pfadlayout sowie ein Manifest jeder Stelle, an der Livia in geteilten Dateien verdrahtet war. Beides ist mit Runde 8 nicht überholt, sondern der Referenzstand: die Exposé-Funktion wurde nicht entfernt, sondern nachgeordnet. Das Manifest beschreibt allerdings den Rückbau eines vollständigen Entfernens und damit ein Szenario, das so nicht eingetreten ist. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
ba8dc39483
commit
2037e5a463
33 files changed
+4431
No files matched your search
@@ -0,0 +1,113 @@
|
||||
import { useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { useSearchParams } from 'react-router'
|
||||
import { Box, Tab, Tabs } from '@mui/material'
|
||||
import { AgentWorkspaceHero } from '../../components/team'
|
||||
import { LeadTable } from '../../components/expose'
|
||||
import { LoadingPage, ErrorState } from '../../components/ui'
|
||||
import { useExposeLeads } from '../../hooks/useExposeLeads'
|
||||
import { useProperties } from '../../hooks/useProperties'
|
||||
import { ExposeLeadStatus } from '../../domain/exposeLead'
|
||||
import { EXPOSE_LEAD_TABS, EXPOSE_LEAD_TAB_LABELS } from '../../lib/constants'
|
||||
import type { ExposeLeadTab } from '../../lib/constants'
|
||||
import { agentWorkspaceById } from '../../lib/agentWorkspaces'
|
||||
|
||||
const LIVIA = agentWorkspaceById('livia')!
|
||||
|
||||
const PARAM_TAB = 'tab'
|
||||
|
||||
function resolveTab(raw: string | null): ExposeLeadTab {
|
||||
return raw === EXPOSE_LEAD_TABS.ARCHIVED ? EXPOSE_LEAD_TABS.ARCHIVED : EXPOSE_LEAD_TABS.ACTIVE
|
||||
}
|
||||
|
||||
/**
|
||||
* Livia — Exposé Master (Runde 4, §8).
|
||||
*
|
||||
* Die frühere Inserate-Seite ist vollständig ersetzt: Titel, Beschreibung, der
|
||||
* Knopf «Neues Inserat» und die Inserate-Tabelle sind entfallen. Ziel der Seite
|
||||
* ist die schnelle Verarbeitung von Leads, die Nora erkannt und ein
|
||||
* Bewirtschafter zur Exposé-Erstellung weitergeleitet hat.
|
||||
*/
|
||||
export default function MyListings() {
|
||||
const [params, setParams] = useSearchParams()
|
||||
const tab = resolveTab(params.get(PARAM_TAB))
|
||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||
const scrollRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const { data: leads = [], isLoading, isError, refetch } = useExposeLeads()
|
||||
const { data: properties = [] } = useProperties()
|
||||
|
||||
const visible = useMemo(() => {
|
||||
const wanted = tab === EXPOSE_LEAD_TABS.ARCHIVED ? ExposeLeadStatus.ARCHIVED : ExposeLeadStatus.ACTIVE
|
||||
return leads
|
||||
.filter(l => l.status === wanted)
|
||||
.sort((a, b) => b.receivedAt.localeCompare(a.receivedAt))
|
||||
}, [leads, tab])
|
||||
|
||||
/**
|
||||
* Beim Öffnen gleitet die gewählte Zeile an den oberen Rand des sichtbaren
|
||||
* Inhaltsbereichs; die nachfolgenden Leads rücken nach unten und dürfen aus
|
||||
* dem Blickfeld verschwinden (Runde 4, §8.4). Umgesetzt mit `scrollTo` auf dem
|
||||
* bestehenden Scrollcontainer — dafür braucht es keine Animationsbibliothek.
|
||||
*
|
||||
* Bei `prefers-reduced-motion` wird ohne Bewegung direkt gesprungen.
|
||||
*/
|
||||
const handleSelect = useCallback((id: string, row: HTMLElement | null) => {
|
||||
const next = id === selectedId ? null : id
|
||||
setSelectedId(next)
|
||||
if (!next || !row || !scrollRef.current) return
|
||||
|
||||
const container = scrollRef.current
|
||||
const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||
const top = container.scrollTop + row.getBoundingClientRect().top - container.getBoundingClientRect().top
|
||||
|
||||
// Nach dem Zustandswechsel scrollen, sonst misst der Browser die alte Höhe.
|
||||
requestAnimationFrame(() => {
|
||||
container.scrollTo({ top, behavior: reduced ? 'auto' : 'smooth' })
|
||||
})
|
||||
}, [selectedId])
|
||||
|
||||
const handleTabChange = useCallback((next: ExposeLeadTab) => {
|
||||
setSelectedId(null)
|
||||
setParams({ [PARAM_TAB]: next })
|
||||
}, [setParams])
|
||||
|
||||
if (isLoading) return <LoadingPage />
|
||||
if (isError) return <ErrorState message="Die Leads konnten nicht geladen werden." onRetry={() => refetch()} />
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
|
||||
<Box ref={scrollRef} sx={{ flex: 1, overflowY: 'auto' }}>
|
||||
<AgentWorkspaceHero agentId={LIVIA.id} name={LIVIA.name} role={LIVIA.role} />
|
||||
|
||||
<Box sx={{ px: 3, pb: 5 }}>
|
||||
<Tabs
|
||||
value={tab}
|
||||
onChange={(_, v: ExposeLeadTab) => handleTabChange(v)}
|
||||
sx={{ mb: 2, '& .MuiTab-root': { textTransform: 'none', fontWeight: 600, fontSize: '0.9rem' } }}
|
||||
>
|
||||
{Object.values(EXPOSE_LEAD_TABS).map(value => (
|
||||
<Tab key={value} value={value} label={EXPOSE_LEAD_TAB_LABELS[value]} />
|
||||
))}
|
||||
</Tabs>
|
||||
|
||||
<LeadTable
|
||||
leads={visible}
|
||||
properties={properties}
|
||||
selectedId={selectedId}
|
||||
onSelect={handleSelect}
|
||||
withWorkspace={tab === EXPOSE_LEAD_TABS.ACTIVE}
|
||||
emptyText={
|
||||
tab === EXPOSE_LEAD_TABS.ACTIVE
|
||||
? 'Keine aktiven Leads. Sobald Nora ein Signal erkennt und es weitergeleitet wird, erscheint es hier.'
|
||||
: 'Noch keine archivierten Leads.'
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Weissraum unter der Liste, damit auch die letzte Zeile an den
|
||||
oberen Rand gleiten kann. */}
|
||||
<Box sx={{ height: '60vh' }} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user