Spaces:
Sleeping
Sleeping
| import { useState } from "react"; | |
| import { zodResolver } from "@hookform/resolvers/zod"; | |
| import { useForm } from "react-hook-form"; | |
| import * as z from "zod"; | |
| import { format } from "date-fns"; | |
| import { CalendarIcon, Check, ChevronDown } from "lucide-react"; | |
| import { Button } from "@/components/ui/button"; | |
| import { Input } from "@/components/ui/input"; | |
| import { | |
| Form, | |
| FormControl, | |
| FormField, | |
| FormItem, | |
| FormLabel, | |
| FormMessage, | |
| } from "@/components/ui/form"; | |
| import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; | |
| import { Calendar } from "@/components/ui/calendar"; | |
| import { cn } from "@/lib/utils"; | |
| import { AppraisalCycle, AppraisalCycleCreateRequest, AppraisalCycleUpdateRequest } from "@/types"; | |
| import CustomDialog from "@/components/CustomDialog"; | |
| // Custom date dropdown component | |
| interface CustomDateDropdownProps { | |
| value?: Date; | |
| onChange: (date: Date) => void; | |
| placeholder: string; | |
| label: string; | |
| disabled?: boolean; | |
| minDate?: Date; | |
| error?: string; | |
| } | |
| function CustomDateDropdown({ | |
| value, | |
| onChange, | |
| placeholder, | |
| label, | |
| disabled = false, | |
| minDate, | |
| error | |
| }: CustomDateDropdownProps) { | |
| const [open, setOpen] = useState(false); | |
| const handleButtonClick = (e: React.MouseEvent) => { | |
| if (disabled) return; | |
| setOpen(!open); | |
| }; | |
| const handleDateSelect = (date: Date | undefined) => { | |
| if (date) { | |
| onChange(date); | |
| setOpen(false); | |
| } | |
| }; | |
| return ( | |
| <div className="w-full space-y-2"> | |
| <div className="font-medium text-sm">{label}</div> | |
| <div className="relative w-full"> | |
| <div | |
| className={cn( | |
| "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background cursor-pointer", | |
| open && "ring-2 ring-ring ring-offset-2", | |
| disabled && "opacity-50 cursor-not-allowed" | |
| )} | |
| onClick={handleButtonClick} | |
| > | |
| <span className={!value ? "text-muted-foreground" : ""}> | |
| {value ? format(value, "PPP") : placeholder} | |
| </span> | |
| <CalendarIcon className="h-4 w-4 opacity-50" /> | |
| </div> | |
| {open && ( | |
| <div className="absolute z-50 mt-1 w-full rounded-md border bg-popover p-1 text-popover-foreground shadow-md"> | |
| <Calendar | |
| mode="single" | |
| selected={value} | |
| onSelect={handleDateSelect} | |
| disabled={(date) => minDate ? date < minDate : false} | |
| initialFocus | |
| /> | |
| </div> | |
| )} | |
| </div> | |
| {error && <p className="text-sm font-medium text-destructive">{error}</p>} | |
| </div> | |
| ); | |
| } | |
| // Define form schema | |
| const formSchema = z.object({ | |
| name: z.string().min(2, { message: "Name must be at least 2 characters." }), | |
| startDate: z.date({ required_error: "Start date is required." }), | |
| endDate: z.date({ required_error: "End date is required." }), | |
| }).refine(data => data.endDate > data.startDate, { | |
| message: "End date must be after start date.", | |
| path: ["endDate"], | |
| }); | |
| interface AppraisalCycleDialogProps { | |
| open: boolean; | |
| onOpenChange: (open: boolean) => void; | |
| onSubmit: (data: AppraisalCycleCreateRequest | AppraisalCycleUpdateRequest) => void; | |
| cycleToEdit?: AppraisalCycle; | |
| isLoading?: boolean; | |
| } | |
| export function AppraisalCycleDialog({ | |
| open, | |
| onOpenChange, | |
| onSubmit, | |
| cycleToEdit, | |
| isLoading = false, | |
| }: AppraisalCycleDialogProps) { | |
| const [error, setError] = useState<string | null>(null); | |
| const form = useForm<z.infer<typeof formSchema>>({ | |
| resolver: zodResolver(formSchema), | |
| defaultValues: { | |
| name: cycleToEdit?.name || "", | |
| startDate: cycleToEdit?.startDate ? new Date(cycleToEdit.startDate) : undefined, | |
| endDate: cycleToEdit?.endDate ? new Date(cycleToEdit.endDate) : undefined, | |
| }, | |
| }); | |
| function handleSubmit(values: z.infer<typeof formSchema>) { | |
| setError(null); | |
| const formattedData = { | |
| name: values.name, | |
| startDate: format(values.startDate, "yyyy-MM-dd"), | |
| endDate: format(values.endDate, "yyyy-MM-dd"), | |
| }; | |
| if (cycleToEdit) { | |
| onSubmit({ | |
| ...formattedData, | |
| cycleId: cycleToEdit.cycleId, | |
| } as AppraisalCycleUpdateRequest); | |
| } else { | |
| onSubmit(formattedData as AppraisalCycleCreateRequest); | |
| } | |
| } | |
| // Handle dialog closing | |
| const handleClose = () => { | |
| onOpenChange(false); | |
| }; | |
| return ( | |
| <CustomDialog | |
| isOpen={open} | |
| onClose={handleClose} | |
| title={cycleToEdit ? "Edit Appraisal Cycle" : "Create Appraisal Cycle"} | |
| description={cycleToEdit ? "Edit the details of this appraisal cycle." : "Add a new appraisal cycle to the system."} | |
| allowClose={!isLoading} | |
| isPending={isLoading} | |
| > | |
| <Form {...form}> | |
| <form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-6"> | |
| <FormField | |
| control={form.control} | |
| name="name" | |
| render={({ field }) => ( | |
| <FormItem> | |
| <FormLabel>Name</FormLabel> | |
| <FormControl> | |
| <Input placeholder="Annual Appraisal 2023" {...field} /> | |
| </FormControl> | |
| <FormMessage /> | |
| </FormItem> | |
| )} | |
| /> | |
| <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> | |
| <FormField | |
| control={form.control} | |
| name="startDate" | |
| render={({ field, fieldState }) => ( | |
| <CustomDateDropdown | |
| label="Start Date" | |
| placeholder="Select start date" | |
| value={field.value} | |
| onChange={field.onChange} | |
| disabled={isLoading} | |
| error={fieldState.error?.message} | |
| /> | |
| )} | |
| /> | |
| <FormField | |
| control={form.control} | |
| name="endDate" | |
| render={({ field, fieldState }) => ( | |
| <CustomDateDropdown | |
| label="End Date" | |
| placeholder="Select end date" | |
| value={field.value} | |
| onChange={field.onChange} | |
| disabled={isLoading} | |
| minDate={form.watch("startDate")} | |
| error={fieldState.error?.message} | |
| /> | |
| )} | |
| /> | |
| </div> | |
| {error && <p className="text-sm font-medium text-destructive">{error}</p>} | |
| <div className="flex justify-end space-x-2 pt-4"> | |
| <Button | |
| type="button" | |
| variant="outline" | |
| onClick={handleClose} | |
| disabled={isLoading} | |
| > | |
| Cancel | |
| </Button> | |
| <Button type="submit" disabled={isLoading}> | |
| {isLoading ? "Saving..." : cycleToEdit ? "Update" : "Create"} | |
| </Button> | |
| </div> | |
| </form> | |
| </Form> | |
| </CustomDialog> | |
| ); | |
| } |