Files
property-match/src/components/supply/ReminderKpiBar.tsx
T
Benjamin Sutter ec5d567dae refine: Reminder Manager — align to premium design
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>
2026-06-10 23:05:08 +02:00

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>
)
}