feat: Add edit
pages with cache
This commit is contained in:
@ -23,15 +23,15 @@ export const CarrierCreatePage = observer(() => {
|
||||
const [fullName, setFullName] = useState("");
|
||||
const [shortName, setShortName] = useState("");
|
||||
const [cityId, setCityId] = useState<number | null>(null);
|
||||
const [primaryColor, setPrimaryColor] = useState("#000000");
|
||||
const [secondaryColor, setSecondaryColor] = useState("#ffffff");
|
||||
const [accentColor, setAccentColor] = useState("#ff0000");
|
||||
const [main_color, setMainColor] = useState("#000000");
|
||||
const [left_color, setLeftColor] = useState("#ffffff");
|
||||
const [right_color, setRightColor] = useState("#ff0000");
|
||||
const [slogan, setSlogan] = useState("");
|
||||
const [selectedMediaId, setSelectedMediaId] = useState<string | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
cityStore.getCities();
|
||||
cityStore.getCities("ru");
|
||||
mediaStore.getMedia();
|
||||
}, []);
|
||||
|
||||
@ -41,11 +41,11 @@ export const CarrierCreatePage = observer(() => {
|
||||
await carrierStore.createCarrier(
|
||||
fullName,
|
||||
shortName,
|
||||
cityStore.cities.find((c) => c.id === cityId)?.name!,
|
||||
cityStore.cities.ru.find((c) => c.id === cityId)?.name!,
|
||||
cityId!,
|
||||
primaryColor,
|
||||
secondaryColor,
|
||||
accentColor,
|
||||
main_color,
|
||||
left_color,
|
||||
right_color,
|
||||
slogan,
|
||||
selectedMediaId!
|
||||
);
|
||||
@ -60,7 +60,6 @@ export const CarrierCreatePage = observer(() => {
|
||||
|
||||
return (
|
||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||
<LanguageSwitcher />
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
@ -80,7 +79,7 @@ export const CarrierCreatePage = observer(() => {
|
||||
required
|
||||
onChange={(e) => setCityId(e.target.value as number)}
|
||||
>
|
||||
{cityStore.cities.map((city) => (
|
||||
{cityStore.cities.ru.map((city) => (
|
||||
<MenuItem key={city.id} value={city.id}>
|
||||
{city.name}
|
||||
</MenuItem>
|
||||
@ -104,51 +103,55 @@ export const CarrierCreatePage = observer(() => {
|
||||
onChange={(e) => setShortName(e.target.value)}
|
||||
/>
|
||||
|
||||
<div className="w-full flex flex-col gap-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="w-32">Основной цвет:</span>
|
||||
<Box
|
||||
sx={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
backgroundColor: primaryColor,
|
||||
border: "1px solid #ccc",
|
||||
<div className="flex gap-4 w-full ">
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Основной цвет"
|
||||
value={main_color}
|
||||
className="flex-1 w-full"
|
||||
onChange={(e) => setMainColor(e.target.value)}
|
||||
type="color"
|
||||
sx={{
|
||||
"& input": {
|
||||
height: "50px",
|
||||
paddingBlock: "14px",
|
||||
paddingInline: "14px",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
/>
|
||||
<HexColorPicker color={primaryColor} onChange={setPrimaryColor} />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="w-32">Вторичный цвет:</span>
|
||||
<Box
|
||||
sx={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
backgroundColor: secondaryColor,
|
||||
border: "1px solid #ccc",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Цвет левого виджета"
|
||||
value={left_color}
|
||||
className="flex-1 w-full"
|
||||
onChange={(e) => setLeftColor(e.target.value)}
|
||||
type="color"
|
||||
sx={{
|
||||
"& input": {
|
||||
height: "50px",
|
||||
paddingBlock: "14px",
|
||||
paddingInline: "14px",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
/>
|
||||
<HexColorPicker
|
||||
color={secondaryColor}
|
||||
onChange={setSecondaryColor}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="w-32">Акцентный цвет:</span>
|
||||
<Box
|
||||
sx={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
backgroundColor: accentColor,
|
||||
border: "1px solid #ccc",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Цвет правого виджета"
|
||||
value={right_color}
|
||||
className="flex-1 w-full"
|
||||
onChange={(e) => setRightColor(e.target.value)}
|
||||
type="color"
|
||||
sx={{
|
||||
"& input": {
|
||||
height: "50px",
|
||||
paddingBlock: "14px",
|
||||
paddingInline: "14px",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
/>
|
||||
<HexColorPicker color={accentColor} onChange={setAccentColor} />
|
||||
</div>
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TextField
|
||||
@ -167,11 +170,13 @@ export const CarrierCreatePage = observer(() => {
|
||||
required
|
||||
onChange={(e) => setSelectedMediaId(e.target.value as string)}
|
||||
>
|
||||
{mediaStore.media.map((media) => (
|
||||
<MenuItem key={media.id} value={media.id}>
|
||||
{media.media_name || media.filename}
|
||||
</MenuItem>
|
||||
))}
|
||||
{mediaStore.media
|
||||
.filter((media) => media.media_type === 3)
|
||||
.map((media) => (
|
||||
<MenuItem key={media.id} value={media.id}>
|
||||
{media.media_name || media.filename}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
{selectedMediaId && (
|
||||
|
307
src/pages/Carrier/CarrierEditPage/index.tsx
Normal file
307
src/pages/Carrier/CarrierEditPage/index.tsx
Normal file
@ -0,0 +1,307 @@
|
||||
import {
|
||||
Button,
|
||||
Paper,
|
||||
TextField,
|
||||
Select,
|
||||
MenuItem,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
} from "@mui/material";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { ArrowLeft, Save } from "lucide-react";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
import { carrierStore, cityStore, mediaStore } from "@shared";
|
||||
import { useState, useEffect } from "react";
|
||||
import { MediaViewer } from "@widgets";
|
||||
|
||||
export const CarrierEditPage = observer(() => {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams();
|
||||
const { carrier, getCarrier, setEditCarrierData, editCarrierData } =
|
||||
carrierStore;
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
await getCarrier(Number(id));
|
||||
setEditCarrierData(
|
||||
carrier?.[Number(id)]?.full_name as string,
|
||||
carrier?.[Number(id)]?.short_name as string,
|
||||
carrier?.[Number(id)]?.city as string,
|
||||
carrier?.[Number(id)]?.city_id as number,
|
||||
carrier?.[Number(id)]?.main_color as string,
|
||||
carrier?.[Number(id)]?.left_color as string,
|
||||
carrier?.[Number(id)]?.right_color as string,
|
||||
carrier?.[Number(id)]?.slogan as string,
|
||||
carrier?.[Number(id)]?.logo as string
|
||||
);
|
||||
cityStore.getCities("ru");
|
||||
mediaStore.getMedia();
|
||||
})();
|
||||
}, [id]);
|
||||
|
||||
const handleEdit = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await carrierStore.editCarrier(Number(id));
|
||||
toast.success("Перевозчик успешно обновлен");
|
||||
navigate("/carrier");
|
||||
} catch (error) {
|
||||
toast.error("Ошибка при обновлении перевозчика");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => navigate("/carrier")}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-10 w-full items-end">
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Город</InputLabel>
|
||||
<Select
|
||||
value={editCarrierData.city_id || ""}
|
||||
label="Город"
|
||||
required
|
||||
onChange={(e) =>
|
||||
setEditCarrierData(
|
||||
editCarrierData.full_name,
|
||||
editCarrierData.short_name,
|
||||
editCarrierData.city,
|
||||
Number(e.target.value),
|
||||
editCarrierData.main_color,
|
||||
editCarrierData.left_color,
|
||||
editCarrierData.right_color,
|
||||
editCarrierData.slogan,
|
||||
editCarrierData.logo
|
||||
)
|
||||
}
|
||||
>
|
||||
{cityStore.cities.ru.map((city) => (
|
||||
<MenuItem key={city.id} value={city.id}>
|
||||
{city.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Полное название"
|
||||
value={editCarrierData.full_name}
|
||||
required
|
||||
onChange={(e) =>
|
||||
setEditCarrierData(
|
||||
e.target.value,
|
||||
editCarrierData.short_name,
|
||||
editCarrierData.city,
|
||||
editCarrierData.city_id,
|
||||
editCarrierData.main_color,
|
||||
editCarrierData.left_color,
|
||||
editCarrierData.right_color,
|
||||
editCarrierData.slogan,
|
||||
editCarrierData.logo
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Короткое название"
|
||||
value={editCarrierData.short_name}
|
||||
required
|
||||
onChange={(e) =>
|
||||
setEditCarrierData(
|
||||
editCarrierData.full_name,
|
||||
e.target.value,
|
||||
editCarrierData.city,
|
||||
editCarrierData.city_id,
|
||||
editCarrierData.main_color,
|
||||
editCarrierData.left_color,
|
||||
editCarrierData.right_color,
|
||||
editCarrierData.slogan,
|
||||
editCarrierData.logo
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="flex gap-4 w-full">
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Основной цвет"
|
||||
value={editCarrierData.main_color}
|
||||
className="flex-1 w-full"
|
||||
onChange={(e) =>
|
||||
setEditCarrierData(
|
||||
editCarrierData.full_name,
|
||||
editCarrierData.short_name,
|
||||
editCarrierData.city,
|
||||
editCarrierData.city_id,
|
||||
e.target.value,
|
||||
editCarrierData.left_color,
|
||||
editCarrierData.right_color,
|
||||
editCarrierData.slogan,
|
||||
editCarrierData.logo
|
||||
)
|
||||
}
|
||||
type="color"
|
||||
sx={{
|
||||
"& input": {
|
||||
height: "50px",
|
||||
paddingBlock: "14px",
|
||||
paddingInline: "14px",
|
||||
cursor: "pointer",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Цвет левого виджета"
|
||||
value={editCarrierData.left_color}
|
||||
className="flex-1 w-full"
|
||||
onChange={(e) =>
|
||||
setEditCarrierData(
|
||||
editCarrierData.full_name,
|
||||
editCarrierData.short_name,
|
||||
editCarrierData.city,
|
||||
editCarrierData.city_id,
|
||||
editCarrierData.main_color,
|
||||
e.target.value,
|
||||
editCarrierData.right_color,
|
||||
editCarrierData.slogan,
|
||||
editCarrierData.logo
|
||||
)
|
||||
}
|
||||
type="color"
|
||||
sx={{
|
||||
"& input": {
|
||||
height: "50px",
|
||||
paddingBlock: "14px",
|
||||
paddingInline: "14px",
|
||||
cursor: "pointer",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Цвет правого виджета"
|
||||
value={editCarrierData.right_color}
|
||||
className="flex-1 w-full"
|
||||
onChange={(e) =>
|
||||
setEditCarrierData(
|
||||
editCarrierData.full_name,
|
||||
editCarrierData.short_name,
|
||||
editCarrierData.city,
|
||||
editCarrierData.city_id,
|
||||
editCarrierData.main_color,
|
||||
editCarrierData.left_color,
|
||||
e.target.value,
|
||||
editCarrierData.slogan,
|
||||
editCarrierData.logo
|
||||
)
|
||||
}
|
||||
type="color"
|
||||
sx={{
|
||||
"& input": {
|
||||
height: "50px",
|
||||
paddingBlock: "14px",
|
||||
paddingInline: "14px",
|
||||
cursor: "pointer",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Слоган"
|
||||
value={editCarrierData.slogan}
|
||||
onChange={(e) =>
|
||||
setEditCarrierData(
|
||||
editCarrierData.full_name,
|
||||
editCarrierData.short_name,
|
||||
editCarrierData.city,
|
||||
editCarrierData.city_id,
|
||||
editCarrierData.main_color,
|
||||
editCarrierData.left_color,
|
||||
editCarrierData.right_color,
|
||||
e.target.value,
|
||||
editCarrierData.logo
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="w-full flex flex-col gap-4">
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Логотип</InputLabel>
|
||||
<Select
|
||||
value={editCarrierData.logo || ""}
|
||||
label="Логотип"
|
||||
required
|
||||
onChange={(e) =>
|
||||
setEditCarrierData(
|
||||
editCarrierData.full_name,
|
||||
editCarrierData.short_name,
|
||||
editCarrierData.city,
|
||||
editCarrierData.city_id,
|
||||
editCarrierData.main_color,
|
||||
editCarrierData.left_color,
|
||||
editCarrierData.right_color,
|
||||
editCarrierData.slogan,
|
||||
e.target.value as string
|
||||
)
|
||||
}
|
||||
>
|
||||
{mediaStore.media
|
||||
.filter((media) => media.media_type === 3)
|
||||
.map((media) => (
|
||||
<MenuItem key={media.id} value={media.id}>
|
||||
{media.media_name || media.filename}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
{editCarrierData.logo && (
|
||||
<div className="w-32 h-32">
|
||||
<MediaViewer
|
||||
media={{ id: editCarrierData.logo, media_type: 1 }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
className="w-min flex gap-2 items-center"
|
||||
startIcon={<Save size={20} />}
|
||||
onClick={handleEdit}
|
||||
disabled={
|
||||
isLoading ||
|
||||
!editCarrierData.full_name ||
|
||||
!editCarrierData.short_name ||
|
||||
!editCarrierData.city_id ||
|
||||
!editCarrierData.logo
|
||||
}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Loader2 size={20} className="animate-spin" />
|
||||
) : (
|
||||
"Обновить"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</Paper>
|
||||
);
|
||||
});
|
@ -1,21 +1,20 @@
|
||||
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
||||
import { carrierStore, languageStore } from "@shared";
|
||||
import { carrierStore } from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Eye, Trash2 } from "lucide-react";
|
||||
import { Eye, Pencil, Trash2 } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { DeleteModal, LanguageSwitcher } from "@widgets";
|
||||
import { CreateButton, DeleteModal } from "@widgets";
|
||||
|
||||
export const CarrierListPage = observer(() => {
|
||||
const { carriers, getCarriers, deleteCarrier } = carrierStore;
|
||||
const navigate = useNavigate();
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [rowId, setRowId] = useState<number | null>(null); // Lifted state
|
||||
const { language } = languageStore;
|
||||
|
||||
useEffect(() => {
|
||||
getCarriers();
|
||||
}, [language]);
|
||||
}, []);
|
||||
|
||||
const columns: GridColDef[] = [
|
||||
{
|
||||
@ -37,10 +36,15 @@ export const CarrierListPage = observer(() => {
|
||||
{
|
||||
field: "actions",
|
||||
headerName: "Действия",
|
||||
headerAlign: "center",
|
||||
width: 200,
|
||||
|
||||
renderCell: (params: GridRenderCellParams) => {
|
||||
return (
|
||||
<div className="flex h-full gap-7 justify-center items-center">
|
||||
<button onClick={() => navigate(`/carrier/${params.row.id}/edit`)}>
|
||||
<Pencil size={20} className="text-blue-500" />
|
||||
</button>
|
||||
<button onClick={() => navigate(`/carrier/${params.row.id}`)}>
|
||||
<Eye size={20} className="text-green-500" />
|
||||
</button>
|
||||
@ -67,12 +71,10 @@ export const CarrierListPage = observer(() => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<LanguageSwitcher />
|
||||
|
||||
<div className="w-full">
|
||||
<div className="flex justify-between items-center mb-10">
|
||||
<h1 className="text-2xl">Перевозчики</h1>
|
||||
{/* <CreateButton label="Создать перевозчика" path="/carrier/create" /> */}
|
||||
<CreateButton label="Создать перевозчика" path="/carrier/create" />
|
||||
</div>
|
||||
<DataGrid
|
||||
rows={rows}
|
||||
@ -86,7 +88,7 @@ export const CarrierListPage = observer(() => {
|
||||
open={isDeleteModalOpen}
|
||||
onDelete={async () => {
|
||||
if (rowId) {
|
||||
deleteCarrier(rowId);
|
||||
await deleteCarrier(rowId);
|
||||
}
|
||||
setIsDeleteModalOpen(false);
|
||||
setRowId(null);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Paper } from "@mui/material";
|
||||
import { carrierStore, mediaStore } from "@shared";
|
||||
import { carrierStore, languageStore, mediaStore } from "@shared";
|
||||
import { MediaViewer } from "@widgets";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
@ -8,13 +8,25 @@ import { useNavigate, useParams } from "react-router-dom";
|
||||
|
||||
export const CarrierPreviewPage = observer(() => {
|
||||
const { id } = useParams();
|
||||
const { getCarrier, carrier } = carrierStore;
|
||||
const { getCarrier, carrier, setEditCarrierData } = carrierStore;
|
||||
const { oneMedia, getOneMedia } = mediaStore;
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const carrierResponse = await getCarrier(Number(id));
|
||||
setEditCarrierData(
|
||||
carrierResponse?.full_name as string,
|
||||
carrierResponse?.short_name as string,
|
||||
carrierResponse?.city as string,
|
||||
carrierResponse?.city_id as number,
|
||||
carrierResponse?.main_color as string,
|
||||
carrierResponse?.left_color as string,
|
||||
carrierResponse?.right_color as string,
|
||||
carrierResponse?.slogan as string,
|
||||
carrierResponse?.logo as string
|
||||
);
|
||||
console.log(carrierResponse);
|
||||
await getOneMedia(carrierResponse?.logo as string);
|
||||
})();
|
||||
}, [id]);
|
||||
@ -31,48 +43,30 @@ export const CarrierPreviewPage = observer(() => {
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
</button>
|
||||
{/* <div className="flex gap-2">
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => navigate(`/carrier/${id}/edit`)}
|
||||
startIcon={<Pencil size={20} />}
|
||||
>
|
||||
Редактировать
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={() => navigate(`/carrier/${id}/delete`)}
|
||||
startIcon={<Trash2 size={20} />}
|
||||
>
|
||||
Удалить
|
||||
</Button>
|
||||
</div> */}
|
||||
</div>
|
||||
<div className="flex flex-col gap-10 w-full">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-lg font-bold">Полное имя</h1>
|
||||
<p>{carrier?.full_name}</p>
|
||||
<p>{carrier[Number(id)]?.full_name}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-lg font-bold">Полное имя</h1>
|
||||
<p>{carrier?.full_name}</p>
|
||||
<p>{carrier[Number(id)]?.full_name}</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-lg font-bold">Город</h1>
|
||||
<p>{carrier?.city}</p>
|
||||
<p>{carrier[Number(id)]?.city}</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 ">
|
||||
<h1 className="text-lg font-bold">Основной цвет</h1>
|
||||
<div
|
||||
className="w-min"
|
||||
style={{
|
||||
backgroundColor: `${carrier?.main_color}90`,
|
||||
backgroundColor: `${carrier[Number(id)]?.main_color}90`,
|
||||
}}
|
||||
>
|
||||
{carrier?.main_color}
|
||||
{carrier[Number(id)]?.main_color}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
@ -80,10 +74,10 @@ export const CarrierPreviewPage = observer(() => {
|
||||
<div
|
||||
className="w-min"
|
||||
style={{
|
||||
backgroundColor: `${carrier?.left_color}90`,
|
||||
backgroundColor: `${carrier[Number(id)]?.left_color}90`,
|
||||
}}
|
||||
>
|
||||
{carrier?.left_color}
|
||||
{carrier[Number(id)]?.left_color}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
@ -91,15 +85,15 @@ export const CarrierPreviewPage = observer(() => {
|
||||
<div
|
||||
className="w-min"
|
||||
style={{
|
||||
backgroundColor: `${carrier?.right_color}90`,
|
||||
backgroundColor: `${carrier[Number(id)]?.right_color}90`,
|
||||
}}
|
||||
>
|
||||
{carrier?.right_color}
|
||||
{carrier[Number(id)]?.right_color}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-lg font-bold">Краткое имя</h1>
|
||||
<p>{carrier?.short_name}</p>
|
||||
<p>{carrier[Number(id)]?.short_name}</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-lg font-bold">Логотип</h1>
|
||||
|
@ -1,3 +1,4 @@
|
||||
export * from "./CarrierListPage";
|
||||
export * from "./CarrierPreviewPage";
|
||||
export * from "./CarrierCreatePage";
|
||||
export * from "./CarrierEditPage";
|
||||
|
@ -13,33 +13,31 @@ import { ArrowLeft, Save, ImagePlus } from "lucide-react";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
import { cityStore, countryStore, mediaStore } from "@shared";
|
||||
import { cityStore, countryStore, languageStore, mediaStore } from "@shared";
|
||||
import { useState, useEffect } from "react";
|
||||
import { LanguageSwitcher, MediaViewer } from "@widgets";
|
||||
import { SelectMediaDialog } from "@shared";
|
||||
|
||||
export const CityCreatePage = observer(() => {
|
||||
const navigate = useNavigate();
|
||||
const [name, setName] = useState("");
|
||||
const [countryCode, setCountryCode] = useState("");
|
||||
const [selectedMediaId, setSelectedMediaId] = useState<string | null>(null);
|
||||
const { language } = languageStore;
|
||||
const { createCityData, setCreateCityData } = cityStore;
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isSelectMediaOpen, setIsSelectMediaOpen] = useState(false);
|
||||
const { getCountries } = countryStore;
|
||||
const { getMedia } = mediaStore;
|
||||
|
||||
useEffect(() => {
|
||||
countryStore.getCountries();
|
||||
mediaStore.getMedia();
|
||||
}, []);
|
||||
(async () => {
|
||||
await getCountries(language);
|
||||
await getMedia();
|
||||
})();
|
||||
}, [language]);
|
||||
|
||||
const handleCreate = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await cityStore.createCity(
|
||||
name,
|
||||
countryStore.countries.find((c) => c.code === countryCode)?.name!,
|
||||
countryCode,
|
||||
selectedMediaId!
|
||||
);
|
||||
await cityStore.createCity();
|
||||
toast.success("Город успешно создан");
|
||||
navigate("/city");
|
||||
} catch (error) {
|
||||
@ -55,11 +53,17 @@ export const CityCreatePage = observer(() => {
|
||||
media_name?: string;
|
||||
media_type: number;
|
||||
}) => {
|
||||
setSelectedMediaId(media.id);
|
||||
setCreateCityData(
|
||||
createCityData[language].name,
|
||||
createCityData.country,
|
||||
createCityData.country_code,
|
||||
media.id,
|
||||
language
|
||||
);
|
||||
};
|
||||
|
||||
const selectedMedia = selectedMediaId
|
||||
? mediaStore.media.find((m) => m.id === selectedMediaId)
|
||||
const selectedMedia = createCityData.arms
|
||||
? mediaStore.media.find((m) => m.id === createCityData.arms)
|
||||
: null;
|
||||
|
||||
return (
|
||||
@ -79,20 +83,39 @@ export const CityCreatePage = observer(() => {
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Название города"
|
||||
value={name}
|
||||
value={createCityData[language]?.name || ""}
|
||||
required
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
onChange={(e) =>
|
||||
setCreateCityData(
|
||||
e.target.value,
|
||||
createCityData.country,
|
||||
createCityData.country_code,
|
||||
createCityData.arms,
|
||||
language
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Страна</InputLabel>
|
||||
<Select
|
||||
value={countryCode}
|
||||
value={createCityData.country_code || ""}
|
||||
label="Страна"
|
||||
required
|
||||
onChange={(e) => setCountryCode(e.target.value)}
|
||||
onChange={(e) => {
|
||||
const selectedCountry = countryStore.countries[language]?.find(
|
||||
(country) => country.code === e.target.value
|
||||
);
|
||||
setCreateCityData(
|
||||
createCityData[language].name,
|
||||
selectedCountry?.name || "",
|
||||
e.target.value,
|
||||
createCityData.arms,
|
||||
language
|
||||
);
|
||||
}}
|
||||
>
|
||||
{countryStore.countries.map((country) => (
|
||||
{countryStore.countries[language].map((country) => (
|
||||
<MenuItem key={country.code} value={country.code}>
|
||||
{country.name}
|
||||
</MenuItem>
|
||||
@ -145,7 +168,7 @@ export const CityCreatePage = observer(() => {
|
||||
className="w-min flex gap-2 items-center"
|
||||
startIcon={<Save size={20} />}
|
||||
onClick={handleCreate}
|
||||
disabled={isLoading || !name || !countryCode}
|
||||
disabled={isLoading || !createCityData[language]?.name}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Loader2 size={20} className="animate-spin" />
|
||||
|
207
src/pages/City/CityEditPage/index.tsx
Normal file
207
src/pages/City/CityEditPage/index.tsx
Normal file
@ -0,0 +1,207 @@
|
||||
import {
|
||||
Button,
|
||||
Paper,
|
||||
TextField,
|
||||
Select,
|
||||
MenuItem,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Box,
|
||||
} from "@mui/material";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { ArrowLeft, Save, ImagePlus } from "lucide-react";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
import {
|
||||
cityStore,
|
||||
countryStore,
|
||||
languageStore,
|
||||
mediaStore,
|
||||
CashedCities,
|
||||
} from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { LanguageSwitcher, MediaViewer } from "@widgets";
|
||||
import { SelectMediaDialog } from "@shared";
|
||||
|
||||
export const CityEditPage = observer(() => {
|
||||
const navigate = useNavigate();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isSelectMediaOpen, setIsSelectMediaOpen] = useState(false);
|
||||
const { language } = languageStore;
|
||||
const { id } = useParams();
|
||||
const { editCityData, editCity, getCity, setEditCityData } = cityStore;
|
||||
const { getCountries } = countryStore;
|
||||
const { getMedia, getOneMedia, oneMedia } = mediaStore;
|
||||
|
||||
const handleEdit = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await editCity(id as string);
|
||||
toast.success("Город успешно обновлен");
|
||||
} catch (error) {
|
||||
toast.error("Ошибка при обновлении города");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (id) {
|
||||
const data = await getCity(id as string, language);
|
||||
setEditCityData(
|
||||
data.name,
|
||||
data.country,
|
||||
data.country_code,
|
||||
data.arms,
|
||||
language
|
||||
);
|
||||
await getOneMedia(data.arms as string);
|
||||
await getCountries(language);
|
||||
await getMedia();
|
||||
}
|
||||
})();
|
||||
}, [id, language]);
|
||||
|
||||
const handleMediaSelect = (media: {
|
||||
id: string;
|
||||
filename: string;
|
||||
media_name?: string;
|
||||
media_type: number;
|
||||
}) => {
|
||||
setEditCityData(
|
||||
editCityData[language].name,
|
||||
editCityData.country,
|
||||
editCityData.country_code,
|
||||
media.id,
|
||||
language
|
||||
);
|
||||
};
|
||||
|
||||
const selectedMedia = editCityData.arms
|
||||
? mediaStore.media.find((m) => m.id === editCityData.arms)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||
<LanguageSwitcher />
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => navigate("/city")}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-10 w-full items-end">
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Название"
|
||||
value={editCityData[language].name}
|
||||
required
|
||||
onChange={(e) =>
|
||||
setEditCityData(
|
||||
e.target.value,
|
||||
editCityData.country,
|
||||
editCityData.country_code,
|
||||
editCityData.arms,
|
||||
language
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Страна</InputLabel>
|
||||
<Select
|
||||
value={editCityData.country_code || ""}
|
||||
label="Страна"
|
||||
required
|
||||
onChange={(e) => {
|
||||
const selectedCountry = countryStore.countries[language]?.find(
|
||||
(country) => country.code === e.target.value
|
||||
);
|
||||
setEditCityData(
|
||||
editCityData[language as keyof CashedCities]?.name || "",
|
||||
selectedCountry?.name || "",
|
||||
e.target.value,
|
||||
editCityData.arms,
|
||||
language
|
||||
);
|
||||
}}
|
||||
>
|
||||
{countryStore.countries[language].map((country) => (
|
||||
<MenuItem key={country.code} value={country.code}>
|
||||
{country.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<div className="w-full flex flex-col gap-4">
|
||||
<label className="text-sm text-gray-600">Герб города</label>
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => setIsSelectMediaOpen(true)}
|
||||
startIcon={<ImagePlus size={20} />}
|
||||
>
|
||||
Выбрать герб
|
||||
</Button>
|
||||
{selectedMedia && (
|
||||
<span className="text-sm text-gray-600">
|
||||
{selectedMedia.media_name || selectedMedia.filename}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{selectedMedia && (
|
||||
<Box
|
||||
sx={{
|
||||
width: "200px",
|
||||
height: "200px",
|
||||
border: "1px solid #e0e0e0",
|
||||
borderRadius: "8px",
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<MediaViewer
|
||||
media={{
|
||||
id: selectedMedia.id,
|
||||
media_type: selectedMedia.media_type,
|
||||
filename: selectedMedia.filename,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
className="w-min flex gap-2 items-center"
|
||||
startIcon={<Save size={20} />}
|
||||
onClick={handleEdit}
|
||||
disabled={
|
||||
isLoading || !editCityData[language as keyof CashedCities]?.name
|
||||
}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Loader2 size={20} className="animate-spin" />
|
||||
) : (
|
||||
"Обновить"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<SelectMediaDialog
|
||||
open={isSelectMediaOpen}
|
||||
onClose={() => setIsSelectMediaOpen(false)}
|
||||
onSelectMedia={handleMediaSelect}
|
||||
/>
|
||||
</Paper>
|
||||
);
|
||||
});
|
@ -1,8 +1,8 @@
|
||||
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
||||
import { cityStore, languageStore } from "@shared";
|
||||
import { languageStore, cityStore, CashedCities } from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Eye, Trash2 } from "lucide-react";
|
||||
import { Eye, Pencil, Trash2 } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { CreateButton, DeleteModal, LanguageSwitcher } from "@widgets";
|
||||
|
||||
@ -14,7 +14,7 @@ export const CityListPage = observer(() => {
|
||||
const { language } = languageStore;
|
||||
|
||||
useEffect(() => {
|
||||
getCities();
|
||||
getCities(language);
|
||||
}, [language]);
|
||||
|
||||
const columns: GridColDef[] = [
|
||||
@ -33,10 +33,14 @@ export const CityListPage = observer(() => {
|
||||
headerName: "Действия",
|
||||
align: "center",
|
||||
headerAlign: "center",
|
||||
width: 200,
|
||||
|
||||
renderCell: (params: GridRenderCellParams) => {
|
||||
return (
|
||||
<div className="flex h-full gap-7 justify-center items-center">
|
||||
<button onClick={() => navigate(`/city/${params.row.id}/edit`)}>
|
||||
<Pencil size={20} className="text-blue-500" />
|
||||
</button>
|
||||
<button onClick={() => navigate(`/city/${params.row.id}`)}>
|
||||
<Eye size={20} className="text-green-500" />
|
||||
</button>
|
||||
@ -54,7 +58,7 @@ export const CityListPage = observer(() => {
|
||||
},
|
||||
];
|
||||
|
||||
const rows = cities.map((city) => ({
|
||||
const rows = cities[language].map((city) => ({
|
||||
id: city.id,
|
||||
name: city.name,
|
||||
country: city.country,
|
||||
@ -81,7 +85,7 @@ export const CityListPage = observer(() => {
|
||||
open={isDeleteModalOpen}
|
||||
onDelete={async () => {
|
||||
if (rowId) {
|
||||
deleteCity(rowId);
|
||||
deleteCity(rowId.toString(), language as keyof CashedCities);
|
||||
}
|
||||
setIsDeleteModalOpen(false);
|
||||
setRowId(null);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Paper } from "@mui/material";
|
||||
import { cityStore, mediaStore } from "@shared";
|
||||
import { MediaViewer } from "@widgets";
|
||||
import { cityStore, languageStore, mediaStore } from "@shared";
|
||||
import { LanguageSwitcher, MediaViewer } from "@widgets";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
@ -8,19 +8,30 @@ import { useNavigate, useParams } from "react-router-dom";
|
||||
|
||||
export const CityPreviewPage = observer(() => {
|
||||
const { id } = useParams();
|
||||
const { getCity, city } = cityStore;
|
||||
const { getCity, city, setEditCityData } = cityStore;
|
||||
const { oneMedia, getOneMedia } = mediaStore;
|
||||
const navigate = useNavigate();
|
||||
const { language } = languageStore;
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const cityResponse = await getCity(id as string);
|
||||
await getOneMedia(cityResponse.arms as string);
|
||||
if (id) {
|
||||
const cityResponse = await getCity(id as string, language);
|
||||
setEditCityData(
|
||||
cityResponse.name,
|
||||
cityResponse.country,
|
||||
cityResponse.country_code,
|
||||
cityResponse.arms,
|
||||
language
|
||||
);
|
||||
await getOneMedia(cityResponse.arms as string);
|
||||
}
|
||||
})();
|
||||
}, [id]);
|
||||
}, [id, language]);
|
||||
|
||||
return (
|
||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||
<LanguageSwitcher />
|
||||
<div className="flex justify-between items-center">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
@ -29,36 +40,18 @@ export const CityPreviewPage = observer(() => {
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
</button>
|
||||
{/* <div className="flex gap-2">
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => navigate(`/city/${id}/edit`)}
|
||||
startIcon={<Pencil size={20} />}
|
||||
>
|
||||
Редактировать
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={() => navigate(`/city/${id}/edit`)}
|
||||
startIcon={<Trash2 size={20} />}
|
||||
>
|
||||
Удалить
|
||||
</Button>
|
||||
</div> */}
|
||||
</div>
|
||||
<div className="flex flex-col gap-10 w-full">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-lg font-bold">Название</h1>
|
||||
<p>{city?.name}</p>
|
||||
<p>{city[id!]?.[language]?.name}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-lg font-bold">Страна</h1>
|
||||
<p>{city?.country}</p>
|
||||
<p>{city[id!]?.[language]?.country}</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-2 pb-10">
|
||||
<h1 className="text-lg font-bold">Герб</h1>
|
||||
<div className="w-[300px] h-[200px]">
|
||||
<MediaViewer
|
||||
|
@ -1,3 +1,4 @@
|
||||
export * from "./CityListPage";
|
||||
export * from "./CityPreviewPage";
|
||||
export * from "./CityCreatePage";
|
||||
export * from "./CityEditPage";
|
||||
|
@ -4,20 +4,20 @@ import { ArrowLeft, Save } from "lucide-react";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
import { countryStore } from "@shared";
|
||||
import { countryStore, languageStore } from "@shared";
|
||||
import { useState } from "react";
|
||||
import { LanguageSwitcher } from "@widgets";
|
||||
|
||||
export const CountryCreatePage = observer(() => {
|
||||
const navigate = useNavigate();
|
||||
const [name, setName] = useState("");
|
||||
const [code, setCode] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { language } = languageStore;
|
||||
const { createCountryData, setCountryData, createCountry } = countryStore;
|
||||
|
||||
const handleCreate = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await countryStore.createCountry(code, name);
|
||||
await createCountry();
|
||||
toast.success("Страна успешно создана");
|
||||
navigate("/country");
|
||||
} catch (error) {
|
||||
@ -44,16 +44,24 @@ export const CountryCreatePage = observer(() => {
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Код страны"
|
||||
value={code}
|
||||
value={createCountryData.code}
|
||||
required
|
||||
onChange={(e) => setCode(e.target.value)}
|
||||
onChange={(e) =>
|
||||
setCountryData(
|
||||
e.target.value,
|
||||
createCountryData[language].name,
|
||||
language
|
||||
)
|
||||
}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Название"
|
||||
value={name}
|
||||
value={createCountryData[language].name}
|
||||
required
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
onChange={(e) =>
|
||||
setCountryData(createCountryData.code, e.target.value, language)
|
||||
}
|
||||
/>
|
||||
|
||||
<Button
|
||||
@ -61,7 +69,7 @@ export const CountryCreatePage = observer(() => {
|
||||
className="w-min flex gap-2 items-center"
|
||||
startIcon={<Save size={20} />}
|
||||
onClick={handleCreate}
|
||||
disabled={isLoading || !name || !code}
|
||||
disabled={isLoading || !createCountryData[language].name}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Loader2 size={20} className="animate-spin" />
|
||||
|
87
src/pages/Country/CountryEditPage/index.tsx
Normal file
87
src/pages/Country/CountryEditPage/index.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import { Button, Paper, TextField } from "@mui/material";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { ArrowLeft, Save } from "lucide-react";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
import { countryStore, languageStore } from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { LanguageSwitcher } from "@widgets";
|
||||
|
||||
export const CountryEditPage = observer(() => {
|
||||
const navigate = useNavigate();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { language } = languageStore;
|
||||
const { id } = useParams();
|
||||
const { editCountryData, editCountry, getCountry, setEditCountryData } =
|
||||
countryStore;
|
||||
|
||||
const handleEdit = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await editCountry(id as string);
|
||||
toast.success("Страна успешно обновлена");
|
||||
} catch (error) {
|
||||
toast.error("Ошибка при обновлении страны");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (id) {
|
||||
const data = await getCountry(id as string, language);
|
||||
setEditCountryData(data.name, language);
|
||||
}
|
||||
})();
|
||||
}, [id, language]);
|
||||
|
||||
return (
|
||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||
<LanguageSwitcher />
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => navigate("/country")}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-10 w-full items-end">
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Код страны"
|
||||
value={id as string}
|
||||
required
|
||||
disabled
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Название"
|
||||
value={editCountryData[language].name}
|
||||
required
|
||||
onChange={(e) =>
|
||||
countryStore.setEditCountryData(e.target.value, language)
|
||||
}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
className="w-min flex gap-2 items-center"
|
||||
startIcon={<Save size={20} />}
|
||||
onClick={handleEdit}
|
||||
disabled={isLoading || !editCountryData[language].name}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Loader2 size={20} className="animate-spin" />
|
||||
) : (
|
||||
"Обновить"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</Paper>
|
||||
);
|
||||
});
|
@ -2,7 +2,7 @@ import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
||||
import { countryStore, languageStore } from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Eye, Trash2 } from "lucide-react";
|
||||
import { Eye, Pencil, Trash2 } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { CreateButton, DeleteModal, LanguageSwitcher } from "@widgets";
|
||||
|
||||
@ -14,7 +14,7 @@ export const CountryListPage = observer(() => {
|
||||
const { language } = languageStore;
|
||||
|
||||
useEffect(() => {
|
||||
getCountries();
|
||||
getCountries(language);
|
||||
}, [language]);
|
||||
|
||||
const columns: GridColDef[] = [
|
||||
@ -28,10 +28,15 @@ export const CountryListPage = observer(() => {
|
||||
headerName: "Действия",
|
||||
align: "center",
|
||||
headerAlign: "center",
|
||||
|
||||
width: 200,
|
||||
renderCell: (params: GridRenderCellParams) => {
|
||||
return (
|
||||
<div className="flex h-full gap-7 justify-center items-center">
|
||||
<button
|
||||
onClick={() => navigate(`/country/${params.row.code}/edit`)}
|
||||
>
|
||||
<Pencil size={20} className="text-blue-500" />
|
||||
</button>
|
||||
<button onClick={() => navigate(`/country/${params.row.code}`)}>
|
||||
<Eye size={20} className="text-green-500" />
|
||||
</button>
|
||||
@ -49,7 +54,7 @@ export const CountryListPage = observer(() => {
|
||||
},
|
||||
];
|
||||
|
||||
const rows = countries.map((country) => ({
|
||||
const rows = countries[language]?.map((country) => ({
|
||||
id: country.code,
|
||||
code: country.code,
|
||||
name: country.name,
|
||||
@ -66,12 +71,14 @@ export const CountryListPage = observer(() => {
|
||||
</div>
|
||||
<DataGrid rows={rows} columns={columns} hideFooter />
|
||||
</div>
|
||||
|
||||
<DeleteModal
|
||||
open={isDeleteModalOpen}
|
||||
onDelete={async () => {
|
||||
if (rowId) {
|
||||
await countryStore.deleteCountry(rowId);
|
||||
getCountries(); // Refresh the list after deletion
|
||||
await countryStore.deleteCountry(rowId, language);
|
||||
getCountries(language); // Refresh the list after deletion
|
||||
setIsDeleteModalOpen(false);
|
||||
}
|
||||
setIsDeleteModalOpen(false);
|
||||
setRowId(null);
|
||||
|
@ -1,23 +1,29 @@
|
||||
import { Paper } from "@mui/material";
|
||||
import { countryStore } from "@shared";
|
||||
import { countryStore, languageStore } from "@shared";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { LanguageSwitcher } from "@widgets";
|
||||
|
||||
export const CountryPreviewPage = observer(() => {
|
||||
const { id } = useParams();
|
||||
const { getCountry, country } = countryStore;
|
||||
const { getCountry, country, setEditCountryData } = countryStore;
|
||||
const navigate = useNavigate();
|
||||
const { language } = languageStore;
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
await getCountry(id as string);
|
||||
if (id) {
|
||||
const data = await getCountry(id as string, language);
|
||||
setEditCountryData(data.name, language);
|
||||
}
|
||||
})();
|
||||
}, [id]);
|
||||
}, [id, language]);
|
||||
|
||||
return (
|
||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||
<LanguageSwitcher />
|
||||
<div className="flex justify-between items-center">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
@ -45,11 +51,11 @@ export const CountryPreviewPage = observer(() => {
|
||||
</Button>
|
||||
</div> */}
|
||||
</div>
|
||||
{country && (
|
||||
{country[id!]?.[language] && (
|
||||
<div className="flex flex-col gap-10 w-full">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-lg font-bold">Название</h1>
|
||||
<p>{country?.name}</p>
|
||||
<p>{country[id!]?.[language]?.name}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
@ -1,3 +1,4 @@
|
||||
export * from "./CountryListPage";
|
||||
export * from "./CountryPreviewPage";
|
||||
export * from "./CountryCreatePage";
|
||||
export * from "./CountryEditPage";
|
||||
|
129
src/pages/User/UserCreatePage/index.tsx
Normal file
129
src/pages/User/UserCreatePage/index.tsx
Normal file
@ -0,0 +1,129 @@
|
||||
import {
|
||||
Button,
|
||||
Paper,
|
||||
TextField,
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
} from "@mui/material";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { ArrowLeft, Save } from "lucide-react";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
import { userStore } from "@shared";
|
||||
import { useState } from "react";
|
||||
|
||||
export const UserCreatePage = observer(() => {
|
||||
const navigate = useNavigate();
|
||||
const { createUserData, setCreateUserData, createUser } = userStore;
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleCreate = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await createUser();
|
||||
toast.success("Пользователь успешно создан");
|
||||
navigate("/user");
|
||||
} catch (error) {
|
||||
toast.error("Ошибка при создании пользователя");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => navigate("/user")}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-10 w-full items-end">
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Имя"
|
||||
value={createUserData.name || ""}
|
||||
required
|
||||
onChange={(e) =>
|
||||
setCreateUserData(
|
||||
e.target.value,
|
||||
createUserData.email || "",
|
||||
createUserData.password || "",
|
||||
createUserData.is_admin || false
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Email"
|
||||
value={createUserData.email || ""}
|
||||
required
|
||||
onChange={(e) =>
|
||||
setCreateUserData(
|
||||
createUserData.name || "",
|
||||
e.target.value,
|
||||
createUserData.password || "",
|
||||
createUserData.is_admin || false
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Пароль"
|
||||
value={createUserData.password || ""}
|
||||
required
|
||||
onChange={(e) =>
|
||||
setCreateUserData(
|
||||
createUserData.name || "",
|
||||
createUserData.email || "",
|
||||
e.target.value,
|
||||
createUserData.is_admin || false
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="w-full flex flex-col items-start">
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={createUserData.is_admin || false}
|
||||
onChange={(e) => {
|
||||
setCreateUserData(
|
||||
createUserData.name || "",
|
||||
createUserData.email || "",
|
||||
createUserData.password || "",
|
||||
e.target.checked
|
||||
);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Администратор"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
className="w-min flex gap-2 items-center"
|
||||
startIcon={<Save size={20} />}
|
||||
onClick={handleCreate}
|
||||
disabled={
|
||||
isLoading || !createUserData.name || !createUserData.password
|
||||
}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Loader2 size={20} className="animate-spin" />
|
||||
) : (
|
||||
"Создать"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</Paper>
|
||||
);
|
||||
});
|
139
src/pages/User/UserEditPage/index.tsx
Normal file
139
src/pages/User/UserEditPage/index.tsx
Normal file
@ -0,0 +1,139 @@
|
||||
import {
|
||||
Button,
|
||||
FormControlLabel,
|
||||
Checkbox,
|
||||
Paper,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { ArrowLeft, Save } from "lucide-react";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
import { userStore } from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export const UserEditPage = observer(() => {
|
||||
const navigate = useNavigate();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { id } = useParams();
|
||||
const { editUserData, editUser, getUser, setEditUserData, user } = userStore;
|
||||
|
||||
const handleEdit = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await editUser(Number(id));
|
||||
toast.success("Пользователь успешно обновлен");
|
||||
navigate("/user");
|
||||
} catch (error) {
|
||||
toast.error("Ошибка при обновлении пользователя");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (id) {
|
||||
const data = await getUser(Number(id));
|
||||
|
||||
setEditUserData(
|
||||
data?.name || "",
|
||||
data?.email || "",
|
||||
data?.password || "",
|
||||
data?.is_admin || false
|
||||
);
|
||||
}
|
||||
})();
|
||||
}, [id]);
|
||||
|
||||
return (
|
||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => navigate("/user")}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-10 w-full items-start">
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Имя"
|
||||
value={editUserData.name || ""}
|
||||
required
|
||||
onChange={(e) =>
|
||||
setEditUserData(
|
||||
e.target.value,
|
||||
editUserData.email || "",
|
||||
editUserData.password || "",
|
||||
editUserData.is_admin || false
|
||||
)
|
||||
}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Email"
|
||||
value={editUserData.email || ""}
|
||||
required
|
||||
onChange={(e) =>
|
||||
setEditUserData(
|
||||
editUserData.name || "",
|
||||
e.target.value,
|
||||
editUserData.password || "",
|
||||
editUserData.is_admin || false
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Пароль"
|
||||
value={editUserData.password || ""}
|
||||
required
|
||||
onChange={(e) =>
|
||||
setEditUserData(
|
||||
editUserData.name || "",
|
||||
editUserData.email || "",
|
||||
e.target.value,
|
||||
editUserData.is_admin || false
|
||||
)
|
||||
}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={editUserData.is_admin || false}
|
||||
onChange={(e) =>
|
||||
setEditUserData(
|
||||
editUserData.name || "",
|
||||
editUserData.email || "",
|
||||
editUserData.password || "",
|
||||
e.target.checked
|
||||
)
|
||||
}
|
||||
/>
|
||||
}
|
||||
label="Администратор"
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
className="w-min flex gap-2 items-center self-end"
|
||||
startIcon={<Save size={20} />}
|
||||
onClick={handleEdit}
|
||||
disabled={isLoading || !editUserData.name || !editUserData.email}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Loader2 size={20} className="animate-spin" />
|
||||
) : (
|
||||
"Обновить"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</Paper>
|
||||
);
|
||||
});
|
@ -1,21 +1,21 @@
|
||||
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
||||
import { languageStore, userStore } from "@shared";
|
||||
import { userStore } from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Eye, Trash2 } from "lucide-react";
|
||||
import { Pencil, Trash2 } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { DeleteModal, LanguageSwitcher } from "@widgets";
|
||||
|
||||
import { CreateButton, DeleteModal } from "@widgets";
|
||||
|
||||
export const UserListPage = observer(() => {
|
||||
const { users, getUsers, deleteUser } = userStore;
|
||||
const navigate = useNavigate();
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [rowId, setRowId] = useState<number | null>(null); // Lifted state
|
||||
const { language } = languageStore;
|
||||
|
||||
useEffect(() => {
|
||||
getUsers();
|
||||
}, [language]);
|
||||
}, []);
|
||||
|
||||
const columns: GridColDef[] = [
|
||||
{
|
||||
@ -56,6 +56,15 @@ export const UserListPage = observer(() => {
|
||||
renderCell: (params: GridRenderCellParams) => {
|
||||
return (
|
||||
<div className="flex h-full gap-7 justify-center items-center">
|
||||
<button>
|
||||
<Pencil
|
||||
size={20}
|
||||
className="text-blue-500"
|
||||
onClick={() => {
|
||||
navigate(`/user/${params.row.id}/edit`);
|
||||
}}
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsDeleteModalOpen(true);
|
||||
@ -79,9 +88,11 @@ export const UserListPage = observer(() => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<LanguageSwitcher />
|
||||
|
||||
<div style={{ width: "100%" }}>
|
||||
<div className="w-full">
|
||||
<div className="flex justify-between items-center mb-10">
|
||||
<h1 className="text-2xl">Пользователи</h1>
|
||||
<CreateButton label="Создать пользователя" path="/user/create" />
|
||||
</div>
|
||||
<DataGrid
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
@ -96,6 +107,7 @@ export const UserListPage = observer(() => {
|
||||
if (rowId) {
|
||||
await deleteUser(rowId);
|
||||
}
|
||||
|
||||
setIsDeleteModalOpen(false);
|
||||
setRowId(null);
|
||||
}}
|
||||
|
@ -1 +1,3 @@
|
||||
export * from "./UserListPage";
|
||||
export * from "./UserCreatePage";
|
||||
export * from "./UserEditPage";
|
||||
|
@ -14,7 +14,6 @@ import { Loader2 } from "lucide-react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
import { LanguageSwitcher } from "@widgets";
|
||||
|
||||
export const VehicleCreatePage = observer(() => {
|
||||
const navigate = useNavigate();
|
||||
@ -46,7 +45,6 @@ export const VehicleCreatePage = observer(() => {
|
||||
|
||||
return (
|
||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||
<LanguageSwitcher />
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
|
140
src/pages/Vehicle/VehicleEditPage/index.tsx
Normal file
140
src/pages/Vehicle/VehicleEditPage/index.tsx
Normal file
@ -0,0 +1,140 @@
|
||||
import {
|
||||
Paper,
|
||||
TextField,
|
||||
Select,
|
||||
MenuItem,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Button,
|
||||
} from "@mui/material";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { ArrowLeft, Loader2, Save } from "lucide-react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useEffect, useState } from "react";
|
||||
import { carrierStore, VEHICLE_TYPES, vehicleStore } from "@shared";
|
||||
import { toast } from "react-toastify";
|
||||
|
||||
export const VehicleEditPage = observer(() => {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams();
|
||||
const {
|
||||
getVehicle,
|
||||
vehicle,
|
||||
editVehicleData,
|
||||
setEditVehicleData,
|
||||
editVehicle,
|
||||
} = vehicleStore;
|
||||
const { getCarriers } = carrierStore;
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
await getVehicle(Number(id));
|
||||
await getCarriers();
|
||||
setEditVehicleData({
|
||||
tail_number: vehicle[Number(id)]?.vehicle.tail_number,
|
||||
type: vehicle[Number(id)]?.vehicle.type,
|
||||
carrier: vehicle[Number(id)]?.vehicle.carrier,
|
||||
carrier_id: vehicle[Number(id)]?.vehicle.carrier_id,
|
||||
});
|
||||
})();
|
||||
}, [id]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const handleEdit = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await editVehicle(Number(id), editVehicleData);
|
||||
toast.success("Транспортное средство успешно обновлено");
|
||||
navigate("/vehicle");
|
||||
} catch (error) {
|
||||
toast.error("Ошибка при обновлении транспортного средства");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
className="flex items-center gap-2"
|
||||
onClick={() => navigate("/vehicle")}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
Назад
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-10 w-full items-end">
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Бортовой номер"
|
||||
value={editVehicleData.tail_number}
|
||||
required
|
||||
onChange={(e) =>
|
||||
setEditVehicleData({
|
||||
...editVehicleData,
|
||||
tail_number: Number(e.target.value),
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Тип</InputLabel>
|
||||
<Select
|
||||
value={editVehicleData.type}
|
||||
label="Тип"
|
||||
required
|
||||
onChange={(e) =>
|
||||
setEditVehicleData({ ...editVehicleData, type: e.target.value })
|
||||
}
|
||||
>
|
||||
{VEHICLE_TYPES.map((type) => (
|
||||
<MenuItem key={type.value} value={type.value}>
|
||||
{type.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Перевозчик</InputLabel>
|
||||
<Select
|
||||
value={editVehicleData.carrier_id}
|
||||
label="Перевозчик"
|
||||
required
|
||||
onChange={(e) =>
|
||||
setEditVehicleData({
|
||||
...editVehicleData,
|
||||
carrier_id: e.target.value as number,
|
||||
})
|
||||
}
|
||||
>
|
||||
{carrierStore.carriers.map((carrier) => (
|
||||
<MenuItem key={carrier.id} value={carrier.id}>
|
||||
{carrier.full_name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
className="w-min flex gap-2 items-center"
|
||||
startIcon={<Save size={20} />}
|
||||
onClick={handleEdit}
|
||||
disabled={
|
||||
isLoading ||
|
||||
!editVehicleData.tail_number ||
|
||||
!editVehicleData.type ||
|
||||
!editVehicleData.carrier_id
|
||||
}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Loader2 size={20} className="animate-spin" />
|
||||
) : (
|
||||
"Сохранить"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</Paper>
|
||||
);
|
||||
});
|
@ -2,9 +2,9 @@ import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
||||
import { carrierStore, languageStore, vehicleStore } from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Eye, Trash2 } from "lucide-react";
|
||||
import { Eye, Pencil, Trash2 } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { CreateButton, DeleteModal, LanguageSwitcher } from "@widgets";
|
||||
import { CreateButton, DeleteModal } from "@widgets";
|
||||
import { VEHICLE_TYPES } from "@shared";
|
||||
|
||||
export const VehicleListPage = observer(() => {
|
||||
@ -68,6 +68,9 @@ export const VehicleListPage = observer(() => {
|
||||
renderCell: (params: GridRenderCellParams) => {
|
||||
return (
|
||||
<div className="flex h-full gap-7 justify-center items-center">
|
||||
<button onClick={() => navigate(`/vehicle/${params.row.id}/edit`)}>
|
||||
<Pencil size={20} className="text-blue-500" />
|
||||
</button>
|
||||
<button onClick={() => navigate(`/vehicle/${params.row.id}`)}>
|
||||
<Eye size={20} className="text-green-500" />
|
||||
</button>
|
||||
@ -96,8 +99,6 @@ export const VehicleListPage = observer(() => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<LanguageSwitcher />
|
||||
|
||||
<div style={{ width: "100%" }}>
|
||||
<div className="flex justify-between items-center mb-10">
|
||||
<h1 className="text-2xl">Транспортные средства</h1>
|
||||
|
@ -45,23 +45,23 @@ export const VehiclePreviewPage = observer(() => {
|
||||
</Button>
|
||||
</div> */}
|
||||
</div>
|
||||
{vehicle && (
|
||||
{vehicle[id!] && (
|
||||
<div className="flex flex-col gap-10 w-full">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-lg font-bold">Системный номер</h1>
|
||||
<p>{vehicle?.vehicle.tail_number}</p>
|
||||
<p>{vehicle[id!]?.vehicle.tail_number}</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-lg font-bold">Тип транспортного средства</h1>
|
||||
<p>
|
||||
{VEHICLE_TYPES.find(
|
||||
(type) => type.value === vehicle?.vehicle.type
|
||||
)?.label || vehicle?.vehicle.type}
|
||||
(type) => type.value === vehicle[id!]?.vehicle.type
|
||||
)?.label || vehicle[id!]?.vehicle.type}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-lg font-bold">Перевозчик</h1>
|
||||
<p>{vehicle?.vehicle.carrier}</p>
|
||||
<p>{vehicle[id!]?.vehicle.carrier}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
@ -1,3 +1,4 @@
|
||||
export * from "./VehicleListPage";
|
||||
export * from "./VehiclePreviewPage";
|
||||
export * from "./VehicleCreatePage";
|
||||
export * from "./VehicleEditPage";
|
||||
|
Reference in New Issue
Block a user