diff --git a/frontend/src/components/dashboard/FleetDashboardShell.jsx b/frontend/src/components/dashboard/FleetDashboardShell.jsx
index bd80e42..9d820a9 100644
--- a/frontend/src/components/dashboard/FleetDashboardShell.jsx
+++ b/frontend/src/components/dashboard/FleetDashboardShell.jsx
@@ -33,7 +33,7 @@ import {
import { showToast } from '../../stores/toastStore';
import { $accessToken, $authReady, $user, hydrateAuthFromStorage, validateToken } from '../../stores/authStore';
import { cacheCraneSerial, cacheCraneSerialsFromVehicles } from '../../stores/craneSerialStore';
-import { formatWorkOrderDisplayCode } from '../../lib/displayIds';
+import { formatWorkOrderDisplayCode, parseWorkOrderDisplayCode } from '../../lib/displayIds';
import { $tasks, fetchTasks, getStatusLabel, isTaskActive, updateTask } from '../../stores/taskStore';
import AnimatedDataTable from '../ui/AnimatedDataTable';
import AnimatedPage from '../ui/AnimatedPage';
@@ -98,6 +98,44 @@ function normalizeComparableId(value) {
return value ?? '';
}
+function normalizeWorkOrderCodeForSort(value) {
+ return String(value || '').trim().toUpperCase().split(/\s+/)[0] || '';
+}
+
+function compareWorkOrderCodesByDate(leftCode, rightCode, direction = 'asc') {
+ const left = parseWorkOrderDisplayCode(normalizeWorkOrderCodeForSort(leftCode));
+ const right = parseWorkOrderDisplayCode(normalizeWorkOrderCodeForSort(rightCode));
+ if (left.valid && right.valid) {
+ if (left.sortKey !== right.sortKey) {
+ return direction === 'asc' ? left.sortKey - right.sortKey : right.sortKey - left.sortKey;
+ }
+ return left.normalized.localeCompare(right.normalized);
+ }
+ if (left.valid !== right.valid) {
+ return left.valid ? -1 : 1;
+ }
+ return String(leftCode || '').localeCompare(String(rightCode || ''));
+}
+
+function nextSortDirection(direction) {
+ return direction === 'asc' ? 'desc' : 'asc';
+}
+
+function SortableCodeLabel({ title, direction, onToggle }) {
+ return (
+
+ );
+}
+
const PAGE_MODE_TO_SECTION = {
dashboard: 'dashboard',
'work-orders': 'work-orders',
@@ -120,6 +158,7 @@ export default function FleetDashboardShell({ initialSection = 'dashboard', page
const [selectedServiceRecord, setSelectedServiceRecord] = useState(null);
// photoUpload: { open: bool, serviceRecordId: string|null }
const [photoUpload, setPhotoUpload] = useState({ open: false, serviceRecordId: null });
+ const [serviceRecordFilesRefreshToken, setServiceRecordFilesRefreshToken] = useState(0);
const [workOrderPhotoUpload, setWorkOrderPhotoUpload] = useState({ open: false, workOrderId: null });
const loading = useStore($dashboardLoading);
const error = useStore($dashboardError);
@@ -141,6 +180,8 @@ export default function FleetDashboardShell({ initialSection = 'dashboard', page
const [supervisorServicers, setSupervisorServicers] = useState([]);
const [selectedServicerId, setSelectedServicerId] = useState('all');
const [workOrderScope, setWorkOrderScope] = useState('context');
+ const [workOrderCodeSortDirection, setWorkOrderCodeSortDirection] = useState('desc');
+ const [taskWorkOrderSortDirection, setTaskWorkOrderSortDirection] = useState('desc');
const [selectedCraneRowId, setSelectedCraneRowId] = useState(null);
const [expandedServiceTaskId, setExpandedServiceTaskId] = useState(null);
const [hasMounted, setHasMounted] = useState(false);
@@ -368,14 +409,22 @@ export default function FleetDashboardShell({ initialSection = 'dashboard', page
));
});
+ const codeSorted = [...roleScoped].sort((left, right) => (
+ compareWorkOrderCodesByDate(
+ formatWorkOrderDisplayCode(left),
+ formatWorkOrderDisplayCode(right),
+ workOrderCodeSortDirection
+ )
+ ));
+
if (workOrderScope === 'all') {
- return roleScoped;
+ return codeSorted;
}
if (!hydratedSelectedVehicleId) {
return [];
}
- return roleScoped.filter((order) => String(order.vehicle) === String(hydratedSelectedVehicleId));
- }, [workOrders, workOrderScope, hydratedSelectedVehicleId, user?.id, user?.is_serviser, user?.is_team_member]);
+ return codeSorted.filter((order) => String(order.vehicle) === String(hydratedSelectedVehicleId));
+ }, [workOrders, workOrderScope, hydratedSelectedVehicleId, user?.id, user?.is_serviser, user?.is_team_member, workOrderCodeSortDirection]);
const pageSize = 10;
const totalPages = Math.max(1, Math.ceil(scopedWorkOrders.length / pageSize));
const latestWorkOrders = useMemo(
@@ -466,14 +515,24 @@ export default function FleetDashboardShell({ initialSection = 'dashboard', page
(task) => String(task?.assigned_to || '') === String(selectedServicerId)
);
})();
- if (taskFilter === 'active') {
- return filteredByServicer.filter((task) => isTaskActive(task));
- }
- if (taskFilter === 'inactive') {
- return filteredByServicer.filter((task) => !isTaskActive(task));
- }
- return filteredByServicer;
- }, [tasks, taskFilter, canViewAllTasks, canToggleTaskScope, taskScope, user?.id, isSupervisorNotServiser, selectedServicerId]);
+ const filteredByStatus = (() => {
+ if (taskFilter === 'active') {
+ return filteredByServicer.filter((task) => isTaskActive(task));
+ }
+ if (taskFilter === 'inactive') {
+ return filteredByServicer.filter((task) => !isTaskActive(task));
+ }
+ return filteredByServicer;
+ })();
+
+ return [...filteredByStatus].sort((left, right) => (
+ compareWorkOrderCodesByDate(
+ left?.work_order_label || '',
+ right?.work_order_label || '',
+ taskWorkOrderSortDirection
+ )
+ ));
+ }, [tasks, taskFilter, canViewAllTasks, canToggleTaskScope, taskScope, user?.id, isSupervisorNotServiser, selectedServicerId, taskWorkOrderSortDirection]);
const selectedTaskServiceRecords = useMemo(() => {
if (!selectedTask?.id) return [];
return [...serviceRecords]
@@ -587,7 +646,17 @@ export default function FleetDashboardShell({ initialSection = 'dashboard', page
}
const workOrderColumns = [
- { key: 'code', label: 'Putni nalog', className: 'px-4 py-3' },
+ {
+ key: 'code',
+ label: (
+ setWorkOrderCodeSortDirection((prev) => nextSortDirection(prev))}
+ />
+ ),
+ className: 'px-4 py-3',
+ },
{ key: 'vehicle', label: 'Dizalica', className: 'px-4 py-3' },
{ key: 'serialNumber', label: 'SN', className: 'px-4 py-3' },
{ key: 'createdAt', label: 'Kreirano', className: 'px-4 py-3' },
@@ -609,7 +678,17 @@ export default function FleetDashboardShell({ initialSection = 'dashboard', page
{ key: 'scheduledDate', label: 'Planirano', className: 'px-4 py-3' },
{ key: 'createdAt', label: 'Vrijeme izrade', className: 'px-4 py-3' },
{ key: 'vehicle', label: 'Dizalica', className: 'px-4 py-3' },
- { key: 'workOrder', label: 'Putni nalog', className: 'px-4 py-3' },
+ {
+ key: 'workOrder',
+ label: (
+ setTaskWorkOrderSortDirection((prev) => nextSortDirection(prev))}
+ />
+ ),
+ className: 'px-4 py-3',
+ },
{ key: 'assigned', label: 'Dodijeljeno', className: 'px-4 py-3' },
];
const isDashboardPage = pageMode === 'dashboard';
@@ -1217,7 +1296,10 @@ export default function FleetDashboardShell({ initialSection = 'dashboard', page
open={photoUpload.open}
serviceRecordId={photoUpload.serviceRecordId}
onClose={() => setPhotoUpload({ open: false, serviceRecordId: null })}
- onSuccess={() => fetchFleetDashboardData({ silent: true })}
+ onSuccess={() => {
+ setServiceRecordFilesRefreshToken((prev) => prev + 1);
+ fetchFleetDashboardData({ silent: true });
+ }}
/>
{
const targetId = record?.id ?? selectedServiceRecord?.id;
if (!targetId) return;
- setSelectedServiceRecord(null);
- setServiceRecordBackTask(null);
setPhotoUpload({ open: true, serviceRecordId: targetId });
}}
+ filesRefreshToken={serviceRecordFilesRefreshToken}
onBack={serviceRecordBackTask
? () => {
setSelectedServiceRecord(null);
diff --git a/frontend/src/components/fleet/ServicePhotoUpload.jsx b/frontend/src/components/fleet/ServicePhotoUpload.jsx
index dde82da..250b512 100644
--- a/frontend/src/components/fleet/ServicePhotoUpload.jsx
+++ b/frontend/src/components/fleet/ServicePhotoUpload.jsx
@@ -134,7 +134,7 @@ export default function ServicePhotoUpload({ open, serviceRecordId, onClose, onS
return (
diff --git a/frontend/src/components/fleet/ServiceRecordDetailModal.jsx b/frontend/src/components/fleet/ServiceRecordDetailModal.jsx
index 5af354f..b039a22 100644
--- a/frontend/src/components/fleet/ServiceRecordDetailModal.jsx
+++ b/frontend/src/components/fleet/ServiceRecordDetailModal.jsx
@@ -35,6 +35,7 @@ export default function ServiceRecordDetailModal({
onClose,
onUpdated,
onOpenPhotoUpload = null,
+ filesRefreshToken = 0,
onBack = null,
backLabel = 'Natrag',
}) {
@@ -117,7 +118,7 @@ export default function ServiceRecordDetailModal({
return () => {
cancelled = true;
};
- }, [open, serviceRecord?.id]);
+ }, [open, serviceRecord?.id, filesRefreshToken]);
useEffect(() => {
if (!emailModalOpen) return;
diff --git a/frontend/src/lib/displayIds.js b/frontend/src/lib/displayIds.js
index 5ce4fda..e2e2504 100644
--- a/frontend/src/lib/displayIds.js
+++ b/frontend/src/lib/displayIds.js
@@ -30,6 +30,32 @@ export function formatWorkOrderDisplayCode(workOrder) {
return '-';
}
+const WORK_ORDER_CODE_PATTERN = /^([A-Z]{2})(\d{2})(\d{2})(\d{2})$/;
+
+export function parseWorkOrderDisplayCode(value) {
+ const normalized = String(value || '').trim().toUpperCase();
+ const match = normalized.match(WORK_ORDER_CODE_PATTERN);
+ if (!match) {
+ return { valid: false, normalized };
+ }
+ const day = Number(match[2]);
+ const month = Number(match[3]);
+ const year = 2000 + Number(match[4]);
+ if (day < 1 || day > 31 || month < 1 || month > 12) {
+ return { valid: false, normalized };
+ }
+ const sortKey = year * 10000 + month * 100 + day;
+ return {
+ valid: true,
+ normalized,
+ prefix: match[1],
+ day,
+ month,
+ year,
+ sortKey,
+ };
+}
+
export function formatPurposeLabel(value) {
const normalized = String(value || '').trim().toLowerCase();
if (!normalized) return '';