import React from 'react'; import Box from '@mui/material/Box'; import Paper from '@mui/material/Paper'; import TextField from '@mui/material/TextField'; import ForwardToInboxOutlinedIcon from '@mui/icons-material/ForwardToInboxOutlined'; import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; import LocationOnOutlinedIcon from '@mui/icons-material/LocationOnOutlined'; import CheckIcon from '@mui/icons-material/Check'; import AddIcon from '@mui/icons-material/Add'; import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; import type { SxProps, Theme } from '@mui/material/styles'; import { WizardLayout } from '../../templates/WizardLayout'; import { DialogShell } from '../../atoms/DialogShell'; import { Card } from '../../atoms/Card'; import { Typography } from '../../atoms/Typography'; import { Button } from '../../atoms/Button'; import { IconButton } from '../../atoms/IconButton'; import { Divider } from '../../atoms/Divider'; // ─── Types ─────────────────────────────────────────────────────────────────── /** A line item within a summary section */ export interface SummaryLineItem { label: string; value?: string; price?: number; /** Custom price label like "POA" */ priceLabel?: string; } /** A section of the plan summary */ export interface SummarySection { id: string; /** Section heading (e.g. "Coffin", "Service Venue") */ title: string; /** Step ID for edit navigation */ editStepId?: string; /** Image URL for the visual card thumbnail */ imageUrl?: string; /** Primary selection name (e.g. provider name, coffin name) */ name?: string; /** Supporting text (e.g. package name) */ subtitle?: string; /** Location with pin icon (e.g. "Strathfield, NSW") */ location?: string; /** Colour swatch for coffin */ colourName?: string; colourHex?: string; /** Price of the primary selection */ price?: number; /** Package allowance for this section's primary price */ allowanceAmount?: number; /** Additional line items (services, extras, details) */ items?: SummaryLineItem[]; } /** Arrangement details shown at the top of the summary */ export interface ArrangementDetails { /** Name of the person arranging */ arrangerName?: string; /** Name of the deceased or person the arrangement is for */ deceasedName?: string; /** Service tradition / religious considerations */ serviceTradition?: string; /** Preferred date(s) */ preferredDates?: string[]; /** Preferred time of day */ preferredTime?: string; /** Step ID for editing these details */ editStepId?: string; } /** Props for the SummaryStep page component */ export interface SummaryStepProps { /** Arrangement details shown at the top */ arrangementDetails?: ArrangementDetails; /** Summary sections */ sections: SummarySection[]; /** Total cost */ totalPrice: number; /** Total allowances applied */ totalAllowances?: number; /** Callback when Confirm is clicked */ onConfirm: () => void; /** Callback for back navigation */ onBack?: () => void; /** Callback for save-and-exit */ onSaveAndExit?: () => void; /** Callback when edit is clicked on a section */ onEdit?: (stepId: string) => void; /** Callback when plan is shared via email */ onShareByEmail?: (emails: string[]) => void; /** Whether the share is in progress */ shareLoading?: boolean; /** Whether the Confirm button is in a loading state */ loading?: boolean; /** Whether this is a pre-planning flow */ isPrePlanning?: boolean; /** Navigation bar */ navigation?: React.ReactNode; /** Progress stepper */ progressStepper?: React.ReactNode; /** Hide the help bar */ hideHelpBar?: boolean; /** MUI sx prop */ sx?: SxProps; } // ─── Internal: Share Dialog ───────────────────────────────────────────────── const ShareDialog: React.FC<{ open: boolean; onClose: () => void; onSend: (emails: string[]) => void; loading?: boolean; }> = ({ open, onClose, onSend, loading }) => { const [emails, setEmails] = React.useState(['']); const [sent, setSent] = React.useState(false); const handleAdd = () => setEmails((prev) => [...prev, '']); const handleRemove = (index: number) => setEmails((prev) => prev.filter((_, i) => i !== index)); const handleChange = (index: number, value: string) => setEmails((prev) => prev.map((e, i) => (i === index ? value : e))); const validEmails = emails.filter((e) => e.includes('@') && e.includes('.')); const handleSend = () => { if (validEmails.length > 0) { onSend(validEmails); setSent(true); } }; const handleClose = () => { onClose(); setTimeout(() => { setEmails(['']); setSent(false); }, 300); }; return ( ) : ( ) } > {!sent ? ( <> Enter the email address of anyone you'd like to share this plan with. {emails.map((email, index) => ( handleChange(index, e.target.value)} inputRef={(el) => { if (el && index === emails.length - 1 && emails.length > 1) el.focus(); }} /> {emails.length > 1 && ( handleRemove(index)} > )} ))} ) : ( Plan sent Your plan has been sent to{' '} {validEmails.length === 1 ? validEmails[0] : `${validEmails.length} recipients`}. )} ); }; // ─── Internal: Section Header ─────────────────────────────────────────────── const SectionHeader: React.FC<{ title: string; editStepId?: string; onEdit?: (stepId: string) => void; }> = ({ title, editStepId, onEdit }) => ( {title} {editStepId && onEdit && ( )} ); // ─── Component ─────────────────────────────────────────────────────────────── /** * Step 13 — Summary / Review for the FA arrangement wizard. * * Visual cart-style summary of the funeral plan. Each selection is shown * as a compact card with image, details, and pricing. Allowances are * displayed inline per section. Email sharing via dialog. * * Pure presentation component — props in, callbacks out. * * Spec: documentation/steps/steps/13_summary.yaml */ export const SummaryStep: React.FC = ({ arrangementDetails, sections, totalPrice, totalAllowances, onConfirm, onBack, onSaveAndExit, onEdit, onShareByEmail, shareLoading = false, loading = false, isPrePlanning = false, navigation, progressStepper, hideHelpBar, sx, }) => { const [shareOpen, setShareOpen] = React.useState(false); return ( {/* Header */} Review your plan {isPrePlanning ? "Here's an overview of your plan. You can make changes at any time." : 'Please review your selections below. You can edit any section before confirming.'} {onShareByEmail && ( )} {/* ─── Arrangement details ─── */} {arrangementDetails && ( <> {arrangementDetails.arrangerName && ( Arranged by {arrangementDetails.arrangerName} )} {arrangementDetails.deceasedName && ( In memory of {arrangementDetails.deceasedName} )} {arrangementDetails.serviceTradition && ( Service tradition {arrangementDetails.serviceTradition} )} {arrangementDetails.preferredTime && ( Preferred time {arrangementDetails.preferredTime} )} {arrangementDetails.preferredDates && arrangementDetails.preferredDates.length > 0 && ( Preferred date{arrangementDetails.preferredDates.length > 1 ? 's' : ''} {arrangementDetails.preferredDates.join(' · ')} )} )} {/* ─── Summary sections ─── */} {sections.map((section) => { // Determine if this section has any priced items (affects tick display) const hasPricedItems = section.items?.some((item) => item.price != null || item.priceLabel) ?? false; // Allowance logic for primary price const hasAllowance = section.allowanceAmount != null && section.price != null; const isFullyCovered = hasAllowance && section.allowanceAmount! >= section.price!; const remainingCost = hasAllowance && !isFullyCovered ? section.price! - section.allowanceAmount! : 0; return ( {/* Visual card for sections with image/name */} {section.name && ( {/* Thumbnail */} {section.imageUrl && ( )} {/* Details */} {section.name} {section.subtitle && ( {section.subtitle} )} {/* Location with pin */} {section.location && ( {section.location} )} {/* Colour swatch */} {section.colourName && ( {section.colourHex && ( )} {section.colourName} )} {/* Spacer pushes price to bottom */} {/* Price — hugs bottom of card detail area */} {section.price != null && !hasAllowance && ( ${section.price.toLocaleString('en-AU')} )} {/* Fully covered — no price breakdown */} {isFullyCovered && ( Included in your package )} {/* Allowance breakdown — full-width divider section */} {hasAllowance && !isFullyCovered && ( Price ${section.price!.toLocaleString('en-AU')} Allowance applied −${section.allowanceAmount!.toLocaleString('en-AU')} Remaining ${remainingCost.toLocaleString('en-AU')} )} {/* Sub-items — full-width divider section */} {section.items && section.items.length > 0 && ( {section.items.map((item, i) => ( {item.label} {item.value && ` — ${item.value}`} {item.priceLabel ? ( {item.priceLabel} ) : ( item.price != null && ( ${item.price.toLocaleString('en-AU')} ) )} ))} )} )} {/* List-style section (no image/name — included services, extras) */} {!section.name && section.items && section.items.length > 0 && ( {section.items.map((item, i) => ( {/* Ticks only in sections with no priced items (included services) */} {!hasPricedItems && ( )} {item.label} {item.value && ( {' '} — {item.value} )} {item.priceLabel ? ( {item.priceLabel} ) : ( item.price != null && ( ${item.price.toLocaleString('en-AU')} ) )} ))} )} ); })} {/* ─── Total bar ─── */} {totalAllowances != null && totalAllowances > 0 && ( Package allowances applied −${totalAllowances.toLocaleString('en-AU')} )} Total ${totalPrice.toLocaleString('en-AU')} {/* Payment reassurance */} {!isPrePlanning && ( You won't be charged until you complete the next step. )} {/* CTAs */} {onSaveAndExit && ( )} {/* Share dialog */} {onShareByEmail && ( setShareOpen(false)} onSend={onShareByEmail} loading={shareLoading} /> )} ); }; SummaryStep.displayName = 'SummaryStep'; export default SummaryStep;