"use client";
import { useState, useEffect, useMemo } from "react";
import {
Pencil,
Save,
X,
AlertCircle,
Loader2,
RotateCcw,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
type FormData = {
name: string;
email: string;
status: string;
description: string;
};
type FormErrors = Partial<Record<keyof FormData, string>>;
const initialData: FormData = {
name: "John Doe",
email: "[email protected]",
status: "active",
description: "Senior software engineer with 10 years of experience.",
};
const statuses = [
{ value: "active", label: "Active" },
{ value: "inactive", label: "Inactive" },
{ value: "pending", label: "Pending" },
];
export const title = "React Dialog Block Edit Record";
export default function DialogEditRecord() {
const [open, setOpen] = useState(false);
const [showDiscardAlert, setShowDiscardAlert] = useState(false);
const [formData, setFormData] = useState<FormData>(initialData);
const [errors, setErrors] = useState<FormErrors>({});
const [isSaving, setIsSaving] = useState(false);
const [saveSuccess, setSaveSuccess] = useState(false);
const hasChanges = useMemo(() => {
return JSON.stringify(formData) !== JSON.stringify(initialData);
}, [formData]);
const validateForm = (): boolean => {
const newErrors: FormErrors = {};
if (!formData.name.trim()) {
newErrors.name = "Name is required";
}
if (!formData.email.trim()) {
newErrors.email = "Email is required";
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
newErrors.email = "Invalid email format";
}
if (!formData.status) {
newErrors.status = "Status is required";
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleChange = (field: keyof FormData, value: string) => {
setFormData((prev) => ({ ...prev, [field]: value }));
if (errors[field]) {
setErrors((prev) => ({ ...prev, [field]: undefined }));
}
};
const handleSave = async () => {
if (!validateForm()) return;
setIsSaving(true);
await new Promise((resolve) => setTimeout(resolve, 1000));
setIsSaving(false);
setSaveSuccess(true);
setTimeout(() => {
setSaveSuccess(false);
setOpen(false);
}, 1000);
};
const handleReset = () => {
setFormData(initialData);
setErrors({});
};
const handleClose = () => {
if (hasChanges) {
setShowDiscardAlert(true);
} else {
setOpen(false);
}
};
const handleDiscard = () => {
setShowDiscardAlert(false);
setFormData(initialData);
setErrors({});
setOpen(false);
};
const handleOpenChange = (newOpen: boolean) => {
if (!newOpen && hasChanges) {
setShowDiscardAlert(true);
} else {
setOpen(newOpen);
}
};
return (
<>
<Dialog open={open} onOpenChange={handleOpenChange}>
<div className="flex min-h-[350px] items-center justify-center">
<DialogTrigger asChild>
<Button variant="outline">
<Pencil className="mr-2 h-4 w-4" />
Edit Record
</Button>
</DialogTrigger>
</div>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Pencil className="h-5 w-5" />
Edit Record
</div>
{hasChanges && (
<Badge variant="secondary" className="text-xs">
Unsaved changes
</Badge>
)}
</DialogTitle>
<DialogDescription>
Make changes to the record below.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label htmlFor="name">
Name <span className="text-destructive">*</span>
</Label>
<Input
id="name"
value={formData.name}
onChange={(e) => handleChange("name", e.target.value)}
placeholder="Enter name"
className={errors.name ? "border-destructive" : ""}
/>
{errors.name && (
<p className="text-xs text-destructive flex items-center gap-1">
<AlertCircle className="h-3 w-3" />
{errors.name}
</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="email">
Email <span className="text-destructive">*</span>
</Label>
<Input
id="email"
type="email"
value={formData.email}
onChange={(e) => handleChange("email", e.target.value)}
placeholder="Enter email"
className={errors.email ? "border-destructive" : ""}
/>
{errors.email && (
<p className="text-xs text-destructive flex items-center gap-1">
<AlertCircle className="h-3 w-3" />
{errors.email}
</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="status">
Status <span className="text-destructive">*</span>
</Label>
<Select
value={formData.status}
onValueChange={(value) => handleChange("status", value)}
>
<SelectTrigger className={errors.status ? "border-destructive" : ""}>
<SelectValue placeholder="Select status" />
</SelectTrigger>
<SelectContent>
{statuses.map((status) => (
<SelectItem key={status.value} value={status.value}>
{status.label}
</SelectItem>
))}
</SelectContent>
</Select>
{errors.status && (
<p className="text-xs text-destructive flex items-center gap-1">
<AlertCircle className="h-3 w-3" />
{errors.status}
</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="description">Description</Label>
<Textarea
id="description"
value={formData.description}
onChange={(e) => handleChange("description", e.target.value)}
placeholder="Enter description"
rows={3}
/>
</div>
</div>
<DialogFooter className="flex-col sm:flex-row gap-2">
{hasChanges && (
<Button
variant="ghost"
size="sm"
onClick={handleReset}
className="sm:mr-auto"
>
<RotateCcw className="mr-2 h-4 w-4" />
Reset
</Button>
)}
<Button variant="outline" onClick={handleClose}>
Cancel
</Button>
<Button onClick={handleSave} disabled={isSaving || !hasChanges}>
{isSaving ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Saving...
</>
) : saveSuccess ? (
<>
<Save className="mr-2 h-4 w-4" />
Saved!
</>
) : (
<>
<Save className="mr-2 h-4 w-4" />
Save Changes
</>
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<AlertDialog open={showDiscardAlert} onOpenChange={setShowDiscardAlert}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Discard changes?</AlertDialogTitle>
<AlertDialogDescription>
You have unsaved changes. Are you sure you want to discard them?
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Keep Editing</AlertDialogCancel>
<AlertDialogAction onClick={handleDiscard}>
Discard
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
);
}