import { useState } from "react"; import { Link as RouterLink } from "react-router-dom"; import { motion, useReducedMotion } from "framer-motion"; import { useQueryClient } from "@tanstack/react-query"; import Box from "@mui/material/Box"; import { useAuth } from "../../context/AuthContext"; import { useAlert } from "../../context/AlertContext"; import { formatKm, todayLocalStr } from "../../utils/formatters"; import { Button, Modal, Field, Select, DateField, TextField } from "../../ui"; import apiFetch from "../../utils/api"; const API_BASE = "/api/admin"; interface Vehicle { id: number | string; spz: string; name: string; } // Standard { success, data, error, message } envelope returned by the API. interface ApiResult { success: boolean; data?: T; error?: string; message?: string; } interface TripForm { vehicle_id: string; trip_date: string; start_km: string; end_km: string; route_from: string; route_to: string; is_business: number; notes: string; } interface TripErrors { vehicle_id?: string; trip_date?: string; start_km?: string; end_km?: string; route_from?: string; route_to?: string; } interface DashQuickActionsProps { dashData: { my_shift?: { has_ongoing: boolean; }; } | null; punching: boolean; onPunch: () => void; } // Maps the legacy quick-action color tokens to the MUI Button palette color. const ACTION_COLOR: Record = { success: "success", danger: "error", info: "info", warning: "warning", }; export default function DashQuickActions({ dashData, punching, onPunch, }: DashQuickActionsProps) { const { hasPermission } = useAuth(); const alert = useAlert(); const queryClient = useQueryClient(); const reduce = useReducedMotion(); const [showTripModal, setShowTripModal] = useState(false); const [tripSubmitting, setTripSubmitting] = useState(false); const [tripVehicles, setTripVehicles] = useState([]); const [tripForm, setTripForm] = useState({ vehicle_id: "", trip_date: "", start_km: "", end_km: "", route_from: "", route_to: "", is_business: 1, notes: "", }); const [tripErrors, setTripErrors] = useState({}); const openTripModal = async () => { setTripForm({ vehicle_id: "", trip_date: todayLocalStr(), start_km: "", end_km: "", route_from: "", route_to: "", is_business: 1, notes: "", }); setTripErrors({}); setShowTripModal(true); try { const response = await apiFetch(`${API_BASE}/vehicles`); const result: ApiResult = await response.json(); if (result.success) { setTripVehicles( Array.isArray(result.data) ? result.data : (result.data?.vehicles ?? []), ); } } catch (e) { console.error("DashQuickActions: nepodařilo se načíst vozidla", e); } }; const handleTripVehicleChange = async (vehicleId: string) => { setTripForm((prev) => ({ ...prev, vehicle_id: vehicleId })); if (!vehicleId) { return; } try { const response = await apiFetch(`${API_BASE}/trips/last-km/${vehicleId}`); const result: ApiResult<{ last_km: number | string }> = await response.json(); if (result.success && result.data) { setTripForm((prev) => ({ ...prev, start_km: String(result.data!.last_km ?? ""), })); } } catch (e) { console.error("DashQuickActions: nepodařilo se načíst poslední km", e); } }; const handleTripSubmit = async () => { const errs: TripErrors = {}; if (!tripForm.vehicle_id) { errs.vehicle_id = "Vyberte vozidlo"; } if (!tripForm.trip_date) { errs.trip_date = "Zadejte datum"; } if (!tripForm.start_km) { errs.start_km = "Zadejte počáteční km"; } if (!tripForm.end_km) { errs.end_km = "Zadejte konečný km"; } if ( tripForm.start_km && tripForm.end_km && parseInt(tripForm.end_km, 10) <= parseInt(tripForm.start_km, 10) ) { errs.end_km = "Musí být větší než počáteční"; } if (!tripForm.route_from) { errs.route_from = "Zadejte místo odjezdu"; } if (!tripForm.route_to) { errs.route_to = "Zadejte místo příjezdu"; } setTripErrors(errs); if (Object.keys(errs).length > 0) { return; } setTripSubmitting(true); try { const response = await apiFetch(`${API_BASE}/trips`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(tripForm), }); const result: ApiResult = await response.json(); if (result.success) { // A new trip changes the trip list, the dashboard vehicle widgets AND // the vehicles domain (actual_km moves) — invalidate all three broad // domains (prefix-matching covers sub-queries). queryClient.invalidateQueries({ queryKey: ["trips"] }); queryClient.invalidateQueries({ queryKey: ["dashboard"] }); queryClient.invalidateQueries({ queryKey: ["vehicles"] }); setShowTripModal(false); alert.success(result.message ?? "Jízda uložena"); } else { alert.error(result.error ?? "Uložení jízdy selhalo"); } } catch (e) { console.error("DashQuickActions: uložení jízdy selhalo", e); alert.error("Chyba připojení"); } finally { setTripSubmitting(false); } }; const tripDistance = (): number => { const s = parseInt(tripForm.start_km, 10) || 0; const e = parseInt(tripForm.end_km, 10) || 0; return e > s ? e - s : 0; }; const hasOngoingShift = dashData?.my_shift?.has_ongoing; const punchLabel = hasOngoingShift ? "Zaznamenat odchod" : "Zaznamenat příchod"; const quickActions: Array<{ label: string; color: string; icon: React.ReactNode; onClick?: () => void; path?: string; disabled?: boolean; }> = []; if (hasPermission("attendance.record")) { quickActions.push({ label: punching ? "Odesílám..." : punchLabel, color: hasOngoingShift ? "danger" : "success", icon: hasOngoingShift ? ( ) : ( ), onClick: onPunch, disabled: punching, }); } if (hasPermission("offers.create")) { quickActions.push({ label: "Nová nabídka", path: "/offers/new", color: "info", icon: ( ), }); } if (hasPermission("trips.record")) { quickActions.push({ label: "Přidat jízdu", color: "warning", icon: ( ), onClick: openTripModal, }); } if (hasPermission("invoices.create")) { quickActions.push({ label: "Vystavit fakturu", path: "/invoices/new", color: "danger", icon: ( ), }); } return ( <> {quickActions.map((action) => { const color = ACTION_COLOR[action.color] ?? "primary"; const common = { variant: "contained" as const, color, startIcon: action.icon, sx: { py: 1.5, justifyContent: "flex-start" }, }; return action.onClick ? ( ) : ( ); })} setShowTripModal(false)} title="Přidat jízdu" maxWidth="md" onSubmit={handleTripSubmit} submitText="Uložit" loading={tripSubmitting} > setTripForm((prev) => ({ ...prev, is_business: parseInt(val) })) } options={[ { value: "1", label: "Služební" }, { value: "0", label: "Soukromá" }, ]} /> setTripForm((prev) => ({ ...prev, notes: e.target.value })) } placeholder="Volitelné poznámky..." /> ); }