import Table from "@mui/material/Table"; import TableBody from "@mui/material/TableBody"; import TableCell from "@mui/material/TableCell"; import TableContainer from "@mui/material/TableContainer"; import TableHead from "@mui/material/TableHead"; import TableRow from "@mui/material/TableRow"; import Paper from "@mui/material/Paper"; import { Check, Copy, RotateCcw, X } from "lucide-react"; import { authInstance, devicesStore, Modal, snapshotStore, vehicleStore, // Not directly used in this component's rendering logic anymore } from "@shared"; // Assuming @shared exports these import { useEffect, useState } from "react"; import { observer } from "mobx-react-lite"; import { Button, Checkbox, Typography } from "@mui/material"; import { Vehicle } from "@shared"; import { toast } from "react-toastify"; import { useNavigate } from "react-router-dom"; export type ConnectedDevice = string; interface Snapshot { ID: string; // Assuming ID is string based on usage Name: string; // Add other snapshot properties if needed } // --- HELPER FUNCTIONS --- const formatDate = (dateString: string | null) => { if (!dateString) return "Нет данных"; try { const date = new Date(dateString); return new Intl.DateTimeFormat("ru-RU", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false, }).format(date); } catch (error) { return "Некорректная дата"; } }; type TableRowData = { tail_number: number; online: boolean; lastUpdate: string | null; gps: boolean; media: boolean; connection: boolean; device_uuid: string | null; }; function createData( tail_number: number, online: boolean, lastUpdate: string | null, gps: boolean, media: boolean, connection: boolean, device_uuid: string | null ): TableRowData { return { tail_number, online, lastUpdate, gps, media, connection, device_uuid, }; } // This function transforms the raw device data (which includes vehicle and device_status) // into the format expected by the table. It now filters for devices that have a UUID. const transformDevicesToRows = ( vehicles: Vehicle[] // devices: ConnectedDevice[] ): TableRowData[] => { return vehicles.map((vehicle) => { const uuid = vehicle.vehicle.uuid; if (!uuid) return { tail_number: vehicle.vehicle.tail_number, online: false, lastUpdate: null, gps: false, media: false, connection: false, device_uuid: null, }; return createData( vehicle.vehicle.tail_number, vehicle.device_status?.online ?? false, vehicle.device_status?.last_update ?? null, vehicle.device_status?.gps_ok ?? false, vehicle.device_status?.media_service_ok ?? false, vehicle.device_status?.is_connected ?? false, uuid ); }); }; export const DevicesTable = observer(() => { const { getDevices, setSelectedDevice, sendSnapshotModalOpen, toggleSendSnapshotModal, } = devicesStore; const { snapshots, getSnapshots } = snapshotStore; const { getVehicles, vehicles } = vehicleStore; // Removed as devicesStore.devices should be the source of truth const { devices } = devicesStore; const navigate = useNavigate(); const [selectedDeviceUuids, setSelectedDeviceUuids] = useState([]); // Transform the raw devices data into rows suitable for the table // This will also filter out devices without a UUID, as those cannot be acted upon. const currentTableRows = transformDevicesToRows( vehicles.data as Vehicle[] // devices as ConnectedDevice[] ); useEffect(() => { const fetchData = async () => { await getVehicles(); // Not strictly needed if devicesStore.devices is populated correctly by getDevices await getDevices(); // This should fetch the combined vehicle/device_status data await getSnapshots(); }; fetchData(); }, [getDevices, getSnapshots]); // Added dependencies const isAllSelected = currentTableRows.length > 0 && selectedDeviceUuids.length === currentTableRows.length; const handleSelectAllDevices = () => { if (isAllSelected) { setSelectedDeviceUuids([]); } else { // Select all device UUIDs from the *currently visible and selectable* rows setSelectedDeviceUuids( currentTableRows.map((row) => row.device_uuid ?? "") ); } }; const handleSelectDevice = ( event: React.ChangeEvent, deviceUuid: string ) => { if (event.target.checked) { setSelectedDeviceUuids((prevSelected) => [...prevSelected, deviceUuid]); } else { setSelectedDeviceUuids((prevSelected) => prevSelected.filter((uuid) => uuid !== deviceUuid) ); } }; const handleOpenSendSnapshotModal = () => { if (selectedDeviceUuids.length > 0) { toggleSendSnapshotModal(); } }; const handleReloadStatus = async (uuid: string) => { setSelectedDevice(uuid); // Sets the device in the store, useful for context elsewhere try { await authInstance.post(`/devices/${uuid}/request-status`); await getVehicles(); await getDevices(); // Refresh devices to show updated status } catch (error) { console.error(`Error requesting status for device ${uuid}:`, error); // Optionally: show a user-facing error message } }; const handleSendSnapshotAction = async (snapshotId: string) => { if (selectedDeviceUuids.length === 0) return; const send = async (deviceUuid: string) => { try { await authInstance.post( `/devices/${deviceUuid}/force-snapshot-update`, { snapshot_id: snapshotId, } ); toast.success(`Снапшот отправлен на устройство `); } catch (error) { console.error(`Error sending snapshot to device ${deviceUuid}:`, error); toast.error(`Не удалось отправить снапшот на устройство`); } }; try { // Create an array of promises for all snapshot requests const snapshotPromises = selectedDeviceUuids.map((deviceUuid) => { return send(deviceUuid); }); // Wait for all promises to settle (either resolve or reject) await Promise.allSettled(snapshotPromises); // After all requests are attempted await getDevices(); // Refresh the device list setSelectedDeviceUuids([]); // Clear the selection toggleSendSnapshotModal(); // Close the modal } catch (error) { // This catch block might not be hit if Promise.allSettled is used, // as it doesn't reject on individual promise failures. // Individual errors should be handled if needed within the .map or by checking results. console.error("Error in snapshot sending process:", error); } }; return ( <>
0 && selectedDeviceUuids.length < currentTableRows.length } checked={isAllSelected} onChange={handleSelectAllDevices} inputProps={{ "aria-label": "select all devices" }} size="small" /> Борт. номер Онлайн Обновлено GPS Медиа Связь Действия {currentTableRows.map((row) => ( { // Allow clicking row to toggle checkbox, if not clicking on button if ( (event.target as HTMLElement).closest("button") === null && (event.target as HTMLElement).closest( 'input[type="checkbox"]' ) === null ) { if (event.shiftKey) { if (row.device_uuid) { navigator.clipboard .writeText(row.device_uuid) .then(() => { toast.success(`UUID скопирован`); }) .catch(() => { toast.error("Не удалось скопировать UUID"); }); } else { toast.warning("Устройство не имеет UUID"); } } // Only toggle checkbox if Shift key is not pressed if (!event.shiftKey) { handleSelectDevice( { target: { checked: !selectedDeviceUuids.includes( row.device_uuid ?? "" ), }, } as React.ChangeEvent, // Simulate event row.device_uuid ?? "" ); } } }} sx={{ cursor: "pointer", "&:last-child td, &:last-child th": { border: 0 }, }} > handleSelectDevice(event, row.device_uuid ?? "") } size="small" /> {row.tail_number}
{row.online ? ( ) : ( )}
{formatDate(row.lastUpdate)}
{row.gps ? ( ) : ( )}
{row.media ? ( ) : ( )}
{row.connection ? ( ) : ( )}
))} {currentTableRows.length === 0 && ( Нет устройств для отображения. )}
Отправить снапшот Выбрано устройств:{" "} {selectedDeviceUuids.length}
{snapshots && (snapshots as Snapshot[]).length > 0 ? ( // Cast snapshots (snapshots as Snapshot[]).map((snapshot) => ( )) ) : ( Нет доступных снапшотов. )}
); });