prvi kod za live uporabu
Some checks failed
ERP CI Pipeline / test (push) Has been cancelled

This commit is contained in:
mariomitte
2026-07-09 21:21:17 +02:00
parent 857bb65d52
commit 5d39e048ba
239 changed files with 25151 additions and 0 deletions

View File

@@ -0,0 +1,199 @@
import { useEffect, useMemo, useState } from 'preact/hooks';
import ModalShell from '../ui/ModalShell';
import { formatEntityCode } from '../../lib/displayIds';
function formatDate(value) {
if (!value) return '-';
const parsed = new Date(value);
if (Number.isNaN(parsed.getTime())) return value;
return parsed.toLocaleDateString('hr-HR');
}
function formatCost(value) {
const numeric = Number(value);
if (Number.isNaN(numeric)) return '-';
return `${numeric.toFixed(2)} EUR`;
}
export default function TaskServiceRecordsModal({
open,
task,
records = [],
workOrders = [],
onClose,
onOpenServiceRecord,
onAssignWorkOrder,
onCreateServiceRecord,
}) {
const [showWorkOrderEditor, setShowWorkOrderEditor] = useState(false);
const [selectedWorkOrderId, setSelectedWorkOrderId] = useState('');
const [assigningWorkOrder, setAssigningWorkOrder] = useState(false);
const [assignError, setAssignError] = useState('');
useEffect(() => {
if (!open || typeof window === 'undefined') return;
window.scrollTo({ top: 0, behavior: 'smooth' });
}, [open]);
useEffect(() => {
if (!task) return;
setSelectedWorkOrderId(task.work_order ? String(task.work_order) : '');
setShowWorkOrderEditor(!task.work_order);
setAssignError('');
setAssigningWorkOrder(false);
}, [task?.id, task?.work_order]);
const currentWorkOrder = useMemo(
() => workOrders.find((item) => String(item.id) === String(task?.work_order || selectedWorkOrderId)) || null,
[workOrders, task?.work_order, selectedWorkOrderId]
);
const sortedWorkOrders = useMemo(
() => [...workOrders].sort((a, b) => String(b.date || '').localeCompare(String(a.date || ''))),
[workOrders]
);
const handleWorkOrderChange = async (event) => {
const nextValue = String(event.currentTarget.value || '');
setSelectedWorkOrderId(nextValue);
setAssignError('');
setAssigningWorkOrder(true);
try {
await onAssignWorkOrder?.(task, nextValue || null);
setShowWorkOrderEditor(false);
} catch (error) {
setAssignError(error?.message || 'Neuspješno spremanje putnog naloga za radni zadatak.');
} finally {
setAssigningWorkOrder(false);
}
};
if (!open || !task) return null;
return (
<ModalShell
onClose={onClose}
overlayClassName="z-50 overflow-y-auto p-4 pt-2"
contentClassName="flex items-start justify-center min-h-full"
panelClassName="w-full max-w-4xl rounded-xl border border-border-hairline bg-canvas-elevated shadow-xl max-h-[calc(100vh-6rem)] overflow-hidden"
>
<div className="w-full flex flex-col h-full">
<div className="flex items-center justify-between border-b border-border-hairline px-5 py-4">
<div>
<h3 className="text-lg font-semibold text-text-main">Radni zadatak detalji</h3>
<p className="text-xs text-text-muted">
Radni zadatak: {task.title || '-'} Status: {task.status || '-'} Zapisa: {records.length}
</p>
</div>
<button
type="button"
onClick={onClose}
className="rounded-md px-2 py-1 text-sm text-text-muted hover:bg-canvas-deep"
aria-label="Zatvori"
>
</button>
</div>
<div className="max-h-[calc(100vh-12rem)] overflow-y-auto p-4">
<div className="space-y-4">
<div className="rounded-lg border border-border-hairline bg-canvas-base p-3">
<h4 className="mb-2 text-sm font-semibold text-text-main">Putni nalozi</h4>
{currentWorkOrder && !showWorkOrderEditor ? (
<div className="flex flex-wrap items-center justify-between gap-2 rounded border border-border-hairline bg-canvas-elevated px-3 py-2">
<p className="text-sm text-text-main">
{formatEntityCode('PN', currentWorkOrder.id)} {currentWorkOrder.craneLabel || currentWorkOrder.craneInfo?.registration_number || '-'} {currentWorkOrder.status || '-'}
</p>
<button
type="button"
onClick={() => setShowWorkOrderEditor(true)}
className="rounded border border-border-hairline px-2 py-1 text-xs text-text-main hover:bg-canvas-base"
>
Izmijeni putni nalog
</button>
</div>
) : (
<div className="space-y-2">
<select
value={selectedWorkOrderId}
onChange={handleWorkOrderChange}
disabled={assigningWorkOrder}
className="w-full rounded-lg border border-border-hairline bg-canvas-elevated px-3 py-2 text-sm text-text-main disabled:opacity-60"
>
<option value=""> Odaberi putni nalog </option>
{sortedWorkOrders.map((workOrder) => (
<option key={workOrder.id} value={String(workOrder.id)}>
{formatEntityCode('PN', workOrder.id)} {workOrder.craneLabel || workOrder.craneInfo?.registration_number || '-'} {workOrder.status || '-'}
</option>
))}
</select>
<p className="text-xs text-text-muted">
{assigningWorkOrder ? 'Spremanje odabira putnog naloga...' : 'Odabir se automatski sprema u bazu.'}
</p>
</div>
)}
{assignError && (
<div className="mt-2 rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700">
{assignError}
</div>
)}
</div>
<div className="rounded-lg border border-border-hairline bg-canvas-base p-3">
<div className="mb-2 flex items-center justify-between gap-2">
<h4 className="text-sm font-semibold text-text-main">Povezani servisni zapisi</h4>
<button
type="button"
onClick={() => onCreateServiceRecord?.(task)}
className="rounded-lg bg-emerald-600 px-3 py-1.5 text-xs font-semibold text-white hover:bg-emerald-700"
>
+ Servisni zapis
</button>
</div>
{records.length === 0 ? (
<div className="rounded-lg border border-border-hairline bg-canvas-elevated px-4 py-3 text-sm text-text-muted">
Nema povezanih servisnih zapisa za odabrani radni zadatak.
</div>
) : (
<div className="overflow-hidden rounded-lg border border-border-hairline">
<table className="w-full table-auto text-sm">
<thead className="bg-canvas-base text-left text-text-muted">
<tr>
<th className="px-3 py-2">Datum</th>
<th className="px-3 py-2">Opis</th>
<th className="px-3 py-2">Trošak</th>
<th className="px-3 py-2">Datoteke</th>
<th className="px-3 py-2">Akcija</th>
</tr>
</thead>
<tbody>
{records.map((record) => (
<tr key={record.id} className="border-t border-border-hairline">
<td className="px-3 py-2">{formatDate(record.service_date)}</td>
<td className="max-w-[420px] truncate px-3 py-2" title={record.description || ''}>
{record.description || '-'}
</td>
<td className="px-3 py-2">{formatCost(record.cost)}</td>
<td className="px-3 py-2">{record.files_count ?? (record.photos_count ?? 0)}</td>
<td className="px-3 py-2">
<button
type="button"
onClick={() => onOpenServiceRecord?.(record)}
className="rounded border border-border-hairline px-2 py-1 text-xs text-text-main hover:bg-canvas-base"
>
Detalji
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
</div>
</div>
</div>
</ModalShell>
);
}