From 27b7c31c1302a9ad7bbcbf7089d8ef014c15244b Mon Sep 17 00:00:00 2001 From: mariomitte Date: Fri, 7 Aug 2026 07:19:32 +0200 Subject: [PATCH] fix: restore crane edit modal in task service context Expose task details from the service-context task list and restore visible crane data editing within the task flow. Add crane working-hours and mileage editing to the task creation modal when a service context is selected, and improve the task details modal action so servicers can update crane data directly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../dashboard/FleetDashboardShell.jsx | 112 ++++++----- .../components/dashboard/TaskCreateModal.jsx | 176 ++++++++++++++++++ .../dashboard/TaskServiceRecordsModal.jsx | 24 ++- 3 files changed, 257 insertions(+), 55 deletions(-) diff --git a/frontend/src/components/dashboard/FleetDashboardShell.jsx b/frontend/src/components/dashboard/FleetDashboardShell.jsx index a45bc9b..a9f2562 100644 --- a/frontend/src/components/dashboard/FleetDashboardShell.jsx +++ b/frontend/src/components/dashboard/FleetDashboardShell.jsx @@ -1,4 +1,4 @@ -import { useStore } from '@nanostores/preact'; +import { useStore } from '@nanostores/preact'; import { useEffect, useMemo, useState } from 'preact/hooks'; import { animated, useSpring, useTransition } from '@react-spring/web'; import DashboardTopbar from './DashboardTopbar'; @@ -130,8 +130,8 @@ function SortableCodeLabel({ title, direction, onToggle }) { title="Sortiraj po datumu iz koda (godina, mjesec, dan)" > {title} - - + ↑ + ↓ ); } @@ -276,7 +276,7 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { if (targetWorkOrder) { openWorkOrderDetails(targetWorkOrder); } else { - showToast('Traženi putni nalog nije pronađen u trenutnom prikazu.', 'warning'); + showToast('TraĹľeni putni nalog nije pronaÄ‘en u trenutnom prikazu.', 'warning'); } return; } @@ -293,7 +293,7 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { setServiceRecordBackTask(null); setSelectedServiceRecord(targetServiceRecord); } else { - showToast('Traženi servisni zapis nije pronađen u trenutnom prikazu.', 'warning'); + showToast('TraĹľeni servisni zapis nije pronaÄ‘en u trenutnom prikazu.', 'warning'); } return; } @@ -445,7 +445,7 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { } // Collect work order IDs that are directly assigned to tasks of the context crane. // This covers cross-crane shared work orders: a WO created for crane A can be - // assigned to a task of crane B — it should still appear in crane B's context view. + // assigned to a task of crane B — it should still appear in crane B's context view. const contextTaskWorkOrderIds = new Set( tasks .filter((t) => String(t.vehicle || '') === String(hydratedSelectedVehicleId) && t.work_order) @@ -463,7 +463,7 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { [scopedWorkOrders, currentPage] ); - // Map: workOrderId → array of unique crane objects (own + cross-crane task links) + // Map: workOrderId → array of unique crane objects (own + cross-crane task links) const workOrderSharedCranes = useMemo(() => { const craneById = new Map(cranes.map((c) => [String(c.id), c])); const map = new Map(); @@ -658,11 +658,11 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { async function handleUpdateWorkOrder(workOrder, changes) { try { await updateWorkOrder(workOrder.id, changes); - showToast('Nalog je uspješno ažuriran.', 'success'); + showToast('Nalog je uspješno aĹľuriran.', 'success'); await fetchFleetDashboardData({ silent: true }); await fetchTasks({ force: true }); } catch (error) { - showToast(error?.message || 'Ažuriranje naloga nije uspjelo.', 'error'); + showToast(error?.message || 'AĹľuriranje naloga nije uspjelo.', 'error'); } } @@ -674,7 +674,7 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { async function handleSetServiceContext() { if (!selectedCraneRowId) { - showToast('Prvo označite redak dizalice.', 'warning'); + showToast('Prvo oznaÄŤite redak dizalice.', 'warning'); return; } const selectedCrane = cranes.find((item) => String(item.id) === String(selectedCraneRowId)); @@ -688,7 +688,7 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { function handleSetServiceContextForCrane(selectedCrane) { if (!selectedCrane) { - showToast('Odabrana dizalica više nije dostupna.', 'warning'); + showToast('Odabrana dizalica više nije dostupna.', 'warning'); return; } setSelectedCraneRowId(selectedCrane.id); @@ -711,7 +711,7 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { return; } } catch { - showToast('Ne mogu dohvatiti dizalice. Provjerite mrežu ili prijavu.', 'error'); + showToast('Ne mogu dohvatiti dizalice. Provjerite mreĹľu ili prijavu.', 'error'); return; } setIsTaskModalOpen(true); @@ -741,7 +741,7 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { { key: 'serialNumber', label: 'SN', className: 'px-4 py-3' }, { key: 'model', label: 'Marka / Model', className: 'px-4 py-3' }, { key: 'client', label: 'Kupac', className: 'px-4 py-3' }, - { key: 'mileage', label: 'Kilometraža', className: 'px-4 py-3' }, + { key: 'mileage', label: 'KilometraĹľa', className: 'px-4 py-3' }, { key: 'status', label: 'Status', className: 'px-4 py-3' }, ]; const taskColumns = [ @@ -784,7 +784,7 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { return; } } catch { - showToast('Ne mogu dohvatiti dizalice. Provjerite mrežu ili prijavu.', 'error'); + showToast('Ne mogu dohvatiti dizalice. Provjerite mreĹľu ili prijavu.', 'error'); return; } setIsWorkOrderModalOpen(true); @@ -839,9 +839,9 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) {

