feat: Reminder Manager UX — timeline-first grouped layout

Replace 3-row filter panel with a grouped feed (Überfällig / Diese Woche /
Dieser Monat / Später). KPI cards are now clickable shortcuts that filter to
their time horizon or type (Pre-Market). Filter bar condenses to one row:
search + type dropdown + archive toggle. Rows get a left priority-color
border (red/orange/yellow). Default status filter is ACTIVE+SNOOZED.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-24 22:42:37 +02:00
parent a9e038a64f
commit d804d1c923
6 changed files with 263 additions and 162 deletions
+60 -22
View File
@@ -1,32 +1,46 @@
import { Box, Paper, Typography, Skeleton } from '@mui/material'
import { AlertTriangle, Calendar, CalendarDays, Eye } from 'lucide-react'
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 KpiItemProps {
interface KpiCardProps {
icon: React.ReactNode
label: string
value: number | string
color: string
active: boolean
onClick: () => void
}
function KpiItem({ icon, label, value, color }: KpiItemProps) {
function KpiCard({ icon, label, value, color, active, onClick }: KpiCardProps) {
return (
<Paper
variant="outlined"
<Box
onClick={onClick}
sx={{
flex: 1,
p: 1.5,
display: 'flex',
alignItems: 'center',
gap: 1.5,
borderColor: '#e2e8f0',
border: active ? `2px solid ${color}` : '1px solid #e2e8f0',
borderRadius: 1.5,
bgcolor: active ? `${color}10` : 'white',
cursor: 'pointer',
transition: 'all 0.15s',
minWidth: 0,
'&:hover': {
bgcolor: `${color}0d`,
borderColor: color,
},
}}
>
<Box
sx={{
width: 36,
height: 36,
width: 34,
height: 34,
borderRadius: 1,
bgcolor: `${color}18`,
display: 'flex',
@@ -41,16 +55,32 @@ function KpiItem({ icon, label, value, color }: KpiItemProps) {
<Typography variant="h6" sx={{ fontWeight: 700, color, lineHeight: 1.2, fontSize: '1.25rem' }}>
{value}
</Typography>
<Typography variant="caption" color="text.secondary" sx={{ whiteSpace: 'nowrap' }}>
<Typography variant="caption" color="text.secondary" sx={{ whiteSpace: 'nowrap', fontSize: '0.7rem' }}>
{label}
</Typography>
</Box>
</Paper>
</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) {
setFilterHorizon(filterHorizon === h ? 'ALL' : h)
}
function togglePreMarket() {
setFilterType(filterType === ReminderType.SCHATTENMARKT_RELEASE ? 'ALL' : ReminderType.SCHATTENMARKT_RELEASE)
}
if (isLoading) {
return (
@@ -62,33 +92,41 @@ export function ReminderKpiBar() {
)
}
const insights = data ?? { urgentCount: 0, dueThisWeek: 0, dueThisMonth: 0, schattenmarktReadyCount: 0 }
const ins = data ?? { overdueCount: 0, dueThisWeek: 0, dueThisMonth: 0, schattenmarktReadyCount: 0 }
return (
<Box className="flex gap-3">
<KpiItem
icon={<AlertTriangle size={18} color="#dc2626" />}
label="Dringend"
value={insights.urgentCount}
<KpiCard
icon={<AlertOctagon size={18} color="#dc2626" />}
label="Überfällig"
value={ins.overdueCount}
color="#dc2626"
active={filterHorizon === 'OVERDUE'}
onClick={() => toggleHorizon('OVERDUE')}
/>
<KpiItem
<KpiCard
icon={<Calendar size={18} color="#ea580c" />}
label="Diese Woche"
value={insights.dueThisWeek}
value={ins.dueThisWeek}
color="#ea580c"
active={filterHorizon === 'THIS_WEEK'}
onClick={() => toggleHorizon('THIS_WEEK')}
/>
<KpiItem
<KpiCard
icon={<CalendarDays size={18} color="#0369a1" />}
label="Dieser Monat"
value={insights.dueThisMonth}
value={ins.dueThisMonth}
color="#0369a1"
active={filterHorizon === 'THIS_MONTH'}
onClick={() => toggleHorizon('THIS_MONTH')}
/>
<KpiItem
<KpiCard
icon={<Eye size={18} color="#be185d" />}
label="Pre-Market Risiko"
value={insights.schattenmarktReadyCount}
value={ins.schattenmarktReadyCount}
color="#be185d"
active={filterType === ReminderType.SCHATTENMARKT_RELEASE}
onClick={togglePreMarket}
/>
</Box>
)