import { useEffect, useMemo, useState } from 'preact/hooks'; import { useStore } from '@nanostores/preact'; import { downloadWorkOrderDocx, downloadWorkOrderPdf, downloadWorkOrderInvoicesDocx, downloadWorkOrderInvoicesPdf, downloadWorkOrderServiceRecordsDocx, downloadWorkOrderServiceRecordsPdf, fetchWorkOrderAdditionalCostsTable, fetchWorkOrderById, fetchWorkOrderInvoices, fetchWorkOrderTaskServiceContext, updateTaskWorkHoursTable, updateWorkOrder, updateWorkOrderAdditionalCostsTable, } from '../../stores/fleetDashboardStore'; import { $accessToken, $authReady, hydrateAuthFromStorage } from '../../stores/authStore'; import { fetchNotifications, connectNotifications } from '../../stores/notificationStore'; import { formatWorkOrderDisplayCode } from '../../lib/displayIds'; import { showToast } from '../../stores/toastStore'; import { useAuthenticatedMediaSources } from './WorkOrderImageCarousel'; import TaskWorkHoursTableModal from './TaskWorkHoursTableModal'; import WorkOrderAdditionalCostsModal from './WorkOrderAdditionalCostsModal'; import WorkOrderServiceNotesModal from './WorkOrderServiceNotesModal'; function readWorkOrderIdFromQuery() { if (typeof window === 'undefined') { return ''; } const params = new URLSearchParams(window.location.search); return params.get('workOrderId') || ''; } function InvoiceImagePreview({ imageUrl, rawUrl, alt, contentType, onClick }) { const [broken, setBroken] = useState(false); const isPdf = String(contentType || '').toLowerCase() === 'application/pdf'; const isProtected = typeof rawUrl === 'string' && /\/api\/fleet\//i.test(rawUrl); const isLoading = isProtected && !imageUrl && !broken; if (isPdf && imageUrl) { return ( Otvori PDF ); } if (isLoading) { return (
Učitavanje…
); } if (!imageUrl || broken) { return (
Bez slike
); } return ( ); } function InvoiceDetailModal({ invoice, imageUrl, contentType, onClose }) { const isPdf = String(contentType || '').toLowerCase() === 'application/pdf'; useEffect(() => { const handleKey = (e) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', handleKey); return () => window.removeEventListener('keydown', handleKey); }, [onClose]); return (
{ if (e.target === e.currentTarget) onClose(); }} >

{invoice.naziv_racuna || 'Račun'}

Naziv: {invoice.naziv_racuna || '-'}
Lokacija: {invoice.lokacija || '-'}
Datum: {invoice.datum || '-'}
Opis: {invoice.opis || '-'}
{isPdf && imageUrl ? ( Otvori PDF u novom tabu ) : imageUrl ? ( {invoice.naziv_racuna} ) : (

Slika nije dostupna.

)}
); } export default function WorkOrderInvoicesPdfPage() { const authReady = useStore($authReady); const token = useStore($accessToken); const [workOrderId, setWorkOrderId] = useState(''); const [workOrder, setWorkOrder] = useState(null); const [invoices, setInvoices] = useState([]); const [taskContext, setTaskContext] = useState({ tasks: [] }); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [editingTask, setEditingTask] = useState(null); const [savingWorkHours, setSavingWorkHours] = useState(false); const [additionalCostsTable, setAdditionalCostsTable] = useState({ data: { rows: [] }, total_for_payout: '0.00' }); const [editingAdditionalCosts, setEditingAdditionalCosts] = useState(false); const [savingAdditionalCosts, setSavingAdditionalCosts] = useState(false); const [editingServiceReportNote, setEditingServiceReportNote] = useState(false); const [savingServiceReportNote, setSavingServiceReportNote] = useState(false); const [serviceReportNoteError, setServiceReportNoteError] = useState(''); const [invoiceDetailModal, setInvoiceDetailModal] = useState(null); useEffect(() => { hydrateAuthFromStorage(); const id = readWorkOrderIdFromQuery(); setWorkOrderId(id); }, []); useEffect(() => { if (authReady) { fetchNotifications(); connectNotifications(); } }, [authReady]); useEffect(() => { if (!workOrderId) { setLoading(false); setError('Nedostaje workOrderId u URL-u.'); return; } if (!authReady) { return; } if (!token) { setLoading(false); setError('Sesija nije aktivna. Prijavite se ponovno.'); return; } let cancelled = false; setLoading(true); setError(''); (async () => { try { const [contextPayload, order, invoiceItems, additionalCostsPayload] = await Promise.all([ fetchWorkOrderTaskServiceContext(workOrderId), fetchWorkOrderById(workOrderId), fetchWorkOrderInvoices(workOrderId), fetchWorkOrderAdditionalCostsTable(workOrderId), ]); if (cancelled) { return; } setTaskContext(contextPayload); setWorkOrder(order); setInvoices(invoiceItems); setAdditionalCostsTable(additionalCostsPayload || contextPayload?.additional_costs_table || { data: { rows: [] }, total_for_payout: '0.00' }); } catch (err) { if (!cancelled) { setError(err?.message || 'Neuspješno dohvaćanje podataka o računima.'); } } finally { if (!cancelled) { setLoading(false); } } })(); return () => { cancelled = true; }; }, [workOrderId, authReady, token]); const title = useMemo(() => { if (!workOrder) { return 'Računi putnog naloga'; } return formatWorkOrderDisplayCode(workOrder); }, [workOrder]); const mediaPaths = useMemo(() => { const paths = []; if (Array.isArray(taskContext.tasks)) { taskContext.tasks.forEach((task) => { if (!Array.isArray(task?.service_records)) return; task.service_records.forEach((record) => { if (!Array.isArray(record?.photos)) return; record.photos.forEach((photo) => { const candidate = photo?.optimized_url || photo?.image_url; if (candidate) { paths.push(candidate); } }); }); }); } if (Array.isArray(invoices)) { invoices.forEach((invoice) => { const invoiceImage = invoice?.image_url || invoice?.image; if (invoiceImage) { paths.push(invoiceImage); } }); } return paths; }, [taskContext.tasks, invoices]); const { getAuthenticatedMediaSrc } = useAuthenticatedMediaSources(mediaPaths); const handleSaveWorkHoursTable = async (tableData) => { if (!editingTask?.id) return; setSavingWorkHours(true); try { const response = await updateTaskWorkHoursTable(editingTask.id, tableData); setTaskContext((prev) => ({ ...prev, tasks: (Array.isArray(prev.tasks) ? prev.tasks : []).map((task) => ( String(task.id) === String(editingTask.id) ? { ...task, work_hours_table: response?.data ?? null } : task )), })); setEditingTask(null); } finally { setSavingWorkHours(false); } }; const handleSaveAdditionalCostsTable = async (tableData) => { if (!workOrderId) return; setSavingAdditionalCosts(true); try { const response = await updateWorkOrderAdditionalCostsTable(workOrderId, tableData); setAdditionalCostsTable(response || { data: { rows: [] }, total_for_payout: '0.00' }); setEditingAdditionalCosts(false); } finally { setSavingAdditionalCosts(false); } }; const handleSaveServiceReportNote = async (notes) => { if (!workOrderId) return; setSavingServiceReportNote(true); setServiceReportNoteError(''); try { const updated = await updateWorkOrder(workOrderId, { notes: String(notes || '') }); setWorkOrder(updated); setEditingServiceReportNote(false); showToast('Napomena za izvještaj servisera je spremljena.', 'success'); } catch (err) { const message = err?.message || 'Spremanje napomene nije uspjelo.'; setServiceReportNoteError(message); showToast(message, 'error'); } finally { setSavingServiceReportNote(false); } }; return (

{title}

{workOrder ? `Dizalica: ${workOrder.crane_label || workOrder.crane || '-'}` : 'Pregled računa prije kreiranja PDF-a.'}

Servisni zapisi

{loading &&

Učitavanje task konteksta...

} {!loading && !error && (!Array.isArray(taskContext.tasks) || taskContext.tasks.length === 0) && (

Nema taskova povezanih s ovim putnim nalogom.

)} {!loading && !error && Array.isArray(taskContext.tasks) && taskContext.tasks.length > 0 && (
{taskContext.tasks.map((task) => (
Radni zadatak: {task.title || '-'}
Status: {task.status || '-'}
Dodijeljeno: {task.assigned_to_name || '-'}
Dizalica: {task.vehicle_registration || '-'}
{!Array.isArray(task.service_records) || task.service_records.length === 0 ? (

Nema povezanih servisnih zapisa.

) : ( task.service_records.map((record) => (
Servis: {record.description || '-'}
Datum: {record.service_date || '-'}
Trošak: {record.cost || '-'} EUR
KM: {record.mileage ?? '-'}
{Array.isArray(record.photos) && record.photos.length > 0 ? ( record.photos.map((photo) => ( {`Servisna )) ) : ( Bez slika )}
)) )}
))}
)}

Računi

{!loading && !error && (

Ukupno dodatni troškovi: {additionalCostsTable?.total_for_payout || '0.00'} €

)} {loading &&

Učitavanje računa...

} {!loading && error && (
{error}
)} {!loading && !error && invoices.length === 0 && (

Za ovaj putni nalog nema unesenih računa.

)} {!loading && !error && invoices.length > 0 && (
{invoices.map((invoice) => { const rawUrl = invoice.image_url || invoice.image || ''; const blobUrl = getAuthenticatedMediaSrc(rawUrl); return (
setInvoiceDetailModal(invoice)} role="button" tabIndex={0} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') setInvoiceDetailModal(invoice); }} >
naziv_računa: {invoice.naziv_racuna}
lokacija: {invoice.lokacija || '-'}
datum: {invoice.datum || '-'}
opis: {invoice.opis || '-'}
{ e.stopPropagation(); setInvoiceDetailModal(invoice); }} />
); })}
)}
setEditingTask(null)} onSave={handleSaveWorkHoursTable} /> setEditingAdditionalCosts(false)} onSave={handleSaveAdditionalCostsTable} /> { setEditingServiceReportNote(false); setServiceReportNoteError(''); }} onSave={handleSaveServiceReportNote} /> {invoiceDetailModal && ( setInvoiceDetailModal(null)} /> )}
); }