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 || '-'}
);
}
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) => (
))
) : (
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)}
/>
)}
);
}