Compare commits
2 Commits
2a9449ba58
...
feature/we
| Author | SHA1 | Date | |
|---|---|---|---|
| 73070fe233 | |||
| 7cf188a55c |
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "white-nights",
|
||||
"private": true,
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"type": "module",
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { languageInstance } from "@shared";
|
||||
import { authInstance, languageInstance } from "@shared";
|
||||
import { makeAutoObservable, runInAction } from "mobx";
|
||||
|
||||
export type Vehicle = {
|
||||
@@ -12,6 +12,9 @@ export type Vehicle = {
|
||||
model?: string;
|
||||
current_snapshot_uuid?: string;
|
||||
snapshot_update_blocked?: boolean;
|
||||
demo_mode_enabled?: boolean;
|
||||
maintenance_mode_on?: boolean;
|
||||
city_id?: number;
|
||||
};
|
||||
device_status?: {
|
||||
device_uuid: string;
|
||||
@@ -37,11 +40,75 @@ class VehicleStore {
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
private normalizeVehicleItem = (item: any): Vehicle => {
|
||||
if (item && typeof item === "object" && "vehicle" in item) {
|
||||
return {
|
||||
vehicle: item.vehicle ?? {},
|
||||
device_status: item.device_status,
|
||||
} as Vehicle;
|
||||
}
|
||||
|
||||
return {
|
||||
vehicle: item ?? {},
|
||||
} as Vehicle;
|
||||
};
|
||||
|
||||
private mergeVehicleInCaches = (updatedVehicle: any) => {
|
||||
if (!updatedVehicle) return;
|
||||
|
||||
const updatedId = updatedVehicle.id;
|
||||
const updatedUuid = updatedVehicle.uuid;
|
||||
|
||||
const mergeItem = (item: Vehicle): Vehicle => ({
|
||||
...item,
|
||||
vehicle: {
|
||||
...item.vehicle,
|
||||
...updatedVehicle,
|
||||
},
|
||||
});
|
||||
|
||||
this.vehicles.data = this.vehicles.data.map((item) => {
|
||||
const sameId = updatedId != null && item.vehicle.id === updatedId;
|
||||
const sameUuid =
|
||||
updatedUuid != null &&
|
||||
item.vehicle.uuid != null &&
|
||||
item.vehicle.uuid === updatedUuid;
|
||||
|
||||
if (!sameId && !sameUuid) return item;
|
||||
|
||||
return mergeItem(item);
|
||||
});
|
||||
|
||||
if (updatedId != null) {
|
||||
const existing = this.vehicle[updatedId];
|
||||
this.vehicle[updatedId] = existing
|
||||
? mergeItem(existing)
|
||||
: ({ vehicle: updatedVehicle } as Vehicle);
|
||||
return;
|
||||
}
|
||||
|
||||
if (updatedUuid != null) {
|
||||
const entry = Object.entries(this.vehicle).find(
|
||||
([, item]) => item.vehicle.uuid === updatedUuid
|
||||
);
|
||||
|
||||
if (entry) {
|
||||
const [key, item] = entry;
|
||||
this.vehicle[key] = mergeItem(item);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
getVehicles = async () => {
|
||||
const response = await languageInstance("ru").get(`/vehicle`);
|
||||
const vehiclesList = Array.isArray(response.data)
|
||||
? response.data
|
||||
: Array.isArray(response.data?.vehicles)
|
||||
? response.data.vehicles
|
||||
: [];
|
||||
|
||||
runInAction(() => {
|
||||
this.vehicles.data = response.data;
|
||||
this.vehicles.data = vehiclesList.map(this.normalizeVehicleItem);
|
||||
this.vehicles.loaded = true;
|
||||
});
|
||||
};
|
||||
@@ -58,9 +125,10 @@ class VehicleStore {
|
||||
|
||||
getVehicle = async (id: number) => {
|
||||
const response = await languageInstance("ru").get(`/vehicle/${id}`);
|
||||
const normalizedVehicle = this.normalizeVehicleItem(response.data);
|
||||
|
||||
runInAction(() => {
|
||||
this.vehicle[id] = response.data;
|
||||
this.vehicle[id] = normalizedVehicle;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -80,19 +148,13 @@ class VehicleStore {
|
||||
// TODO: когда будет бекенд — добавить model в payload и в ответ
|
||||
if (model != null && model !== "") payload.model = model;
|
||||
const response = await languageInstance("ru").post("/vehicle", payload);
|
||||
const normalizedVehicle = this.normalizeVehicleItem(response.data);
|
||||
|
||||
runInAction(() => {
|
||||
this.vehicles.data.push({
|
||||
vehicle: {
|
||||
id: response.data.id,
|
||||
tail_number: response.data.tail_number,
|
||||
type: response.data.type,
|
||||
carrier_id: response.data.carrier_id,
|
||||
carrier: response.data.carrier,
|
||||
uuid: response.data.uuid,
|
||||
model: response.data.model ?? model,
|
||||
},
|
||||
});
|
||||
this.vehicles.data.push(normalizedVehicle);
|
||||
if (normalizedVehicle.vehicle?.id != null) {
|
||||
this.vehicle[normalizedVehicle.vehicle.id] = normalizedVehicle;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -150,31 +212,51 @@ class VehicleStore {
|
||||
`/vehicle/${id}`,
|
||||
payload
|
||||
);
|
||||
const normalizedVehicle = this.normalizeVehicleItem(response.data);
|
||||
const updatedVehiclePayload = {
|
||||
...normalizedVehicle.vehicle,
|
||||
model: normalizedVehicle.vehicle.model ?? data.model,
|
||||
snapshot_update_blocked:
|
||||
normalizedVehicle.vehicle.snapshot_update_blocked ??
|
||||
data.snapshot_update_blocked,
|
||||
};
|
||||
|
||||
runInAction(() => {
|
||||
const updated = {
|
||||
...response.data,
|
||||
model: response.data.model ?? data.model,
|
||||
snapshot_update_blocked:
|
||||
response.data.snapshot_update_blocked ?? data.snapshot_update_blocked,
|
||||
this.mergeVehicleInCaches({
|
||||
...updatedVehiclePayload,
|
||||
id,
|
||||
});
|
||||
});
|
||||
};
|
||||
this.vehicle[id] = {
|
||||
vehicle: {
|
||||
...this.vehicle[id].vehicle,
|
||||
...updated,
|
||||
},
|
||||
|
||||
setMaintenanceMode = async (uuid: string, enabled: boolean) => {
|
||||
const response = await authInstance.post(`/devices/${uuid}/maintenance-mode`, {
|
||||
enabled,
|
||||
});
|
||||
const normalizedVehicle = this.normalizeVehicleItem(response.data);
|
||||
|
||||
runInAction(() => {
|
||||
this.mergeVehicleInCaches({
|
||||
...normalizedVehicle.vehicle,
|
||||
uuid,
|
||||
maintenance_mode_on:
|
||||
normalizedVehicle.vehicle.maintenance_mode_on ?? enabled,
|
||||
});
|
||||
});
|
||||
};
|
||||
this.vehicles.data = this.vehicles.data.map((vehicle) =>
|
||||
vehicle.vehicle.id === id
|
||||
? {
|
||||
...vehicle,
|
||||
vehicle: {
|
||||
...vehicle.vehicle,
|
||||
...updated,
|
||||
},
|
||||
}
|
||||
: vehicle
|
||||
);
|
||||
|
||||
setDemoMode = async (uuid: string, enabled: boolean) => {
|
||||
const response = await authInstance.post(`/devices/${uuid}/demo-mode`, {
|
||||
enabled,
|
||||
});
|
||||
const normalizedVehicle = this.normalizeVehicleItem(response.data);
|
||||
|
||||
runInAction(() => {
|
||||
this.mergeVehicleInCaches({
|
||||
...normalizedVehicle.vehicle,
|
||||
uuid,
|
||||
demo_mode_enabled: normalizedVehicle.vehicle.demo_mode_enabled ?? enabled,
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,6 +15,12 @@ interface DeviceLogsModalProps {
|
||||
}
|
||||
|
||||
const toYYYYMMDD = (d: Date) => d.toISOString().slice(0, 10);
|
||||
const shiftYYYYMMDD = (value: string, days: number) => {
|
||||
const d = new Date(`${value}T00:00:00Z`);
|
||||
if (Number.isNaN(d.getTime())) return value;
|
||||
d.setUTCDate(d.getUTCDate() + days);
|
||||
return toYYYYMMDD(d);
|
||||
};
|
||||
|
||||
type LogLevel = "info" | "warn" | "error" | "debug" | "fatal" | "unknown";
|
||||
|
||||
@@ -133,6 +139,24 @@ export const DeviceLogsModal = ({
|
||||
const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
|
||||
const [dateFrom, setDateFrom] = useState(toYYYYMMDD(yesterday));
|
||||
const [dateTo, setDateTo] = useState(toYYYYMMDD(today));
|
||||
const dateToMin = shiftYYYYMMDD(dateFrom, 1);
|
||||
const dateFromMax = shiftYYYYMMDD(dateTo, -1);
|
||||
|
||||
const handleDateFromChange = (value: string) => {
|
||||
setDateFrom(value);
|
||||
if (!dateTo || dateTo <= value) {
|
||||
setDateTo(shiftYYYYMMDD(value, 1));
|
||||
}
|
||||
};
|
||||
|
||||
const handleDateToChange = (value: string) => {
|
||||
if (value <= dateFrom) {
|
||||
toast.info("Дата 'До' должна быть позже даты 'От'");
|
||||
setDateTo(shiftYYYYMMDD(dateFrom, 1));
|
||||
return;
|
||||
}
|
||||
setDateTo(value);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || !deviceUuid) return;
|
||||
@@ -235,16 +259,22 @@ export const DeviceLogsModal = ({
|
||||
label="От"
|
||||
size="small"
|
||||
value={dateFrom}
|
||||
onChange={(e) => setDateFrom(e.target.value)}
|
||||
slotProps={{ inputLabel: { shrink: true } }}
|
||||
onChange={(e) => handleDateFromChange(e.target.value)}
|
||||
slotProps={{
|
||||
inputLabel: { shrink: true },
|
||||
htmlInput: { max: dateFromMax },
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
type="date"
|
||||
label="До"
|
||||
size="small"
|
||||
value={dateTo}
|
||||
onChange={(e) => setDateTo(e.target.value)}
|
||||
slotProps={{ inputLabel: { shrink: true } }}
|
||||
onChange={(e) => handleDateToChange(e.target.value)}
|
||||
slotProps={{
|
||||
inputLabel: { shrink: true },
|
||||
htmlInput: { min: dateToMin },
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="outlined"
|
||||
|
||||
@@ -75,6 +75,8 @@ type RowData = {
|
||||
device_uuid: string | null;
|
||||
current_snapshot_uuid: string | null;
|
||||
snapshot_update_blocked: boolean;
|
||||
maintenance_mode_on: boolean;
|
||||
demo_mode_enabled: boolean;
|
||||
};
|
||||
|
||||
function getVehicleTypeLabel(vehicle: Vehicle): string {
|
||||
@@ -97,14 +99,16 @@ const transformToRows = (vehicles: Vehicle[]): RowData[] => {
|
||||
type,
|
||||
model,
|
||||
tail_number: vehicle.vehicle.tail_number,
|
||||
online: uuid ? vehicle.device_status?.online ?? false : false,
|
||||
online: uuid ? (vehicle.device_status?.online ?? false) : false,
|
||||
lastUpdate: vehicle.device_status?.last_update ?? null,
|
||||
gps: uuid ? vehicle.device_status?.gps_ok ?? false : false,
|
||||
media: uuid ? vehicle.device_status?.media_service_ok ?? false : false,
|
||||
connection: uuid ? vehicle.device_status?.is_connected ?? false : false,
|
||||
gps: uuid ? (vehicle.device_status?.gps_ok ?? false) : false,
|
||||
media: uuid ? (vehicle.device_status?.media_service_ok ?? false) : false,
|
||||
connection: uuid ? (vehicle.device_status?.is_connected ?? false) : false,
|
||||
device_uuid: uuid ?? null,
|
||||
current_snapshot_uuid: vehicle.vehicle.current_snapshot_uuid ?? null,
|
||||
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,
|
||||
};
|
||||
});
|
||||
};
|
||||
@@ -119,17 +123,29 @@ export const DevicesTable = observer(() => {
|
||||
} = devicesStore;
|
||||
|
||||
const { snapshots, getSnapshots } = snapshotStore;
|
||||
const { getVehicles, vehicles, deleteVehicle } = vehicleStore;
|
||||
const {
|
||||
getVehicles,
|
||||
vehicles,
|
||||
deleteVehicle,
|
||||
setMaintenanceMode,
|
||||
setDemoMode,
|
||||
} = vehicleStore;
|
||||
const navigate = useNavigate();
|
||||
const [selectedIds, setSelectedIds] = useState<number[]>([]);
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [logsModalOpen, setLogsModalOpen] = useState(false);
|
||||
const [logsModalDeviceUuid, setLogsModalDeviceUuid] = useState<string | null>(
|
||||
null
|
||||
null,
|
||||
);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [maintenanceLoadingUuids, setMaintenanceLoadingUuids] = useState<
|
||||
Set<string>
|
||||
>(new Set());
|
||||
const [demoLoadingUuids, setDemoLoadingUuids] = useState<Set<string>>(
|
||||
new Set(),
|
||||
);
|
||||
const [collapsedModels, setCollapsedModels] = useState<Set<string>>(
|
||||
new Set()
|
||||
new Set(),
|
||||
);
|
||||
const [paginationModel, setPaginationModel] = useState({
|
||||
page: 0,
|
||||
@@ -146,7 +162,7 @@ export const DevicesTable = observer(() => {
|
||||
const carriersInSelectedCityIds = new Set(
|
||||
carrierStore.carriers.ru.data
|
||||
.filter((carrier) => carrier.city_id === selectedCityId)
|
||||
.map((carrier) => carrier.id)
|
||||
.map((carrier) => carrier.id),
|
||||
);
|
||||
|
||||
if (carriersInSelectedCityIds.size === 0) {
|
||||
@@ -154,17 +170,17 @@ export const DevicesTable = observer(() => {
|
||||
}
|
||||
|
||||
return vehiclesList.filter((vehicle) =>
|
||||
carriersInSelectedCityIds.has(vehicle.vehicle.carrier_id)
|
||||
carriersInSelectedCityIds.has(vehicle.vehicle.carrier_id),
|
||||
);
|
||||
};
|
||||
|
||||
const filteredVehicles = filterVehiclesBySelectedCity(
|
||||
vehicles.data as Vehicle[]
|
||||
vehicles.data as Vehicle[],
|
||||
);
|
||||
|
||||
const rows = useMemo(
|
||||
() => transformToRows(filteredVehicles),
|
||||
[filteredVehicles]
|
||||
[filteredVehicles],
|
||||
);
|
||||
|
||||
const groupsByModel = useMemo(() => {
|
||||
@@ -202,11 +218,70 @@ export const DevicesTable = observer(() => {
|
||||
(r) =>
|
||||
selectedIds.includes(r.id) &&
|
||||
r.device_uuid != null &&
|
||||
!r.snapshot_update_blocked
|
||||
!r.snapshot_update_blocked,
|
||||
)
|
||||
.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;
|
||||
setMaintenanceLoadingUuids((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(row.device_uuid!);
|
||||
return next;
|
||||
});
|
||||
|
||||
try {
|
||||
await setMaintenanceMode(row.device_uuid, nextEnabled);
|
||||
await getVehicles();
|
||||
await getDevices();
|
||||
toast.success(
|
||||
nextEnabled ? "Устройство отправлено на ТО" : "Режим ТО отключен",
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error toggling maintenance mode for ${row.device_uuid}:`,
|
||||
error,
|
||||
);
|
||||
toast.error("Не удалось изменить режим ТО");
|
||||
} finally {
|
||||
setMaintenanceLoadingUuids((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.delete(row.device_uuid!);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleDemoMode = async (row: RowData) => {
|
||||
if (!row.device_uuid) return;
|
||||
|
||||
const nextEnabled = !row.demo_mode_enabled;
|
||||
setDemoLoadingUuids((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.add(row.device_uuid!);
|
||||
return next;
|
||||
});
|
||||
|
||||
try {
|
||||
await setDemoMode(row.device_uuid, nextEnabled);
|
||||
await getVehicles();
|
||||
await getDevices();
|
||||
toast.success(nextEnabled ? "Демо-режим включен" : "Демо-режим отключен");
|
||||
} catch (error) {
|
||||
console.error(`Error toggling demo mode for ${row.device_uuid}:`, error);
|
||||
toast.error("Не удалось изменить демо-режим");
|
||||
} finally {
|
||||
setDemoLoadingUuids((prev) => {
|
||||
const next = new Set(prev);
|
||||
next.delete(row.device_uuid!);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const columns: GridColDef[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
@@ -220,7 +295,7 @@ export const DevicesTable = observer(() => {
|
||||
field: "tail_number",
|
||||
headerName: "Бортовой номер",
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
minWidth: 90,
|
||||
filterable: true,
|
||||
},
|
||||
{
|
||||
@@ -279,6 +354,65 @@ export const DevicesTable = observer(() => {
|
||||
</Box>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: "maintenance_mode_on",
|
||||
headerName: "Режим ТО",
|
||||
width: 90,
|
||||
align: "center",
|
||||
headerAlign: "center",
|
||||
type: "boolean",
|
||||
filterable: true,
|
||||
renderCell: (params: GridRenderCellParams) => {
|
||||
const rowData = params.row as RowData;
|
||||
const isMaintenanceLoading =
|
||||
!!rowData.device_uuid &&
|
||||
maintenanceLoadingUuids.has(rowData.device_uuid);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{ display: "flex", justifyContent: "center", width: "100%" }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Checkbox
|
||||
checked={rowData.maintenance_mode_on}
|
||||
disabled={!rowData.device_uuid || isMaintenanceLoading}
|
||||
size="small"
|
||||
sx={{ p: 0 }}
|
||||
onChange={() => handleToggleMaintenanceMode(rowData)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "demo_mode_enabled",
|
||||
headerName: "Режим Демо",
|
||||
width: 120,
|
||||
align: "center",
|
||||
headerAlign: "center",
|
||||
type: "boolean",
|
||||
filterable: true,
|
||||
renderCell: (params: GridRenderCellParams) => {
|
||||
const rowData = params.row as RowData;
|
||||
const isDemoLoading =
|
||||
!!rowData.device_uuid && demoLoadingUuids.has(rowData.device_uuid);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{ display: "flex", justifyContent: "center", width: "100%" }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Checkbox
|
||||
checked={rowData.demo_mode_enabled}
|
||||
disabled={!rowData.device_uuid || isDemoLoading}
|
||||
size="small"
|
||||
sx={{ p: 0 }}
|
||||
onChange={() => handleToggleDemoMode(rowData)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "lastUpdate",
|
||||
headerName: "Обновлено",
|
||||
@@ -338,7 +472,7 @@ export const DevicesTable = observer(() => {
|
||||
setSelectedDevice(row.device_uuid);
|
||||
try {
|
||||
await authInstance.post(
|
||||
`/devices/${row.device_uuid}/request-status`
|
||||
`/devices/${row.device_uuid}/request-status`,
|
||||
);
|
||||
await getVehicles();
|
||||
await getDevices();
|
||||
@@ -346,7 +480,7 @@ export const DevicesTable = observer(() => {
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error requesting status for device ${row.device_uuid}:`,
|
||||
error
|
||||
error,
|
||||
);
|
||||
toast.error("Ошибка сервера");
|
||||
}
|
||||
@@ -356,7 +490,7 @@ export const DevicesTable = observer(() => {
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "20px",
|
||||
gap: "8px",
|
||||
height: "100%",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
@@ -421,7 +555,13 @@ export const DevicesTable = observer(() => {
|
||||
snapshots,
|
||||
setLogsModalDeviceUuid,
|
||||
setLogsModalOpen,
|
||||
]
|
||||
maintenanceLoadingUuids,
|
||||
demoLoadingUuids,
|
||||
setMaintenanceMode,
|
||||
setDemoMode,
|
||||
handleToggleMaintenanceMode,
|
||||
handleToggleDemoMode,
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -452,7 +592,7 @@ export const DevicesTable = observer(() => {
|
||||
selectedDeviceUuids.length - selectedDeviceUuidsAllowed.length;
|
||||
if (blockedCount > 0) {
|
||||
toast.info(
|
||||
`Обновление ПО не отправлено на ${blockedCount} устройств (блокировка)`
|
||||
`Обновление ПО не отправлено на ${blockedCount} устройств (блокировка)`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -460,7 +600,7 @@ export const DevicesTable = observer(() => {
|
||||
try {
|
||||
await authInstance.post(
|
||||
`/devices/${deviceUuid}/force-snapshot-update`,
|
||||
{ snapshot_id: snapshotId }
|
||||
{ snapshot_id: snapshotId },
|
||||
);
|
||||
toast.success("Обновление ПО отправлено на устройство");
|
||||
} catch (error) {
|
||||
@@ -572,7 +712,7 @@ export const DevicesTable = observer(() => {
|
||||
const isCollapsed = collapsedModels.has(groupModel);
|
||||
const groupRowIds = groupRows.map((r) => r.id);
|
||||
const selectedInGroup = selectedIds.filter((id) =>
|
||||
groupRowIds.includes(id)
|
||||
groupRowIds.includes(id),
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -612,7 +752,7 @@ export const DevicesTable = observer(() => {
|
||||
pageSizeOptions={[50]}
|
||||
onRowSelectionModelChange={
|
||||
createSelectionHandler(groupRowIds) as (
|
||||
ids: unknown
|
||||
ids: unknown,
|
||||
) => void
|
||||
}
|
||||
rowSelectionModel={{
|
||||
|
||||
Reference in New Issue
Block a user