pmtool / src /components /appraisal /AppraisalCycleDialog.tsx
devarshia5's picture
Upload 487 files
d97b8f9 verified
Raw
History Blame Contribute Delete
7.26 kB
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>
);
}