feature/webgl-preview-update (#21)

Co-authored-by: itoshi <kkzemeow@gmail.com>
Reviewed-on: #21
This commit was merged in pull request #21.
This commit is contained in:
2026-10-13 16:49:18 +00:00
parent 50ad374cf5
commit 71834904a1
327 changed files with 39034 additions and 8829 deletions

View File

@@ -0,0 +1,180 @@
import { Modal, vehicleStore } from "@shared";
import {
Box,
Button,
CircularProgress,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Typography,
} from "@mui/material";
import { observer } from "mobx-react-lite";
import { useEffect } from "react";
interface VehicleSessionsModalProps {
open: boolean;
vehicleId: number | null;
tailNumber?: string | null;
onClose: () => void;
}
const formatDateTime = (value: string) => {
if (!value) return "-";
const date = new Date(value);
if (Number.isNaN(date.getTime())) return value;
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);
};
const formatDuration = (durationSeconds: number) => {
if (!Number.isFinite(durationSeconds) || durationSeconds < 0) {
return "-";
}
const hours = Math.floor(durationSeconds / 3600);
const minutes = Math.floor((durationSeconds % 3600) / 60);
const seconds = durationSeconds % 60;
return [hours, minutes, seconds]
.map((part) => String(part).padStart(2, "0"))
.join(":");
};
export const VehicleSessionsModal = observer(
({ open, vehicleId, tailNumber, onClose }: VehicleSessionsModalProps) => {
const {
vehicleSessions,
vehicleSessionsLoading,
vehicleSessionsError,
getVehicleSessions,
} = vehicleStore;
useEffect(() => {
if (!open || vehicleId == null) return;
getVehicleSessions(vehicleId).catch(() => undefined);
}, [open, vehicleId, getVehicleSessions]);
const title =
tailNumber && tailNumber !== ""
? `Сессии ТО: ${tailNumber}`
: "Сессии ТО";
return (
<Modal
open={open}
onClose={onClose}
sx={{ width: "min(1080px, 95vw)", p: 3 }}
>
<div className="flex flex-col gap-4 max-h-[82vh]">
<Typography variant="h6" component="h2">
{title}
</Typography>
<Box sx={{ minHeight: 220 }}>
{vehicleSessionsLoading && (
<Box
sx={{
minHeight: 220,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<CircularProgress size={24} />
</Box>
)}
{!vehicleSessionsLoading && vehicleSessionsError && (
<Box
sx={{
minHeight: 220,
display: "flex",
alignItems: "center",
justifyContent: "center",
color: "error.main",
}}
>
{vehicleSessionsError}
</Box>
)}
{!vehicleSessionsLoading &&
!vehicleSessionsError &&
vehicleSessions &&
vehicleSessions.length === 0 && (
<Box
sx={{
minHeight: 220,
display: "flex",
alignItems: "center",
justifyContent: "center",
color: "text.secondary",
}}
>
По этому транспорту нет сессий ТО.
</Box>
)}
{!vehicleSessionsLoading &&
!vehicleSessionsError &&
vehicleSessions &&
vehicleSessions.length > 0 && (
<TableContainer
sx={{ maxHeight: "60vh", border: 1, borderColor: "divider" }}
>
<Table stickyHeader size="small">
<TableHead>
<TableRow>
<TableCell>ID</TableCell>
<TableCell>Начало</TableCell>
<TableCell>Окончание</TableCell>
<TableCell align="right">Длительность</TableCell>
</TableRow>
</TableHead>
<TableBody>
{vehicleSessions.map((session) => (
<TableRow key={session.id} hover>
<TableCell>{session.id}</TableCell>
<TableCell>
{formatDateTime(session.started_at)}
</TableCell>
<TableCell>
{formatDateTime(session.ended_at)}
</TableCell>
<TableCell align="right">
{formatDuration(session.duration_seconds)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)}
</Box>
<Button
variant="outlined"
color="inherit"
onClick={onClose}
fullWidth
>
Закрыть
</Button>
</div>
</Modal>
);
},
);