import React, { useState, useEffect } from 'react'; import { ClipboardCheck, Plus, Edit, Eye, Search, X, CheckCircle, XCircle, AlertTriangle, Star, } from 'lucide-react'; import { toast } from 'react-toastify'; import Loading from '../common/Loading'; import Pagination from '../common/Pagination'; import advancedRoomService, { RoomInspection, InspectionChecklistItem, Issue, } from '../../services/api/advancedRoomService'; import { roomService, Room } from '../../services/api'; import { userService, User as UserType } from '../../services/api'; import useAuthStore from '../../store/useAuthStore'; import DatePicker from 'react-datepicker'; import 'react-datepicker/dist/react-datepicker.css'; const InspectionManagement: React.FC = () => { const { userInfo } = useAuthStore(); const isAdmin = userInfo?.role === 'admin'; const [loading, setLoading] = useState(true); const [inspections, setInspections] = useState([]); const [rooms, setRooms] = useState([]); const [staff, setStaff] = useState([]); const [showModal, setShowModal] = useState(false); const [editingInspection, setEditingInspection] = useState(null); const [viewingInspection, setViewingInspection] = useState(null); const [currentPage, setCurrentPage] = useState(1); const [totalPages, setTotalPages] = useState(1); const [filters, setFilters] = useState({ room_id: '', inspection_type: '', status: '', }); const defaultChecklistCategories = [ { category: 'Bathroom', items: ['Toilet', 'Shower', 'Sink', 'Mirror', 'Tiles', 'Ventilation'], }, { category: 'Bedroom', items: ['Bed', 'Linens', 'Pillows', 'Furniture', 'Closet', 'Lighting'], }, { category: 'Electronics', items: ['TV', 'Remote', 'AC', 'WiFi', 'Safe', 'Charging ports'], }, { category: 'Amenities', items: ['Towels', 'Toiletries', 'Coffee/Tea', 'Mini bar', 'Hangers', 'Iron'], }, { category: 'General', items: ['Floor', 'Walls', 'Windows', 'Doors', 'Curtains', 'Smoke detector'], }, ]; const [formData, setFormData] = useState({ room_id: '', booking_id: '', inspection_type: 'routine' as 'pre_checkin' | 'post_checkout' | 'routine' | 'maintenance' | 'damage', scheduled_at: new Date(), inspected_by: '', checklist_items: [] as InspectionChecklistItem[], overall_score: '', overall_notes: '', issues_found: [] as Issue[], requires_followup: false, }); useEffect(() => { fetchInspections(); fetchRooms(); fetchStaff(); }, [currentPage, filters]); const fetchInspections = async () => { try { setLoading(true); const params: any = { page: currentPage, limit: 10 }; if (filters.room_id) params.room_id = parseInt(filters.room_id); if (filters.inspection_type) params.inspection_type = filters.inspection_type; if (filters.status) params.status = filters.status; const response = await advancedRoomService.getRoomInspections(params); if (response.status === 'success') { setInspections(response.data.inspections); setTotalPages(response.data.pagination?.total_pages || 1); } } catch (error: any) { toast.error(error.response?.data?.detail || 'Failed to fetch inspections'); } finally { setLoading(false); } }; const fetchRooms = async () => { try { const response = await roomService.getRooms({ limit: 1000, page: 1 }); if (response.data?.rooms) { setRooms(response.data.rooms); } } catch (error) { console.error('Failed to fetch rooms:', error); } }; const fetchStaff = async () => { try { const response = await userService.getUsers({ role: 'staff', limit: 100 }); if (response.data?.users) { setStaff(response.data.users); } } catch (error) { console.error('Failed to fetch staff:', error); } }; const initializeChecklist = () => { const items: InspectionChecklistItem[] = []; defaultChecklistCategories.forEach((category) => { category.items.forEach((item) => { items.push({ category: category.category, item: item, status: 'not_applicable', notes: '', photos: [], }); }); }); setFormData({ ...formData, checklist_items: items }); }; const handleCreate = () => { setEditingInspection(null); setFormData({ room_id: '', booking_id: '', inspection_type: 'routine', scheduled_at: new Date(), inspected_by: '', checklist_items: [], overall_score: '', overall_notes: '', issues_found: [], requires_followup: false, }); initializeChecklist(); setShowModal(true); }; const handleMarkAsDone = async (inspection: RoomInspection) => { // Double check that the inspection is assigned to the current user if (!inspection.inspected_by) { toast.error('Inspection must be assigned before it can be marked as done'); return; } if (inspection.inspected_by !== userInfo?.id) { toast.error('Only the assigned inspector can mark this inspection as done'); return; } try { await advancedRoomService.updateRoomInspection(inspection.id, { status: 'completed', completed_at: new Date().toISOString(), }); toast.success('Inspection marked as completed successfully'); fetchInspections(); } catch (error: any) { toast.error(error.response?.data?.detail || 'Failed to mark inspection as done'); } }; const handleEdit = (inspection: RoomInspection) => { setEditingInspection(inspection); setFormData({ room_id: inspection.room_id.toString(), booking_id: inspection.booking_id?.toString() || '', inspection_type: inspection.inspection_type, scheduled_at: new Date(inspection.scheduled_at), inspected_by: inspection.inspected_by?.toString() || '', checklist_items: inspection.checklist_items || [], overall_score: inspection.overall_score?.toString() || '', overall_notes: inspection.overall_notes || '', issues_found: inspection.issues_found || [], requires_followup: inspection.requires_followup, }); setShowModal(true); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { const data = { room_id: parseInt(formData.room_id), booking_id: formData.booking_id ? parseInt(formData.booking_id) : undefined, inspection_type: formData.inspection_type, scheduled_at: formData.scheduled_at.toISOString(), inspected_by: formData.inspected_by ? parseInt(formData.inspected_by) : undefined, checklist_items: formData.checklist_items, overall_score: formData.overall_score ? parseFloat(formData.overall_score) : undefined, overall_notes: formData.overall_notes, issues_found: formData.issues_found, requires_followup: formData.requires_followup, }; if (editingInspection) { await advancedRoomService.updateRoomInspection(editingInspection.id, { status: editingInspection.status, ...data, }); toast.success('Inspection updated successfully'); } else { await advancedRoomService.createRoomInspection(data); toast.success('Inspection created successfully'); } setShowModal(false); fetchInspections(); } catch (error: any) { toast.error(error.response?.data?.detail || 'Failed to save inspection'); } }; const updateChecklistItem = (index: number, field: 'status' | 'notes', value: any) => { const updated = [...formData.checklist_items]; updated[index] = { ...updated[index], [field]: value }; setFormData({ ...formData, checklist_items: updated }); }; const addIssue = () => { setFormData({ ...formData, issues_found: [ ...formData.issues_found, { severity: 'minor', description: '', photo: '' }, ], }); }; const updateIssue = (index: number, field: 'severity' | 'description' | 'photo', value: any) => { const updated = [...formData.issues_found]; updated[index] = { ...updated[index], [field]: value }; setFormData({ ...formData, issues_found: updated }); }; const removeIssue = (index: number) => { setFormData({ ...formData, issues_found: formData.issues_found.filter((_, i) => i !== index), }); }; const getStatusColor = (status: string) => { switch (status) { case 'completed': return 'bg-green-100 text-green-800'; case 'in_progress': return 'bg-blue-100 text-blue-800'; case 'pending': return 'bg-yellow-100 text-yellow-800'; case 'failed': return 'bg-red-100 text-red-800'; default: return 'bg-gray-100 text-gray-800'; } }; const getStatusIcon = (status: string) => { switch (status) { case 'pass': return ; case 'fail': return ; case 'needs_attention': return ; default: return null; } }; if (loading && inspections.length === 0) { return ; } return (
setFilters({ ...filters, room_id: e.target.value })} />
{isAdmin && ( )}
{inspections.map((inspection) => ( ))}
Room Type Status Scheduled Score Inspector Actions
{inspection.room_number || `Room ${inspection.room_id}`}
{inspection.inspection_type.replace('_', ' ')}
{inspection.status.replace('_', ' ')} {new Date(inspection.scheduled_at).toLocaleDateString()} {inspection.overall_score ? (
{inspection.overall_score.toFixed(1)}
) : ( N/A )}
{inspection.inspector_name || 'Unassigned'}
{isAdmin ? ( ) : ( // Staff can mark their own assigned inspections as done inspection.inspected_by === userInfo?.id && inspection.status !== 'completed' && ( ) )}
{totalPages > 1 && (
)} {/* Create/Edit Modal - Simplified for space, full version would be similar to others */} {showModal && (

{editingInspection ? 'Edit Inspection' : 'New Inspection'}

date && setFormData({ ...formData, scheduled_at: date })} showTimeSelect dateFormat="yyyy-MM-dd HH:mm" className="w-full border border-gray-300 rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
{defaultChecklistCategories.map((category, catIndex) => (

{category.category}

{category.items.map((item, itemIndex) => { const checklistIndex = formData.checklist_items.findIndex( (ci) => ci.category === category.category && ci.item === item ); if (checklistIndex === -1) return null; return (
{item} {getStatusIcon(formData.checklist_items[checklistIndex].status)}
); })}
))}
setFormData({ ...formData, overall_score: e.target.value })} className="w-full border border-gray-300 rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />