ec5d567dae
Replace SaaS-generic KPI icon boxes with flat inline layout. Replace MUI color-prop status chips with design-system colors matching the rest of the supply workspace. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
129 lines
3.8 KiB
TypeScript
129 lines
3.8 KiB
TypeScript
import { Box, Typography, Skeleton } from '@mui/material'
|
|
import { AlertOctagon, Calendar, CalendarDays, Eye } from 'lucide-react'
|
|
import { useShallow } from 'zustand/react/shallow'
|
|
import { useReminderInsights } from '../../hooks/useReminders'
|
|
import { useReminderStore } from '../../stores/reminderStore'
|
|
import type { FilterHorizon } from '../../stores/reminderStore'
|
|
import { ReminderType } from '../../domain/reminder'
|
|
|
|
interface KpiCardProps {
|
|
icon: React.ReactNode
|
|
label: string
|
|
value: number | string
|
|
color: string
|
|
active: boolean
|
|
onClick: () => void
|
|
}
|
|
|
|
function KpiCard({ icon, label, value, color, active, onClick }: KpiCardProps) {
|
|
return (
|
|
<Box
|
|
onClick={onClick}
|
|
sx={{
|
|
flex: 1,
|
|
px: 1.5,
|
|
py: 1.125,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 1,
|
|
border: active ? `1.5px solid ${color}` : '1px solid #e8e7e4',
|
|
borderRadius: 1.5,
|
|
bgcolor: active ? `${color}08` : 'white',
|
|
cursor: 'pointer',
|
|
transition: 'border-color 0.15s, background 0.15s',
|
|
minWidth: 0,
|
|
'&:hover': { bgcolor: `${color}06`, borderColor: color },
|
|
}}
|
|
>
|
|
<Box sx={{ opacity: active ? 1 : 0.55, flexShrink: 0 }}>{icon}</Box>
|
|
<Box sx={{ minWidth: 0 }}>
|
|
<Typography sx={{ fontWeight: 700, color, lineHeight: 1.1, fontSize: '1.1rem' }}>
|
|
{value}
|
|
</Typography>
|
|
<Typography variant="caption" sx={{ color: '#64748b', whiteSpace: 'nowrap', fontSize: '0.68rem' }}>
|
|
{label}
|
|
</Typography>
|
|
</Box>
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
export function ReminderKpiBar() {
|
|
const { data, isLoading } = useReminderInsights()
|
|
const { filterHorizon, setFilterHorizon, filterType, setFilterType } = useReminderStore(
|
|
useShallow(s => ({
|
|
filterHorizon: s.filterHorizon,
|
|
setFilterHorizon: s.setFilterHorizon,
|
|
filterType: s.filterType,
|
|
setFilterType: s.setFilterType,
|
|
}))
|
|
)
|
|
|
|
function toggleHorizon(h: FilterHorizon) {
|
|
if (filterHorizon === h) {
|
|
setFilterHorizon('ALL')
|
|
} else {
|
|
setFilterHorizon(h)
|
|
setFilterType('ALL') // clear type filter so count matches
|
|
}
|
|
}
|
|
|
|
function togglePreMarket() {
|
|
if (filterType === ReminderType.SCHATTENMARKT_RELEASE) {
|
|
setFilterType('ALL')
|
|
} else {
|
|
setFilterType(ReminderType.SCHATTENMARKT_RELEASE)
|
|
setFilterHorizon('ALL') // clear horizon filter so count matches
|
|
}
|
|
}
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<Box className="flex gap-3">
|
|
{[1, 2, 3, 4].map(i => (
|
|
<Skeleton key={i} variant="rounded" height={68} sx={{ flex: 1 }} />
|
|
))}
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
const ins = data ?? { overdueCount: 0, dueThisWeek: 0, dueThisMonth: 0, schattenmarktReadyCount: 0 }
|
|
|
|
return (
|
|
<Box className="flex gap-3">
|
|
<KpiCard
|
|
icon={<AlertOctagon size={18} color="#dc2626" />}
|
|
label="Überfällig"
|
|
value={ins.overdueCount}
|
|
color="#dc2626"
|
|
active={filterHorizon === 'OVERDUE'}
|
|
onClick={() => toggleHorizon('OVERDUE')}
|
|
/>
|
|
<KpiCard
|
|
icon={<Calendar size={18} color="#ea580c" />}
|
|
label="Diese Woche"
|
|
value={ins.dueThisWeek}
|
|
color="#ea580c"
|
|
active={filterHorizon === 'THIS_WEEK'}
|
|
onClick={() => toggleHorizon('THIS_WEEK')}
|
|
/>
|
|
<KpiCard
|
|
icon={<CalendarDays size={18} color="#0369a1" />}
|
|
label="Dieser Monat"
|
|
value={ins.dueThisMonth}
|
|
color="#0369a1"
|
|
active={filterHorizon === 'THIS_MONTH'}
|
|
onClick={() => toggleHorizon('THIS_MONTH')}
|
|
/>
|
|
<KpiCard
|
|
icon={<Eye size={18} color="#be185d" />}
|
|
label="Pre-Market Risiko"
|
|
value={ins.schattenmarktReadyCount}
|
|
color="#be185d"
|
|
active={filterType === ReminderType.SCHATTENMARKT_RELEASE}
|
|
onClick={togglePreMarket}
|
|
/>
|
|
</Box>
|
|
)
|
|
}
|