From fe4ba3b5ddc02696d870a9f28a77f53075d3b47b Mon Sep 17 00:00:00 2001 From: mariomitte Date: Fri, 7 Aug 2026 10:15:35 +0200 Subject: [PATCH] Revert "predzadnji unstagaeni dijelovi" This reverts commit 1f6c0d60868ec53ca42de86aba1b11ca16d0d002. --- backend/modules/fleet/views.py | 16 +- frontend/src/components/Navbar.jsx | 14 +- .../dashboard/TaskCalendarWidget.jsx | 32 ++-- .../components/dashboard/TaskCreateModal.jsx | 176 ++++++++++++++++++ .../dashboard/TaskServiceRecordsModal.jsx | 22 ++- .../dashboard/WorkOrderInvoicesPdfPage.jsx | 34 ++-- frontend/src/stores/fleetDashboardStore.js | 76 +++++++- 7 files changed, 320 insertions(+), 50 deletions(-) diff --git a/backend/modules/fleet/views.py b/backend/modules/fleet/views.py index 6333f65..9f8f161 100644 --- a/backend/modules/fleet/views.py +++ b/backend/modules/fleet/views.py @@ -3055,10 +3055,17 @@ def _request_monthly_archive_generation(*, request, archive_type): .first() ) if existing_pending: - return { - 'status': 'processing', - 'generated_archive_id': str(existing_pending.pk), - } + stale_pending_threshold = timezone.now() - timedelta(minutes=3) + if existing_pending.created_at and existing_pending.created_at < stale_pending_threshold: + existing_pending.is_active = False + existing_pending.status = 'failed' + existing_pending.error_message = 'ZIP zahtjev je ostao predugo u pending statusu; pokrece se novi zahtjev.' + existing_pending.save(update_fields=['is_active', 'status', 'error_message', 'updated_at']) + else: + return { + 'status': 'processing', + 'generated_archive_id': str(existing_pending.pk), + } generated_archive = GeneratedFleetArchive.objects.create( requested_by=request.user, @@ -3080,6 +3087,7 @@ def _request_monthly_archive_generation(*, request, archive_type): stage='requested', year=year, month=month, + generated_archive=generated_archive, ) try: diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx index c732828..2e2c03f 100644 --- a/frontend/src/components/Navbar.jsx +++ b/frontend/src/components/Navbar.jsx @@ -4,7 +4,7 @@ import AuthWidget from './AuthWidget'; import UserDisplay from './ui/UserDisplay'; import { useEffect, useRef, useState } from 'preact/hooks'; import { useSpring, animated } from '@react-spring/web'; -import { hydrateAuthFromStorage } from '../stores/authStore'; +import { hydrateAuthFromStorage, loadCurrentUser, $user, $accessToken } from '../stores/authStore'; const ITEMS = [ { id: 'dashboard', label: 'Dashboard', href: '/' }, @@ -21,8 +21,12 @@ function normalizePath(p) { } function getActiveIndex(path) { - const idx = ITEMS.findIndex((item) => item.href === path); - return idx >= 0 ? idx : 0; + // Exact match (e.g. '/' → Dashboard) + const exact = ITEMS.findIndex((item) => item.href === path); + if (exact >= 0) return exact; + // Prefix match for sub-paths (e.g. '/putni-nalozi/racuni' → Putni nalozi) + const prefix = ITEMS.findIndex((item) => item.href !== '/' && path.startsWith(item.href)); + return prefix >= 0 ? prefix : 0; } export default function Navbar({ minimal = false }) { @@ -55,6 +59,10 @@ export default function Navbar({ minimal = false }) { // Mount: izmjeri početnu poziciju bez animacije, pa uključi animaciju za buduće navigacije useEffect(() => { hydrateAuthFromStorage(); + // Učitaj korisnika ako token postoji ali $user još nije popunjen + if (!$user.get() && $accessToken.get()) { + loadCurrentUser(); + } const timer = setTimeout(() => { measureIndicator(); diff --git a/frontend/src/components/dashboard/TaskCalendarWidget.jsx b/frontend/src/components/dashboard/TaskCalendarWidget.jsx index 294eb4a..8cfcb36 100644 --- a/frontend/src/components/dashboard/TaskCalendarWidget.jsx +++ b/frontend/src/components/dashboard/TaskCalendarWidget.jsx @@ -367,10 +367,10 @@ export default function TaskCalendarWidget({ tasks = [], notes = [], workOrders async function handleDownloadAllTasksArchive() { if (downloadingAllTasks) return; + setBulkDownloadOpen(false); setDownloadingAllTasks(true); try { await downloadMonthlyServiceTasksArchive(viewYear, viewMonth + 1); - setBulkDownloadOpen(false); } finally { setDownloadingAllTasks(false); } @@ -378,10 +378,10 @@ export default function TaskCalendarWidget({ tasks = [], notes = [], workOrders async function handleDownloadAllWorkOrdersArchive() { if (downloadingAllWorkOrders) return; + setBulkDownloadOpen(false); setDownloadingAllWorkOrders(true); try { await downloadMonthlyWorkOrdersArchive(viewYear, viewMonth + 1); - setBulkDownloadOpen(false); } finally { setDownloadingAllWorkOrders(false); } @@ -655,23 +655,25 @@ export default function TaskCalendarWidget({ tasks = [], notes = [], workOrders {MONTH_NAMES[viewMonth]} {viewYear} - - {reportType === 'servicer' && ( +
- )} + {reportType === 'servicer' && ( + + )} +

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 ?? '-'}
+
+
+
+ )} +