feat: role system
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
||||
import { ruRU } from "@mui/x-data-grid/locales";
|
||||
import {
|
||||
authStore,
|
||||
authInstance,
|
||||
devicesStore,
|
||||
Modal,
|
||||
snapshotStore,
|
||||
vehicleStore,
|
||||
routeStore,
|
||||
Vehicle,
|
||||
carrierStore,
|
||||
selectedCityStore,
|
||||
@@ -22,6 +24,7 @@ import {
|
||||
RotateCcw,
|
||||
ScrollText,
|
||||
Trash2,
|
||||
Wrench,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
@@ -35,6 +38,7 @@ import { toast } from "react-toastify";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { DeleteModal } from "@widgets";
|
||||
import { DeviceLogsModal } from "./DeviceLogsModal";
|
||||
import { VehicleSessionsModal } from "./VehicleSessionsModal";
|
||||
|
||||
export type ConnectedDevice = string;
|
||||
|
||||
@@ -77,6 +81,13 @@ type RowData = {
|
||||
snapshot_update_blocked: boolean;
|
||||
maintenance_mode_on: boolean;
|
||||
demo_mode_enabled: boolean;
|
||||
current_route_id: number | null;
|
||||
};
|
||||
|
||||
type PendingModeToggle = {
|
||||
deviceUuid: string;
|
||||
nextEnabled: boolean;
|
||||
tailNumber: string;
|
||||
};
|
||||
|
||||
function getVehicleTypeLabel(vehicle: Vehicle): string {
|
||||
@@ -109,11 +120,13 @@ const transformToRows = (vehicles: Vehicle[]): RowData[] => {
|
||||
snapshot_update_blocked: vehicle.vehicle.snapshot_update_blocked ?? false,
|
||||
maintenance_mode_on: vehicle.vehicle.maintenance_mode_on ?? false,
|
||||
demo_mode_enabled: vehicle.vehicle.demo_mode_enabled ?? false,
|
||||
current_route_id: vehicle.device_status?.current_route_id ?? null,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const DevicesTable = observer(() => {
|
||||
const canWriteDevices = authStore.canWrite("devices");
|
||||
const {
|
||||
getDevices,
|
||||
setSelectedDevice,
|
||||
@@ -123,6 +136,7 @@ export const DevicesTable = observer(() => {
|
||||
} = devicesStore;
|
||||
|
||||
const { snapshots, getSnapshots } = snapshotStore;
|
||||
const { routes, getRoutes } = routeStore;
|
||||
const {
|
||||
getVehicles,
|
||||
vehicles,
|
||||
@@ -137,6 +151,12 @@ export const DevicesTable = observer(() => {
|
||||
const [logsModalDeviceUuid, setLogsModalDeviceUuid] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const [sessionsModalOpen, setSessionsModalOpen] = useState(false);
|
||||
const [sessionsModalVehicleId, setSessionsModalVehicleId] = useState<
|
||||
number | null
|
||||
>(null);
|
||||
const [sessionsModalVehicleTailNumber, setSessionsModalVehicleTailNumber] =
|
||||
useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [maintenanceLoadingUuids, setMaintenanceLoadingUuids] = useState<
|
||||
Set<string>
|
||||
@@ -144,6 +164,14 @@ export const DevicesTable = observer(() => {
|
||||
const [demoLoadingUuids, setDemoLoadingUuids] = useState<Set<string>>(
|
||||
new Set(),
|
||||
);
|
||||
const [maintenanceConfirm, setMaintenanceConfirm] =
|
||||
useState<PendingModeToggle | null>(null);
|
||||
const [demoConfirm, setDemoConfirm] = useState<PendingModeToggle | null>(
|
||||
null,
|
||||
);
|
||||
const [maintenanceConfirmSubmitting, setMaintenanceConfirmSubmitting] =
|
||||
useState(false);
|
||||
const [demoConfirmSubmitting, setDemoConfirmSubmitting] = useState(false);
|
||||
const [collapsedModels, setCollapsedModels] = useState<Set<string>>(
|
||||
new Set(),
|
||||
);
|
||||
@@ -223,65 +251,108 @@ export const DevicesTable = observer(() => {
|
||||
.map((r) => r.device_uuid as string);
|
||||
}, [rows, selectedIds]);
|
||||
|
||||
const handleToggleMaintenanceMode = async (row: RowData) => {
|
||||
if (!row.device_uuid) return;
|
||||
|
||||
const nextEnabled = !row.maintenance_mode_on;
|
||||
const applyMaintenanceMode = async (toggle: PendingModeToggle) => {
|
||||
setMaintenanceLoadingUuids((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(row.device_uuid!);
|
||||
next.add(toggle.deviceUuid);
|
||||
return next;
|
||||
});
|
||||
|
||||
try {
|
||||
await setMaintenanceMode(row.device_uuid, nextEnabled);
|
||||
await setMaintenanceMode(toggle.deviceUuid, toggle.nextEnabled);
|
||||
await getVehicles();
|
||||
await getDevices();
|
||||
toast.success(
|
||||
nextEnabled ? "Устройство отправлено на ТО" : "Режим ТО отключен",
|
||||
toggle.nextEnabled
|
||||
? "Устройство отправлено на ТО"
|
||||
: "Режим ТО отключен",
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error toggling maintenance mode for ${row.device_uuid}:`,
|
||||
`Error toggling maintenance mode for ${toggle.deviceUuid}:`,
|
||||
error,
|
||||
);
|
||||
toast.error("Не удалось изменить режим ТО");
|
||||
} finally {
|
||||
setMaintenanceLoadingUuids((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.delete(row.device_uuid!);
|
||||
next.delete(toggle.deviceUuid);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleDemoMode = async (row: RowData) => {
|
||||
if (!row.device_uuid) return;
|
||||
|
||||
const nextEnabled = !row.demo_mode_enabled;
|
||||
const applyDemoMode = async (toggle: PendingModeToggle) => {
|
||||
setDemoLoadingUuids((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(row.device_uuid!);
|
||||
next.add(toggle.deviceUuid);
|
||||
return next;
|
||||
});
|
||||
|
||||
try {
|
||||
await setDemoMode(row.device_uuid, nextEnabled);
|
||||
await setDemoMode(toggle.deviceUuid, toggle.nextEnabled);
|
||||
await getVehicles();
|
||||
await getDevices();
|
||||
toast.success(nextEnabled ? "Демо-режим включен" : "Демо-режим отключен");
|
||||
toast.success(
|
||||
toggle.nextEnabled ? "Демо-режим включен" : "Демо-режим отключен",
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(`Error toggling demo mode for ${row.device_uuid}:`, error);
|
||||
console.error(
|
||||
`Error toggling demo mode for ${toggle.deviceUuid}:`,
|
||||
error,
|
||||
);
|
||||
toast.error("Не удалось изменить демо-режим");
|
||||
} finally {
|
||||
setDemoLoadingUuids((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.delete(row.device_uuid!);
|
||||
next.delete(toggle.deviceUuid);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const openMaintenanceConfirm = (row: RowData) => {
|
||||
if (!row.device_uuid) return;
|
||||
setMaintenanceConfirm({
|
||||
deviceUuid: row.device_uuid,
|
||||
nextEnabled: !row.maintenance_mode_on,
|
||||
tailNumber: row.tail_number,
|
||||
});
|
||||
};
|
||||
|
||||
const openDemoConfirm = (row: RowData) => {
|
||||
if (!row.device_uuid) return;
|
||||
setDemoConfirm({
|
||||
deviceUuid: row.device_uuid,
|
||||
nextEnabled: !row.demo_mode_enabled,
|
||||
tailNumber: row.tail_number,
|
||||
});
|
||||
};
|
||||
|
||||
const handleConfirmMaintenanceToggle = async () => {
|
||||
if (!maintenanceConfirm) return;
|
||||
|
||||
setMaintenanceConfirmSubmitting(true);
|
||||
try {
|
||||
await applyMaintenanceMode(maintenanceConfirm);
|
||||
setMaintenanceConfirm(null);
|
||||
} finally {
|
||||
setMaintenanceConfirmSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirmDemoToggle = async () => {
|
||||
if (!demoConfirm) return;
|
||||
|
||||
setDemoConfirmSubmitting(true);
|
||||
try {
|
||||
await applyDemoMode(demoConfirm);
|
||||
setDemoConfirm(null);
|
||||
} finally {
|
||||
setDemoConfirmSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const columns: GridColDef[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
@@ -375,10 +446,14 @@ export const DevicesTable = observer(() => {
|
||||
>
|
||||
<Checkbox
|
||||
checked={rowData.maintenance_mode_on}
|
||||
disabled={!rowData.device_uuid || isMaintenanceLoading}
|
||||
disabled={
|
||||
!rowData.device_uuid ||
|
||||
isMaintenanceLoading ||
|
||||
maintenanceConfirmSubmitting
|
||||
}
|
||||
size="small"
|
||||
sx={{ p: 0 }}
|
||||
onChange={() => handleToggleMaintenanceMode(rowData)}
|
||||
onChange={() => openMaintenanceConfirm(rowData)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
@@ -404,10 +479,12 @@ export const DevicesTable = observer(() => {
|
||||
>
|
||||
<Checkbox
|
||||
checked={rowData.demo_mode_enabled}
|
||||
disabled={!rowData.device_uuid || isDemoLoading}
|
||||
disabled={
|
||||
!rowData.device_uuid || isDemoLoading || demoConfirmSubmitting
|
||||
}
|
||||
size="small"
|
||||
sx={{ p: 0 }}
|
||||
onChange={() => handleToggleDemoMode(rowData)}
|
||||
onChange={() => openDemoConfirm(rowData)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
@@ -436,6 +513,20 @@ export const DevicesTable = observer(() => {
|
||||
return snapshot?.Name ?? uuid;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "current_route",
|
||||
headerName: "Текущий маршрут",
|
||||
flex: 1,
|
||||
minWidth: 140,
|
||||
filterable: true,
|
||||
valueGetter: (_value, row) => {
|
||||
const rowData = row as RowData;
|
||||
const routeId = rowData.current_route_id;
|
||||
if (!routeId) return "—";
|
||||
const route = routes.data.find((r) => r.id === routeId);
|
||||
return route?.route_number || "—";
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "gps",
|
||||
headerName: "GPS",
|
||||
@@ -496,15 +587,17 @@ export const DevicesTable = observer(() => {
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate(`/vehicle/${row.vehicle_id}/edit`);
|
||||
}}
|
||||
title="Редактировать транспорт"
|
||||
>
|
||||
<Pencil size={16} />
|
||||
</button>
|
||||
{canWriteDevices && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
navigate(`/vehicle/${row.vehicle_id}/edit`);
|
||||
}}
|
||||
title="Редактировать транспорт"
|
||||
>
|
||||
<Pencil size={16} />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@@ -529,6 +622,17 @@ export const DevicesTable = observer(() => {
|
||||
>
|
||||
<Copy size={16} />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSessionsModalVehicleId(row.vehicle_id);
|
||||
setSessionsModalVehicleTailNumber(row.tail_number);
|
||||
setSessionsModalOpen(true);
|
||||
}}
|
||||
title="Сессии ТО"
|
||||
>
|
||||
<Wrench size={16} />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@@ -557,35 +661,44 @@ export const DevicesTable = observer(() => {
|
||||
setLogsModalOpen,
|
||||
maintenanceLoadingUuids,
|
||||
demoLoadingUuids,
|
||||
setMaintenanceMode,
|
||||
setDemoMode,
|
||||
handleToggleMaintenanceMode,
|
||||
handleToggleDemoMode,
|
||||
openMaintenanceConfirm,
|
||||
openDemoConfirm,
|
||||
maintenanceConfirmSubmitting,
|
||||
demoConfirmSubmitting,
|
||||
routes,
|
||||
canWriteDevices,
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
setIsLoading(true);
|
||||
await getVehicles();
|
||||
await getDevices();
|
||||
await getSnapshots();
|
||||
await Promise.all([
|
||||
getVehicles(),
|
||||
getDevices(),
|
||||
getSnapshots(),
|
||||
getRoutes(),
|
||||
]);
|
||||
setIsLoading(false);
|
||||
};
|
||||
fetchData();
|
||||
}, [getDevices, getSnapshots, getVehicles]);
|
||||
}, [getDevices, getSnapshots, getVehicles, getRoutes]);
|
||||
|
||||
useEffect(() => {
|
||||
carrierStore.getCarriers("ru");
|
||||
}, []);
|
||||
|
||||
const handleOpenSendSnapshotModal = () => {
|
||||
if (!canWriteDevices) {
|
||||
return;
|
||||
}
|
||||
if (selectedDeviceUuidsAllowed.length > 0) {
|
||||
toggleSendSnapshotModal();
|
||||
}
|
||||
};
|
||||
|
||||
const handleSendSnapshotAction = async (snapshotId: string) => {
|
||||
if (!canWriteDevices) return;
|
||||
if (selectedDeviceUuidsAllowed.length === 0) return;
|
||||
|
||||
const blockedCount =
|
||||
@@ -658,14 +771,16 @@ export const DevicesTable = observer(() => {
|
||||
<>
|
||||
<div className="w-full">
|
||||
<div className="flex justify-end mb-5 gap-2 flex-wrap">
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="small"
|
||||
onClick={() => navigate("/vehicle/create")}
|
||||
>
|
||||
Добавить устройство
|
||||
</Button>
|
||||
{canWriteDevices && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="small"
|
||||
onClick={() => navigate("/vehicle/create")}
|
||||
>
|
||||
Добавить устройство
|
||||
</Button>
|
||||
)}
|
||||
{selectedIds.length > 0 && (
|
||||
<Button
|
||||
variant="contained"
|
||||
@@ -677,18 +792,21 @@ export const DevicesTable = observer(() => {
|
||||
Удалить ({selectedIds.length})
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={selectedDeviceUuidsAllowed.length === 0}
|
||||
onClick={handleOpenSendSnapshotModal}
|
||||
size="small"
|
||||
>
|
||||
Обновление ПО ({selectedDeviceUuidsAllowed.length}
|
||||
{selectedDeviceUuids.length !== selectedDeviceUuidsAllowed.length &&
|
||||
`/${selectedDeviceUuids.length}`}
|
||||
)
|
||||
</Button>
|
||||
{canWriteDevices && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={selectedDeviceUuidsAllowed.length === 0}
|
||||
onClick={handleOpenSendSnapshotModal}
|
||||
size="small"
|
||||
>
|
||||
Обновление ПО ({selectedDeviceUuidsAllowed.length}
|
||||
{selectedDeviceUuids.length !==
|
||||
selectedDeviceUuidsAllowed.length &&
|
||||
`/${selectedDeviceUuids.length}`}
|
||||
)
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{groupsByModel.length === 0 ? (
|
||||
@@ -787,52 +905,59 @@ export const DevicesTable = observer(() => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Modal open={sendSnapshotModalOpen} onClose={toggleSendSnapshotModal}>
|
||||
<Box component="h2" sx={{ mb: 1, typography: "h6" }}>
|
||||
Обновление ПО
|
||||
</Box>
|
||||
<Box sx={{ mb: 2 }}>
|
||||
Выбрано устройств для обновления:{" "}
|
||||
<strong className="text-blue-600">
|
||||
{selectedDeviceUuidsAllowed.length}
|
||||
</strong>
|
||||
{selectedDeviceUuids.length !== selectedDeviceUuidsAllowed.length && (
|
||||
<span className="text-amber-600 ml-1">
|
||||
(пропущено{" "}
|
||||
{selectedDeviceUuids.length - selectedDeviceUuidsAllowed.length} с
|
||||
блокировкой)
|
||||
</span>
|
||||
)}
|
||||
</Box>
|
||||
<div className="mt-2 flex flex-col gap-2 max-h-[300px] overflow-y-auto pr-2">
|
||||
{snapshots && (snapshots as Snapshot[]).length > 0 ? (
|
||||
(snapshots as Snapshot[]).map((snapshot) => (
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onClick={() => handleSendSnapshotAction(snapshot.ID)}
|
||||
key={snapshot.ID}
|
||||
sx={{ justifyContent: "flex-start" }}
|
||||
>
|
||||
{snapshot.Name}
|
||||
</Button>
|
||||
))
|
||||
) : (
|
||||
<Box sx={{ typography: "body2", color: "text.secondary" }}>
|
||||
Нет доступных экспортов медиа.
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
onClick={toggleSendSnapshotModal}
|
||||
color="inherit"
|
||||
variant="outlined"
|
||||
sx={{ mt: 3 }}
|
||||
fullWidth
|
||||
{canWriteDevices && (
|
||||
<Modal
|
||||
open={sendSnapshotModalOpen}
|
||||
onClose={toggleSendSnapshotModal}
|
||||
sx={{ width: "min(760px, 94vw)", p: 3 }}
|
||||
>
|
||||
Отмена
|
||||
</Button>
|
||||
</Modal>
|
||||
<Box component="h2" sx={{ mb: 1, typography: "h6" }}>
|
||||
Обновление ПО
|
||||
</Box>
|
||||
<Box sx={{ mb: 2 }}>
|
||||
Выбрано устройств для обновления:{" "}
|
||||
<strong className="text-blue-600">
|
||||
{selectedDeviceUuidsAllowed.length}
|
||||
</strong>
|
||||
{selectedDeviceUuids.length !==
|
||||
selectedDeviceUuidsAllowed.length && (
|
||||
<span className="text-amber-600 ml-1">
|
||||
(пропущено{" "}
|
||||
{selectedDeviceUuids.length - selectedDeviceUuidsAllowed.length}{" "}
|
||||
с блокировкой)
|
||||
</span>
|
||||
)}
|
||||
</Box>
|
||||
<div className="mt-2 flex flex-col gap-2 max-h-[300px] overflow-y-auto pr-2">
|
||||
{snapshots && (snapshots as Snapshot[]).length > 0 ? (
|
||||
(snapshots as Snapshot[]).map((snapshot) => (
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
onClick={() => handleSendSnapshotAction(snapshot.ID)}
|
||||
key={snapshot.ID}
|
||||
sx={{ justifyContent: "flex-start" }}
|
||||
>
|
||||
{snapshot.Name}
|
||||
</Button>
|
||||
))
|
||||
) : (
|
||||
<Box sx={{ typography: "body2", color: "text.secondary" }}>
|
||||
Нет доступных экспортов медиа.
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
onClick={toggleSendSnapshotModal}
|
||||
color="inherit"
|
||||
variant="outlined"
|
||||
sx={{ mt: 3 }}
|
||||
fullWidth
|
||||
>
|
||||
Отмена
|
||||
</Button>
|
||||
</Modal>
|
||||
)}
|
||||
|
||||
<DeleteModal
|
||||
open={isDeleteModalOpen}
|
||||
@@ -840,6 +965,78 @@ export const DevicesTable = observer(() => {
|
||||
onCancel={() => setIsDeleteModalOpen(false)}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
open={maintenanceConfirm != null}
|
||||
onClose={() => {
|
||||
if (!maintenanceConfirmSubmitting) setMaintenanceConfirm(null);
|
||||
}}
|
||||
sx={{ width: "min(640px, 92vw)", p: 3 }}
|
||||
>
|
||||
<Box component="h2" sx={{ mb: 1, typography: "h6" }}>
|
||||
Подтверждение режима ТО
|
||||
</Box>
|
||||
<Box sx={{ mb: 3 }}>
|
||||
{maintenanceConfirm?.nextEnabled
|
||||
? `Включить режим ТО для устройства ${maintenanceConfirm.tailNumber}?`
|
||||
: `Отключить режим ТО для устройства ${maintenanceConfirm?.tailNumber}?`}
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", gap: 1.5 }}>
|
||||
<Button
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
color="inherit"
|
||||
disabled={maintenanceConfirmSubmitting}
|
||||
onClick={() => setMaintenanceConfirm(null)}
|
||||
>
|
||||
Отмена
|
||||
</Button>
|
||||
<Button
|
||||
fullWidth
|
||||
variant="contained"
|
||||
disabled={maintenanceConfirmSubmitting}
|
||||
onClick={handleConfirmMaintenanceToggle}
|
||||
>
|
||||
Подтвердить
|
||||
</Button>
|
||||
</Box>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
open={demoConfirm != null}
|
||||
onClose={() => {
|
||||
if (!demoConfirmSubmitting) setDemoConfirm(null);
|
||||
}}
|
||||
sx={{ width: "min(640px, 92vw)", p: 3 }}
|
||||
>
|
||||
<Box component="h2" sx={{ mb: 1, typography: "h6" }}>
|
||||
Подтверждение демо-режима
|
||||
</Box>
|
||||
<Box sx={{ mb: 3 }}>
|
||||
{demoConfirm?.nextEnabled
|
||||
? `Включить демо-режим для устройства ${demoConfirm.tailNumber}?`
|
||||
: `Отключить демо-режим для устройства ${demoConfirm?.tailNumber}?`}
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", gap: 1.5 }}>
|
||||
<Button
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
color="inherit"
|
||||
disabled={demoConfirmSubmitting}
|
||||
onClick={() => setDemoConfirm(null)}
|
||||
>
|
||||
Отмена
|
||||
</Button>
|
||||
<Button
|
||||
fullWidth
|
||||
variant="contained"
|
||||
disabled={demoConfirmSubmitting}
|
||||
onClick={handleConfirmDemoToggle}
|
||||
>
|
||||
Подтвердить
|
||||
</Button>
|
||||
</Box>
|
||||
</Modal>
|
||||
|
||||
<DeviceLogsModal
|
||||
open={logsModalOpen}
|
||||
deviceUuid={logsModalDeviceUuid}
|
||||
@@ -848,6 +1045,17 @@ export const DevicesTable = observer(() => {
|
||||
setLogsModalDeviceUuid(null);
|
||||
}}
|
||||
/>
|
||||
|
||||
<VehicleSessionsModal
|
||||
open={sessionsModalOpen}
|
||||
vehicleId={sessionsModalVehicleId}
|
||||
tailNumber={sessionsModalVehicleTailNumber}
|
||||
onClose={() => {
|
||||
setSessionsModalOpen(false);
|
||||
setSessionsModalVehicleId(null);
|
||||
setSessionsModalVehicleTailNumber(null);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user