Spaces:
Sleeping
Sleeping
| import React, { useState, useEffect } from "react"; | |
| import { Plus, Edit, Trash, CalendarDays } from "lucide-react"; | |
| import { format, parseISO } from "date-fns"; | |
| import { useQueryClient, useQuery, useMutation } from "@tanstack/react-query"; | |
| import { Button } from "@/components/ui/button"; | |
| import { Label } from "@/components/ui/label"; | |
| import { Input } from "@/components/ui/input"; | |
| import { Textarea } from "@/components/ui/textarea"; | |
| import { Calendar } from "@/components/ui/calendar"; | |
| import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; | |
| import { | |
| Table, | |
| TableBody, | |
| TableCell, | |
| TableHead, | |
| TableHeader, | |
| TableRow | |
| } from "@/components/ui/table"; | |
| import { toast } from "@/lib/custom-toast"; | |
| import { cn } from "@/lib/utils"; | |
| import { | |
| MaintenanceAmcDate, | |
| MaintenanceAmcDateCreateRequest, | |
| MaintenanceAmcDateUpdateRequest | |
| } from "@/types"; | |
| import { maintenanceAmcDateService } from "@/lib/api"; | |
| import CustomDialog from "@/components/CustomDialog"; | |
| interface MaintenanceAmcDatesSectionProps { | |
| projectId: number; | |
| } | |
| export default function MaintenanceAmcDatesSection({ projectId }: MaintenanceAmcDatesSectionProps) { | |
| const queryClient = useQueryClient(); | |
| const [open, setOpen] = useState(false); | |
| const [editMode, setEditMode] = useState(false); | |
| const [formData, setFormData] = useState<MaintenanceAmcDateCreateRequest>({ | |
| projectId: projectId, | |
| maintenanceType: '', | |
| startDate: new Date().toISOString(), | |
| endDate: new Date().toISOString(), | |
| notes: '' | |
| }); | |
| const [selectedId, setSelectedId] = useState<number | null>(null); | |
| const [startDateOpen, setStartDateOpen] = useState(false); | |
| const [endDateOpen, setEndDateOpen] = useState(false); | |
| // Fetch maintenance AMC dates for the project | |
| const { data: maintenanceAmcDates = [], isLoading } = useQuery({ | |
| queryKey: ['maintenanceAmcDates', projectId], | |
| queryFn: () => maintenanceAmcDateService.getByProjectId(projectId), | |
| enabled: !!projectId | |
| }); | |
| // Create mutation | |
| const createMutation = useMutation({ | |
| mutationFn: (data: MaintenanceAmcDateCreateRequest) => maintenanceAmcDateService.create(data), | |
| onSuccess: () => { | |
| queryClient.invalidateQueries({ queryKey: ['maintenanceAmcDates', projectId] }); | |
| toast.success('Maintenance/AMC date added successfully'); | |
| resetForm(); | |
| setOpen(false); | |
| }, | |
| onError: (error) => { | |
| console.error('Error creating maintenance AMC date:', error); | |
| toast.error('Failed to add maintenance/AMC date'); | |
| } | |
| }); | |
| // Update mutation | |
| const updateMutation = useMutation({ | |
| mutationFn: (data: MaintenanceAmcDateUpdateRequest) => maintenanceAmcDateService.update(data), | |
| onSuccess: () => { | |
| queryClient.invalidateQueries({ queryKey: ['maintenanceAmcDates', projectId] }); | |
| toast.success('Maintenance/AMC date updated successfully'); | |
| resetForm(); | |
| setOpen(false); | |
| }, | |
| onError: (error) => { | |
| console.error('Error updating maintenance AMC date:', error); | |
| toast.error('Failed to update maintenance/AMC date'); | |
| } | |
| }); | |
| // Delete mutation | |
| const deleteMutation = useMutation({ | |
| mutationFn: (id: number) => maintenanceAmcDateService.delete(id), | |
| onSuccess: () => { | |
| queryClient.invalidateQueries({ queryKey: ['maintenanceAmcDates', projectId] }); | |
| toast.success('Maintenance/AMC date deleted successfully'); | |
| }, | |
| onError: (error) => { | |
| console.error('Error deleting maintenance AMC date:', error); | |
| toast.error('Failed to delete maintenance/AMC date'); | |
| } | |
| }); | |
| // Reset form data | |
| const resetForm = () => { | |
| setFormData({ | |
| projectId: projectId, | |
| maintenanceType: '', | |
| startDate: new Date().toISOString(), | |
| endDate: new Date().toISOString(), | |
| notes: '' | |
| }); | |
| setEditMode(false); | |
| setSelectedId(null); | |
| }; | |
| // Handle submit | |
| const handleSubmit = (e: React.FormEvent) => { | |
| e.preventDefault(); | |
| if (!formData.maintenanceType.trim()) { | |
| toast.error('Maintenance Type is required'); | |
| return; | |
| } | |
| if (editMode && selectedId) { | |
| updateMutation.mutate({ | |
| ...formData, | |
| maintenanceId: selectedId | |
| } as MaintenanceAmcDateUpdateRequest); | |
| } else { | |
| createMutation.mutate(formData); | |
| } | |
| }; | |
| // Handle edit button click | |
| const handleEdit = (item: MaintenanceAmcDate) => { | |
| setEditMode(true); | |
| setSelectedId(item.maintenanceId); | |
| setFormData({ | |
| projectId: item.projectId, | |
| maintenanceType: item.maintenanceType, | |
| startDate: item.startDate, | |
| endDate: item.endDate, | |
| notes: item.notes | |
| }); | |
| setOpen(true); | |
| }; | |
| // Handle delete button click | |
| const handleDelete = (id: number) => { | |
| if (confirm('Are you sure you want to delete this maintenance/AMC date?')) { | |
| deleteMutation.mutate(id); | |
| } | |
| }; | |
| // Handle form input changes | |
| const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { | |
| const { name, value } = e.target; | |
| setFormData(prev => ({ ...prev, [name]: value })); | |
| }; | |
| // Handle date changes | |
| const handleStartDateChange = (date: Date | undefined) => { | |
| if (date) { | |
| setFormData(prev => ({ ...prev, startDate: date.toISOString() })); | |
| } | |
| }; | |
| const handleEndDateChange = (date: Date | undefined) => { | |
| if (date) { | |
| setFormData(prev => ({ ...prev, endDate: date.toISOString() })); | |
| } | |
| }; | |
| // Format date for display | |
| const formatDate = (dateString: string) => { | |
| try { | |
| return format(parseISO(dateString), 'PPP'); | |
| } catch (error) { | |
| console.error('Error formatting date:', error); | |
| return 'Invalid date'; | |
| } | |
| }; | |
| return ( | |
| <div className="space-y-4"> | |
| <div className="flex items-center justify-between"> | |
| <h3 className="text-lg font-medium">Maintenance & AMC Dates</h3> | |
| <Button | |
| size="sm" | |
| onClick={() => { | |
| resetForm(); | |
| setOpen(true); | |
| }} | |
| > | |
| <Plus className="h-4 w-4 mr-2" /> | |
| Add New | |
| </Button> | |
| </div> | |
| <CustomDialog | |
| isOpen={open} | |
| onClose={() => setOpen(false)} | |
| title={`${editMode ? 'Edit' : 'Add'} Maintenance or AMC Date`} | |
| > | |
| <form onSubmit={handleSubmit} className="space-y-4"> | |
| <div className="space-y-2"> | |
| <Label htmlFor="maintenanceType">Maintenance Type</Label> | |
| <Input | |
| id="maintenanceType" | |
| name="maintenanceType" | |
| value={formData.maintenanceType} | |
| onChange={handleInputChange} | |
| placeholder="e.g. Regular Maintenance, AMC, Service" | |
| required | |
| /> | |
| </div> | |
| <div className="grid grid-cols-2 gap-4"> | |
| <div className="space-y-2"> | |
| <Label htmlFor="startDate">Start Date</Label> | |
| <Popover open={startDateOpen} onOpenChange={setStartDateOpen}> | |
| <PopoverTrigger asChild> | |
| <Button | |
| variant="outline" | |
| className={cn( | |
| "w-full justify-start text-left font-normal", | |
| !formData.startDate && "text-muted-foreground" | |
| )} | |
| > | |
| <CalendarDays className="mr-2 h-4 w-4" /> | |
| {formData.startDate ? formatDate(formData.startDate) : <span>Select date</span>} | |
| </Button> | |
| </PopoverTrigger> | |
| <PopoverContent className="w-auto p-0"> | |
| <Calendar | |
| mode="single" | |
| selected={formData.startDate ? parseISO(formData.startDate) : undefined} | |
| onSelect={(date) => { | |
| handleStartDateChange(date); | |
| setStartDateOpen(false); | |
| }} | |
| initialFocus | |
| /> | |
| </PopoverContent> | |
| </Popover> | |
| </div> | |
| <div className="space-y-2"> | |
| <Label htmlFor="endDate">End Date</Label> | |
| <Popover open={endDateOpen} onOpenChange={setEndDateOpen}> | |
| <PopoverTrigger asChild> | |
| <Button | |
| variant="outline" | |
| className={cn( | |
| "w-full justify-start text-left font-normal", | |
| !formData.endDate && "text-muted-foreground" | |
| )} | |
| > | |
| <CalendarDays className="mr-2 h-4 w-4" /> | |
| {formData.endDate ? formatDate(formData.endDate) : <span>Select date</span>} | |
| </Button> | |
| </PopoverTrigger> | |
| <PopoverContent className="w-auto p-0"> | |
| <Calendar | |
| mode="single" | |
| selected={formData.endDate ? parseISO(formData.endDate) : undefined} | |
| onSelect={(date) => { | |
| handleEndDateChange(date); | |
| setEndDateOpen(false); | |
| }} | |
| initialFocus | |
| /> | |
| </PopoverContent> | |
| </Popover> | |
| </div> | |
| </div> | |
| <div className="space-y-2"> | |
| <Label htmlFor="notes">Notes</Label> | |
| <Textarea | |
| id="notes" | |
| name="notes" | |
| value={formData.notes} | |
| onChange={handleInputChange} | |
| placeholder="Additional details or notes" | |
| rows={3} | |
| /> | |
| </div> | |
| <div className="flex justify-end gap-2 pt-2"> | |
| <Button type="button" variant="outline" onClick={() => setOpen(false)}> | |
| Cancel | |
| </Button> | |
| <Button type="submit"> | |
| {editMode ? 'Update' : 'Add'} | |
| </Button> | |
| </div> | |
| </form> | |
| </CustomDialog> | |
| {isLoading ? ( | |
| <div className="text-center py-4">Loading...</div> | |
| ) : maintenanceAmcDates.length === 0 ? ( | |
| <div className="text-center py-8 border rounded-md bg-muted/20"> | |
| <CalendarDays className="h-8 w-8 mx-auto mb-2 text-muted-foreground" /> | |
| <p className="text-muted-foreground">No maintenance or AMC dates have been added yet.</p> | |
| </div> | |
| ) : ( | |
| <Table> | |
| <TableHeader> | |
| <TableRow> | |
| <TableHead>Type</TableHead> | |
| <TableHead>Start Date</TableHead> | |
| <TableHead>End Date</TableHead> | |
| <TableHead>Notes</TableHead> | |
| <TableHead className="text-right">Actions</TableHead> | |
| </TableRow> | |
| </TableHeader> | |
| <TableBody> | |
| {maintenanceAmcDates.map(item => ( | |
| <TableRow key={item.maintenanceId}> | |
| <TableCell className="font-medium">{item.maintenanceType}</TableCell> | |
| <TableCell>{formatDate(item.startDate)}</TableCell> | |
| <TableCell>{formatDate(item.endDate)}</TableCell> | |
| <TableCell className="max-w-[200px] truncate">{item.notes}</TableCell> | |
| <TableCell className="text-right"> | |
| <div className="flex justify-end gap-2"> | |
| <Button | |
| size="icon" | |
| variant="ghost" | |
| onClick={() => handleEdit(item)} | |
| > | |
| <Edit className="h-4 w-4" /> | |
| </Button> | |
| <Button | |
| size="icon" | |
| variant="ghost" | |
| className="text-destructive" | |
| onClick={() => handleDelete(item.maintenanceId)} | |
| > | |
| <Trash className="h-4 w-4" /> | |
| </Button> | |
| </div> | |
| </TableCell> | |
| </TableRow> | |
| ))} | |
| </TableBody> | |
| </Table> | |
| )} | |
| </div> | |
| ); | |
| } |