"use client"; import { useState } from "react"; import { Button } from "@/components/ui/button"; import { DataTable } from "@/components/common/data-table"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from "@/components/ui/dialog"; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { projectService } from "@/lib/services/project.service"; import { ColumnDef } from "@tanstack/react-table"; import { Pencil, Trash, Plus, Search } from "lucide-react"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import * as z from "zod"; import { toast } from "sonner"; import apiClient from "@/lib/api/client"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { contractService } from "@/lib/services/contract.service"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Skeleton } from "@/components/ui/skeleton"; import { SearchContractDto, CreateContractDto, UpdateContractDto } from "@/types/dto/contract/contract.dto"; import { AxiosError } from "axios"; interface Project { id: number; projectCode: string; projectName: string; } interface Contract { id: number; contractCode: string; contractName: string; projectId: number; description?: string; startDate?: string; endDate?: string; project?: { projectCode: string; projectName: string; } } const contractSchema = z.object({ contractCode: z.string().min(1, "Contract Code is required"), contractName: z.string().min(1, "Contract Name is required"), projectId: z.string().min(1, "Project is required"), description: z.string().optional(), startDate: z.string().optional(), endDate: z.string().optional(), }); type ContractFormData = z.infer; const useContracts = (params?: SearchContractDto) => { return useQuery({ queryKey: ['contracts', params], queryFn: () => contractService.getAll(params), }); }; const useProjectsList = () => { return useQuery({ queryKey: ['projects-list'], queryFn: () => projectService.getAll(), }); }; export default function ContractsPage() { const [search, setSearch] = useState(""); const { data: contracts, isLoading } = useContracts({ search: search || undefined }); const { data: projects } = useProjectsList(); const queryClient = useQueryClient(); const createContract = useMutation({ mutationFn: (data: CreateContractDto) => apiClient.post("/contracts", data).then(res => res.data), onSuccess: () => { toast.success("Contract created successfully"); queryClient.invalidateQueries({ queryKey: ['contracts'] }); setDialogOpen(false); }, onError: (err: AxiosError<{ message: string }>) => toast.error(err.response?.data?.message || "Failed to create contract") }); const updateContract = useMutation({ mutationFn: ({ id, data }: { id: number, data: UpdateContractDto }) => apiClient.patch(`/contracts/${id}`, data).then(res => res.data), onSuccess: () => { toast.success("Contract updated successfully"); queryClient.invalidateQueries({ queryKey: ['contracts'] }); setDialogOpen(false); }, onError: (err: AxiosError<{ message: string }>) => toast.error(err.response?.data?.message || "Failed to update contract") }); const deleteContract = useMutation({ mutationFn: (id: number) => apiClient.delete(`/contracts/${id}`).then(res => res.data), onSuccess: () => { toast.success("Contract deleted successfully"); queryClient.invalidateQueries({ queryKey: ['contracts'] }); }, onError: (err: AxiosError<{ message: string }>) => toast.error(err.response?.data?.message || "Failed to delete contract") }); const [dialogOpen, setDialogOpen] = useState(false); const [editingId, setEditingId] = useState(null); // Stats for Delete Dialog const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [contractToDelete, setContractToDelete] = useState(null); const handleDeleteClick = (contract: Contract) => { setContractToDelete(contract); setDeleteDialogOpen(true); }; const confirmDelete = () => { if (contractToDelete) { deleteContract.mutate(contractToDelete.id, { onSuccess: () => { setDeleteDialogOpen(false); setContractToDelete(null); }, }); } }; const { register, handleSubmit, reset, setValue, watch, formState: { errors }, } = useForm({ resolver: zodResolver(contractSchema), defaultValues: { contractCode: "", contractName: "", projectId: "", description: "", }, }); const columns: ColumnDef[] = [ { accessorKey: "contractCode", header: "Code", cell: ({ row }) => {row.original.contractCode} }, { accessorKey: "contractName", header: "Name" }, { accessorKey: "project.projectCode", header: "Project", cell: ({ row }) => row.original.project?.projectCode || "-" }, { accessorKey: "startDate", header: "Start Date" }, { accessorKey: "endDate", header: "End Date" }, { id: "actions", header: "Actions", cell: ({ row }) => ( handleEdit(row.original)}> Edit handleDeleteClick(row.original)} > Delete ) } ]; const handleEdit = (contract: Contract) => { setEditingId(contract.id); reset({ contractCode: contract.contractCode, contractName: contract.contractName, projectId: contract.projectId?.toString() || "", description: contract.description || "", startDate: contract.startDate ? new Date(contract.startDate).toISOString().split('T')[0] : "", endDate: contract.endDate ? new Date(contract.endDate).toISOString().split('T')[0] : "", }); setDialogOpen(true); }; const handleCreate = () => { setEditingId(null); reset({ contractCode: "", contractName: "", projectId: "", description: "", startDate: "", endDate: "", }); setDialogOpen(true); }; const onSubmit = (data: ContractFormData) => { const submitData = { ...data, projectId: parseInt(data.projectId), }; if (editingId) { updateContract.mutate({ id: editingId, data: submitData }); } else { createContract.mutate(submitData); } }; return (

Contracts

Manage construction contracts

setSearch(e.target.value)} className="pl-8 bg-background" />
{isLoading ? (
{[1, 2, 3, 4, 5].map((i) => (
))}
) : ( )} {editingId ? "Edit Contract" : "New Contract"}
{errors.projectId && (

{errors.projectId.message}

)}
{errors.contractCode && (

{errors.contractCode.message}

)}
{errors.contractName && (

{errors.contractName.message}

)}
Are you absolutely sure? This action cannot be undone. This will permanently delete the contract {contractToDelete?.contractCode} and remove it from the system. Cancel {deleteContract.isPending ? "Deleting..." : "Delete Contract"}
); }