import { useEffect, useMemo, useState } from 'preact/hooks'; import { createTask, fetchTaskTemplates, getStatusLabel } from '../../stores/taskStore'; import { formatPurposeLabel, formatWorkOrderDisplayCode } from '../../lib/displayIds'; import ModalShell from '../ui/ModalShell'; import { updateVehicle } from '../../stores/fleetDashboardStore'; const STATUS_OPTIONS = ['aktivan', 'servis', 'zavrsen', 'neaktivan']; const INITIAL_FORM = { title: '', description: '', status: 'aktivan', work_order: '', scheduled_date: '', }; function todayIso() { return new Date().toISOString().slice(0, 10); } function formatWODate(dateStr) { if (!dateStr) return ''; const d = new Date(dateStr); if (Number.isNaN(d.getTime())) return ''; return d.toLocaleDateString('hr-HR'); } export default function TaskCreateModal({ open, workOrders = [], contextCrane = null, onClose, onSuccess }) { const [form, setForm] = useState(INITIAL_FORM); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(''); const [templateMode, setTemplateMode] = useState(false); const [templateLoading, setTemplateLoading] = useState(false); const [templateId, setTemplateId] = useState(''); const [templates, setTemplates] = useState([]); const [craneDataModalOpen, setCraneDataModalOpen] = useState(false); const [craneDataForm, setCraneDataForm] = useState({ superstructure_working_hours: '', chassis_working_hours: '', current_mileage: '' }); const [savingCraneData, setSavingCraneData] = useState(false); const [craneDataError, setCraneDataError] = useState(''); const selectedTemplate = useMemo( () => templates.find((template) => String(template.id) === String(templateId)) || null, [templates, templateId] ); const availableWorkOrders = useMemo( () => workOrders.filter((workOrder) => String(workOrder?.status || '').toLowerCase() !== 'closed'), [workOrders] ); useEffect(() => { if (!open) { setForm(INITIAL_FORM); setSubmitting(false); setError(''); setTemplateMode(false); setTemplateId(''); setTemplates([]); setTemplateLoading(false); setCraneDataModalOpen(false); setCraneDataForm({ superstructure_working_hours: '', chassis_working_hours: '', current_mileage: '' }); setSavingCraneData(false); setCraneDataError(''); return; } // Auto-pre-select today's open work order if exactly one exists for this crane const today = todayIso(); const todayOpen = workOrders.filter( (wo) => String(wo?.status || '').toLowerCase() !== 'closed' && String(wo?.date || '').slice(0, 10) === today ); if (todayOpen.length === 1) { setForm((prev) => ({ ...prev, work_order: String(todayOpen[0].id) })); } }, [open]); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { if (!selectedTemplate) return; setForm((prev) => ({ ...prev, title: selectedTemplate.title || '', description: selectedTemplate.description || '', status: 'aktivan', work_order: '', // scheduled_date is kept so user can set it in template mode too })); }, [selectedTemplate]); function openCraneDataModal() { if (!contextCrane?.id) { setCraneDataError('Dizalica nije odabrana u servisnom kontekstu.'); return; } setCraneDataForm({ superstructure_working_hours: String(contextCrane?.superstructure_working_hours ?? ''), chassis_working_hours: String(contextCrane?.chassis_working_hours ?? ''), current_mileage: String(contextCrane?.current_mileage ?? ''), }); setCraneDataError(''); setCraneDataModalOpen(true); } async function handleSaveCraneData() { if (!contextCrane?.id) { setCraneDataError('Dizalica nije odabrana u servisnom kontekstu.'); return; } const superstructure = craneDataForm.superstructure_working_hours === '' ? undefined : Number(craneDataForm.superstructure_working_hours); const chassis = craneDataForm.chassis_working_hours === '' ? undefined : Number(craneDataForm.chassis_working_hours); const mileage = craneDataForm.current_mileage === '' ? undefined : Number(craneDataForm.current_mileage); if (superstructure !== undefined && (Number.isNaN(superstructure) || superstructure < 0)) { setCraneDataError('Radni sati nadogradnje moraju biti pozitivan broj.'); return; } if (chassis !== undefined && (Number.isNaN(chassis) || chassis < 0)) { setCraneDataError('Radni sati podvozja moraju biti pozitivan broj.'); return; } if (mileage !== undefined && (Number.isNaN(mileage) || mileage < 0)) { setCraneDataError('Kilometraza mora biti pozitivan broj.'); return; } const payload = {}; if (superstructure !== undefined) payload.superstructure_working_hours = superstructure; if (chassis !== undefined) payload.chassis_working_hours = chassis; if (mileage !== undefined) payload.current_mileage = mileage; if (Object.keys(payload).length === 0) { setCraneDataError('Unesite barem jednu vrijednost za azuriranje.'); return; } setSavingCraneData(true); setCraneDataError(''); try { await updateVehicle(contextCrane.id, payload); setCraneDataModalOpen(false); } catch (err) { setCraneDataError(err?.message || 'Azuriranje podataka dizalice nije uspjelo.'); } finally { setSavingCraneData(false); } } if (!open) return null; function setField(name, value) { setForm((prev) => ({ ...prev, [name]: value })); } async function enableTemplateMode() { if (templateMode) { setTemplateMode(false); setTemplateId(''); setForm(INITIAL_FORM); return; } setError(''); setTemplateLoading(true); try { const fetched = await fetchTaskTemplates(); setTemplates(fetched); setTemplateMode(true); } catch (err) { setError(err?.message || 'Neuspješno učitavanje templatea.'); } finally { setTemplateLoading(false); } } async function submit(event) { event.preventDefault(); setError(''); if (!contextCrane?.id) { setError('Odaberite dizalicu u Servisnom kontekstu prije kreiranja zadatka.'); return; } let payload; if (templateMode) { if (!templateId) { setError('Odaberite template za unos zadatka.'); return; } payload = { template_id: templateId, vehicle: contextCrane.id, work_order: null, scheduled_date: form.scheduled_date || null, }; } else { const title = String(form.title || '').trim(); if (!title) { setError('Naslov zadatka je obavezan.'); return; } if ((form.status === 'neaktivan' || form.status === 'zavrsen') && !form.work_order) { setError('Putni nalog je obavezan prije zatvaranja zadatka.'); return; } payload = { title, description: String(form.description || '').trim(), status: form.status || 'aktivan', vehicle: contextCrane.id, work_order: form.work_order || null, scheduled_date: form.scheduled_date || null, }; } setSubmitting(true); try { await createTask(payload); await onSuccess?.(); onClose?.(); } catch (err) { setError(err?.message || 'Neuspješno kreiranje zadatka.'); } finally { setSubmitting(false); } } return ( <> Novi radni zadatak Kontekst dizalice: {contextCrane?.registration_number || 'Nije odabrana'} {templateMode ? 'Ručni unos' : 'Unos iz templatea'} ✕ {templateMode && ( Template zadatka * setTemplateId(event.currentTarget.value)} disabled={submitting || templateLoading} className="rounded-lg border border-border-hairline bg-canvas-base px-3 py-2 text-text-main disabled:opacity-50" > — Odaberite template — {templates.map((template) => ( {template.title} ))} {selectedTemplate && ( Generirat će se do 10 servisnih zapisa iz predloška (vidljivo u tablici Servisni zapisi). )} )} {contextCrane && ( Podaci odabrane dizalice {contextCrane.registration_number || '-'} / {contextCrane.make || '-'} {contextCrane.model || ''} Uredi podatke dizalice Radni sati nadogradnje {contextCrane.superstructure_working_hours ?? '-'} Radni sati podvozja {contextCrane.chassis_working_hours ?? '-'} Kilometraza {contextCrane.current_mileage ?? '-'} )} Naslov zadatka * setField('title', event.currentTarget.value)} disabled={submitting || templateMode} className="rounded-lg border border-border-hairline bg-canvas-base px-3 py-2 text-text-main disabled:opacity-50" required={!templateMode} /> Opis setField('description', event.currentTarget.value)} disabled={submitting || templateMode} className="resize-none rounded-lg border border-border-hairline bg-canvas-base px-3 py-2 text-text-main disabled:opacity-50" /> Status setField('status', event.currentTarget.value)} disabled={submitting || templateMode} className="rounded-lg border border-border-hairline bg-canvas-base px-3 py-2 text-text-main disabled:opacity-50" > {STATUS_OPTIONS.map((status) => ( {getStatusLabel(status)} ))} Planirani datum izvršenja setField('scheduled_date', event.currentTarget.value)} disabled={submitting} className="rounded-lg border border-border-hairline bg-canvas-base px-3 py-2 text-text-main disabled:opacity-50" /> Povezani putni nalog setField('work_order', event.currentTarget.value)} disabled={submitting || templateMode} className="rounded-lg border border-border-hairline bg-canvas-base px-3 py-2 text-text-main disabled:opacity-50" > — Bez povezanog naloga — {availableWorkOrders.map((workOrder) => { const isToday = String(workOrder.date || '').slice(0, 10) === todayIso(); return ( {isToday ? '★ ' : ''}{formatWorkOrderDisplayCode(workOrder)} • {formatWODate(workOrder.date)} • {workOrder.craneLabel || workOrder.craneInfo?.registration_number || '-'} • {formatPurposeLabel(workOrder.purpose) || '-'} ); })} Više zadataka može dijeliti isti putni nalog. Danas kreiran nalog označen je s ★. {templateMode && ( Kod template unosa nalog nije obavezan i backend ga neće automatski povezati. )} {availableWorkOrders.length === 0 && ( Nema dostupnih naloga za odabranu dizalicu. )} {error && ( {error} )} Odustani {submitting ? 'Spremanje...' : 'Kreiraj radni zadatak'} {craneDataModalOpen && ( setCraneDataModalOpen(false)} overlayClassName="z-[60] bg-black/40 p-4" contentClassName="flex min-h-full items-center justify-center" panelClassName="w-full max-w-lg rounded-xl border border-border-hairline bg-canvas-elevated shadow-2xl" > Izmijeni podatke dizalice Azurirajte radne sate i kilometrazu za dizalicu: {contextCrane?.registration_number || "-"} Radni sati nadogradnje setCraneDataForm((prev) => ({ ...prev, superstructure_working_hours: event.currentTarget.value }))} disabled={savingCraneData} className="rounded-lg border border-border-hairline bg-canvas-base px-3 py-2 text-text-main disabled:opacity-60" /> Radni sati podvozja setCraneDataForm((prev) => ({ ...prev, chassis_working_hours: event.currentTarget.value }))} disabled={savingCraneData} className="rounded-lg border border-border-hairline bg-canvas-base px-3 py-2 text-text-main disabled:opacity-60" /> Kilometraza (km) setCraneDataForm((prev) => ({ ...prev, current_mileage: event.currentTarget.value }))} disabled={savingCraneData} className="rounded-lg border border-border-hairline bg-canvas-base px-3 py-2 text-text-main disabled:opacity-60" /> {craneDataError && ( {craneDataError} )} setCraneDataModalOpen(false)} disabled={savingCraneData} className="rounded-lg border border-border-hairline px-4 py-2 text-sm font-medium text-text-main hover:bg-canvas-deep disabled:opacity-60" > Odustani {savingCraneData ? "Spremanje..." : "Spremi podatke"} )} > ); }
Kontekst dizalice: {contextCrane?.registration_number || 'Nije odabrana'}
Podaci odabrane dizalice
{contextCrane.registration_number || '-'} / {contextCrane.make || '-'} {contextCrane.model || ''}
Azurirajte radne sate i kilometrazu za dizalicu: {contextCrane?.registration_number || "-"}