feat: remove Administration workspace — keep only Verwaltung + Suche
- Delete all ops page components (ReviewQueue, AIMonitoring, Governance, SourceMonitoring, ActivityTimeline, SignalPipeline) - Remove OPERATIONS workspace from AppShell config, nav order, path detection - Remove all /ops/* routes from App.tsx - Remove WorkspaceType.OPERATIONS from allowedWorkspaces in authService, sessionStore, permissions - Keep MarketIntelligence page (already moved to /supply/market-intelligence) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
import {
|
||||
Box,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Typography,
|
||||
} from '@mui/material'
|
||||
import { AIOutputStatusBadge } from './AIOutputStatusBadge'
|
||||
import { PromptVersionBadge } from './PromptVersionBadge'
|
||||
import { AIErrorBadge } from './AIErrorBadge'
|
||||
import { AIMonitoringEmptyState } from './AIMonitoringEmptyState'
|
||||
import type { AIOutput, AIOutputType } from '../../domain/aiOutput'
|
||||
|
||||
const TYPE_LABELS: Record<AIOutputType, string> = {
|
||||
NEED_PARSE: 'Bedarf-Parsing',
|
||||
FOLLOW_UP_QUESTIONS: 'Rückfragen',
|
||||
MATCH_EXPLANATION: 'Match-Begründung',
|
||||
COMPARE_SUMMARY: 'Vergleich',
|
||||
DECISION_BRIEF: 'Entscheidungs-Brief',
|
||||
DATA_QUALITY_SUMMARY: 'Datenqualität',
|
||||
}
|
||||
|
||||
const TYPE_COLORS: Record<AIOutputType, string> = {
|
||||
NEED_PARSE: '#1e3a5f',
|
||||
FOLLOW_UP_QUESTIONS: '#0891b2',
|
||||
MATCH_EXPLANATION: '#4f46e5',
|
||||
COMPARE_SUMMARY: '#1a7a4a',
|
||||
DECISION_BRIEF: '#7c3aed',
|
||||
DATA_QUALITY_SUMMARY: '#d97706',
|
||||
}
|
||||
|
||||
const MODEL_SHORT: Record<string, string> = {
|
||||
'claude-3-5-sonnet-20241022': 'Sonnet 3.5',
|
||||
'claude-3-haiku-20240307': 'Haiku 3',
|
||||
'claude-3-opus-20240229': 'Opus 3',
|
||||
}
|
||||
|
||||
function shortTime(iso: string) {
|
||||
return new Date(iso).toLocaleString('de-CH', { dateStyle: 'short', timeStyle: 'short' })
|
||||
}
|
||||
|
||||
interface Props {
|
||||
outputs: AIOutput[]
|
||||
selectedId: string | null
|
||||
onSelect: (output: AIOutput) => void
|
||||
isEmpty: boolean
|
||||
}
|
||||
|
||||
export function AIOutputTable({ outputs, selectedId, onSelect, isEmpty }: Props) {
|
||||
if (isEmpty && outputs.length === 0) {
|
||||
return <AIMonitoringEmptyState context="no-outputs" />
|
||||
}
|
||||
if (outputs.length === 0) {
|
||||
return <AIMonitoringEmptyState context="filtered-empty" />
|
||||
}
|
||||
|
||||
return (
|
||||
<Table size="small" stickyHeader>
|
||||
<TableHead>
|
||||
<TableRow sx={{ '& th': { bgcolor: '#f8fafc', fontSize: '0.7rem', fontWeight: 700, color: '#64748b', py: 0.75, textTransform: 'uppercase', letterSpacing: 0.4 } }}>
|
||||
<TableCell sx={{ minWidth: 110 }}>Zeitpunkt</TableCell>
|
||||
<TableCell sx={{ minWidth: 130 }}>Typ</TableCell>
|
||||
<TableCell sx={{ minWidth: 100 }}>Modell</TableCell>
|
||||
<TableCell sx={{ minWidth: 140 }}>Version</TableCell>
|
||||
<TableCell sx={{ minWidth: 95 }}>Status</TableCell>
|
||||
<TableCell sx={{ minWidth: 65 }}>Latenz</TableCell>
|
||||
<TableCell sx={{ minWidth: 80 }}>Fehler</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{outputs.map(output => {
|
||||
const isSelected = selectedId === output.id
|
||||
const color = TYPE_COLORS[output.type] ?? '#64748b'
|
||||
return (
|
||||
<TableRow
|
||||
key={output.id}
|
||||
hover
|
||||
onClick={() => onSelect(output)}
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
bgcolor: isSelected ? '#eff6ff' : undefined,
|
||||
borderLeft: isSelected ? '3px solid #1e3a5f' : '3px solid transparent',
|
||||
'&:hover': { bgcolor: isSelected ? '#eff6ff' : '#f8fafc' },
|
||||
'& td': { py: 0.75, borderBottom: '1px solid #f1f5f9' },
|
||||
}}
|
||||
>
|
||||
<TableCell>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.7rem', whiteSpace: 'nowrap' }}>
|
||||
{shortTime(output.createdAt)}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
px: 0.75,
|
||||
py: 0.2,
|
||||
borderRadius: 1,
|
||||
bgcolor: `${color}12`,
|
||||
}}
|
||||
>
|
||||
<Typography variant="caption" sx={{ color, fontWeight: 700, fontSize: '0.7rem', whiteSpace: 'nowrap' }}>
|
||||
{TYPE_LABELS[output.type] ?? output.type}
|
||||
</Typography>
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="caption" sx={{ fontWeight: 500, fontSize: '0.7rem', color: '#334155' }}>
|
||||
{MODEL_SHORT[output.model] ?? output.model}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<PromptVersionBadge promptVersion={output.promptVersion} schemaVersion={output.schemaVersion} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<AIOutputStatusBadge status={output.reviewStatus} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography variant="caption" sx={{ fontSize: '0.7rem', color: output.latencyMs && output.latencyMs > 5000 ? '#c0392b' : '#64748b' }}>
|
||||
{output.latencyMs != null ? `${(output.latencyMs / 1000).toFixed(1)}s` : '–'}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{output.error ? <AIErrorBadge error={output.error} /> : (
|
||||
<Typography variant="caption" color="text.disabled" sx={{ fontSize: '0.7rem' }}>–</Typography>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user