diff --git a/src/components/anfragencenter/OfferCreationWizard.tsx b/src/components/anfragencenter/OfferCreationWizard.tsx index 304197d..bcae3a4 100644 --- a/src/components/anfragencenter/OfferCreationWizard.tsx +++ b/src/components/anfragencenter/OfferCreationWizard.tsx @@ -1,14 +1,15 @@ import { useEffect, useRef, useState } from 'react' import { Box, Button, CircularProgress, Dialog, DialogContent, - IconButton, LinearProgress, Stack, Step, StepLabel, Stepper, + IconButton, Stack, Step, StepLabel, Stepper, TextField, Typography, } from '@mui/material' -import { Download, Plus, Send, Trash2, X } from 'lucide-react' +import { Plus, Trash2, X } from 'lucide-react' import type { OfferReportDraft, ViewingAppointmentOption } from '../../domain/offerReport' import { offerReportService } from '../../services/offerReportService' import { usePropertyById } from '../../hooks/useProperties' import { useToastStore } from '../../stores/toastStore' +import { OfferWizardPdfStep } from './OfferWizardPdfStep' interface Props { inquiryId: string @@ -213,94 +214,19 @@ export function OfferCreationWizard({ inquiryId, propertyId, tenantName, onClose {/* Step 3: PDF */} {step === 3 && ( - - {generating ? ( - <> - - - PDF wird generiert… - - - - - {Math.round(progress)}% - - - - ) : ready ? ( - - - - - - - - Angebot_{draft?.propertyId ?? 'Objekt'}.pdf - - - - - Angebotsschreiben - - {draft?.editableFields.slice(0, 3).map(f => ( - - {f.label} - - {f.value.slice(0, 80)}{f.value.length > 80 ? '…' : ''} - - - ))} - {(draft?.viewingAppointments.length ?? 0) > 0 && ( - - - Besichtigungstermine - - {draft!.viewingAppointments.map(a => ( - - {new Date(a.date).toLocaleDateString('de-CH')} · {a.timeSlot} - - ))} - - )} - - - - - - - - ) : null} - + showToast('PDF wird heruntergeladen…', 'info')} + onAttach={() => { + const label = `Angebot_${property?.title ?? 'Objekt'}.pdf` + onAttach(label) + showToast('Angebot als Anhang hinzugefügt', 'success') + }} + /> )} diff --git a/src/components/anfragencenter/OfferWizardPdfStep.tsx b/src/components/anfragencenter/OfferWizardPdfStep.tsx new file mode 100644 index 0000000..91f7119 --- /dev/null +++ b/src/components/anfragencenter/OfferWizardPdfStep.tsx @@ -0,0 +1,103 @@ +import { Box, Button, CircularProgress, LinearProgress, Typography } from '@mui/material' +import { Download, Send } from 'lucide-react' +import type { OfferReportDraft } from '../../domain/offerReport' +import type { Property } from '../../domain/property' + +interface OfferWizardPdfStepProps { + generating: boolean + progress: number + ready: boolean + draft: OfferReportDraft | null + property: Property | undefined + onDownload: () => void + onAttach: () => void +} + +export function OfferWizardPdfStep({ generating, progress, ready, draft, property, onDownload, onAttach }: OfferWizardPdfStepProps) { + return ( + + {generating ? ( + <> + + + PDF wird generiert… + + + + + {Math.round(progress)}% + + + + ) : ready ? ( + + + + + + + + Angebot_{property?.title ?? 'Objekt'}.pdf + + + + + Angebotsschreiben + + {draft?.editableFields.slice(0, 3).map(f => ( + + {f.label} + + {f.value.slice(0, 80)}{f.value.length > 80 ? '…' : ''} + + + ))} + {(draft?.viewingAppointments.length ?? 0) > 0 && ( + + + Besichtigungstermine + + {draft!.viewingAppointments.map(a => ( + + {new Date(a.date).toLocaleDateString('de-CH')} · {a.timeSlot} + + ))} + + )} + + + + + + + + ) : null} + + ) +} diff --git a/src/components/supply/ReminderDetailDrawer.tsx b/src/components/supply/ReminderDetailDrawer.tsx index 6b4614b..d0d78dc 100644 --- a/src/components/supply/ReminderDetailDrawer.tsx +++ b/src/components/supply/ReminderDetailDrawer.tsx @@ -18,91 +18,8 @@ import { useReminder, useCompleteReminder, useDismissReminder, useSnoozeReminder import { ReminderPriorityBadge } from './ReminderPriorityBadge' import { ReminderTypeBadge } from './ReminderTypeBadge' import { ReminderDaysIndicator } from './ReminderDaysIndicator' -import { ReminderStatus, ShadowMarketRisk } from '../../domain/reminder' -import type { ReminderActivity } from '../../domain/reminder' - -const SHADOW_RISK_COLOR: Record = { - NONE: '#64748b', - LOW: '#16a34a', - MEDIUM: '#ca8a04', - HIGH: '#dc2626', -} - -const SHADOW_RISK_LABEL: Record = { - NONE: 'Kein Risiko', - LOW: 'Niedrig', - MEDIUM: 'Mittel', - HIGH: 'Hoch', -} - -const STATUS_CHIP_COLOR: Record = { - ACTIVE: 'info', - SNOOZED: 'warning', - COMPLETED: 'success', - DISMISSED: 'default', -} - -const STATUS_LABEL: Record = { - ACTIVE: 'Aktiv', - SNOOZED: 'Schlummernd', - COMPLETED: 'Erledigt', - DISMISSED: 'Verworfen', -} - -const ACTION_LABEL: Record = { - CREATED: 'Erstellt', - SNOOZED: 'Zurückgestellt', - COMPLETED: 'Erledigt', - DISMISSED: 'Verworfen', - NOTED: 'Notiz', -} - -function SectionTitle({ icon, children }: { icon: React.ReactNode; children: React.ReactNode }) { - return ( - - {icon} - - {children} - - - ) -} - -function DateRow({ label, value }: { label: string; value?: string }) { - if (!value) return null - return ( - - {label} - {new Date(value).toLocaleDateString('de-CH')} - - ) -} - -function ActivityEntry({ entry }: { entry: ReminderActivity }) { - return ( - - - - - - - - - {ACTION_LABEL[entry.action]} - - - {entry.by} · {new Date(entry.at).toLocaleDateString('de-CH')} - - - {entry.note && ( - - {entry.note} - - )} - - - ) -} +import { ReminderStatus } from '../../domain/reminder' +import { SHADOW_RISK_COLOR, SHADOW_RISK_LABEL, STATUS_CHIP_COLOR, STATUS_LABEL, ACTION_LABEL, SectionTitle, DateRow, ActivityEntry } from './reminderDetailHelpers' export function ReminderDetailDrawer() { const { selectedId, drawerOpen, setDrawerOpen, setSelectedId } = useReminderStore() diff --git a/src/components/supply/reminderDetailHelpers.tsx b/src/components/supply/reminderDetailHelpers.tsx new file mode 100644 index 0000000..df454ff --- /dev/null +++ b/src/components/supply/reminderDetailHelpers.tsx @@ -0,0 +1,85 @@ +import { Box, Typography } from '@mui/material' +import type { ReminderActivity } from '../../domain/reminder' + +export const SHADOW_RISK_COLOR: Record = { + NONE: '#64748b', + LOW: '#16a34a', + MEDIUM: '#ca8a04', + HIGH: '#dc2626', +} + +export const SHADOW_RISK_LABEL: Record = { + NONE: 'Kein Risiko', + LOW: 'Niedrig', + MEDIUM: 'Mittel', + HIGH: 'Hoch', +} + +export const STATUS_CHIP_COLOR: Record = { + ACTIVE: 'info', + SNOOZED: 'warning', + COMPLETED: 'success', + DISMISSED: 'default', +} + +export const STATUS_LABEL: Record = { + ACTIVE: 'Aktiv', + SNOOZED: 'Schlummernd', + COMPLETED: 'Erledigt', + DISMISSED: 'Verworfen', +} + +export const ACTION_LABEL: Record = { + CREATED: 'Erstellt', + SNOOZED: 'Zurückgestellt', + COMPLETED: 'Erledigt', + DISMISSED: 'Verworfen', + NOTED: 'Notiz', +} + +export function SectionTitle({ icon, children }: { icon: React.ReactNode; children: React.ReactNode }) { + return ( + + {icon} + + {children} + + + ) +} + +export function DateRow({ label, value }: { label: string; value?: string }) { + if (!value) return null + return ( + + {label} + {new Date(value).toLocaleDateString('de-CH')} + + ) +} + +export function ActivityEntry({ entry }: { entry: ReminderActivity }) { + return ( + + + + + + + + + {ACTION_LABEL[entry.action]} + + + {entry.by} · {new Date(entry.at).toLocaleDateString('de-CH')} + + + {entry.note && ( + + {entry.note} + + )} + + + ) +}