import { useEffect, useMemo, useState } from 'preact/hooks'; import WorkOrderImageCarousel from './WorkOrderImageCarousel'; import WorkOrderPhotoUpload from '../fleet/WorkOrderPhotoUpload'; import ModalShell from '../ui/ModalShell'; import { createWorkOrderInvoice, fetchTeamMembers, fetchWorkOrderInvoices, fetchWorkOrderImages, openWorkOrderInvoicesPdfPage, sendWorkOrderEmail, } from '../../stores/fleetDashboardStore'; import { showToast } from '../../stores/toastStore'; import { formatWorkOrderDisplayCode } from '../../lib/displayIds'; const PURPOSE_OPTIONS = [ { value: 'defektaza', label: 'Defektaža' }, { value: 'kontrola', label: 'Kontrola' }, { value: 'redovni_pregled', label: 'Redovni pregled' }, ]; export default function WorkOrderDetailModal({ open, workOrder, onClose, onSubmit }) { const [isEditing, setIsEditing] = useState(false); const [form, setForm] = useState({ has_travel_order: false, display_code: '', status: 'open', start_mileage: '', end_mileage: '', servicer_vehicle_registration: '', servicer_vehicle_make_model: '', servicer_vehicle_start_mileage: '', servicer_vehicle_end_mileage: '', servicer_vehicle_fuel_cost: '', origin_location: 'Zagreb', location: '', travel_start_at: '', travel_end_at: '', purpose: '', notes: '', }); const [imageFiles, setImageFiles] = useState([]); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(''); const [emailSending, setEmailSending] = useState(false); const [widgetError, setWidgetError] = useState(''); const [emailModalOpen, setEmailModalOpen] = useState(false); const [teamMembers, setTeamMembers] = useState([]); const [loadingTeamMembers, setLoadingTeamMembers] = useState(false); const [selectedTeamMemberIds, setSelectedTeamMemberIds] = useState([]); const [sendToAllTeamMembers, setSendToAllTeamMembers] = useState(false); const [emailPayload, setEmailPayload] = useState({ subject: '', message: '', }); const [invoices, setInvoices] = useState([]); const [invoiceModalOpen, setInvoiceModalOpen] = useState(false); const [photoUploadOpen, setPhotoUploadOpen] = useState(false); const [invoiceSaving, setInvoiceSaving] = useState(false); const [invoiceForm, setInvoiceForm] = useState({ naziv_racuna: '', lokacija: '', datum: new Date().toISOString().slice(0, 10), opis: '', imageFile: null, }); const [mapPickerOpen, setMapPickerOpen] = useState(false); const [locationQuery, setLocationQuery] = useState(''); const [locationResults, setLocationResults] = useState([]); const [selectedLocation, setSelectedLocation] = useState(null); const [searchingLocation, setSearchingLocation] = useState(false); const [mapError, setMapError] = useState(''); const craneLabel = useMemo(() => { if (!workOrder) return '-'; return workOrder.craneLabel || workOrder.craneInfo?.display_name || workOrder.craneInfo?.registration_number || `#${workOrder.crane || workOrder.vehicle}`; }, [workOrder]); const normalizedPurposeOptions = useMemo(() => { const known = new Set(PURPOSE_OPTIONS.map((item) => item.value)); const current = form.purpose?.trim(); if (current && !known.has(current)) { return [{ value: current, label: `${current} (postojeća vrijednost)` }, ...PURPOSE_OPTIONS]; } return PURPOSE_OPTIONS; }, [form.purpose]); useEffect(() => { if (!open || typeof window === 'undefined') return; window.scrollTo({ top: 0, behavior: 'smooth' }); }, [open]); useEffect(() => { if (!open || !workOrder) return; setIsEditing(false); setForm({ has_travel_order: !!(workOrder.travel_start_at || workOrder.travel_end_at || workOrder.origin_location || workOrder.location || workOrder.start_mileage != null || workOrder.end_mileage != null), display_code: workOrder.display_code || '', status: workOrder.status || 'open', start_mileage: String(workOrder.start_mileage ?? ''), end_mileage: workOrder.end_mileage == null ? '' : String(workOrder.end_mileage), origin_location: workOrder.origin_location || 'Zagreb', location: workOrder.location || '', travel_start_at: workOrder.travel_start_at ? String(workOrder.travel_start_at).slice(0, 16) : '', travel_end_at: workOrder.travel_end_at ? String(workOrder.travel_end_at).slice(0, 16) : '', purpose: workOrder.purpose || '', notes: workOrder.notes || '', servicer_vehicle_registration: workOrder.servicer_vehicle_registration || '', servicer_vehicle_make_model: workOrder.servicer_vehicle_make_model || '', servicer_vehicle_start_mileage: workOrder.servicer_vehicle_start_mileage == null ? '' : String(workOrder.servicer_vehicle_start_mileage), servicer_vehicle_end_mileage: workOrder.servicer_vehicle_end_mileage == null ? '' : String(workOrder.servicer_vehicle_end_mileage), servicer_vehicle_fuel_cost: workOrder.servicer_vehicle_fuel_cost == null ? '' : String(workOrder.servicer_vehicle_fuel_cost), }); setSubmitting(false); setError(''); setWidgetError(''); setEmailSending(false); setEmailModalOpen(false); setTeamMembers([]); setLoadingTeamMembers(false); setSelectedTeamMemberIds([]); setSendToAllTeamMembers(false); setEmailPayload({ subject: '', message: '', }); setInvoices([]); setInvoiceModalOpen(false); setPhotoUploadOpen(false); setInvoiceSaving(false); setInvoiceForm({ naziv_racuna: '', lokacija: '', datum: new Date().toISOString().slice(0, 10), opis: '', imageFile: null, }); setMapPickerOpen(false); setLocationQuery(''); setLocationResults([]); setSelectedLocation(null); setSearchingLocation(false); setMapError(''); }, [open, workOrder]); useEffect(() => { if (!emailModalOpen) return; let cancelled = false; setLoadingTeamMembers(true); (async () => { try { const members = await fetchTeamMembers(); if (!cancelled) { setTeamMembers(members); } } catch (err) { if (!cancelled) { setWidgetError(err?.message || 'Neuspješno dohvaćanje članova tima.'); } } finally { if (!cancelled) { setLoadingTeamMembers(false); } } })(); return () => { cancelled = true; }; }, [emailModalOpen]); useEffect(() => { if (!open || !workOrder?.id) { setImageFiles([]); return; } let cancelled = false; setImageFiles([]); (async () => { try { const files = await fetchWorkOrderImages(workOrder.id, { w: 1280, q: 75, fmt: 'webp' }); if (!cancelled) { setImageFiles( files .map((item) => item.optimized_url || item.original_url) .filter(Boolean) ); } } catch { if (!cancelled) { setImageFiles([]); } } })(); return () => { cancelled = true; }; }, [open, workOrder?.id]); useEffect(() => { if (!open || !workOrder?.id) { setInvoices([]); return; } let cancelled = false; (async () => { try { const items = await fetchWorkOrderInvoices(workOrder.id); if (!cancelled) { setInvoices(items); } } catch { if (!cancelled) { setInvoices([]); } } })(); return () => { cancelled = true; }; }, [open, workOrder?.id]); const selectedMapEmbedUrl = (() => { if (!selectedLocation) return null; const lat = Number(selectedLocation.lat); const lon = Number(selectedLocation.lon); if (Number.isNaN(lat) || Number.isNaN(lon)) return null; const delta = 0.01; const bbox = `${lon - delta}%2C${lat - delta}%2C${lon + delta}%2C${lat + delta}`; return `https://www.openstreetmap.org/export/embed.html?bbox=${bbox}&layer=mapnik&marker=${lat}%2C${lon}`; })(); if (!open || !workOrder) { return null; } const searchLocation = async () => { const query = locationQuery.trim(); if (!query) { setMapError('Unesite naziv lokacije za pretragu.'); return; } setMapError(''); setSearchingLocation(true); try { const response = await fetch( `https://nominatim.openstreetmap.org/search?format=jsonv2&limit=8&q=${encodeURIComponent(query)}` ); if (!response.ok) { throw new Error('Pretraga lokacije nije uspjela.'); } const payload = await response.json(); const results = Array.isArray(payload) ? payload : []; setLocationResults(results); if (results.length === 0) { setMapError('Lokacija nije pronađena. Pokušajte s detaljnijim nazivom.'); } } catch (error) { setMapError(error?.message || 'Greška pri dohvaćanju karte.'); } finally { setSearchingLocation(false); } }; const submit = async () => { if (!isEditing) { return; } setError(''); const startMileage = form.start_mileage === '' ? null : Number(form.start_mileage); const endMileage = form.end_mileage === '' ? null : Number(form.end_mileage); let travelStart = null; let travelEnd = null; if (startMileage != null && (Number.isNaN(startMileage) || startMileage < 0)) { setError('Početna kilometraža mora biti valjan nenegativan broj.'); return; } if (endMileage != null && (Number.isNaN(endMileage) || endMileage < 0)) { setError('Završna kilometraža mora biti valjan nenegativan broj.'); return; } if (endMileage != null && startMileage == null) { setError('Početna kilometraža je obavezna ako je unesena završna.'); return; } if (startMileage != null && endMileage != null && endMileage < startMileage) { setError('Završna kilometraža mora biti veća ili jednaka početnoj.'); return; } if ((form.travel_start_at && !form.travel_end_at) || (!form.travel_start_at && form.travel_end_at)) { setError('Unesite i početak i kraj puta, ili ostavite oba prazna.'); return; } if (form.travel_start_at && form.travel_end_at) { travelStart = new Date(form.travel_start_at); travelEnd = new Date(form.travel_end_at); if (Number.isNaN(travelStart.getTime()) || Number.isNaN(travelEnd.getTime())) { setError('Vrijeme puta nije u ispravnom formatu.'); return; } if (travelEnd < travelStart) { setError('Vrijeme kraja puta ne može biti prije početka puta.'); return; } if (!form.location.trim()) { setError('Lokacija odredišta je obavezna kada je putovanje uneseno.'); return; } } if (!form.purpose.trim()) { setError('Svrha naloga je obavezna.'); return; } const displayCode = String(form.display_code || '').trim().toUpperCase(); if (!displayCode) { setError('Prikaz putnog naloga je obavezan.'); return; } if (!/^[A-Z]{2}[0-9]{6}$/.test(displayCode)) { setError('Prikaz putnog naloga mora biti u formatu MT070626.'); return; } setSubmitting(true); try { await onSubmit(workOrder, { display_code: displayCode, status: form.status, start_mileage: startMileage, end_mileage: endMileage, servicer_vehicle_registration: form.servicer_vehicle_registration.trim(), servicer_vehicle_make_model: form.servicer_vehicle_make_model.trim(), servicer_vehicle_start_mileage: form.servicer_vehicle_start_mileage === '' ? null : Number(form.servicer_vehicle_start_mileage), servicer_vehicle_end_mileage: form.servicer_vehicle_end_mileage === '' ? null : Number(form.servicer_vehicle_end_mileage), servicer_vehicle_fuel_cost: form.servicer_vehicle_fuel_cost === '' ? null : Number(form.servicer_vehicle_fuel_cost), origin_location: form.origin_location.trim() || 'Zagreb', location: form.location.trim(), travel_start_at: travelStart ? travelStart.toISOString() : null, travel_end_at: travelEnd ? travelEnd.toISOString() : null, purpose: form.purpose.trim(), notes: form.notes.trim(), }); onClose(); } catch (err) { setError(err?.message || 'Spremanje putnog naloga nije uspjelo.'); } finally { setSubmitting(false); } }; const handleDownloadPdf = async () => { setWidgetError(''); try { openWorkOrderInvoicesPdfPage(workOrder.id); } catch (err) { setWidgetError(err?.message || 'Neuspješno preuzimanje PDF-a putnog naloga.'); } }; const handleCreateInvoice = async () => { if (!invoiceForm.naziv_racuna.trim()) { setWidgetError('Polje naziv_računa je obavezno.'); return; } if (!invoiceForm.datum) { setWidgetError('Polje datum je obavezno.'); return; } setWidgetError(''); setInvoiceSaving(true); try { await createWorkOrderInvoice(workOrder.id, invoiceForm); const refreshed = await fetchWorkOrderInvoices(workOrder.id); setInvoices(refreshed); setInvoiceForm({ naziv_racuna: '', lokacija: '', datum: new Date().toISOString().slice(0, 10), opis: '', imageFile: null, }); setInvoiceModalOpen(false); } catch (err) { setWidgetError(err?.message || 'Neuspješno spremanje računa.'); } finally { setInvoiceSaving(false); } }; const handleSendEmail = async () => { setWidgetError(''); setEmailSending(true); try { const recipients = sendToAllTeamMembers ? teamMembers.map((member) => member.email).filter(Boolean) : teamMembers .filter((member) => selectedTeamMemberIds.includes(String(member.id))) .map((member) => member.email) .filter(Boolean); if (recipients.length === 0) { setWidgetError('Odaberite barem jednog člana tima ili uključite slanje svima.'); return; } let failed = 0; for (const recipient of recipients) { try { await sendWorkOrderEmail( workOrder.id, { recipient, subject: emailPayload.subject.trim() || undefined, message: emailPayload.message.trim() || undefined, }, { toast: false } ); showToast(`Email je poslan: ${recipient}`, 'success'); } catch { failed += 1; showToast(`Neuspješno slanje: ${recipient}`, 'error'); } } if (failed > 0) { setWidgetError(`Slanje nije uspjelo za ${failed} primatelja.`); return; } setEmailModalOpen(false); } catch (err) { setWidgetError(err?.message || 'Neuspješno slanje emaila za putni nalog.'); } finally { setEmailSending(false); } }; return ( <>

Uredi putni nalog {formatWorkOrderDisplayCode(workOrder)}

Dizalica: {craneLabel}

event.preventDefault()} className="max-h-[calc(100vh-10rem)] space-y-4 overflow-y-auto px-5 py-4">

Dokument i email

{widgetError && (
{widgetError}
)}

Status

Vozilo servisera

Putni podaci