Files
property-match/src/components/supply/ReminderListRow.tsx
T
Benjamin Sutter 6aa4f96bd2 feat: responsive layout + Reminder Manager redesign
- Auto-collapse sidebar at <1536px (all laptops), expand at ≥1536px
- Responsive drawer/panel widths across Pipeline, Properties, MyListings, Anfragen, MatchDetail, AISearch
- Reminder Manager: dot+label priority badge, Fläche column removed, status only for non-active rows
- ReminderKpiBar: focal Überfällig card, three secondary KPIs
- ReminderDetailDrawer: Task Panel redesign — Finanzen removed, Notiz promoted, Pre-Market as inline badge, full create form
- useCreateReminder hook wired to reminderService.create with cache invalidation
- Mock data: contract-derived reminders (LEASE_EXPIRY, BREAK_OPTION, RENT_REVIEW, INSURANCE_RENEWAL, SCHATTENMARKT_RELEASE) now show auto-creation note in Verlauf

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 14:30:06 +02:00

158 lines
5.3 KiB
TypeScript

import { memo } from 'react'
import { Box, Typography, Chip, IconButton, Tooltip } from '@mui/material'
import { Check, Bell, X } from 'lucide-react'
import type { Reminder } from '../../domain/reminder'
import { ReminderPriorityBadge } from './ReminderPriorityBadge'
import { ReminderTypeBadge } from './ReminderTypeBadge'
import { ReminderDaysIndicator } from './ReminderDaysIndicator'
import { useCompleteReminder, useDismissReminder, useSnoozeReminder } from '../../hooks/useReminders'
import { useReminderStore } from '../../stores/reminderStore'
import { ReminderStatus, ReminderPriority } from '../../domain/reminder'
const PRIORITY_BORDER: Record<string, string> = {
[ReminderPriority.URGENT]: '#dc2626',
[ReminderPriority.HIGH]: '#ea580c',
[ReminderPriority.MEDIUM]: '#ca8a04',
[ReminderPriority.LOW]: 'transparent',
}
const STATUS_LABEL: Record<string, string> = {
ACTIVE: 'Aktiv',
SNOOZED: 'Schlummernd',
COMPLETED: 'Erledigt',
DISMISSED: 'Verworfen',
}
const STATUS_STYLE: Record<string, { bg: string; color: string; border: string }> = {
ACTIVE: { bg: '#f0fdf4', color: '#15803d', border: '#bbf7d0' },
SNOOZED: { bg: '#fefce8', color: '#92400e', border: '#fde68a' },
COMPLETED: { bg: '#f8fafc', color: '#475569', border: '#e2e8f0' },
DISMISSED: { bg: '#f8fafc', color: '#94a3b8', border: '#e8e7e4' },
}
interface Props {
reminder: Reminder
}
export const ReminderListRow = memo(function ReminderListRow({ reminder }: Props) {
const { setSelectedId, setDrawerOpen } = useReminderStore()
const complete = useCompleteReminder()
const dismiss = useDismissReminder()
const snooze = useSnoozeReminder()
const isActionable = reminder.status === ReminderStatus.ACTIVE || reminder.status === ReminderStatus.SNOOZED
function handleRowClick() {
setSelectedId(reminder.id)
setDrawerOpen(true)
}
function handleComplete(e: React.MouseEvent) {
e.stopPropagation()
complete.mutate({ id: reminder.id })
}
function handleDismiss(e: React.MouseEvent) {
e.stopPropagation()
dismiss.mutate({ id: reminder.id })
}
function handleSnooze(e: React.MouseEvent) {
e.stopPropagation()
// Snooze 7 days from mock today
snooze.mutate({ id: reminder.id, until: '2026-05-27' })
}
const borderColor = PRIORITY_BORDER[reminder.priority] ?? 'transparent'
return (
<Box
onClick={handleRowClick}
sx={{
display: 'grid',
gridTemplateColumns: '90px 130px 1fr 140px 80px 90px',
alignItems: 'center',
gap: 1,
pl: 2,
pr: 2,
py: 1.25,
borderBottom: '1px solid #f1f5f9',
borderLeft: `3px solid ${borderColor}`,
bgcolor: 'white',
cursor: 'pointer',
'&:hover': { bgcolor: '#f8fafc' },
transition: 'background-color 0.1s',
}}
>
{/* Priority dot */}
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<ReminderPriorityBadge priority={reminder.priority} />
</Box>
{/* Type badge */}
<Box>
<ReminderTypeBadge type={reminder.type} />
</Box>
{/* Property + tenant */}
<Box sx={{ minWidth: 0 }}>
<Typography variant="body2" sx={{ fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{reminder.propertyTitle}
</Typography>
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{reminder.propertyCity} · {reminder.tenantName}
</Typography>
</Box>
{/* Due date */}
<Box>
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', fontSize: '0.7rem' }}>
{new Date(reminder.dueDate).toLocaleDateString('de-CH')}
</Typography>
<ReminderDaysIndicator dueDate={reminder.dueDate} />
</Box>
{/* Status — only shown for non-default states */}
<Box>
{reminder.status !== ReminderStatus.ACTIVE && (
<Chip
label={STATUS_LABEL[reminder.status]}
size="small"
sx={{
height: 20,
fontSize: '0.7rem',
fontWeight: 500,
bgcolor: STATUS_STYLE[reminder.status]?.bg ?? '#f8fafc',
color: STATUS_STYLE[reminder.status]?.color ?? '#64748b',
border: `1px solid ${STATUS_STYLE[reminder.status]?.border ?? '#e2e8f0'}`,
}}
/>
)}
</Box>
{/* Actions */}
<Box sx={{ display: 'flex', gap: 0.25 }} onClick={e => e.stopPropagation()}>
{isActionable && (
<>
<Tooltip title="Erledigen">
<IconButton size="small" onClick={handleComplete} sx={{ color: '#16a34a' }}>
<Check size={14} />
</IconButton>
</Tooltip>
<Tooltip title="7 Tage schlummern">
<IconButton size="small" onClick={handleSnooze} sx={{ color: '#ca8a04' }}>
<Bell size={14} />
</IconButton>
</Tooltip>
<Tooltip title="Verwerfen">
<IconButton size="small" onClick={handleDismiss} sx={{ color: '#dc2626' }}>
<X size={14} />
</IconButton>
</Tooltip>
</>
)}
</Box>
</Box>
)
})