Files
edr-platform/apps/edr-freight-web/backoffice/src/components/bookings/BookingConfirmDialog.tsx
2026-06-22 08:11:11 +00:00

163 lines
4.2 KiB
TypeScript

import type { ReactNode } from "react";
import {
Modal,
Group,
Stack,
Text,
Box,
Button,
Textarea,
FileInput,
} from "@mantine/core";
import type { BookingActionDef } from "@/features/bookings/booking-actions.config";
interface BookingConfirmDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
action: BookingActionDef | null;
reference?: string;
inputValue: string;
onInputChange: (value: string) => void;
selectedFile?: File | null;
onFileChange?: (file: File | null) => void;
onConfirm: () => void;
isPending: boolean;
confirmDisabled?: boolean;
extra?: ReactNode;
}
export function BookingConfirmDialog({
open,
onOpenChange,
action,
reference,
inputValue,
onInputChange,
selectedFile = null,
onFileChange,
onConfirm,
isPending,
confirmDisabled = false,
extra,
}: BookingConfirmDialogProps) {
if (!action || !action.confirmTitle) return null;
const Icon = action.icon;
const needsTextInput = action.input === "note" || action.input === "reason";
const needsFileInput = action.input === "file";
const inputMissing =
(needsTextInput && !inputValue.trim()) || (needsFileInput && !selectedFile);
const isDestructive = action.variant === "destructive";
const accent = isDestructive ? "red" : "edr-green";
return (
<Modal
opened={open}
onClose={() => onOpenChange(false)}
withCloseButton={false}
centered
radius="md"
size="md"
padding={0}
title={null}
>
{/* Header */}
<Box
px="lg"
py="md"
style={{
background: `var(--mantine-color-${accent}-0)`,
borderBottom: `1px solid var(--mantine-color-${accent}-1)`,
}}
>
<Group align="flex-start" gap="sm" wrap="nowrap">
<Box
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
width: 44,
height: 44,
borderRadius: 12,
flexShrink: 0,
background: `var(--mantine-color-${accent}-1)`,
color: `var(--mantine-color-${accent}-7)`,
}}
>
<Icon size={20} />
</Box>
<Stack gap={2} style={{ minWidth: 0 }}>
<Text fw={700} size="md" style={{ lineHeight: 1.3 }}>
{action.confirmTitle}
</Text>
{reference && (
<Text size="xs" c="dimmed" ff="monospace" fw={600}>
{reference}
</Text>
)}
</Stack>
</Group>
{action.confirmDescription && (
<Text size="sm" c="dimmed" mt="sm" style={{ lineHeight: 1.5 }}>
{action.confirmDescription}
</Text>
)}
</Box>
{/* Body */}
<Stack gap="md" px="lg" py="lg">
{needsTextInput && (
<Textarea
label={action.inputLabel}
withAsterisk
value={inputValue}
onChange={(e) => onInputChange(e.currentTarget.value)}
placeholder={action.inputPlaceholder}
minRows={4}
autosize
/>
)}
{needsFileInput && (
<FileInput
label={action.inputLabel ?? "Bank slip file"}
withAsterisk
placeholder="Select a file"
accept=".pdf,.png,.jpg,.jpeg"
value={selectedFile}
onChange={(file) => onFileChange?.(file)}
clearable
/>
)}
{extra}
</Stack>
{/* Footer */}
<Group
justify="flex-end"
gap="sm"
px="lg"
py="md"
style={{
borderTop: "1px solid var(--mantine-color-gray-2)",
background: "var(--mantine-color-gray-0)",
}}
>
<Button variant="default" disabled={isPending} onClick={() => onOpenChange(false)}>
Cancel
</Button>
<Button
color={accent}
loading={isPending}
disabled={inputMissing || confirmDisabled}
leftSection={<Icon size={16} />}
onClick={onConfirm}
miw={112}
>
{action.shortLabel}
</Button>
</Group>
</Modal>
);
}