fix: filtriraj putne naloge i prosiri servisni zapis modal

Ogranicava prikaz 'Dohvati sve putne naloge' na naloge trenutnog servisera, dok supervisor bez serviser uloge i dalje vidi sve.

U detaljima servisnog zapisa dodaje gumb za upload datoteka i povezuje ga s postojecim ServicePhotoUpload modalom.

U modalu Novi servisni zapis dodaje polje Naziv servisnog zapisa iznad opisa i salje ga kao service_title.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
mariomitte
2026-07-19 19:02:05 +02:00
parent 8485431180
commit ad7b293f54
3 changed files with 57 additions and 3 deletions

View File

@@ -91,6 +91,13 @@ function formatCost(value) {
return `${numeric.toFixed(2)} EUR`; return `${numeric.toFixed(2)} EUR`;
} }
function normalizeComparableId(value) {
if (value && typeof value === 'object') {
return value.id ?? value.pk ?? '';
}
return value ?? '';
}
const PAGE_MODE_TO_SECTION = { const PAGE_MODE_TO_SECTION = {
dashboard: 'dashboard', dashboard: 'dashboard',
'work-orders': 'work-orders', 'work-orders': 'work-orders',
@@ -344,14 +351,31 @@ export default function FleetDashboardShell({ initialSection = 'dashboard', page
}); });
const scopedWorkOrders = useMemo(() => { const scopedWorkOrders = useMemo(() => {
const sorted = [...workOrders].sort((a, b) => String(b.date || '').localeCompare(String(a.date || ''))); const sorted = [...workOrders].sort((a, b) => String(b.date || '').localeCompare(String(a.date || '')));
const isServiserUser = Boolean(user?.is_serviser);
const isSupervisorUser = Boolean(user?.is_team_member) && !Boolean(user?.is_serviser);
const currentUserId = normalizeComparableId(user?.id);
const roleScoped = sorted.filter((order) => {
if (!isServiserUser || isSupervisorUser) {
return true;
}
if (!currentUserId) {
return false;
}
const creatorCandidates = [order?.creator, order?.creator_id];
return creatorCandidates.some((candidate) => (
String(normalizeComparableId(candidate)) === String(currentUserId)
));
});
if (workOrderScope === 'all') { if (workOrderScope === 'all') {
return sorted; return roleScoped;
} }
if (!hydratedSelectedVehicleId) { if (!hydratedSelectedVehicleId) {
return []; return [];
} }
return sorted.filter((order) => String(order.vehicle) === String(hydratedSelectedVehicleId)); return roleScoped.filter((order) => String(order.vehicle) === String(hydratedSelectedVehicleId));
}, [workOrders, workOrderScope, hydratedSelectedVehicleId]); }, [workOrders, workOrderScope, hydratedSelectedVehicleId, user?.id, user?.is_serviser, user?.is_team_member]);
const pageSize = 10; const pageSize = 10;
const totalPages = Math.max(1, Math.ceil(scopedWorkOrders.length / pageSize)); const totalPages = Math.max(1, Math.ceil(scopedWorkOrders.length / pageSize));
const latestWorkOrders = useMemo( const latestWorkOrders = useMemo(
@@ -1214,6 +1238,13 @@ export default function FleetDashboardShell({ initialSection = 'dashboard', page
setSelectedServiceRecord(updatedRecord); setSelectedServiceRecord(updatedRecord);
fetchFleetDashboardData({ silent: true }); fetchFleetDashboardData({ silent: true });
}} }}
onOpenPhotoUpload={(record) => {
const targetId = record?.id ?? selectedServiceRecord?.id;
if (!targetId) return;
setSelectedServiceRecord(null);
setServiceRecordBackTask(null);
setPhotoUpload({ open: true, serviceRecordId: targetId });
}}
onBack={serviceRecordBackTask onBack={serviceRecordBackTask
? () => { ? () => {
setSelectedServiceRecord(null); setSelectedServiceRecord(null);

View File

@@ -34,6 +34,7 @@ export default function ServiceRecordDetailModal({
serviceRecord, serviceRecord,
onClose, onClose,
onUpdated, onUpdated,
onOpenPhotoUpload = null,
onBack = null, onBack = null,
backLabel = 'Natrag', backLabel = 'Natrag',
}) { }) {
@@ -411,6 +412,14 @@ export default function ServiceRecordDetailModal({
)} )}
{editMode && ( {editMode && (
<> <>
<button
type="button"
onClick={() => onOpenPhotoUpload?.(serviceRecord)}
disabled={editSaving}
className="rounded-md border border-border-hairline px-3 py-2 text-xs font-medium text-text-main hover:bg-canvas-deep disabled:opacity-60"
>
📎 Dodaj datoteke
</button>
<button <button
type="button" type="button"
onClick={() => { onClick={() => {

View File

@@ -11,6 +11,7 @@ import { showToast } from '../../stores/toastStore.js';
const INITIAL = { const INITIAL = {
task: '', task: '',
service_title: '',
description: '', description: '',
parts: '', parts: '',
cost: '', cost: '',
@@ -144,6 +145,7 @@ export default function ServiceRecordForm({
try { try {
const payload = { const payload = {
task: form.task, task: form.task,
service_title: form.service_title.trim(),
description: form.description.trim(), description: form.description.trim(),
parts: form.parts.trim(), parts: form.parts.trim(),
cost, cost,
@@ -268,6 +270,18 @@ export default function ServiceRecordForm({
</label> </label>
</div> </div>
<label className="flex flex-col gap-1 text-sm">
<span className="font-medium text-text-main">Naziv servisnog zapisa</span>
<input
type="text"
value={form.service_title}
onInput={field('service_title')}
disabled={disabled}
placeholder="Npr. Redovni servis hidraulike"
className="rounded-lg border border-border-hairline bg-canvas-base px-3 py-2 text-sm text-text-main placeholder-text-muted/40 focus:outline-none disabled:opacity-50"
/>
</label>
<label className="flex flex-col gap-1 text-sm"> <label className="flex flex-col gap-1 text-sm">
<span className="font-medium text-text-main">Opis posla *</span> <span className="font-medium text-text-main">Opis posla *</span>
<textarea <textarea