import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { Link as RouterLink } from "react-router-dom";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import IconButton from "@mui/material/IconButton";
import { useAlert } from "../context/AlertContext";
import { useAuth } from "../context/AuthContext";
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,
} from "../lib/queries/trips";
import { useApiMutation } from "../lib/queries/mutations";
import {
Button,
Card,
DataTable,
Modal,
ConfirmDialog,
Field,
TextField,
Select,
DateField,
StatusChip,
LoadingState,
StatCard,
PageEnter,
type DataColumn,
} from "../ui";
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;
}
const PlusIcon = (
);
const EditIcon = (
);
const DeleteIcon = (
);
const TripsIcon = (
);
const TotalIcon = (
);
const BusinessIcon = (
);
const PrivateIcon = (
);
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 },
);
const recentTrips = trips.slice(0, 10);
const columns: DataColumn[] = [
{
key: "trip_date",
header: "Datum",
width: "12%",
mono: true,
render: (trip) => formatDate(trip.trip_date),
},
{
key: "vehicle",
header: "Vozidlo",
width: "12%",
render: (trip) => (
),
},
{
key: "driver",
header: "Řidič",
width: "16%",
render: (trip) =>
trip.users ? `${trip.users.first_name} ${trip.users.last_name}` : "",
},
{
key: "route",
header: "Trasa",
width: "22%",
render: (trip) => (
{trip.route_from} → {trip.route_to}
),
},
{
key: "distance",
header: "Vzdálenost",
width: "12%",
mono: true,
bold: true,
render: (trip) =>
`${formatKm(trip.distance ?? trip.end_km - trip.start_km)} km`,
},
{
key: "type",
header: "Typ",
width: "12%",
render: (trip) => (
),
},
{
key: "actions",
header: "Akce",
width: "10%",
align: "right",
render: (trip) => (
openEditModal(trip)}
aria-label="Upravit"
title="Upravit"
>
{EditIcon}
setDeleteConfirm({ show: true, tripId: trip.id })}
aria-label="Smazat"
title="Smazat"
>
{DeleteIcon}
),
},
];
return (
Kniha jízd
{new Date().toLocaleDateString("cs-CZ", {
month: "long",
year: "numeric",
})}
{/* Stats Cards */}
{/* Recent Trips */}
Poslední jízdy
columns={columns}
rows={recentTrips}
rowKey={(trip) => trip.id}
empty={
Zatím nemáte žádné záznamy jízd.
}
/>
{/* Add/Edit Modal */}
setShowModal(false)}
onSubmit={handleSubmit}
title={editingTrip ? "Upravit jízdu" : "Přidat jízdu"}
loading={submitMutation.isPending}
maxWidth="md"
>
{
setForm({ ...form, trip_date: val });
setErrors((prev) => ({ ...prev, trip_date: "" }));
}}
/>
{
setForm({ ...form, start_km: e.target.value });
setErrors((prev) => ({ ...prev, start_km: "" }));
}}
slotProps={{ htmlInput: { min: 0 } }}
/>
{
setForm({ ...form, end_km: e.target.value });
setErrors((prev) => ({ ...prev, end_km: "" }));
}}
slotProps={{ htmlInput: { min: 0 } }}
/>
{
setForm({ ...form, route_from: e.target.value });
setErrors((prev) => ({ ...prev, route_from: "" }));
}}
placeholder="Např. Praha"
/>
{
setForm({ ...form, route_to: e.target.value });
setErrors((prev) => ({ ...prev, route_to: "" }));
}}
placeholder="Např. Brno"
/>
setForm({ ...form, notes: e.target.value })}
placeholder="Volitelné poznámky..."
/>
setDeleteConfirm({ show: false, tripId: null })}
onConfirm={() => handleDelete(deleteConfirm.tripId!)}
title="Smazat jízdu"
message="Opravdu chcete smazat tento záznam?"
confirmText="Smazat"
cancelText="Zrušit"
confirmVariant="danger"
loading={deleteMutation.isPending}
/>
);
}