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:
@@ -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>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user