feat: integritet zatvaranja taska i putnog naloga

Dodaje status 'spreman_za_zavrsetak' i backend provjeru koja blokira zatvaranje taska kada je povezani putni nalog otvoren, uz 409 odgovor i opcionalni auto-close flow.\n\nPri zatvaranju putnog naloga taskovi u statusu 'servis' automatski prelaze u 'spreman_za_zavrsetak'. Frontend modal sada podržava potvrdu i retry s auto_close_work_order=true.\n\nUključeni su i ciljani testovi za novi behavior, te ažuriran prikaz statusa u dashboardu.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
mariomitte
2026-07-17 19:20:33 +02:00
parent e14665d8ec
commit 7acaef941f
14 changed files with 408 additions and 18 deletions

View File

@@ -33,7 +33,7 @@ import { showToast } from '../../stores/toastStore';
import { $accessToken, $authReady, $user, hydrateAuthFromStorage, validateToken } from '../../stores/authStore';
import { cacheCraneSerial, cacheCraneSerialsFromVehicles } from '../../stores/craneSerialStore';
import { formatEntityCode } from '../../lib/displayIds';
import { $tasks, fetchTasks, isTaskActive, updateTask } from '../../stores/taskStore';
import { $tasks, fetchTasks, getStatusLabel, isTaskActive, updateTask } from '../../stores/taskStore';
import AnimatedDataTable from '../ui/AnimatedDataTable';
import AnimatedPage from '../ui/AnimatedPage';
@@ -66,6 +66,15 @@ function formatDate(value) {
return parsed.toLocaleDateString('hr-HR');
}
function formatTaskCraneLabel(task, cranes) {
const crane = cranes.find((item) => String(item.id) === String(task?.vehicle || ''));
const serial = String(crane?.crane_serial_number || '').trim();
const model = [crane?.make, crane?.model].filter(Boolean).join(' ').trim();
const registration = String(task?.vehicle_registration || crane?.registration_number || '').trim();
const primary = serial ? `SN ${serial}` : (registration || '-');
return model ? `${primary} / ${model}` : primary;
}
function formatCost(value) {
const numeric = Number(value);
if (Number.isNaN(numeric)) return '-';
@@ -663,14 +672,17 @@ export default function FleetDashboardShell({ initialSection = 'dashboard', page
</td>
<td className="px-4 py-3">
<span className={isTaskActive(task) ? 'text-emerald-700' : 'text-red-700'}>
{task.status || '-'}
{getStatusLabel(task.status || '-')}
</span>
</td>
<td className="px-4 py-3">{formatDateTime(task.created_at)}</td>
<td
className="px-4 py-3"
title={formatDateTime(task.created_at) !== '-' ? formatDateTime(task.created_at) : undefined}
>
<span className="cursor-help">{formatDate(task.created_at)}</span>
</td>
<td className="px-4 py-3">
{task.vehicle_registration
|| cranes.find((item) => String(item.id) === String(task.vehicle))?.registration_number
|| '-'}
{formatTaskCraneLabel(task, cranes)}
</td>
<td className="px-4 py-3">{task.work_order_label || '-'}</td>
<td className="px-4 py-3">{task.assigned_to_name || '-'}</td>
@@ -1138,6 +1150,12 @@ export default function FleetDashboardShell({ initialSection = 'dashboard', page
setSelectedTask(updatedTask);
return updatedTask;
}}
onUpdateTask={async (task, changes) => {
const updatedTask = await updateTask(task.id, changes);
setSelectedTask(updatedTask);
await fetchFleetDashboardData({ silent: true });
return updatedTask;
}}
onCreateServiceRecord={(task) => {
const taskCrane = cranes.find((item) => String(item.id) === String(task?.vehicle));
setSelectedTask(null);