Files
004.erp.generic/frontend/src/components/dashboard/ServiceContextSelector.jsx
mariomitte 4e3bf6dab0
Some checks failed
ERP CI/CD Pipeline / test (push) Has been cancelled
ERP CI/CD Pipeline / Deploy (server git pull + compose) (push) Has been cancelled
patch oko teksta PDFa i UI tablica
2026-07-12 03:25:03 +02:00

181 lines
7.8 KiB
JavaScript

// src/components/dashboard/ServiceContextSelector.jsx
//
// Persistentni selektor "Servisni kontekst" — odabir kupca + dizalice.
// Stanje se čuva u localStorage i u serviceContextStore (reaktivno).
//
// USAGE:
// <ServiceContextSelector onNewServiceRecord={({ craneId, clientId }) => ...} />
import { useStore } from '@nanostores/preact';
import { useEffect, useState } from 'preact/hooks';
import { $clients, fetchClients } from '../../stores/clientStore';
import { $cranes } from '../../stores/fleetDashboardStore';
import { $accessToken } from '../../stores/authStore';
import ServiceRecordCreateButton from '../fleet/ServiceRecordCreateButton';
import {
$selectedClientId, $selectedVehicleId,
hydrateServiceContext, setSelectedClient, setSelectedVehicle, clearServiceContext,
} from '../../stores/serviceContextStore';
export default function ServiceContextSelector({ onNewServiceRecord }) {
const clients = useStore($clients);
const cranes = useStore($cranes);
const accessToken = useStore($accessToken);
const selectedClientId = useStore($selectedClientId);
const selectedVehicleId = useStore($selectedVehicleId);
const [editMode, setEditMode] = useState(false);
const [hasMounted, setHasMounted] = useState(false);
useEffect(() => {
setHasMounted(true);
hydrateServiceContext();
}, []);
useEffect(() => {
if (!accessToken) return;
if (clients.length > 0) return;
fetchClients();
}, [accessToken, clients.length]);
const fallbackClients = Array.from(
new Map(
cranes
.filter((item) => item?.client && item?.client_name)
.map((item) => [String(item.client), { id: item.client, name: item.client_name }])
).values()
);
const availableClients = clients.length > 0 ? clients : fallbackClients;
const clientCranes = cranes.filter((item) => String(item.client) === String(selectedClientId));
const selectedClient = availableClients.find((c) => String(c.id) === String(selectedClientId));
const selectedCrane = cranes.find((item) => String(item.id) === String(selectedVehicleId));
const showSelector = editMode || !selectedClientId;
const selectedCraneLabel = (() => {
if (!selectedCrane) return '';
const serial = String(selectedCrane.crane_serial_number || '').trim();
if (serial) {
return `SN ${serial} ${selectedCrane.make || ''} ${selectedCrane.model || ''}`.trim();
}
return `${selectedCrane.registration_number || '-'} ${selectedCrane.make || ''} ${selectedCrane.model || ''}`.trim();
})();
function handleClientChange(e) {
setSelectedClient(e.target.value);
}
function handleVehicleChange(e) {
setSelectedVehicle(e.target.value);
}
function handleClearSelection() {
clearServiceContext();
setEditMode(false);
}
if (!hasMounted) return null;
return (
<div className="border-t border-border-hairline bg-canvas-base/60 px-4 py-2 sm:px-6">
{showSelector ? (
<div className="flex flex-wrap items-center gap-2">
<span className="text-xs font-medium text-text-muted">Servisni kontekst:</span>
<select
value={selectedClientId ?? ''}
onChange={handleClientChange}
className="rounded-md border border-border-hairline bg-canvas-base px-2 py-1 text-sm text-text-main focus:outline-none focus:ring-2 focus:ring-indigo-400"
>
<option value=""> Odaberi kupca </option>
{availableClients.length === 0 && (
<option value="" disabled>Nema dostupnih kupaca</option>
)}
{availableClients.map((c) => (
<option key={c.id} value={c.id}>{c.name}</option>
))}
</select>
{selectedClientId && (
<select
value={selectedVehicleId ?? ''}
onChange={handleVehicleChange}
className="rounded-md border border-border-hairline bg-canvas-base px-2 py-1 text-sm text-text-main focus:outline-none focus:ring-2 focus:ring-indigo-400"
>
<option value=""> Odaberi dizalicu </option>
{clientCranes.length > 0
? clientCranes.map((item) => (
<option key={item.id} value={item.id}>
{item.crane_serial_number ? `SN ${item.crane_serial_number}` : (item.registration_number || '-')} {item.make} {item.model}
</option>
))
: <option disabled>Nema dizalica za ovog kupca</option>
}
</select>
)}
{selectedClientId && selectedVehicleId && (
<button
type="button"
onClick={() => setEditMode(false)}
className="rounded-md bg-indigo-600 px-3 py-1 text-xs font-semibold text-white hover:bg-indigo-700"
>
Potvrdi
</button>
)}
{editMode && (
<button
type="button"
onClick={() => setEditMode(false)}
className="rounded-md border border-border-hairline px-3 py-1 text-xs text-text-muted hover:bg-canvas-deep"
>
Odustani
</button>
)}
</div>
) : (
<div className="flex flex-wrap items-center gap-3">
<span className="text-xs text-text-muted">Servisni kontekst:</span>
<span className="rounded-md bg-indigo-50 px-2 py-1 text-xs font-semibold text-indigo-700">
{selectedClient?.name ?? '…'}
</span>
{selectedCrane && (
<>
<span className="text-xs text-text-muted">/</span>
<span className="rounded-md bg-indigo-50 px-2 py-1 text-xs font-semibold text-indigo-700">
{selectedCraneLabel}
</span>
</>
)}
{selectedVehicleId && onNewServiceRecord && (
<ServiceRecordCreateButton
onClick={() => {
onNewServiceRecord({ craneId: selectedVehicleId, clientId: selectedClientId });
}}
/>
)}
<button
type="button"
onClick={() => setEditMode(true)}
className="rounded-md border border-border-hairline px-2 py-1 text-xs text-text-muted hover:bg-canvas-deep"
>
Promijeni
</button>
<button
type="button"
onClick={handleClearSelection}
className="rounded-md border border-border-hairline px-2 py-1 text-xs text-text-muted hover:bg-red-50 hover:text-red-600"
>
</button>
</div>
)}
</div>
);
}