This commit is contained in:
172
frontend/src/components/dashboard/ServiceContextSelector.jsx
Normal file
172
frontend/src/components/dashboard/ServiceContextSelector.jsx
Normal file
@@ -0,0 +1,172 @@
|
||||
// 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;
|
||||
|
||||
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.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">
|
||||
{selectedCrane.registration_number} {selectedCrane.make} {selectedCrane.model}
|
||||
</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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user