6aa4f96bd2
- 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>
158 lines
5.3 KiB
TypeScript
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>
|
|
)
|
|
})
|