Spaces:
Sleeping
Sleeping
| import React, { useEffect, useState } from "react"; | |
| import { useNavigate, useParams } from "react-router-dom"; | |
| import { useForm } from "react-hook-form"; | |
| import { zodResolver } from "@hookform/resolvers/zod"; | |
| import * as z from "zod"; | |
| import { useMutation, useQueryClient } from "@tanstack/react-query"; | |
| import { toast } from "@/lib/custom-toast"; | |
| import { format, parseISO } from "date-fns"; | |
| // UI Components | |
| import Layout from "@/components/layout/layout"; | |
| import { Button } from "@/components/ui/button"; | |
| import { | |
| Form, | |
| FormControl, | |
| FormDescription, | |
| FormField, | |
| FormItem, | |
| FormLabel, | |
| FormMessage, | |
| } from "@/components/ui/form"; | |
| import { Input } from "@/components/ui/input"; | |
| import { Textarea } from "@/components/ui/textarea"; | |
| import { cn } from "@/lib/utils"; | |
| // Types & API | |
| import { Deliverable, DeliverableUpdateRequest, ProjectApi } from "@/types"; | |
| import { deliverablesApi, projectService } from "@/lib/api"; | |
| // Form schema | |
| const formSchema = z.object({ | |
| name: z.string().min(1, "Name is required"), | |
| description: z.string().min(1, "Description is required"), | |
| deliveryDate: z.string().min(1, "Delivery date is required"), | |
| projectId: z.string().min(1, "Project is required"), | |
| }); | |
| type FormData = z.infer<typeof formSchema>; | |
| const DeliverableEdit = () => { | |
| const { id } = useParams<{ id: string }>(); | |
| const deliverableId = parseInt(id || "0", 10); | |
| const navigate = useNavigate(); | |
| const queryClient = useQueryClient(); | |
| // Add debug log for ID | |
| console.log(`Editing deliverable with ID: ${deliverableId}, raw ID param: ${id}`); | |
| const [deliverable, setDeliverable] = useState<Deliverable | null>(null); | |
| const [projects, setProjects] = useState<ProjectApi[]>([]); | |
| const [isLoadingDeliverable, setIsLoadingDeliverable] = useState(true); | |
| const [isLoadingProjects, setIsLoadingProjects] = useState(true); | |
| const [error, setError] = useState<string | null>(null); | |
| // Form setup | |
| const form = useForm<FormData>({ | |
| resolver: zodResolver(formSchema), | |
| defaultValues: { | |
| name: "", | |
| description: "", | |
| deliveryDate: format(new Date(), "yyyy-MM-dd"), | |
| projectId: "", | |
| }, | |
| }); | |
| // Fetch deliverable data | |
| useEffect(() => { | |
| const fetchDeliverable = async () => { | |
| if (!deliverableId) return; | |
| try { | |
| setIsLoadingDeliverable(true); | |
| const data = await deliverablesApi.getById(deliverableId); | |
| setDeliverable(data); | |
| // Set form values | |
| form.reset({ | |
| name: data.name, | |
| description: data.description, | |
| deliveryDate: format(parseISO(data.deliveryDate), "yyyy-MM-dd"), | |
| projectId: data.projectId.toString(), | |
| }); | |
| } catch (err) { | |
| console.error("Error fetching deliverable:", err); | |
| setError("Failed to load deliverable data"); | |
| } finally { | |
| setIsLoadingDeliverable(false); | |
| } | |
| }; | |
| fetchDeliverable(); | |
| }, [deliverableId, form]); | |
| // Fetch projects | |
| useEffect(() => { | |
| const fetchProjects = async () => { | |
| try { | |
| setIsLoadingProjects(true); | |
| const fetchedProjects = await projectService.getAll(); | |
| setProjects(fetchedProjects); | |
| } catch (err) { | |
| console.error("Error fetching projects:", err); | |
| // Don't set an error here, as we can still edit without projects list | |
| } finally { | |
| setIsLoadingProjects(false); | |
| } | |
| }; | |
| fetchProjects(); | |
| }, []); | |
| // Update deliverable mutation | |
| const updateDeliverableMutation = useMutation({ | |
| mutationFn: (data: DeliverableUpdateRequest) => deliverablesApi.update(data), | |
| onSuccess: () => { | |
| queryClient.invalidateQueries({ queryKey: ["deliverables"] }); | |
| queryClient.invalidateQueries({ queryKey: ["deliverable", deliverableId] }); | |
| toast.success("Deliverable updated successfully"); | |
| navigate("/deliverables"); | |
| }, | |
| onError: (error) => { | |
| console.error("Update error:", error); | |
| toast.error("Failed to update deliverable"); | |
| }, | |
| }); | |
| // Form submission handler | |
| const onSubmit = (data: FormData) => { | |
| const deliverableData: DeliverableUpdateRequest = { | |
| id: deliverableId, | |
| name: data.name, | |
| description: data.description, | |
| deliveryDate: new Date(data.deliveryDate).toISOString(), | |
| projectId: parseInt(data.projectId, 10), | |
| }; | |
| updateDeliverableMutation.mutate(deliverableData); | |
| }; | |
| if (isLoadingDeliverable) { | |
| return ( | |
| <Layout> | |
| <div className="flex items-center justify-center min-h-screen"> | |
| <p>Loading deliverable data...</p> | |
| </div> | |
| </Layout> | |
| ); | |
| } | |
| if (error) { | |
| return ( | |
| <Layout> | |
| <div className="flex flex-col items-center justify-center min-h-screen gap-4"> | |
| <p className="text-red-500">{error}</p> | |
| <Button variant="outline" onClick={() => navigate("/deliverables")}> | |
| Back to Deliverables | |
| </Button> | |
| </div> | |
| </Layout> | |
| ); | |
| } | |
| // Get today's date formatted for the date input | |
| const today = format(new Date(), "yyyy-MM-dd"); | |
| return ( | |
| <Layout> | |
| <div className="flex flex-col w-full min-h-screen p-4 md:p-6"> | |
| <div className="flex items-center justify-between mb-6"> | |
| <h1 className="text-2xl font-bold tracking-tight">Edit Deliverable</h1> | |
| <Button variant="outline" onClick={() => navigate(`/deliverable/${deliverableId}`)}> | |
| Cancel | |
| </Button> | |
| </div> | |
| <div className="max-w-2xl w-full"> | |
| <Form {...form}> | |
| <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> | |
| <FormField | |
| control={form.control} | |
| name="name" | |
| render={({ field }) => ( | |
| <FormItem> | |
| <FormLabel>Name</FormLabel> | |
| <FormControl> | |
| <Input placeholder="Enter deliverable name" {...field} /> | |
| </FormControl> | |
| <FormMessage /> | |
| </FormItem> | |
| )} | |
| /> | |
| <FormField | |
| control={form.control} | |
| name="description" | |
| render={({ field }) => ( | |
| <FormItem> | |
| <FormLabel>Description</FormLabel> | |
| <FormControl> | |
| <Textarea | |
| placeholder="Enter deliverable description" | |
| className="min-h-[120px]" | |
| {...field} | |
| /> | |
| </FormControl> | |
| <FormMessage /> | |
| </FormItem> | |
| )} | |
| /> | |
| <FormField | |
| control={form.control} | |
| name="deliveryDate" | |
| render={({ field }) => ( | |
| <FormItem> | |
| <FormLabel>Delivery Date</FormLabel> | |
| <FormControl> | |
| <Input type="date" {...field} /> | |
| </FormControl> | |
| <FormMessage /> | |
| </FormItem> | |
| )} | |
| /> | |
| <FormField | |
| control={form.control} | |
| name="projectId" | |
| render={({ field }) => ( | |
| <FormItem> | |
| <FormLabel>Project</FormLabel> | |
| <FormControl> | |
| <select | |
| className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" | |
| value={field.value} | |
| onChange={field.onChange} | |
| > | |
| <option value="" disabled> | |
| Select a project | |
| </option> | |
| {isLoadingProjects ? ( | |
| <option value="" disabled> | |
| Loading projects... | |
| </option> | |
| ) : projects.length === 0 ? ( | |
| <option value="" disabled> | |
| No projects available | |
| </option> | |
| ) : ( | |
| projects.map((project) => ( | |
| <option key={project.id} value={project.id}> | |
| {project.projectName || `Project #${project.id}`} | |
| </option> | |
| )) | |
| )} | |
| {deliverable && ( | |
| <option value={deliverable.projectId}> | |
| Current Project (ID: {deliverable.projectId}) | |
| </option> | |
| )} | |
| </select> | |
| </FormControl> | |
| <FormDescription> | |
| The project this deliverable belongs to | |
| </FormDescription> | |
| <FormMessage /> | |
| </FormItem> | |
| )} | |
| /> | |
| <div className="flex justify-end gap-4 pt-4"> | |
| <Button | |
| type="button" | |
| variant="outline" | |
| onClick={() => navigate(`/deliverable/${deliverableId}`)} | |
| > | |
| Cancel | |
| </Button> | |
| <Button | |
| type="submit" | |
| disabled={updateDeliverableMutation.isPending} | |
| className="bg-primary text-white hover:bg-primary/90" | |
| > | |
| {updateDeliverableMutation.isPending ? "Updating..." : "Update Deliverable"} | |
| </Button> | |
| </div> | |
| </form> | |
| </Form> | |
| </div> | |
| </div> | |
| </Layout> | |
| ); | |
| }; | |
| export default DeliverableEdit; |