181 lines
7.8 KiB
JavaScript
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>
|
|
);
|
|
}
|