feat: add search for list pages
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
||||
import { ruRU } from "@mui/x-data-grid/locales";
|
||||
import { authStore, articlesStore, languageStore } from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { authStore, articlesStore, languageStore, SearchInput } from "@shared";
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Trash2, Eye, Minus } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@@ -22,6 +22,7 @@ export const ArticleListPage = observer(() => {
|
||||
pageSize: 50,
|
||||
});
|
||||
const canWriteArticles = authStore.canWrite("sights");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchArticles = async () => {
|
||||
@@ -72,11 +73,16 @@ export const ArticleListPage = observer(() => {
|
||||
},
|
||||
];
|
||||
|
||||
const rows = articleList[language].data.map((article) => ({
|
||||
id: article.id,
|
||||
heading: article.heading,
|
||||
body: article.body,
|
||||
}));
|
||||
const rows = useMemo(() => {
|
||||
const query = searchQuery.toLowerCase();
|
||||
return articleList[language].data
|
||||
.filter((article) => !query || (article.heading ?? "").toLowerCase().includes(query))
|
||||
.map((article) => ({
|
||||
id: article.id,
|
||||
heading: article.heading,
|
||||
body: article.body,
|
||||
}));
|
||||
}, [articleList[language].data, searchQuery]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -99,6 +105,8 @@ export const ArticleListPage = observer(() => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<SearchInput value={searchQuery} onChange={setSearchQuery} />
|
||||
|
||||
<div className="w-full">
|
||||
<DataGrid
|
||||
rows={rows}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
||||
import { ruRU } from "@mui/x-data-grid/locales";
|
||||
import { authStore, carrierStore, cityStore, languageStore } from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { authStore, carrierStore, cityStore, languageStore, SearchInput } from "@shared";
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Pencil, Trash2, Minus } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@@ -22,6 +22,7 @@ export const CarrierListPage = observer(() => {
|
||||
});
|
||||
const { language } = languageStore;
|
||||
const canReadCities = authStore.canRead("cities");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
@@ -119,16 +120,23 @@ export const CarrierListPage = observer(() => {
|
||||
|
||||
const canWriteCarriers = authStore.canWrite("carriers");
|
||||
|
||||
const rows = carriers[language].data
|
||||
?.filter((carrier) =>
|
||||
!allowedCityIds || allowedCityIds.includes(carrier.city_id),
|
||||
)
|
||||
.map((carrier) => ({
|
||||
id: carrier.id,
|
||||
full_name: carrier.full_name,
|
||||
short_name: carrier.short_name,
|
||||
city_id: carrier.city_id,
|
||||
}));
|
||||
const rows = useMemo(() => {
|
||||
const query = searchQuery.toLowerCase();
|
||||
return (carriers[language].data ?? [])
|
||||
.filter((carrier) => !allowedCityIds || allowedCityIds.includes(carrier.city_id))
|
||||
.filter(
|
||||
(carrier) =>
|
||||
!query ||
|
||||
(carrier.full_name ?? "").toLowerCase().includes(query) ||
|
||||
(carrier.short_name ?? "").toLowerCase().includes(query)
|
||||
)
|
||||
.map((carrier) => ({
|
||||
id: carrier.id,
|
||||
full_name: carrier.full_name,
|
||||
short_name: carrier.short_name,
|
||||
city_id: carrier.city_id,
|
||||
}));
|
||||
}, [carriers[language].data, searchQuery, allowedCityIds]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -153,6 +161,8 @@ export const CarrierListPage = observer(() => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<SearchInput value={searchQuery} onChange={setSearchQuery} />
|
||||
|
||||
<DataGrid
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
||||
import { ruRU } from "@mui/x-data-grid/locales";
|
||||
import { authStore, languageStore, cityStore, countryStore } from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { authStore, languageStore, cityStore, countryStore, SearchInput } from "@shared";
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Pencil, Trash2, Minus } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@@ -24,6 +24,7 @@ export const CityListPage = observer(() => {
|
||||
});
|
||||
const { language } = languageStore;
|
||||
const canWriteCities = authStore.canWrite("cities");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
@@ -57,6 +58,18 @@ export const CityListPage = observer(() => {
|
||||
setRows(newRows2 || []);
|
||||
}, [cities, countryStore.countries, language, isLoading]);
|
||||
|
||||
const filteredRows = useMemo(() => {
|
||||
const query = searchQuery.toLowerCase();
|
||||
if (!query) return rows;
|
||||
return rows.filter((row) => {
|
||||
const cityName = (row.name ?? "").toLowerCase();
|
||||
const countryName = (
|
||||
countryStore.countries[language]?.data?.find((c) => c.code === row.country)?.name ?? ""
|
||||
).toLowerCase();
|
||||
return cityName.includes(query) || countryName.includes(query);
|
||||
});
|
||||
}, [rows, searchQuery, countryStore.countries, language]);
|
||||
|
||||
const columns: GridColDef[] = [
|
||||
{
|
||||
field: "country",
|
||||
@@ -142,8 +155,10 @@ export const CityListPage = observer(() => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<SearchInput value={searchQuery} onChange={setSearchQuery} />
|
||||
|
||||
<DataGrid
|
||||
rows={rows}
|
||||
rows={filteredRows}
|
||||
columns={columns}
|
||||
checkboxSelection={canWriteCities}
|
||||
disableRowSelectionExcludeModel
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
||||
import { ruRU } from "@mui/x-data-grid/locales";
|
||||
import { authStore, countryStore, languageStore } from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { authStore, countryStore, languageStore, SearchInput } from "@shared";
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Trash2, Minus } from "lucide-react";
|
||||
|
||||
@@ -22,6 +22,7 @@ export const CountryListPage = observer(() => {
|
||||
});
|
||||
const { language } = languageStore;
|
||||
const canWriteCountries = authStore.canWrite("countries");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCountries = async () => {
|
||||
@@ -72,11 +73,16 @@ export const CountryListPage = observer(() => {
|
||||
}] : []),
|
||||
];
|
||||
|
||||
const rows = countries[language]?.data.map((country) => ({
|
||||
id: country.code,
|
||||
code: country.code,
|
||||
name: country.name,
|
||||
}));
|
||||
const rows = useMemo(() => {
|
||||
const query = searchQuery.toLowerCase();
|
||||
return (countries[language]?.data ?? [])
|
||||
.filter((country) => !query || (country.name ?? "").toLowerCase().includes(query))
|
||||
.map((country) => ({
|
||||
id: country.code,
|
||||
code: country.code,
|
||||
name: country.name,
|
||||
}));
|
||||
}, [countries[language]?.data, searchQuery]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -102,8 +108,10 @@ export const CountryListPage = observer(() => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<SearchInput value={searchQuery} onChange={setSearchQuery} />
|
||||
|
||||
<DataGrid
|
||||
rows={rows || []}
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
checkboxSelection={canWriteCountries}
|
||||
disableRowSelectionExcludeModel
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
||||
import { ruRU } from "@mui/x-data-grid/locales";
|
||||
import { authStore, languageStore, MEDIA_TYPE_LABELS, mediaStore } from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { authStore, languageStore, MEDIA_TYPE_LABELS, mediaStore, SearchInput } from "@shared";
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Eye, Trash2, Minus } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@@ -22,6 +22,7 @@ export const MediaListPage = observer(() => {
|
||||
});
|
||||
const { language } = languageStore;
|
||||
const canWriteMedia = authStore.canWrite("sights");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchMedia = async () => {
|
||||
@@ -95,15 +96,22 @@ export const MediaListPage = observer(() => {
|
||||
},
|
||||
];
|
||||
|
||||
const rows = media.map((media) => ({
|
||||
id: media.id,
|
||||
media_name: media.media_name,
|
||||
media_type: media.media_type,
|
||||
}));
|
||||
const rows = useMemo(() => {
|
||||
const query = searchQuery.toLowerCase();
|
||||
return media
|
||||
.filter((item) => !query || (item.media_name ?? "").toLowerCase().includes(query))
|
||||
.map((item) => ({
|
||||
id: item.id,
|
||||
media_name: item.media_name,
|
||||
media_type: item.media_type,
|
||||
}));
|
||||
}, [media, searchQuery]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full">
|
||||
<SearchInput value={searchQuery} onChange={setSearchQuery} />
|
||||
|
||||
{canWriteMedia && ids.length > 0 && (
|
||||
<div className="flex justify-end mb-5 duration-300">
|
||||
<button
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
||||
import { ruRU } from "@mui/x-data-grid/locales";
|
||||
import { authStore, carrierStore, languageStore, routeStore, selectedCityStore } from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { authStore, carrierStore, languageStore, routeStore, selectedCityStore, SearchInput } from "@shared";
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Map, Pencil, Trash2, Minus } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@@ -28,6 +28,7 @@ export const RouteListPage = observer(() => {
|
||||
authStore.canWrite("sights") &&
|
||||
authStore.canWrite("routes");
|
||||
const canShowActionsColumn = canWriteRoutes || canShowRoutePreview;
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
@@ -146,26 +147,33 @@ export const RouteListPage = observer(() => {
|
||||
}] : []),
|
||||
];
|
||||
|
||||
const filteredRoutes = () => {
|
||||
const rows = useMemo(() => {
|
||||
const { selectedCityId } = selectedCityStore;
|
||||
if (!selectedCityId) {
|
||||
return routes.data;
|
||||
const query = searchQuery.toLowerCase();
|
||||
let filtered = routes.data;
|
||||
if (selectedCityId) {
|
||||
const cityCarrierIds = new Set(
|
||||
carriers["ru"].data
|
||||
.filter((c) => c.city_id === selectedCityId)
|
||||
.map((c) => c.id)
|
||||
);
|
||||
filtered = filtered.filter((route) => cityCarrierIds.has(route.carrier_id));
|
||||
}
|
||||
const cityCarrierIds = new Set(
|
||||
carriers["ru"].data
|
||||
.filter((c) => c.city_id === selectedCityId)
|
||||
.map((c) => c.id)
|
||||
);
|
||||
return routes.data.filter((route) => cityCarrierIds.has(route.carrier_id));
|
||||
};
|
||||
|
||||
const rows = filteredRoutes().map((route) => ({
|
||||
id: route.id,
|
||||
carrier_id: route.carrier_id,
|
||||
route_number: route.route_number,
|
||||
route_direction: route.route_direction ? "Прямой" : "Обратный",
|
||||
route_name: route.route_name,
|
||||
}));
|
||||
return filtered
|
||||
.filter(
|
||||
(route) =>
|
||||
!query ||
|
||||
(route.route_name ?? "").toLowerCase().includes(query) ||
|
||||
String(route.route_number ?? "").toLowerCase().includes(query)
|
||||
)
|
||||
.map((route) => ({
|
||||
id: route.id,
|
||||
carrier_id: route.carrier_id,
|
||||
route_number: route.route_number,
|
||||
route_direction: route.route_direction ? "Прямой" : "Обратный",
|
||||
route_name: route.route_name,
|
||||
}));
|
||||
}, [routes.data, carriers["ru"].data, selectedCityStore.selectedCityId, searchQuery]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -191,6 +199,8 @@ export const RouteListPage = observer(() => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<SearchInput value={searchQuery} onChange={setSearchQuery} />
|
||||
|
||||
<DataGrid
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
languageStore,
|
||||
sightsStore,
|
||||
selectedCityStore,
|
||||
SearchInput,
|
||||
} from "@shared";
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
@@ -28,6 +29,7 @@ export const SightListPage = observer(() => {
|
||||
});
|
||||
const { language } = languageStore;
|
||||
const canReadCities = authStore.canRead("cities");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSights = async () => {
|
||||
@@ -120,13 +122,16 @@ export const SightListPage = observer(() => {
|
||||
});
|
||||
}, [sights, selectedCityStore.selectedCityId, canReadCities, authStore.meCities]);
|
||||
|
||||
const canWriteSights = authStore.canWrite("sights");
|
||||
const query = searchQuery.toLowerCase();
|
||||
const rows = filteredSights
|
||||
.filter((sight: any) => !query || (sight.name ?? "").toLowerCase().includes(query))
|
||||
.map((sight) => ({
|
||||
id: sight.id,
|
||||
name: sight.name,
|
||||
city_id: sight.city_id,
|
||||
}));
|
||||
|
||||
const rows = filteredSights.map((sight) => ({
|
||||
id: sight.id,
|
||||
name: sight.name,
|
||||
city_id: sight.city_id,
|
||||
}));
|
||||
const canWriteSights = authStore.canWrite("sights");
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -155,6 +160,8 @@ export const SightListPage = observer(() => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<SearchInput value={searchQuery} onChange={setSearchQuery} />
|
||||
|
||||
<DataGrid
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
||||
import { ruRU } from "@mui/x-data-grid/locales";
|
||||
import { authStore, languageStore, snapshotStore } from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { authStore, languageStore, snapshotStore, SearchInput } from "@shared";
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { DatabaseBackup, Trash2 } from "lucide-react";
|
||||
import { CreateButton, DeleteModal, SnapshotRestore } from "@widgets";
|
||||
@@ -19,6 +19,7 @@ export const SnapshotListPage = observer(() => {
|
||||
const { language } = languageStore;
|
||||
const [isRestoreModalOpen, setIsRestoreModalOpen] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [paginationModel, setPaginationModel] = useState({
|
||||
page: 0,
|
||||
pageSize: 50,
|
||||
@@ -89,22 +90,35 @@ export const SnapshotListPage = observer(() => {
|
||||
}] : []),
|
||||
];
|
||||
|
||||
const rows = snapshots.map((snapshot) => ({
|
||||
id: snapshot.ID,
|
||||
name: snapshot.Name,
|
||||
parent: snapshots.find((s) => s.ID === snapshot.ParentID)?.Name,
|
||||
created_at: formatCreationTime(snapshot.CreationTime),
|
||||
}));
|
||||
const rows = useMemo(() => {
|
||||
const query = searchQuery.toLowerCase();
|
||||
return snapshots
|
||||
.filter(
|
||||
(snapshot) =>
|
||||
!query ||
|
||||
(snapshot.Name ?? "").toLowerCase().includes(query) ||
|
||||
(snapshots.find((s) => s.ID === snapshot.ParentID)?.Name ?? "").toLowerCase().includes(query)
|
||||
)
|
||||
.map((snapshot) => ({
|
||||
id: snapshot.ID,
|
||||
name: snapshot.Name,
|
||||
parent: snapshots.find((s) => s.ID === snapshot.ParentID)?.Name,
|
||||
created_at: formatCreationTime(snapshot.CreationTime),
|
||||
}));
|
||||
}, [snapshots, searchQuery]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ width: "100%" }}>
|
||||
<div className="flex justify-between items-center mb-10">
|
||||
<h1 className="text-2xl ">Экспорт Медиа</h1>
|
||||
|
||||
{canCreateSnapshot && (
|
||||
<CreateButton label="Создать экспорт медиа" path="/snapshot/create" />
|
||||
)}
|
||||
</div>
|
||||
<SearchInput value={searchQuery} onChange={setSearchQuery} />
|
||||
|
||||
<DataGrid
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
|
||||
@@ -5,8 +5,9 @@ import {
|
||||
languageStore,
|
||||
stationsStore,
|
||||
selectedCityStore,
|
||||
SearchInput,
|
||||
} from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Pencil, Trash2, Minus, Route } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@@ -36,6 +37,7 @@ export const StationListPage = observer(() => {
|
||||
});
|
||||
const { language } = languageStore;
|
||||
const canWriteStations = authStore.canWrite("stations");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchStations = async () => {
|
||||
@@ -121,21 +123,23 @@ export const StationListPage = observer(() => {
|
||||
},
|
||||
];
|
||||
|
||||
const filteredStations = () => {
|
||||
const rows = useMemo(() => {
|
||||
const { selectedCityId } = selectedCityStore;
|
||||
if (!selectedCityId) {
|
||||
return stationLists[language].data;
|
||||
}
|
||||
return stationLists[language].data.filter(
|
||||
(station: any) => station.city_id === selectedCityId
|
||||
);
|
||||
};
|
||||
|
||||
const rows = filteredStations().map((station: any) => ({
|
||||
id: station.id,
|
||||
name: station.name,
|
||||
description: station.description,
|
||||
}));
|
||||
const query = searchQuery.toLowerCase();
|
||||
return stationLists[language].data
|
||||
.filter((station: any) => !selectedCityId || station.city_id === selectedCityId)
|
||||
.filter(
|
||||
(station: any) =>
|
||||
!query ||
|
||||
(station.name ?? "").toLowerCase().includes(query) ||
|
||||
(station.description ?? "").toLowerCase().includes(query)
|
||||
)
|
||||
.map((station: any) => ({
|
||||
id: station.id,
|
||||
name: station.name,
|
||||
description: station.description,
|
||||
}));
|
||||
}, [stationLists[language].data, selectedCityStore.selectedCityId, searchQuery]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -161,6 +165,8 @@ export const StationListPage = observer(() => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<SearchInput value={searchQuery} onChange={setSearchQuery} />
|
||||
|
||||
<DataGrid
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
||||
import { ruRU } from "@mui/x-data-grid/locales";
|
||||
import { authStore, userStore } from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { authStore, userStore, SearchInput } from "@shared";
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Pencil, Trash2, Minus } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@@ -21,6 +21,7 @@ export const UserListPage = observer(() => {
|
||||
pageSize: 50,
|
||||
});
|
||||
const canWriteUsers = authStore.canWrite("users");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUsers = async () => {
|
||||
@@ -107,12 +108,21 @@ export const UserListPage = observer(() => {
|
||||
}] : []),
|
||||
];
|
||||
|
||||
const rows = users.data?.map((user) => ({
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
is_admin: user.is_admin || (user.roles ?? []).includes("admin"),
|
||||
name: user.name,
|
||||
}));
|
||||
const rows = useMemo(() => {
|
||||
const query = searchQuery.toLowerCase();
|
||||
return (users.data ?? [])
|
||||
.filter((user) =>
|
||||
!query ||
|
||||
(user.name ?? "").toLowerCase().includes(query) ||
|
||||
(user.email ?? "").toLowerCase().includes(query)
|
||||
)
|
||||
.map((user) => ({
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
is_admin: user.is_admin || (user.roles ?? []).includes("admin"),
|
||||
name: user.name,
|
||||
}));
|
||||
}, [users.data, searchQuery]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -136,6 +146,8 @@ export const UserListPage = observer(() => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<SearchInput value={searchQuery} onChange={setSearchQuery} />
|
||||
|
||||
<DataGrid
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
||||
import { ruRU } from "@mui/x-data-grid/locales";
|
||||
import { authStore, carrierStore, languageStore, vehicleStore } from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { authStore, carrierStore, languageStore, vehicleStore, SearchInput } from "@shared";
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Eye, Pencil, Trash2, Minus } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@@ -24,6 +24,7 @@ export const VehicleListPage = observer(() => {
|
||||
});
|
||||
const { language } = languageStore;
|
||||
const canWriteVehicles = authStore.canWrite("devices");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
@@ -136,27 +137,40 @@ export const VehicleListPage = observer(() => {
|
||||
},
|
||||
];
|
||||
|
||||
const rows = vehicles.data?.map((vehicle) => ({
|
||||
id: vehicle.vehicle.id,
|
||||
tail_number: vehicle.vehicle.tail_number,
|
||||
type: vehicle.vehicle.type,
|
||||
carrier: vehicle.vehicle.carrier,
|
||||
city: carriers[language].data?.find(
|
||||
(carrier) => carrier.id === vehicle.vehicle.carrier_id
|
||||
)?.city,
|
||||
}));
|
||||
const rows = useMemo(() => {
|
||||
const query = searchQuery.toLowerCase();
|
||||
return (vehicles.data ?? [])
|
||||
.filter(
|
||||
(vehicle) =>
|
||||
!query ||
|
||||
(vehicle.vehicle.tail_number ?? "").toLowerCase().includes(query) ||
|
||||
(vehicle.vehicle.carrier ?? "").toLowerCase().includes(query)
|
||||
)
|
||||
.map((vehicle) => ({
|
||||
id: vehicle.vehicle.id,
|
||||
tail_number: vehicle.vehicle.tail_number,
|
||||
type: vehicle.vehicle.type,
|
||||
carrier: vehicle.vehicle.carrier,
|
||||
city: carriers[language].data?.find(
|
||||
(carrier) => carrier.id === vehicle.vehicle.carrier_id
|
||||
)?.city,
|
||||
}));
|
||||
}, [vehicles.data, carriers[language].data, searchQuery]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ width: "100%" }}>
|
||||
<div className="flex justify-between items-center mb-10">
|
||||
<h1 className="text-2xl">Транспортные средства</h1>
|
||||
|
||||
<CreateButton
|
||||
label="Создать транспортное средство"
|
||||
path="/vehicle/create"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SearchInput value={searchQuery} onChange={setSearchQuery} />
|
||||
|
||||
{canWriteVehicles && ids.length > 0 && (
|
||||
<div className="flex justify-end mb-5 duration-300">
|
||||
<button
|
||||
|
||||
37
src/shared/ui/SearchInput/index.tsx
Normal file
37
src/shared/ui/SearchInput/index.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Search, X } from "lucide-react";
|
||||
|
||||
interface SearchInputProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export const SearchInput = ({
|
||||
value,
|
||||
onChange,
|
||||
placeholder = "Поиск...",
|
||||
}: SearchInputProps) => {
|
||||
return (
|
||||
<div className="relative mb-4 w-full max-w-sm">
|
||||
<Search
|
||||
size={16}
|
||||
className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 pointer-events-none"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
className="w-full pl-9 pr-8 py-2 border border-gray-300 rounded-md text-sm outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 bg-white"
|
||||
/>
|
||||
{value && (
|
||||
<button
|
||||
onClick={() => onChange("")}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
<X size={14} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -5,3 +5,4 @@ export * from "./CoordinatesInput";
|
||||
export * from "./AnimatedCircleButton";
|
||||
export * from "./LoadingSpinner";
|
||||
export * from "./MultiSelect";
|
||||
export * from "./SearchInput";
|
||||
|
||||
Reference in New Issue
Block a user