Files
edr-platform/apps/edr-freight-web/backoffice/src/components/bookings/BookingConfirmDialog.tsx
2026-06-04 15:16:27 +03:00

135 lines
4.0 KiB
TypeScript

import { Loader2 } from "lucide-react";
import type { BookingActionDef } from "@/features/bookings/booking-actions.config";
import { cn } from "@/lib/utils";
import {
Button,
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
Textarea,
} from "@edr/ui-common";
interface BookingConfirmDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
action: BookingActionDef | null;
reference?: string;
inputValue: string;
onInputChange: (value: string) => void;
onConfirm: () => void;
isPending: boolean;
confirmDisabled?: boolean;
extra?: React.ReactNode;
}
export function BookingConfirmDialog({
open,
onOpenChange,
action,
reference,
inputValue,
onInputChange,
onConfirm,
isPending,
confirmDisabled = false,
extra,
}: BookingConfirmDialogProps) {
if (!action || !action.confirmTitle) return null;
const Icon = action.icon;
const needsInput = Boolean(action.input);
const inputMissing = needsInput && !inputValue.trim();
const isDestructive = action.variant === "destructive";
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="gap-0 overflow-hidden p-0 sm:max-w-md">
<div
className={cn(
"border-b px-6 py-5",
isDestructive
? "bg-gradient-to-br from-red-500/10 via-background to-background"
: "bg-gradient-to-br from-primary/8 via-background to-background",
)}
>
<DialogHeader className="gap-3 text-left">
<div className="flex items-start gap-3">
<div
className={cn(
"flex size-11 shrink-0 items-center justify-center rounded-xl shadow-sm",
isDestructive
? "bg-red-500/15 text-red-700 dark:text-red-300"
: "bg-primary/15 text-primary",
)}
>
<Icon className="size-5" />
</div>
<div className="min-w-0 space-y-1 pt-0.5">
<DialogTitle className="text-base leading-snug">
{action.confirmTitle}
</DialogTitle>
{reference && (
<p className="font-mono text-xs font-semibold text-muted-foreground">
{reference}
</p>
)}
</div>
</div>
<DialogDescription className="text-left text-sm leading-relaxed">
{action.confirmDescription}
</DialogDescription>
</DialogHeader>
</div>
<div className="space-y-4 px-6 py-5">
{needsInput && (
<div className="space-y-2">
<label className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
{action.inputLabel}
<span className="text-red-600"> *</span>
</label>
<Textarea
value={inputValue}
onChange={(e) => onInputChange(e.target.value)}
placeholder={action.inputPlaceholder}
rows={4}
className="min-h-[100px] resize-y"
/>
</div>
)}
{extra}
</div>
<DialogFooter className="gap-2 border-t bg-muted/20 px-6 py-4 sm:justify-end">
<Button
type="button"
variant="outline"
disabled={isPending}
onClick={() => onOpenChange(false)}
>
Cancel
</Button>
<Button
type="button"
variant={isDestructive ? "destructive" : "default"}
disabled={isPending || inputMissing || confirmDisabled}
className="min-w-[7rem] gap-2"
onClick={onConfirm}
>
{isPending ? (
<Loader2 className="size-4 animate-spin" />
) : (
<Icon className="size-4" />
)}
{action.shortLabel}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}