import { useState, useEffect, useCallback, useRef } from 'react' import { useAlert } from '../context/AlertContext' import { useAuth } from '../context/AuthContext' import { Link } from 'react-router-dom' import Forbidden from '../components/Forbidden' import { motion, AnimatePresence } from 'framer-motion' import ConfirmModal from '../components/ConfirmModal' import AdminDatePicker from '../components/AdminDatePicker' import FormField from '../components/FormField' import useModalLock from '../hooks/useModalLock' import { formatDate } from '../utils/attendanceHelpers' import { formatKm } from '../utils/formatters' import apiFetch from '../utils/api' const API_BASE = '/api/admin' interface Vehicle { id: number | string spz: string name: string } interface UserShort { id: number | string name: string } interface Trip { id: number vehicle_id: number | string trip_date: string start_km: number end_km: number distance: number route_from: string route_to: string is_business: number | boolean notes?: string spz: string driver_name: string } interface BackendTrip { id: number vehicle_id: number user_id: number trip_date: string start_km: number end_km: number distance: number | null route_from: string route_to: string is_business: boolean notes: string | null users: { id: number; first_name: string; last_name: string } vehicles: { id: number; name: string; spz: string } } interface EditForm { 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 } function mapTrip(bt: BackendTrip): Trip { const distance = bt.distance ?? (bt.end_km - bt.start_km) return { id: bt.id, vehicle_id: bt.vehicle_id, trip_date: bt.trip_date, start_km: bt.start_km, end_km: bt.end_km, distance, route_from: bt.route_from, route_to: bt.route_to, is_business: bt.is_business ? 1 : 0, notes: bt.notes || undefined, spz: bt.vehicles?.spz ?? '', driver_name: bt.users ? `${bt.users.first_name} ${bt.users.last_name}` : '', } } export default function TripsAdmin() { const alert = useAlert() const { hasPermission } = useAuth() const [loading, setLoading] = useState(true) const [filterMonth, setFilterMonth] = useState(() => String(new Date().getMonth() + 1)) const [filterYear, setFilterYear] = useState(() => String(new Date().getFullYear())) const [filterVehicleId, setFilterVehicleId] = useState('') const [filterUserId, setFilterUserId] = useState('') const [trips, setTrips] = useState([]) const [vehicles, setVehicles] = useState([]) const [users, setUsers] = useState([]) const printRef = useRef(null) const [showEditModal, setShowEditModal] = useState(false) const [editingTrip, setEditingTrip] = useState(null) const [editForm, setEditForm] = useState({ vehicle_id: '', trip_date: '', start_km: '', end_km: '', route_from: '', route_to: '', is_business: 1, notes: '' }) const [deleteConfirm, setDeleteConfirm] = useState<{ show: boolean; trip: Trip | null }>({ show: false, trip: null }) // Fetch vehicles and users once on mount useEffect(() => { const fetchLookups = async () => { try { const [vRes, uRes] = await Promise.all([ apiFetch(`${API_BASE}/vehicles`), apiFetch(`${API_BASE}/users?limit=1000`), ]) const vJson = await vRes.json() const uJson = await uRes.json() if (vJson.success) setVehicles(vJson.data) if (uJson.success) { setUsers(uJson.data.map((u: { id: number; first_name: string; last_name: string }) => ({ id: u.id, name: `${u.first_name} ${u.last_name}`, }))) } } catch { // silently fail, filters will just be empty } } fetchLookups() }, []) const fetchData = useCallback(async (showLoading = true) => { if (showLoading) setLoading(true) try { let url = `${API_BASE}/trips?limit=1000&month=${filterMonth}&year=${filterYear}` if (filterVehicleId) url += `&vehicle_id=${filterVehicleId}` if (filterUserId) url += `&user_id=${filterUserId}` const response = await apiFetch(url) const result = await response.json() if (result.success) { const mapped = (result.data as BackendTrip[]).map(mapTrip) setTrips(mapped) } } catch { alert.error('Nepodařilo se načíst data') } finally { if (showLoading) setLoading(false) } }, [filterMonth, filterYear, filterVehicleId, filterUserId, alert]) useEffect(() => { fetchData() }, [fetchData]) useModalLock(showEditModal) if (!hasPermission('trips.admin')) return const openEditModal = (trip: Trip) => { setEditingTrip(trip) setEditForm({ 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 || '' }) setShowEditModal(true) } const handleEditSubmit = async () => { if (!editingTrip) return if (parseInt(String(editForm.end_km)) <= parseInt(String(editForm.start_km))) { alert.error('Konečný stav km musí být větší než počáteční') return } try { const response = await apiFetch(`${API_BASE}/trips/${editingTrip.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(editForm) }) const result = await response.json() if (result.success) { setShowEditModal(false) await fetchData(false) await new Promise(resolve => setTimeout(resolve, 300)) alert.success(result.message) } else { alert.error(result.error) } } catch { alert.error('Chyba připojení') } } const handleDelete = async () => { if (!deleteConfirm.trip) return try { const response = await apiFetch(`${API_BASE}/trips/${deleteConfirm.trip.id}`, { method: 'DELETE', }) const result = await response.json() if (result.success) { setDeleteConfirm({ show: false, trip: null }) await fetchData(false) alert.success(result.message) } else { alert.error(result.error) } } catch { alert.error('Chyba připojení') } } const getPeriodName = () => new Date(Number(filterYear), Number(filterMonth) - 1).toLocaleString('cs-CZ', { month: 'long', year: 'numeric' }) const getSelectedVehicleName = () => { if (!filterVehicleId) return null const v = vehicles.find(v => String(v.id) === filterVehicleId) return v ? `${v.spz} - ${v.name}` : null } const getSelectedUserName = () => { if (!filterUserId) return null const u = users.find(u => String(u.id) === filterUserId) return u?.name || null } const handlePrint = () => { const periodName = getPeriodName() setTimeout(() => { if (printRef.current) { const content = printRef.current.innerHTML const printWindow = window.open('', '_blank') if (!printWindow) return printWindow.document.write(` Kniha jízd - ${periodName} ${content} `) printWindow.document.close() printWindow.onload = () => { printWindow.print() } } }, 100) } const calculateDistance = (): number => { const start = parseInt(String(editForm.start_km)) || 0 const end = parseInt(String(editForm.end_km)) || 0 return end > start ? end - start : 0 } const totals = { count: trips.length, total: trips.reduce((sum, t) => sum + t.distance, 0), business: trips.filter(t => Number(t.is_business)).reduce((sum, t) => sum + t.distance, 0), } return (

Správa knihy jízd

{trips.length > 0 && ( )} Vozidla
{/* Filters */}
{totals.count} Počet jízd
{formatKm(totals.total)} km Celkem naježděno
{formatKm(totals.business)} km Služební km
{/* Trips Table */}
{loading && (
{[0, 1, 2, 3, 4].map(i => (
))}
)} {!loading && trips.length === 0 && (

Žádné záznamy jízd pro vybrané období.

)} {!loading && trips.length > 0 && (
{trips.map((trip) => ( ))}
Datum Řidič Vozidlo Trasa Stav km Vzdálenost Typ Akce
{formatDate(trip.trip_date)} {trip.driver_name} {trip.spz} {trip.route_from} → {trip.route_to} {formatKm(trip.start_km)} - {formatKm(trip.end_km)} {formatKm(trip.distance)} km {trip.is_business ? 'Služební' : 'Soukromá'}
)}
{/* Edit Modal */} {showEditModal && editingTrip && (
setShowEditModal(false)} />

Upravit jízdu

{editingTrip.driver_name}

setEditForm({ ...editForm, trip_date: val })} />
setEditForm({ ...editForm, start_km: e.target.value })} className="admin-form-input" min="0" /> setEditForm({ ...editForm, end_km: e.target.value })} className="admin-form-input" min="0" />
setEditForm({ ...editForm, route_from: e.target.value })} className="admin-form-input" /> setEditForm({ ...editForm, route_to: e.target.value })} className="admin-form-input" />