From 8b82481bb48d4b43fe51af77a462e83b1150e08c Mon Sep 17 00:00:00 2001 From: mariomitte Date: Fri, 31 Jul 2026 12:28:48 +0200 Subject: [PATCH] feat: vise taskova po putnom nalogu i izmjena podataka dizalice MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - task_management/views.py: dodaj ?work_order= filter u get_queryset (jedini razlog zasto putni nalog nije mogao imati vise taskova bio je nedostatak backend filtera; model uvijek podrzavao vise taskova) - fleet/serializers.py: ukloni current_mileage iz read_only_fields kako bi se moglo azurirati putem PATCH /fleet/cranes/{id}/ - fleetDashboardStore.js: dodaj fetchTasksByWorkOrder(workOrderId) koji dohvaca GET tasks/tasks/?work_order={id} - WorkOrderDetailModal.jsx: * import TaskCreateModal, fetchTasksByWorkOrder, updateVehicle, getStatusLabel * nova sekcija 'Zadaci putnog naloga' s popisom i gumbom '+ Dodaj zadatak' * gumb '🔧 Izmijeni podatke dizalice' otvara CraneDataModal * CraneDataModal: unos superstructure_working_hours, chassis_working_hours, current_mileage; poziva updateVehicle; prikazuje trenutne vrijednosti kao placeholder Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- backend/modules/fleet/serializers.py | 2 +- backend/modules/task_management/views.py | 3 + .../dashboard/WorkOrderDetailModal.jsx | 214 ++++++++++++++++++ frontend/src/stores/fleetDashboardStore.js | 8 + 4 files changed, 226 insertions(+), 1 deletion(-) diff --git a/backend/modules/fleet/serializers.py b/backend/modules/fleet/serializers.py index 74cb1cc..74fc6c4 100644 --- a/backend/modules/fleet/serializers.py +++ b/backend/modules/fleet/serializers.py @@ -38,7 +38,7 @@ class VehicleSerializer(serializers.ModelSerializer): 'is_company_vehicle', 'client', 'client_name', 'assigned_servicer', 'is_active', ] - read_only_fields = ['id', 'current_mileage', 'client_name'] + read_only_fields = ['id', 'client_name'] def get_client_name(self, obj): if obj.client_id: diff --git a/backend/modules/task_management/views.py b/backend/modules/task_management/views.py index 7e4c8c8..9ebc347 100644 --- a/backend/modules/task_management/views.py +++ b/backend/modules/task_management/views.py @@ -30,6 +30,9 @@ class TaskViewSet(viewsets.ModelViewSet): scheduled_date = self.request.query_params.get('scheduled_date') if scheduled_date: qs = qs.filter(scheduled_date=scheduled_date) + work_order_id = self.request.query_params.get('work_order') + if work_order_id: + qs = qs.filter(work_order_id=work_order_id) return qs def perform_create(self, serializer): diff --git a/frontend/src/components/dashboard/WorkOrderDetailModal.jsx b/frontend/src/components/dashboard/WorkOrderDetailModal.jsx index 9040ef8..b8f6393 100644 --- a/frontend/src/components/dashboard/WorkOrderDetailModal.jsx +++ b/frontend/src/components/dashboard/WorkOrderDetailModal.jsx @@ -2,15 +2,19 @@ import { useEffect, useMemo, useState } from 'preact/hooks'; import { useStore } from '@nanostores/preact'; import WorkOrderImageCarousel from './WorkOrderImageCarousel'; import WorkOrderPhotoUpload from '../fleet/WorkOrderPhotoUpload'; +import TaskCreateModal from './TaskCreateModal'; import ModalShell from '../ui/ModalShell'; import { createWorkOrderInvoice, + fetchTasksByWorkOrder, fetchTeamMembers, fetchWorkOrderInvoices, fetchWorkOrderImages, openWorkOrderInvoicesPdfPage, sendWorkOrderEmail, + updateVehicle, } from '../../stores/fleetDashboardStore'; +import { getStatusLabel } from '../../stores/taskStore'; import { showToast } from '../../stores/toastStore'; import { formatWorkOrderDisplayCode } from '../../lib/displayIds'; import { $serviceNotes, $serviceNotesLoading, fetchServiceNotes } from '../../stores/serviceNotesStore'; @@ -81,6 +85,12 @@ export default function WorkOrderDetailModal({ open, workOrder, onClose, onSubmi const [selectedLocation, setSelectedLocation] = useState(null); const [searchingLocation, setSearchingLocation] = useState(false); const [mapError, setMapError] = useState(''); + const [workOrderTasks, setWorkOrderTasks] = useState([]); + const [addTaskModalOpen, setAddTaskModalOpen] = useState(false); + 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 craneLabel = useMemo(() => { if (!workOrder) return '-'; @@ -168,6 +178,12 @@ export default function WorkOrderDetailModal({ open, workOrder, onClose, onSubmi setSelectedLocation(null); setSearchingLocation(false); setMapError(''); + setWorkOrderTasks([]); + setAddTaskModalOpen(false); + setCraneDataModalOpen(false); + setCraneDataForm({ superstructure_working_hours: '', chassis_working_hours: '', current_mileage: '' }); + setSavingCraneData(false); + setCraneDataError(''); }, [open, workOrder]); useEffect(() => { @@ -251,6 +267,23 @@ export default function WorkOrderDetailModal({ open, workOrder, onClose, onSubmi }; }, [open, workOrder?.id]); + useEffect(() => { + if (!open || !workOrder?.id) { + setWorkOrderTasks([]); + return; + } + let cancelled = false; + (async () => { + try { + const tasks = await fetchTasksByWorkOrder(workOrder.id); + if (!cancelled) setWorkOrderTasks(tasks); + } catch { + if (!cancelled) setWorkOrderTasks([]); + } + })(); + return () => { cancelled = true; }; + }, [open, workOrder?.id]); + const selectedMapEmbedUrl = (() => { if (!selectedLocation) return null; const lat = Number(selectedLocation.lat); @@ -265,6 +298,60 @@ export default function WorkOrderDetailModal({ open, workOrder, onClose, onSubmi return null; } + const craneVehicleId = workOrder.vehicle || workOrder.crane; + const craneVehicle = workOrder.craneInfo || null; + + const openCraneDataModal = () => { + setCraneDataForm({ + superstructure_working_hours: String(craneVehicle?.superstructure_working_hours ?? ''), + chassis_working_hours: String(craneVehicle?.chassis_working_hours ?? ''), + current_mileage: String(craneVehicle?.current_mileage ?? ''), + }); + setCraneDataError(''); + setCraneDataModalOpen(true); + }; + + const handleSaveCraneData = async () => { + if (!craneVehicleId) { + setCraneDataError('Dizalica nije pronađena.'); + 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 superstrukture 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('Kilometraža 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('Nije unesena niti jedna vrijednost za ažuriranje.'); + return; + } + setSavingCraneData(true); + setCraneDataError(''); + try { + await updateVehicle(craneVehicleId, payload); + showToast('Podaci dizalice su uspješno ažurirani.', 'success'); + setCraneDataModalOpen(false); + } catch (err) { + setCraneDataError(err?.message || 'Ažuriranje podataka dizalice nije uspjelo.'); + } finally { + setSavingCraneData(false); + } + }; + const searchLocation = async () => { const query = locationQuery.trim(); if (!query) { @@ -522,6 +609,13 @@ export default function WorkOrderDetailModal({ open, workOrder, onClose, onSubmi > Dodaj račun + + + {workOrderTasks.length === 0 ? ( +

Nema zadataka za ovaj putni nalog.

+ ) : ( + + )} + +

Status