import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { useAlert } from "../context/AlertContext"; import { useAuth } from "../context/AuthContext"; import { Link } from "react-router-dom"; import { motion } from "framer-motion"; import AdminDatePicker from "../components/AdminDatePicker"; import ConfirmModal from "../components/ConfirmModal"; import FormModal from "../components/FormModal"; import FormField from "../components/FormField"; import Forbidden from "../components/Forbidden"; import { formatDate } from "../utils/attendanceHelpers"; import { formatKm, todayLocalStr } from "../utils/formatters"; import apiFetch from "../utils/api"; import { tripListOptions, tripVehiclesOptions, type BackendTrip, type TripVehicle, } from "../lib/queries/trips"; import { useApiMutation } from "../lib/queries/mutations"; const API_BASE = "/api/admin"; interface TripForm { vehicle_id: string; trip_date: string; start_km: string | number; end_km: string | number; route_from: string; route_to: string; is_business: number; notes: string; } export default function Trips() { const alert = useAlert(); const { hasPermission } = useAuth(); const { data: tripsData, isPending: tripsLoading } = useQuery( tripListOptions({}), ); const { data: vehiclesData } = useQuery(tripVehiclesOptions()); const trips = tripsData ?? []; const vehicles = vehiclesData ?? []; const [showModal, setShowModal] = useState(false); const [editingTrip, setEditingTrip] = useState(null); const [deleteConfirm, setDeleteConfirm] = useState<{ show: boolean; tripId: number | null; }>({ show: false, tripId: null }); const [form, setForm] = useState({ vehicle_id: "", trip_date: todayLocalStr(), start_km: "", end_km: "", route_from: "", route_to: "", is_business: 1, notes: "", }); const [errors, setErrors] = useState>({}); const [, setLastKm] = useState(0); if (!hasPermission("trips.record")) return ; const submitMutation = useApiMutation< TripForm, { message?: string; error?: string } >({ url: (formData) => editingTrip ? `${API_BASE}/trips/${editingTrip.id}` : `${API_BASE}/trips`, method: () => (editingTrip ? "PUT" : "POST"), invalidate: ["trips", "vehicles"], onSuccess: (data) => { setShowModal(false); alert.success(data?.message || "Hotovo"); }, }); const deleteMutation = useApiMutation< number, { message?: string; error?: string } >({ url: (id) => `${API_BASE}/trips/${id}`, method: () => "DELETE", invalidate: ["trips", "vehicles"], onSuccess: (data) => { alert.success(data?.message || "Smazáno"); setDeleteConfirm({ show: false, tripId: null }); }, }); const fetchLastKm = async (vehicleId: string) => { if (!vehicleId) { setLastKm(0); return; } try { const response = await apiFetch(`${API_BASE}/trips/last-km/${vehicleId}`); const result = await response.json(); if (result.success) { const km = result.data?.last_km || 0; setLastKm(km); if (!editingTrip) { setForm((prev) => ({ ...prev, start_km: km })); } return; } } catch { /* fallback below */ } setLastKm(0); }; const openCreateModal = () => { setEditingTrip(null); setForm({ vehicle_id: "", trip_date: todayLocalStr(), start_km: "", end_km: "", route_from: "", route_to: "", is_business: 1, notes: "", }); setLastKm(0); setErrors({}); setShowModal(true); }; const openEditModal = (trip: BackendTrip) => { setEditingTrip(trip); setForm({ vehicle_id: String(trip.vehicle_id), trip_date: trip.trip_date, start_km: trip.start_km, end_km: trip.end_km, route_from: trip.route_from, route_to: trip.route_to, is_business: Number(trip.is_business), notes: trip.notes || "", }); setLastKm(trip.start_km); setErrors({}); setShowModal(true); }; const handleVehicleChange = (vehicleId: string) => { setForm((prev) => ({ ...prev, vehicle_id: vehicleId })); fetchLastKm(vehicleId); }; const validateForm = (): boolean => { const newErrors: Record = {}; if (!form.vehicle_id) newErrors.vehicle_id = "Vyberte vozidlo"; if (!form.trip_date) newErrors.trip_date = "Zadejte datum"; if (!form.start_km) newErrors.start_km = "Zadejte počáteční km"; if (!form.end_km) newErrors.end_km = "Zadejte konečný km"; if ( form.start_km && form.end_km && parseInt(String(form.end_km)) <= parseInt(String(form.start_km)) ) { newErrors.end_km = "Musí být větší než počáteční"; } if (!form.route_from) newErrors.route_from = "Zadejte místo odjezdu"; if (!form.route_to) newErrors.route_to = "Zadejte místo příjezdu"; setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSubmit = async () => { if (!validateForm()) return; try { await submitMutation.mutateAsync(form); } catch (e) { alert.error(e instanceof Error ? e.message : "Chyba připojení"); } }; const handleDelete = async (tripId: number) => { try { await deleteMutation.mutateAsync(tripId); } catch (e) { alert.error(e instanceof Error ? e.message : "Chyba připojení"); } }; const calculateDistance = (): number => { const start = parseInt(String(form.start_km)) || 0; const end = parseInt(String(form.end_km)) || 0; return end > start ? end - start : 0; }; if (tripsLoading) { return (
); } const totals = trips.reduce( (acc, t) => { const dist = t.distance ?? t.end_km - t.start_km; acc.count++; acc.total += dist; if (t.is_business) acc.business += dist; else acc.private += dist; return acc; }, { total: 0, business: 0, private: 0, count: 0 }, ); return (

Kniha jízd

{new Date().toLocaleDateString("cs-CZ", { month: "long", year: "numeric", })}

{/* Stats Cards */}
{totals.count} Počet jízd
{formatKm(totals.total)} km Celkem naježděno
{formatKm(totals.business)} km Služební
{formatKm(totals.private)} km Soukromé
{/* Recent Trips */}

Poslední jízdy

Zobrazit historii
{trips.length === 0 ? (

Zatím nemáte žádné záznamy jízd.

) : (
{trips.slice(0, 10).map((trip) => ( ))}
Datum Vozidlo Řidič Trasa Vzdálenost Typ Akce
{formatDate(trip.trip_date)} {trip.vehicles?.spz ?? ""} {trip.users ? `${trip.users.first_name} ${trip.users.last_name}` : ""} {trip.route_from} → {trip.route_to} {formatKm( trip.distance ?? trip.end_km - trip.start_km, )}{" "} km {trip.is_business ? "Služební" : "Soukromá"}
)}
{/* Add/Edit Modal */} setShowModal(false)} onSubmit={handleSubmit} title={editingTrip ? "Upravit jízdu" : "Přidat jízdu"} loading={submitMutation.isPending} size="lg" >
{ setForm({ ...form, trip_date: val }); setErrors((prev) => ({ ...prev, trip_date: "" })); }} />
{ setForm({ ...form, start_km: e.target.value }); setErrors((prev) => ({ ...prev, start_km: "" })); }} className="admin-form-input" min="0" /> { setForm({ ...form, end_km: e.target.value }); setErrors((prev) => ({ ...prev, end_km: "" })); }} className="admin-form-input" min="0" />
{ setForm({ ...form, route_from: e.target.value }); setErrors((prev) => ({ ...prev, route_from: "" })); }} className="admin-form-input" placeholder="Např. Praha" /> { setForm({ ...form, route_to: e.target.value }); setErrors((prev) => ({ ...prev, route_to: "" })); }} className="admin-form-input" placeholder="Např. Brno" />