aktivnih zadataka

- {/* Dovršeni nalozi */} + {/* Dovršeni nalozi */}
-

Dovršeni nalozi

+

Dovršeni nalozi

{stats.closedWorkOrders}

{stats.totalWorkOrders > 0 && (

@@ -854,7 +854,7 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) {

Moji danas

0 ? 'text-indigo-600' : 'text-text-main'}`}> - {hasMounted ? myTasksToday : '—'} + {hasMounted ? myTasksToday : '—'}

planirani na mene

@@ -964,7 +964,7 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { rows={dashboardTasks} rowKey={(task) => task.id} loading={loading} - loadingMessage="Učitavanje radnih zadataka..." + loadingMessage="UÄŤitavanje radnih zadataka..." emptyMessage="Nema radnih zadataka za odabrani filter." rowClassName={(task) => { const status = String(task?.status || '').toLowerCase(); @@ -1018,7 +1018,7 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) {
Sync log - + â–Ľ
@@ -1070,7 +1070,7 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { onClick={() => fetchFleetDashboardData()} className="text-sm font-medium text-indigo-600 hover:text-indigo-800" > - Osvježi + OsvjeĹľi
@@ -1079,12 +1079,12 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { rows={latestWorkOrders} rowKey={(workOrder) => workOrder.id} loading={loading} - loadingMessage="Učitavanje..." + loadingMessage="UÄŤitavanje..." emptyMessage={ workOrderScope === 'context' ? (!hydratedSelectedVehicleId ? 'Odaberite dizalicu za prikaz putnih naloga iz servisnog konteksta.' - : 'Nema dostupnih putnih naloga za odabrani servisni kontekst. Možete prebaciti na prikaz svih naloga.') + : 'Nema dostupnih putnih naloga za odabrani servisni kontekst. MoĹľete prebaciti na prikaz svih naloga.') : 'Nema dostupnih putnih naloga.' } renderRow={(workOrder) => ( @@ -1166,7 +1166,7 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { className="rounded px-2 py-1 text-xs font-medium text-text-muted hover:bg-canvas-deep hover:text-text-main" title="Upload fotografija putnog naloga" > - 📷 + đź“· @@ -1196,7 +1196,7 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { disabled={currentPage >= totalPages} className="rounded border border-border-hairline px-2 py-1 text-text-main hover:bg-canvas-deep disabled:opacity-50" > - Sljedeća + Sljedeća @@ -1230,7 +1230,7 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { onClick={() => fetchFleetDashboardData()} className="text-sm font-medium text-indigo-600 hover:text-indigo-800" > - Osvježi + OsvjeĹľi @@ -1249,7 +1249,7 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { { key: 'date', label: 'Datum', className: 'px-4 py-3' }, { key: 'crane', label: 'Dizalica', className: 'px-4 py-3' }, { key: 'description', label: 'Opis', className: 'px-4 py-3' }, - { key: 'cost', label: 'Trošak', className: 'px-4 py-3' }, + { key: 'cost', label: 'Trošak', className: 'px-4 py-3' }, { key: 'actions', label: 'Akcije', className: 'px-4 py-3' }, ]} rows={paginatedRecentServiceRecords} @@ -1302,7 +1302,7 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { disabled={serviceRecordsPage >= serviceRecordsTotalPages} className="rounded border border-border-hairline px-2 py-1 text-text-main hover:bg-canvas-deep disabled:opacity-50" > - Sljedeća + Sljedeća @@ -1312,7 +1312,7 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { )} {hydratedSelectedVehicleId && loading && (

- Učitavanje... + UÄŤitavanje...

)} {hydratedSelectedVehicleId && !loading && serviceTaskGroups.length === 0 && ( @@ -1326,22 +1326,33 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { const isOpen = String(expandedServiceTaskId || '') === String(group.id); return ( - +
+ + {group.task && ( + + )} +
{isOpen && (
{group.records.length === 0 ? ( @@ -1354,7 +1365,7 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { { key: 'date', label: 'Datum', className: 'px-4 py-3' }, { key: 'crane', label: 'DIZALICA', className: 'px-4 py-3' }, { key: 'description', label: 'Opis', className: 'px-4 py-3' }, - { key: 'cost', label: 'Trošak', className: 'px-4 py-3' }, + { key: 'cost', label: 'Trošak', className: 'px-4 py-3' }, { key: 'files', label: 'Datoteke', className: 'px-4 py-3' }, { key: 'actions', label: 'Akcije', className: 'px-4 py-3' }, ]} @@ -1389,7 +1400,7 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { onClick={() => setPhotoUpload({ open: true, serviceRecordId: record.id })} className="rounded border border-border-hairline px-2 py-1 text-xs text-text-main hover:bg-canvas-base" > - 📎 Dodaj datoteke + 📎 Dodaj datoteke @@ -1408,7 +1419,7 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { {showClients && ( <> - {/* ── Tablica klijenata i njihovih dizalica ───────── */} + {/* ── Tablica klijenata i njihovih dizalica ───────── */}
fetchFleetDashboardData()} className="text-sm font-medium text-indigo-600 hover:text-indigo-800" > - Osvježi + OsvjeĹľi
@@ -1446,7 +1457,7 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { rowClassName="hover:bg-canvas-deep" isRowSelected={(vehicle) => String(vehicle.id) === String(selectedCraneRowId)} loading={loading} - loadingMessage="Učitavanje vozila..." + loadingMessage="UÄŤitavanje vozila..." emptyMessage="Nema dostupnih vozila." renderRow={(vehicle) => ( <> @@ -1518,7 +1529,7 @@ export default function FleetDashboardShell({ pageMode = 'dashboard' }) { onSubmit={handleUpdateWorkOrder} /> - {/* Modali su na root razini div-a — van
i
— + {/* Modali su na root razini div-a — van
i
— kako bi fixed pozicioniranje i z-index radili ispravno */} ); } + diff --git a/frontend/src/components/dashboard/TaskCreateModal.jsx b/frontend/src/components/dashboard/TaskCreateModal.jsx index 41b21e8..86d05ef 100644 --- a/frontend/src/components/dashboard/TaskCreateModal.jsx +++ b/frontend/src/components/dashboard/TaskCreateModal.jsx @@ -2,6 +2,7 @@ import { createTask, fetchTaskTemplates, getStatusLabel } from '../../stores/taskStore'; import { formatPurposeLabel, formatWorkOrderDisplayCode } from '../../lib/displayIds'; import ModalShell from '../ui/ModalShell'; +import { updateVehicle } from '../../stores/fleetDashboardStore'; const STATUS_OPTIONS = ['aktivan', 'servis', 'zavrsen', 'neaktivan']; @@ -32,6 +33,10 @@ export default function TaskCreateModal({ open, workOrders = [], contextCrane = const [templateLoading, setTemplateLoading] = useState(false); const [templateId, setTemplateId] = useState(''); const [templates, setTemplates] = useState([]); + const [craneDataModalOpen, setCraneDataModalOpen] = useState(false); + const [craneDataForm, setCraneDataForm] = useState({ superstructure_working_hours: '', chassis_working_hours: '', current_mileage: '' }); + const [savingCraneData, setSavingCraneData] = useState(false); + const [craneDataError, setCraneDataError] = useState(''); const selectedTemplate = useMemo( () => templates.find((template) => String(template.id) === String(templateId)) || null, @@ -51,6 +56,10 @@ export default function TaskCreateModal({ open, workOrders = [], contextCrane = setTemplateId(''); setTemplates([]); setTemplateLoading(false); + setCraneDataModalOpen(false); + setCraneDataForm({ superstructure_working_hours: '', chassis_working_hours: '', current_mileage: '' }); + setSavingCraneData(false); + setCraneDataError(''); return; } // Auto-pre-select today's open work order if exactly one exists for this crane @@ -76,6 +85,60 @@ export default function TaskCreateModal({ open, workOrders = [], contextCrane = })); }, [selectedTemplate]); + function openCraneDataModal() { + if (!contextCrane?.id) { + setCraneDataError('Dizalica nije odabrana u servisnom kontekstu.'); + return; + } + setCraneDataForm({ + superstructure_working_hours: String(contextCrane?.superstructure_working_hours ?? ''), + chassis_working_hours: String(contextCrane?.chassis_working_hours ?? ''), + current_mileage: String(contextCrane?.current_mileage ?? ''), + }); + setCraneDataError(''); + setCraneDataModalOpen(true); + } + + async function handleSaveCraneData() { + if (!contextCrane?.id) { + setCraneDataError('Dizalica nije odabrana u servisnom kontekstu.'); + return; + } + const superstructure = craneDataForm.superstructure_working_hours === '' ? undefined : Number(craneDataForm.superstructure_working_hours); + const chassis = craneDataForm.chassis_working_hours === '' ? undefined : Number(craneDataForm.chassis_working_hours); + const mileage = craneDataForm.current_mileage === '' ? undefined : Number(craneDataForm.current_mileage); + if (superstructure !== undefined && (Number.isNaN(superstructure) || superstructure < 0)) { + setCraneDataError('Radni sati nadogradnje moraju biti pozitivan broj.'); + return; + } + if (chassis !== undefined && (Number.isNaN(chassis) || chassis < 0)) { + setCraneDataError('Radni sati podvozja moraju biti pozitivan broj.'); + return; + } + if (mileage !== undefined && (Number.isNaN(mileage) || mileage < 0)) { + setCraneDataError('Kilometraza mora biti pozitivan broj.'); + return; + } + const payload = {}; + if (superstructure !== undefined) payload.superstructure_working_hours = superstructure; + if (chassis !== undefined) payload.chassis_working_hours = chassis; + if (mileage !== undefined) payload.current_mileage = mileage; + if (Object.keys(payload).length === 0) { + setCraneDataError('Unesite barem jednu vrijednost za azuriranje.'); + return; + } + setSavingCraneData(true); + setCraneDataError(''); + try { + await updateVehicle(contextCrane.id, payload); + setCraneDataModalOpen(false); + } catch (err) { + setCraneDataError(err?.message || 'Azuriranje podataka dizalice nije uspjelo.'); + } finally { + setSavingCraneData(false); + } + } + if (!open) return null; function setField(name, value) { @@ -156,6 +219,7 @@ export default function TaskCreateModal({ open, workOrders = [], contextCrane = } return ( + <> )} + {contextCrane && ( +
+
+
+

Podaci odabrane dizalice

+

{contextCrane.registration_number || '-'} / {contextCrane.make || '-'} {contextCrane.model || ''}

+
+ +
+
+
+
Radni sati nadogradnje
+
{contextCrane.superstructure_working_hours ?? '-'}
+
+
+
Radni sati podvozja
+
{contextCrane.chassis_working_hours ?? '-'}
+
+
+
Kilometraza
+
{contextCrane.current_mileage ?? '-'}
+
+
+
+ )} +
+ + {craneDataModalOpen && ( + setCraneDataModalOpen(false)} + overlayClassName="z-[60] bg-black/40 p-4" + contentClassName="flex min-h-full items-center justify-center" + panelClassName="w-full max-w-lg rounded-xl border border-border-hairline bg-canvas-elevated shadow-2xl" + > +
+
+

Izmijeni podatke dizalice

+

+ Azurirajte radne sate i kilometrazu za dizalicu: {contextCrane?.registration_number || "-"} +

+
+ + + + {craneDataError && ( +
+ {craneDataError} +
+ )} +
+ + +
+
+
+ )} + ); } + diff --git a/frontend/src/components/dashboard/TaskServiceRecordsModal.jsx b/frontend/src/components/dashboard/TaskServiceRecordsModal.jsx index 1189bb3..6b663a7 100644 --- a/frontend/src/components/dashboard/TaskServiceRecordsModal.jsx +++ b/frontend/src/components/dashboard/TaskServiceRecordsModal.jsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'preact/hooks'; +import { useEffect, useMemo, useState } from 'preact/hooks'; import ModalShell from '../ui/ModalShell'; import { formatWorkOrderDisplayCode } from '../../lib/displayIds'; import { getStatusLabel } from '../../stores/taskStore'; @@ -251,6 +251,15 @@ export default function TaskServiceRecordsModal({ > Postavi servisni kontekst + {task?.vehicle && ( + + )} )} @@ -539,7 +548,12 @@ export default function TaskServiceRecordsModal({ {craneDataModalOpen && ( - setCraneDataModalOpen(false)} title="Izmijeni podatke dizalice"> + setCraneDataModalOpen(false)} + overlayClassName="z-[60] bg-black/40 p-4" + contentClassName="flex min-h-full items-center justify-center" + panelClassName="w-full max-w-lg rounded-xl border border-border-hairline bg-canvas-elevated shadow-2xl" + >

Ažurirajte radne sate i kilometražu za dizalicu: {getTaskCraneDisplay(task)}