mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 15:18:11 +00:00
163 lines
4.2 KiB
TypeScript
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>
|
|
);
|
|
}
|