fix: Hot bug fix
This commit is contained in:
@ -139,7 +139,7 @@ const router = createBrowserRouter([
|
|||||||
// City
|
// City
|
||||||
{ path: "city", element: <CityListPage /> },
|
{ path: "city", element: <CityListPage /> },
|
||||||
{ path: "city/create", element: <CityCreatePage /> },
|
{ path: "city/create", element: <CityCreatePage /> },
|
||||||
{ path: "city/:id", element: <CityPreviewPage /> },
|
// { path: "city/:id", element: <CityPreviewPage /> },
|
||||||
{ path: "city/:id/edit", element: <CityEditPage /> },
|
{ path: "city/:id/edit", element: <CityEditPage /> },
|
||||||
// Route
|
// Route
|
||||||
{ path: "route", element: <RouteListPage /> },
|
{ path: "route", element: <RouteListPage /> },
|
||||||
|
@ -12,7 +12,7 @@ import { ArrowLeft, Save } from "lucide-react";
|
|||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { carrierStore, cityStore, mediaStore } from "@shared";
|
import { carrierStore, cityStore, mediaStore, languageStore } from "@shared";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { ImageUploadCard, LanguageSwitcher } from "@widgets";
|
import { ImageUploadCard, LanguageSwitcher } from "@widgets";
|
||||||
import {
|
import {
|
||||||
@ -23,11 +23,8 @@ import {
|
|||||||
|
|
||||||
export const CarrierCreatePage = observer(() => {
|
export const CarrierCreatePage = observer(() => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { createCarrierData, setCreateCarrierData } = carrierStore;
|
||||||
const [fullName, setFullName] = useState("");
|
const { language } = languageStore;
|
||||||
const [shortName, setShortName] = useState("");
|
|
||||||
const [cityId, setCityId] = useState<number | null>(null);
|
|
||||||
const [slogan, setSlogan] = useState("");
|
|
||||||
const [selectedMediaId, setSelectedMediaId] = useState<string | null>(null);
|
const [selectedMediaId, setSelectedMediaId] = useState<string | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [isSelectMediaOpen, setIsSelectMediaOpen] = useState(false);
|
const [isSelectMediaOpen, setIsSelectMediaOpen] = useState(false);
|
||||||
@ -35,7 +32,7 @@ export const CarrierCreatePage = observer(() => {
|
|||||||
const [isPreviewMediaOpen, setIsPreviewMediaOpen] = useState(false);
|
const [isPreviewMediaOpen, setIsPreviewMediaOpen] = useState(false);
|
||||||
const [mediaId, setMediaId] = useState("");
|
const [mediaId, setMediaId] = useState("");
|
||||||
const [activeMenuType, setActiveMenuType] = useState<
|
const [activeMenuType, setActiveMenuType] = useState<
|
||||||
"thumbnail" | "watermark_lu" | "watermark_rd" | null
|
"thumbnail" | "watermark_lu" | "watermark_rd" | "image" | null
|
||||||
>(null);
|
>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -46,13 +43,7 @@ export const CarrierCreatePage = observer(() => {
|
|||||||
const handleCreate = async () => {
|
const handleCreate = async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
await carrierStore.createCarrier(
|
await carrierStore.createCarrier();
|
||||||
fullName,
|
|
||||||
shortName,
|
|
||||||
cityId!,
|
|
||||||
slogan,
|
|
||||||
selectedMediaId!
|
|
||||||
);
|
|
||||||
toast.success("Перевозчик успешно создан");
|
toast.success("Перевозчик успешно создан");
|
||||||
navigate("/carrier");
|
navigate("/carrier");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -69,6 +60,14 @@ export const CarrierCreatePage = observer(() => {
|
|||||||
media_type: number;
|
media_type: number;
|
||||||
}) => {
|
}) => {
|
||||||
setSelectedMediaId(media.id);
|
setSelectedMediaId(media.id);
|
||||||
|
setCreateCarrierData(
|
||||||
|
createCarrierData[language].full_name,
|
||||||
|
createCarrierData[language].short_name,
|
||||||
|
createCarrierData.city_id,
|
||||||
|
createCarrierData[language].slogan,
|
||||||
|
media.id,
|
||||||
|
language
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectedMedia = selectedMediaId
|
const selectedMedia = selectedMediaId
|
||||||
@ -89,19 +88,28 @@ export const CarrierCreatePage = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-10 w-full items-end">
|
<div className="flex flex-col gap-10 w-full items-end">
|
||||||
<div className="flex gap-10 items-center mb-5 max-w-[80%]">
|
<div className="flex gap-10 items-center mb-5 max-w-[80%] self-start">
|
||||||
<h1 className="text-3xl break-words">Создание перевозчика</h1>
|
<h1 className="text-3xl break-words">Создание перевозчика</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>Город</InputLabel>
|
<InputLabel>Город</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={cityId || ""}
|
value={createCarrierData.city_id || ""}
|
||||||
label="Город"
|
label="Город"
|
||||||
required
|
required
|
||||||
onChange={(e) => setCityId(e.target.value as number)}
|
onChange={(e) =>
|
||||||
|
setCreateCarrierData(
|
||||||
|
createCarrierData[language].full_name,
|
||||||
|
createCarrierData[language].short_name,
|
||||||
|
e.target.value as number,
|
||||||
|
createCarrierData[language].slogan,
|
||||||
|
selectedMediaId || "",
|
||||||
|
language
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{cityStore.cities.ru.data.map((city) => (
|
{cityStore.cities["ru"].data.map((city) => (
|
||||||
<MenuItem key={city.id} value={city.id}>
|
<MenuItem key={city.id} value={city.id}>
|
||||||
{city.name}
|
{city.name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@ -112,24 +120,51 @@ export const CarrierCreatePage = observer(() => {
|
|||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
label="Полное название"
|
label="Полное название"
|
||||||
value={fullName}
|
value={createCarrierData[language].full_name}
|
||||||
required
|
required
|
||||||
onChange={(e) => setFullName(e.target.value)}
|
onChange={(e) =>
|
||||||
|
setCreateCarrierData(
|
||||||
|
e.target.value,
|
||||||
|
createCarrierData[language].short_name,
|
||||||
|
createCarrierData.city_id,
|
||||||
|
createCarrierData[language].slogan,
|
||||||
|
selectedMediaId || "",
|
||||||
|
language
|
||||||
|
)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
label="Короткое название"
|
label="Короткое название"
|
||||||
value={shortName}
|
value={createCarrierData[language].short_name}
|
||||||
required
|
required
|
||||||
onChange={(e) => setShortName(e.target.value)}
|
onChange={(e) =>
|
||||||
|
setCreateCarrierData(
|
||||||
|
createCarrierData[language].full_name,
|
||||||
|
e.target.value,
|
||||||
|
createCarrierData.city_id,
|
||||||
|
createCarrierData[language].slogan,
|
||||||
|
selectedMediaId || "",
|
||||||
|
language
|
||||||
|
)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
label="Слоган"
|
label="Слоган"
|
||||||
value={slogan}
|
value={createCarrierData[language].slogan}
|
||||||
onChange={(e) => setSlogan(e.target.value)}
|
onChange={(e) =>
|
||||||
|
setCreateCarrierData(
|
||||||
|
createCarrierData[language].full_name,
|
||||||
|
createCarrierData[language].short_name,
|
||||||
|
createCarrierData.city_id,
|
||||||
|
e.target.value,
|
||||||
|
selectedMediaId || "",
|
||||||
|
language
|
||||||
|
)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="w-full flex flex-col gap-4 max-w-[300px] mx-auto">
|
<div className="w-full flex flex-col gap-4 max-w-[300px] mx-auto">
|
||||||
@ -144,14 +179,22 @@ export const CarrierCreatePage = observer(() => {
|
|||||||
onDeleteImageClick={() => {
|
onDeleteImageClick={() => {
|
||||||
setSelectedMediaId(null);
|
setSelectedMediaId(null);
|
||||||
setActiveMenuType(null);
|
setActiveMenuType(null);
|
||||||
|
setCreateCarrierData(
|
||||||
|
createCarrierData[language].full_name,
|
||||||
|
createCarrierData[language].short_name,
|
||||||
|
createCarrierData.city_id,
|
||||||
|
createCarrierData[language].slogan,
|
||||||
|
"",
|
||||||
|
language
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
onSelectFileClick={() => {
|
onSelectFileClick={() => {
|
||||||
setActiveMenuType("thumbnail");
|
setActiveMenuType("image");
|
||||||
setIsSelectMediaOpen(true);
|
setIsSelectMediaOpen(true);
|
||||||
}}
|
}}
|
||||||
setUploadMediaOpen={() => {
|
setUploadMediaOpen={() => {
|
||||||
setIsUploadMediaOpen(true);
|
setIsUploadMediaOpen(true);
|
||||||
setActiveMenuType("thumbnail");
|
setActiveMenuType("image");
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -162,7 +205,10 @@ export const CarrierCreatePage = observer(() => {
|
|||||||
startIcon={<Save size={20} />}
|
startIcon={<Save size={20} />}
|
||||||
onClick={handleCreate}
|
onClick={handleCreate}
|
||||||
disabled={
|
disabled={
|
||||||
isLoading || !fullName || !shortName || !cityId || !selectedMediaId
|
isLoading ||
|
||||||
|
!createCarrierData[language].full_name ||
|
||||||
|
!createCarrierData[language].short_name ||
|
||||||
|
!createCarrierData.city_id
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@ -177,7 +223,7 @@ export const CarrierCreatePage = observer(() => {
|
|||||||
open={isSelectMediaOpen}
|
open={isSelectMediaOpen}
|
||||||
onClose={() => setIsSelectMediaOpen(false)}
|
onClose={() => setIsSelectMediaOpen(false)}
|
||||||
onSelectMedia={handleMediaSelect}
|
onSelectMedia={handleMediaSelect}
|
||||||
mediaType={3}
|
mediaType={1}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<UploadMediaDialog
|
<UploadMediaDialog
|
||||||
|
@ -33,7 +33,7 @@ export const CarrierEditPage = observer(() => {
|
|||||||
const [isPreviewMediaOpen, setIsPreviewMediaOpen] = useState(false);
|
const [isPreviewMediaOpen, setIsPreviewMediaOpen] = useState(false);
|
||||||
const [mediaId, setMediaId] = useState("");
|
const [mediaId, setMediaId] = useState("");
|
||||||
const [activeMenuType, setActiveMenuType] = useState<
|
const [activeMenuType, setActiveMenuType] = useState<
|
||||||
"thumbnail" | "watermark_lu" | "watermark_rd" | null
|
"thumbnail" | "watermark_lu" | "watermark_rd" | "image" | null
|
||||||
>(null);
|
>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -141,7 +141,7 @@ export const CarrierEditPage = observer(() => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{cityStore.cities[language].data?.map((city) => (
|
{cityStore.cities["ru"].data?.map((city) => (
|
||||||
<MenuItem key={city.id} value={city.id}>
|
<MenuItem key={city.id} value={city.id}>
|
||||||
{city.name}
|
{city.name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@ -220,12 +220,12 @@ export const CarrierEditPage = observer(() => {
|
|||||||
setActiveMenuType(null);
|
setActiveMenuType(null);
|
||||||
}}
|
}}
|
||||||
onSelectFileClick={() => {
|
onSelectFileClick={() => {
|
||||||
setActiveMenuType("thumbnail");
|
setActiveMenuType("image");
|
||||||
setIsSelectMediaOpen(true);
|
setIsSelectMediaOpen(true);
|
||||||
}}
|
}}
|
||||||
setUploadMediaOpen={() => {
|
setUploadMediaOpen={() => {
|
||||||
setIsUploadMediaOpen(true);
|
setIsUploadMediaOpen(true);
|
||||||
setActiveMenuType("thumbnail");
|
setActiveMenuType("image");
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -238,9 +238,7 @@ export const CarrierEditPage = observer(() => {
|
|||||||
disabled={
|
disabled={
|
||||||
isLoading ||
|
isLoading ||
|
||||||
!editCarrierData[language].full_name ||
|
!editCarrierData[language].full_name ||
|
||||||
!editCarrierData[language].short_name ||
|
!editCarrierData.city_id
|
||||||
!editCarrierData.city_id ||
|
|
||||||
!editCarrierData.logo
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@ -255,7 +253,7 @@ export const CarrierEditPage = observer(() => {
|
|||||||
open={isSelectMediaOpen}
|
open={isSelectMediaOpen}
|
||||||
onClose={() => setIsSelectMediaOpen(false)}
|
onClose={() => setIsSelectMediaOpen(false)}
|
||||||
onSelectMedia={handleMediaSelect}
|
onSelectMedia={handleMediaSelect}
|
||||||
mediaType={3}
|
mediaType={1}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<UploadMediaDialog
|
<UploadMediaDialog
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
||||||
import { carrierStore, languageStore } from "@shared";
|
import { carrierStore, cityStore, languageStore } from "@shared";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Pencil, Trash2, Minus } from "lucide-react";
|
import { Pencil, Trash2, Minus } from "lucide-react";
|
||||||
@ -8,6 +8,7 @@ import { CreateButton, DeleteModal, LanguageSwitcher } from "@widgets";
|
|||||||
|
|
||||||
export const CarrierListPage = observer(() => {
|
export const CarrierListPage = observer(() => {
|
||||||
const { carriers, getCarriers, deleteCarrier } = carrierStore;
|
const { carriers, getCarriers, deleteCarrier } = carrierStore;
|
||||||
|
const { getCities, cities } = cityStore;
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
const [isBulkDeleteModalOpen, setIsBulkDeleteModalOpen] = useState(false);
|
const [isBulkDeleteModalOpen, setIsBulkDeleteModalOpen] = useState(false);
|
||||||
@ -17,6 +18,9 @@ export const CarrierListPage = observer(() => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
|
await getCities("ru");
|
||||||
|
await getCities("en");
|
||||||
|
await getCities("zh");
|
||||||
await getCarriers(language);
|
await getCarriers(language);
|
||||||
})();
|
})();
|
||||||
}, [language]);
|
}, [language]);
|
||||||
@ -55,14 +59,15 @@ export const CarrierListPage = observer(() => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: "city",
|
field: "city_id",
|
||||||
headerName: "Город",
|
headerName: "Город",
|
||||||
flex: 1,
|
flex: 1,
|
||||||
renderCell: (params: GridRenderCellParams) => {
|
renderCell: (params: GridRenderCellParams) => {
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full flex items-center">
|
<div className="w-full h-full flex items-center">
|
||||||
{params.value ? (
|
{params.value ? (
|
||||||
params.value
|
cities[language].data.find((city) => city.id == params.value)
|
||||||
|
?.name
|
||||||
) : (
|
) : (
|
||||||
<Minus size={20} className="text-red-500" />
|
<Minus size={20} className="text-red-500" />
|
||||||
)}
|
)}
|
||||||
@ -103,7 +108,7 @@ export const CarrierListPage = observer(() => {
|
|||||||
id: carrier.id,
|
id: carrier.id,
|
||||||
full_name: carrier.full_name,
|
full_name: carrier.full_name,
|
||||||
short_name: carrier.short_name,
|
short_name: carrier.short_name,
|
||||||
city: carrier.city,
|
city_id: carrier.city_id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -31,7 +31,7 @@ export const CityCreatePage = observer(() => {
|
|||||||
const [isPreviewMediaOpen, setIsPreviewMediaOpen] = useState(false);
|
const [isPreviewMediaOpen, setIsPreviewMediaOpen] = useState(false);
|
||||||
const [mediaId, setMediaId] = useState("");
|
const [mediaId, setMediaId] = useState("");
|
||||||
const [activeMenuType, setActiveMenuType] = useState<
|
const [activeMenuType, setActiveMenuType] = useState<
|
||||||
"thumbnail" | "watermark_lu" | "watermark_rd" | null
|
"thumbnail" | "watermark_lu" | "watermark_rd" | "image" | null
|
||||||
>(null);
|
>(null);
|
||||||
const { getCountries } = countryStore;
|
const { getCountries } = countryStore;
|
||||||
const { getMedia } = mediaStore;
|
const { getMedia } = mediaStore;
|
||||||
@ -132,15 +132,9 @@ export const CityCreatePage = observer(() => {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<div className="w-full flex flex-col gap-4 max-w-[300px] mx-auto">
|
<div className="w-full flex flex-col gap-4 max-w-[300px] mx-auto">
|
||||||
{!selectedMedia && (
|
|
||||||
<div className="flex items-center gap-2 text-red-500">
|
|
||||||
<Minus size={20} />
|
|
||||||
<span className="text-sm">Герб города не выбран</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<ImageUploadCard
|
<ImageUploadCard
|
||||||
title="Герб города"
|
title="Герб города"
|
||||||
imageKey="thumbnail"
|
imageKey="image"
|
||||||
imageUrl={selectedMedia?.id}
|
imageUrl={selectedMedia?.id}
|
||||||
onImageClick={() => {
|
onImageClick={() => {
|
||||||
setIsPreviewMediaOpen(true);
|
setIsPreviewMediaOpen(true);
|
||||||
@ -156,12 +150,22 @@ export const CityCreatePage = observer(() => {
|
|||||||
setActiveMenuType(null);
|
setActiveMenuType(null);
|
||||||
}}
|
}}
|
||||||
onSelectFileClick={() => {
|
onSelectFileClick={() => {
|
||||||
setActiveMenuType("thumbnail");
|
setActiveMenuType("image");
|
||||||
setIsSelectMediaOpen(true);
|
setIsSelectMediaOpen(true);
|
||||||
}}
|
}}
|
||||||
setUploadMediaOpen={() => {
|
setUploadMediaOpen={() => {
|
||||||
setIsUploadMediaOpen(true);
|
setIsUploadMediaOpen(true);
|
||||||
setActiveMenuType("thumbnail");
|
setActiveMenuType("image");
|
||||||
|
}}
|
||||||
|
setHardcodeType={(type) => {
|
||||||
|
setActiveMenuType(
|
||||||
|
type as
|
||||||
|
| "thumbnail"
|
||||||
|
| "watermark_lu"
|
||||||
|
| "watermark_rd"
|
||||||
|
| "image"
|
||||||
|
| null
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -185,14 +189,16 @@ export const CityCreatePage = observer(() => {
|
|||||||
open={isSelectMediaOpen}
|
open={isSelectMediaOpen}
|
||||||
onClose={() => setIsSelectMediaOpen(false)}
|
onClose={() => setIsSelectMediaOpen(false)}
|
||||||
onSelectMedia={handleMediaSelect}
|
onSelectMedia={handleMediaSelect}
|
||||||
mediaType={3} // Тип медиа для иконок
|
mediaType={1} // Тип медиа для иконок
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<UploadMediaDialog
|
<UploadMediaDialog
|
||||||
open={isUploadMediaOpen}
|
open={isUploadMediaOpen}
|
||||||
onClose={() => setIsUploadMediaOpen(false)}
|
onClose={() => setIsUploadMediaOpen(false)}
|
||||||
afterUpload={handleMediaSelect}
|
afterUpload={handleMediaSelect}
|
||||||
hardcodeType={activeMenuType}
|
hardcodeType={
|
||||||
|
activeMenuType as "thumbnail" | "watermark_lu" | "watermark_rd" | null
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PreviewMediaDialog
|
<PreviewMediaDialog
|
||||||
|
@ -35,7 +35,7 @@ export const CityEditPage = observer(() => {
|
|||||||
const [isPreviewMediaOpen, setIsPreviewMediaOpen] = useState(false);
|
const [isPreviewMediaOpen, setIsPreviewMediaOpen] = useState(false);
|
||||||
const [mediaId, setMediaId] = useState("");
|
const [mediaId, setMediaId] = useState("");
|
||||||
const [activeMenuType, setActiveMenuType] = useState<
|
const [activeMenuType, setActiveMenuType] = useState<
|
||||||
"thumbnail" | "watermark_lu" | "watermark_rd" | null
|
"thumbnail" | "watermark_lu" | "watermark_rd" | "image" | null
|
||||||
>(null);
|
>(null);
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
@ -151,7 +151,7 @@ export const CityEditPage = observer(() => {
|
|||||||
<div className="w-full flex flex-col gap-4 max-w-[300px] mx-auto">
|
<div className="w-full flex flex-col gap-4 max-w-[300px] mx-auto">
|
||||||
<ImageUploadCard
|
<ImageUploadCard
|
||||||
title="Герб города"
|
title="Герб города"
|
||||||
imageKey="thumbnail"
|
imageKey="image"
|
||||||
imageUrl={selectedMedia?.id}
|
imageUrl={selectedMedia?.id}
|
||||||
onImageClick={() => {
|
onImageClick={() => {
|
||||||
setIsPreviewMediaOpen(true);
|
setIsPreviewMediaOpen(true);
|
||||||
@ -167,12 +167,22 @@ export const CityEditPage = observer(() => {
|
|||||||
setActiveMenuType(null);
|
setActiveMenuType(null);
|
||||||
}}
|
}}
|
||||||
onSelectFileClick={() => {
|
onSelectFileClick={() => {
|
||||||
setActiveMenuType("thumbnail");
|
setActiveMenuType("image");
|
||||||
setIsSelectMediaOpen(true);
|
setIsSelectMediaOpen(true);
|
||||||
}}
|
}}
|
||||||
setUploadMediaOpen={() => {
|
setUploadMediaOpen={() => {
|
||||||
setIsUploadMediaOpen(true);
|
setIsUploadMediaOpen(true);
|
||||||
setActiveMenuType("thumbnail");
|
setActiveMenuType("image");
|
||||||
|
}}
|
||||||
|
setHardcodeType={(type) => {
|
||||||
|
setActiveMenuType(
|
||||||
|
type as
|
||||||
|
| "thumbnail"
|
||||||
|
| "watermark_lu"
|
||||||
|
| "watermark_rd"
|
||||||
|
| "image"
|
||||||
|
| null
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -198,14 +208,21 @@ export const CityEditPage = observer(() => {
|
|||||||
open={isSelectMediaOpen}
|
open={isSelectMediaOpen}
|
||||||
onClose={() => setIsSelectMediaOpen(false)}
|
onClose={() => setIsSelectMediaOpen(false)}
|
||||||
onSelectMedia={handleMediaSelect}
|
onSelectMedia={handleMediaSelect}
|
||||||
mediaType={3} // Тип медиа для иконок
|
mediaType={1} // Тип медиа для иконок
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<UploadMediaDialog
|
<UploadMediaDialog
|
||||||
open={isUploadMediaOpen}
|
open={isUploadMediaOpen}
|
||||||
onClose={() => setIsUploadMediaOpen(false)}
|
onClose={() => setIsUploadMediaOpen(false)}
|
||||||
afterUpload={handleMediaSelect}
|
afterUpload={handleMediaSelect}
|
||||||
hardcodeType={activeMenuType}
|
hardcodeType={
|
||||||
|
activeMenuType as
|
||||||
|
| "thumbnail"
|
||||||
|
| "watermark_lu"
|
||||||
|
| "watermark_rd"
|
||||||
|
| "image"
|
||||||
|
| null
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PreviewMediaDialog
|
<PreviewMediaDialog
|
||||||
|
@ -11,7 +11,9 @@ export const CityListPage = observer(() => {
|
|||||||
const { cities, getCities, deleteCity } = cityStore;
|
const { cities, getCities, deleteCity } = cityStore;
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
|
const [isBulkDeleteModalOpen, setIsBulkDeleteModalOpen] = useState(false);
|
||||||
const [rowId, setRowId] = useState<number | null>(null);
|
const [rowId, setRowId] = useState<number | null>(null);
|
||||||
|
const [ids, setIds] = useState<number[]>([]);
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -57,18 +59,18 @@ export const CityListPage = observer(() => {
|
|||||||
align: "center",
|
align: "center",
|
||||||
headerAlign: "center",
|
headerAlign: "center",
|
||||||
width: 200,
|
width: 200,
|
||||||
|
|
||||||
renderCell: (params: GridRenderCellParams) => {
|
renderCell: (params: GridRenderCellParams) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full gap-7 justify-center items-center">
|
<div className="flex h-full gap-7 justify-center items-center">
|
||||||
<button onClick={() => navigate(`/city/${params.row.id}/edit`)}>
|
<button onClick={() => navigate(`/city/${params.row.id}/edit`)}>
|
||||||
<Pencil size={20} className="text-blue-500" />
|
<Pencil size={20} className="text-blue-500" />
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => navigate(`/city/${params.row.id}`)}>
|
{/* <button onClick={() => navigate(`/city/${params.row.id}`)}>
|
||||||
<Eye size={20} className="text-green-500" />
|
<Eye size={20} className="text-green-500" />
|
||||||
</button>
|
</button> */}
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
setIsDeleteModalOpen(true);
|
setIsDeleteModalOpen(true);
|
||||||
setRowId(params.row.id);
|
setRowId(params.row.id);
|
||||||
}}
|
}}
|
||||||
@ -96,11 +98,29 @@ export const CityListPage = observer(() => {
|
|||||||
<h1 className="text-2xl">Города</h1>
|
<h1 className="text-2xl">Города</h1>
|
||||||
<CreateButton label="Создать город" path="/city/create" />
|
<CreateButton label="Создать город" path="/city/create" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="flex justify-end mb-5 duration-300"
|
||||||
|
style={{ opacity: ids.length > 0 ? 1 : 0 }}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
||||||
|
onClick={() => setIsBulkDeleteModalOpen(true)}
|
||||||
|
>
|
||||||
|
<Trash2 size={20} className="text-white" /> Удалить выбранные (
|
||||||
|
{ids.length})
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<DataGrid
|
<DataGrid
|
||||||
rows={rows}
|
rows={rows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
hideFooterPagination
|
hideFooterPagination
|
||||||
hideFooter
|
hideFooter
|
||||||
|
checkboxSelection
|
||||||
|
onRowSelectionModelChange={(newSelection) => {
|
||||||
|
setIds(Array.from(newSelection.ids as unknown as number[]));
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -119,6 +139,20 @@ export const CityListPage = observer(() => {
|
|||||||
setRowId(null);
|
setRowId(null);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<DeleteModal
|
||||||
|
open={isBulkDeleteModalOpen}
|
||||||
|
onDelete={async () => {
|
||||||
|
await Promise.all(ids.map((id) => deleteCity(id.toString())));
|
||||||
|
toast.success("Города успешно удалены");
|
||||||
|
getCities(language);
|
||||||
|
setIsBulkDeleteModalOpen(false);
|
||||||
|
setIds([]);
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
setIsBulkDeleteModalOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -10,7 +10,9 @@ export const CountryListPage = observer(() => {
|
|||||||
const { countries, getCountries, deleteCountry } = countryStore;
|
const { countries, getCountries, deleteCountry } = countryStore;
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
|
const [isBulkDeleteModalOpen, setIsBulkDeleteModalOpen] = useState(false);
|
||||||
const [rowId, setRowId] = useState<string | null>(null);
|
const [rowId, setRowId] = useState<string | null>(null);
|
||||||
|
const [ids, setIds] = useState<number[]>([]);
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -52,7 +54,8 @@ export const CountryListPage = observer(() => {
|
|||||||
<Eye size={20} className="text-green-500" />
|
<Eye size={20} className="text-green-500" />
|
||||||
</button> */}
|
</button> */}
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
setIsDeleteModalOpen(true);
|
setIsDeleteModalOpen(true);
|
||||||
setRowId(params.row.code);
|
setRowId(params.row.code);
|
||||||
}}
|
}}
|
||||||
@ -80,14 +83,37 @@ export const CountryListPage = observer(() => {
|
|||||||
<h1 className="text-2xl">Страны</h1>
|
<h1 className="text-2xl">Страны</h1>
|
||||||
<CreateButton label="Создать страну" path="/country/create" />
|
<CreateButton label="Создать страну" path="/country/create" />
|
||||||
</div>
|
</div>
|
||||||
<DataGrid rows={rows} columns={columns} hideFooter />
|
|
||||||
|
<div
|
||||||
|
className="flex justify-end mb-5 duration-300"
|
||||||
|
style={{ opacity: ids.length > 0 ? 1 : 0 }}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="px-4 py-2 bg-red-500 text-white rounded flex gap-2 items-center"
|
||||||
|
onClick={() => setIsBulkDeleteModalOpen(true)}
|
||||||
|
>
|
||||||
|
<Trash2 size={20} className="text-white" /> Удалить выбранные (
|
||||||
|
{ids.length})
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DataGrid
|
||||||
|
rows={rows}
|
||||||
|
columns={columns}
|
||||||
|
hideFooter
|
||||||
|
checkboxSelection
|
||||||
|
onRowSelectionModelChange={(newSelection) => {
|
||||||
|
console.log(newSelection);
|
||||||
|
setIds(Array.from(newSelection.ids as unknown as number[]));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DeleteModal
|
<DeleteModal
|
||||||
open={isDeleteModalOpen}
|
open={isDeleteModalOpen}
|
||||||
onDelete={async () => {
|
onDelete={async () => {
|
||||||
if (!rowId) return;
|
if (!rowId) return;
|
||||||
await deleteCountry(rowId, language);
|
await deleteCountry(rowId);
|
||||||
setRowId(null);
|
setRowId(null);
|
||||||
setIsDeleteModalOpen(false);
|
setIsDeleteModalOpen(false);
|
||||||
}}
|
}}
|
||||||
@ -96,6 +122,19 @@ export const CountryListPage = observer(() => {
|
|||||||
setIsDeleteModalOpen(false);
|
setIsDeleteModalOpen(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<DeleteModal
|
||||||
|
open={isBulkDeleteModalOpen}
|
||||||
|
onDelete={async () => {
|
||||||
|
await Promise.all(ids.map((id) => deleteCountry(id.toString())));
|
||||||
|
getCountries(language);
|
||||||
|
setIsBulkDeleteModalOpen(false);
|
||||||
|
setIds([]);
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
setIsBulkDeleteModalOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -132,6 +132,8 @@ class MapStore {
|
|||||||
data = {
|
data = {
|
||||||
route_number: properties.name || "Новый маршрут",
|
route_number: properties.name || "Новый маршрут",
|
||||||
path: geometry.coordinates,
|
path: geometry.coordinates,
|
||||||
|
center_latitude: geometry.coordinates[0][1],
|
||||||
|
center_longitude: geometry.coordinates[0][0],
|
||||||
};
|
};
|
||||||
} else if (featureType === "sight") {
|
} else if (featureType === "sight") {
|
||||||
data = {
|
data = {
|
||||||
@ -192,10 +194,9 @@ class MapStore {
|
|||||||
oldData = this.sights.find((f) => f.id === numericId);
|
oldData = this.sights.find((f) => f.id === numericId);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(oldData);
|
let response;
|
||||||
console.log(data);
|
if (featureType !== "route") {
|
||||||
|
response = await languageInstance("ru").patch(
|
||||||
const response = await languageInstance("ru").patch(
|
|
||||||
`/${featureType}/${numericId}`,
|
`/${featureType}/${numericId}`,
|
||||||
{
|
{
|
||||||
...oldData,
|
...oldData,
|
||||||
@ -203,6 +204,17 @@ class MapStore {
|
|||||||
longitude: data.longitude,
|
longitude: data.longitude,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
response = await languageInstance("ru").patch(
|
||||||
|
`/${featureType}/${numericId}`,
|
||||||
|
{
|
||||||
|
...oldData,
|
||||||
|
path: data.path,
|
||||||
|
center_latitude: data.path[0][1],
|
||||||
|
center_longitude: data.path[0][0],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (featureType === "route") {
|
if (featureType === "route") {
|
||||||
const index = this.routes.findIndex((f) => f.id === numericId);
|
const index = this.routes.findIndex((f) => f.id === numericId);
|
||||||
@ -1078,7 +1090,10 @@ class MapService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!featureAtPixel) {
|
if (!featureAtPixel) {
|
||||||
if (!ctrlKey) this.unselect();
|
if (ctrlKey) {
|
||||||
|
// При ctrl + клик вне сущности сбрасываем выбор
|
||||||
|
this.setSelectedIds(new Set());
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1086,11 +1101,16 @@ class MapService {
|
|||||||
if (featureId === undefined) return;
|
if (featureId === undefined) return;
|
||||||
|
|
||||||
if (ctrlKey) {
|
if (ctrlKey) {
|
||||||
|
// При ctrl + клик на сущность добавляем/удаляем её из выбора
|
||||||
const newSet = new Set(this.selectedIds);
|
const newSet = new Set(this.selectedIds);
|
||||||
if (newSet.has(featureId)) newSet.delete(featureId);
|
if (newSet.has(featureId)) {
|
||||||
else newSet.add(featureId);
|
newSet.delete(featureId);
|
||||||
|
} else {
|
||||||
|
newSet.add(featureId);
|
||||||
|
}
|
||||||
this.setSelectedIds(newSet);
|
this.setSelectedIds(newSet);
|
||||||
} else {
|
} else {
|
||||||
|
// При обычном клике на сущность выбираем только её
|
||||||
this.setSelectedIds(new Set([featureId]));
|
this.setSelectedIds(new Set([featureId]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1153,14 +1173,12 @@ class MapService {
|
|||||||
mapStore
|
mapStore
|
||||||
.deleteFeature(recourse, numericId)
|
.deleteFeature(recourse, numericId)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success("Объект успешно удален");
|
|
||||||
if (stateBeforeDelete)
|
if (stateBeforeDelete)
|
||||||
this.addStateToHistory("delete", stateBeforeDelete);
|
this.addStateToHistory("delete", stateBeforeDelete);
|
||||||
this.vectorSource.removeFeature(feature);
|
this.vectorSource.removeFeature(feature);
|
||||||
this.unselect();
|
this.unselect();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
toast.error("Ошибка при удалении объекта");
|
|
||||||
console.error("Delete failed:", err);
|
console.error("Delete failed:", err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1196,7 +1214,6 @@ class MapService {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
toast.error("Произошла ошибка при массовом удалении");
|
|
||||||
console.error("Bulk delete failed:", err);
|
console.error("Bulk delete failed:", err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1307,12 +1324,9 @@ class MapService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await mapStore.updateFeature(featureType, featureGeoJSON);
|
await mapStore.updateFeature(featureType, featureGeoJSON);
|
||||||
toast.success(`"${feature.get("name")}" успешно обновлен.`);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to update feature:", error);
|
console.error("Failed to update feature:", error);
|
||||||
toast.error(
|
|
||||||
`Не удалось обновить "${feature.get("name")}". Отмена изменений...`
|
|
||||||
);
|
|
||||||
this.undo();
|
this.undo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1338,7 +1352,6 @@ class MapService {
|
|||||||
featureType === "route"
|
featureType === "route"
|
||||||
? createdFeatureData.route_number
|
? createdFeatureData.route_number
|
||||||
: createdFeatureData.name;
|
: createdFeatureData.name;
|
||||||
toast.success(`"${newName}" создано.`);
|
|
||||||
|
|
||||||
const newFeatureId = `${featureType}-${createdFeatureData.id}`;
|
const newFeatureId = `${featureType}-${createdFeatureData.id}`;
|
||||||
feature.setId(newFeatureId);
|
feature.setId(newFeatureId);
|
||||||
@ -1507,15 +1520,20 @@ const MapSightbar: React.FC<MapSightbarProps> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleCheckboxChange = useCallback(
|
const handleCheckboxChange = useCallback(
|
||||||
// @ts-ignore
|
(id: string | number | undefined) => {
|
||||||
(id) => {
|
|
||||||
if (id === undefined) return;
|
if (id === undefined) return;
|
||||||
const newSet = new Set(selectedIds);
|
const newSet = new Set(selectedIds);
|
||||||
if (newSet.has(id)) newSet.delete(id);
|
if (newSet.has(id)) {
|
||||||
else newSet.add(id);
|
newSet.delete(id);
|
||||||
|
} else {
|
||||||
|
newSet.add(id);
|
||||||
|
}
|
||||||
setSelectedIds(newSet);
|
setSelectedIds(newSet);
|
||||||
|
if (mapService) {
|
||||||
|
mapService.setSelectedIds(newSet);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[selectedIds, setSelectedIds]
|
[selectedIds, setSelectedIds, mapService]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleBulkDelete = useCallback(() => {
|
const handleBulkDelete = useCallback(() => {
|
||||||
@ -1990,13 +2008,11 @@ export const MapPage: React.FC = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleMapClick = useCallback(
|
const handleMapClick = useCallback(
|
||||||
(event: any) => {
|
(event: MapBrowserEvent<any>) => {
|
||||||
if (!mapServiceInstance || isLassoActive) return;
|
if (!mapServiceInstance) return;
|
||||||
const ctrlKey =
|
mapServiceInstance.handleMapClick(event, event.originalEvent.ctrlKey);
|
||||||
event.originalEvent.ctrlKey || event.originalEvent.metaKey;
|
|
||||||
mapServiceInstance.handleMapClick(event, ctrlKey);
|
|
||||||
},
|
},
|
||||||
[mapServiceInstance, isLassoActive]
|
[mapServiceInstance]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -35,11 +35,49 @@ export const RouteCreatePage = observer(() => {
|
|||||||
const [centerLng, setCenterLng] = useState("");
|
const [centerLng, setCenterLng] = useState("");
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
carrierStore.getCarriers(language);
|
carrierStore.getCarriers(language);
|
||||||
articlesStore.getArticleList();
|
articlesStore.getArticleList();
|
||||||
}, [language]);
|
}, [language]);
|
||||||
|
|
||||||
|
const validateCoordinates = (value: string) => {
|
||||||
|
try {
|
||||||
|
const lines = value.trim().split("\n");
|
||||||
|
const coordinates = lines.map((line) => {
|
||||||
|
const [lat, lon] = line
|
||||||
|
.trim()
|
||||||
|
.split(/[\s,]+/)
|
||||||
|
.map(Number);
|
||||||
|
return [lat, lon];
|
||||||
|
});
|
||||||
|
|
||||||
|
if (coordinates.length === 0) {
|
||||||
|
return "Введите хотя бы одну пару координат";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!coordinates.every(
|
||||||
|
(point) => Array.isArray(point) && point.length === 2
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return "Каждая строка должна содержать две координаты";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!coordinates.every((point) =>
|
||||||
|
point.every((coord) => !isNaN(coord) && typeof coord === "number")
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return "Координаты должны быть числами";
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return "Неверный формат координат";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleCreateRoute = async () => {
|
const handleCreateRoute = async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@ -52,16 +90,24 @@ export const RouteCreatePage = observer(() => {
|
|||||||
const center_latitude = centerLat ? Number(centerLat) : undefined;
|
const center_latitude = centerLat ? Number(centerLat) : undefined;
|
||||||
const center_longitude = centerLng ? Number(centerLng) : undefined;
|
const center_longitude = centerLng ? Number(centerLng) : undefined;
|
||||||
const route_direction = direction === "forward";
|
const route_direction = direction === "forward";
|
||||||
|
|
||||||
|
const validationResult = validateCoordinates(routeCoords);
|
||||||
|
if (validationResult !== true) {
|
||||||
|
toast.error(validationResult);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Координаты маршрута как массив массивов чисел
|
// Координаты маршрута как массив массивов чисел
|
||||||
const path = routeCoords
|
const path = routeCoords
|
||||||
|
.trim()
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.map((line) =>
|
.map((line) => {
|
||||||
line
|
const [lat, lon] = line
|
||||||
.split(" ")
|
.trim()
|
||||||
.map((coord) => Number(coord.trim()))
|
.split(/[\s,]+/)
|
||||||
.filter((n) => !isNaN(n))
|
.map(Number);
|
||||||
)
|
return [lat, lon];
|
||||||
.filter((arr) => arr.length === 2);
|
});
|
||||||
|
|
||||||
// Собираем объект маршрута
|
// Собираем объект маршрута
|
||||||
const newRoute: Partial<Route> = {
|
const newRoute: Partial<Route> = {
|
||||||
@ -141,9 +187,33 @@ export const RouteCreatePage = observer(() => {
|
|||||||
className="w-full"
|
className="w-full"
|
||||||
label="Координаты маршрута"
|
label="Координаты маршрута"
|
||||||
multiline
|
multiline
|
||||||
minRows={3}
|
minRows={4}
|
||||||
value={routeCoords}
|
value={routeCoords}
|
||||||
onChange={(e) => setRouteCoords(e.target.value)}
|
onChange={(e) => {
|
||||||
|
const newValue = e.target.value;
|
||||||
|
setRouteCoords(newValue);
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
const lines = routeCoords.split("\n");
|
||||||
|
const lastLine = lines[lines.length - 1];
|
||||||
|
|
||||||
|
// Если мы на последней строке и она не пустая
|
||||||
|
if (lastLine && lastLine.trim()) {
|
||||||
|
e.preventDefault();
|
||||||
|
const newValue = routeCoords + "\n";
|
||||||
|
setRouteCoords(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
error={validateCoordinates(routeCoords) !== true}
|
||||||
|
helperText={
|
||||||
|
typeof validateCoordinates(routeCoords) === "string"
|
||||||
|
? validateCoordinates(routeCoords)
|
||||||
|
: "Введите координаты в формате: широта долгота (можно использовать запятые или пробелы)"
|
||||||
|
}
|
||||||
|
placeholder="55.7558 37.6173
|
||||||
|
55.7539 37.6208"
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
className="w-full"
|
className="w-full"
|
||||||
|
@ -27,6 +27,8 @@ export const RouteEditPage = observer(() => {
|
|||||||
const { editRouteData } = routeStore;
|
const { editRouteData } = routeStore;
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
|
const [coordinates, setCoordinates] = useState<string>("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
const response = await routeStore.getRoute(Number(id));
|
const response = await routeStore.getRoute(Number(id));
|
||||||
@ -37,6 +39,15 @@ export const RouteEditPage = observer(() => {
|
|||||||
fetchData();
|
fetchData();
|
||||||
}, [id, language]);
|
}, [id, language]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (editRouteData.path && editRouteData.path.length > 0) {
|
||||||
|
const formattedPath = editRouteData.path
|
||||||
|
.map((coords) => coords.join(" "))
|
||||||
|
.join("\n");
|
||||||
|
setCoordinates(formattedPath);
|
||||||
|
}
|
||||||
|
}, [editRouteData.path]);
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
await routeStore.editRoute(Number(id));
|
await routeStore.editRoute(Number(id));
|
||||||
@ -44,6 +55,43 @@ export const RouteEditPage = observer(() => {
|
|||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const validateCoordinates = (value: string) => {
|
||||||
|
try {
|
||||||
|
const lines = value.trim().split("\n");
|
||||||
|
const coordinates = lines.map((line) => {
|
||||||
|
const [lat, lon] = line
|
||||||
|
.trim()
|
||||||
|
.split(/[\s,]+/)
|
||||||
|
.map(Number);
|
||||||
|
return [lat, lon];
|
||||||
|
});
|
||||||
|
|
||||||
|
if (coordinates.length === 0) {
|
||||||
|
return "Введите хотя бы одну пару координат";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!coordinates.every(
|
||||||
|
(point) => Array.isArray(point) && point.length === 2
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return "Каждая строка должна содержать две координаты";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!coordinates.every((point) =>
|
||||||
|
point.every((coord) => !isNaN(coord) && typeof coord === "number")
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return "Координаты должны быть числами";
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return "Неверный формат координат";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher />
|
||||||
@ -105,15 +153,46 @@ export const RouteEditPage = observer(() => {
|
|||||||
className="w-full"
|
className="w-full"
|
||||||
label="Координаты маршрута"
|
label="Координаты маршрута"
|
||||||
multiline
|
multiline
|
||||||
minRows={3}
|
minRows={4}
|
||||||
value={editRouteData.path.map((p) => p.join(" ")).join("\n") || ""}
|
value={coordinates}
|
||||||
onChange={(e) =>
|
onChange={(e) => {
|
||||||
routeStore.setEditRouteData({
|
const newValue = e.target.value;
|
||||||
path: e.target.value
|
setCoordinates(newValue);
|
||||||
.split("\n")
|
|
||||||
.map((line) => line.split(" ").map(Number)),
|
const validationResult = validateCoordinates(newValue);
|
||||||
})
|
if (validationResult === true) {
|
||||||
|
const lines = newValue.trim().split("\n");
|
||||||
|
const path = lines.map((line) => {
|
||||||
|
const [lat, lon] = line
|
||||||
|
.trim()
|
||||||
|
.split(/[\s,]+/)
|
||||||
|
.map(Number);
|
||||||
|
return [lat, lon];
|
||||||
|
});
|
||||||
|
routeStore.setEditRouteData({ path });
|
||||||
}
|
}
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
const lines = coordinates.split("\n");
|
||||||
|
const lastLine = lines[lines.length - 1];
|
||||||
|
|
||||||
|
// Если мы на последней строке и она не пустая
|
||||||
|
if (lastLine && lastLine.trim()) {
|
||||||
|
e.preventDefault();
|
||||||
|
const newValue = coordinates + "\n";
|
||||||
|
setCoordinates(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
error={validateCoordinates(coordinates) !== true}
|
||||||
|
helperText={
|
||||||
|
typeof validateCoordinates(coordinates) === "string"
|
||||||
|
? validateCoordinates(coordinates)
|
||||||
|
: "Введите координаты в формате: широта долгота (можно использовать запятые или пробелы)"
|
||||||
|
}
|
||||||
|
placeholder="55.7558 37.6173
|
||||||
|
55.7539 37.6208"
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
className="w-full"
|
className="w-full"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
import { DataGrid, GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
|
||||||
import { languageStore, routeStore } from "@shared";
|
import { carrierStore, languageStore, routeStore } from "@shared";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Map, Pencil, Trash2, Minus } from "lucide-react";
|
import { Map, Pencil, Trash2, Minus } from "lucide-react";
|
||||||
@ -9,6 +9,7 @@ import { LanguageSwitcher } from "@widgets";
|
|||||||
|
|
||||||
export const RouteListPage = observer(() => {
|
export const RouteListPage = observer(() => {
|
||||||
const { routes, getRoutes, deleteRoute } = routeStore;
|
const { routes, getRoutes, deleteRoute } = routeStore;
|
||||||
|
const { carriers, getCarriers } = carrierStore;
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
const [isBulkDeleteModalOpen, setIsBulkDeleteModalOpen] = useState(false);
|
const [isBulkDeleteModalOpen, setIsBulkDeleteModalOpen] = useState(false);
|
||||||
@ -17,19 +18,27 @@ export const RouteListPage = observer(() => {
|
|||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getRoutes();
|
const fetchData = async () => {
|
||||||
|
await getCarriers("ru");
|
||||||
|
await getCarriers("en");
|
||||||
|
await getCarriers("zh");
|
||||||
|
await getRoutes();
|
||||||
|
};
|
||||||
|
fetchData();
|
||||||
}, [language]);
|
}, [language]);
|
||||||
|
|
||||||
const columns: GridColDef[] = [
|
const columns: GridColDef[] = [
|
||||||
{
|
{
|
||||||
field: "carrier",
|
field: "carrier_id",
|
||||||
headerName: "Перевозчик",
|
headerName: "Перевозчик",
|
||||||
width: 250,
|
width: 250,
|
||||||
renderCell: (params: GridRenderCellParams) => {
|
renderCell: (params: GridRenderCellParams) => {
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full flex items-center">
|
<div className="w-full h-full flex items-center">
|
||||||
{params.value ? (
|
{params.value ? (
|
||||||
params.value
|
carriers[language].data.find(
|
||||||
|
(carrier) => carrier.id == params.value
|
||||||
|
)?.short_name
|
||||||
) : (
|
) : (
|
||||||
<Minus size={20} className="text-red-500" />
|
<Minus size={20} className="text-red-500" />
|
||||||
)}
|
)}
|
||||||
@ -105,7 +114,7 @@ export const RouteListPage = observer(() => {
|
|||||||
|
|
||||||
const rows = routes.data.map((route) => ({
|
const rows = routes.data.map((route) => ({
|
||||||
id: route.id,
|
id: route.id,
|
||||||
carrier: route.carrier,
|
carrier_id: route.carrier_id,
|
||||||
route_number: route.route_number,
|
route_number: route.route_number,
|
||||||
route_direction: route.route_direction ? "Прямой" : "Обратный",
|
route_direction: route.route_direction ? "Прямой" : "Обратный",
|
||||||
}));
|
}));
|
||||||
|
@ -8,33 +8,62 @@ import {
|
|||||||
InputLabel,
|
InputLabel,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { ArrowLeft, Loader2, Save } from "lucide-react";
|
import { ArrowLeft, Save } from "lucide-react";
|
||||||
|
import { Loader2 } from "lucide-react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { stationsStore } from "@shared";
|
import { stationsStore, languageStore, cityStore } from "@shared";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { LanguageSwitcher } from "@widgets";
|
import { LanguageSwitcher } from "@widgets";
|
||||||
|
|
||||||
export const StationCreatePage = observer(() => {
|
export const StationCreatePage = observer(() => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [name, setName] = useState("");
|
|
||||||
const [systemName, setSystemName] = useState("");
|
|
||||||
const [direction, setDirection] = useState("");
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const { language } = languageStore;
|
||||||
|
const {
|
||||||
|
createStationData,
|
||||||
|
setCreateCommonData,
|
||||||
|
createStation,
|
||||||
|
setLanguageCreateStationData,
|
||||||
|
} = stationsStore;
|
||||||
|
const { cities, getCities } = cityStore;
|
||||||
|
const [coordinates, setCoordinates] = useState<string>("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
createStationData.common.latitude !== 0 ||
|
||||||
|
createStationData.common.longitude !== 0
|
||||||
|
) {
|
||||||
|
setCoordinates(
|
||||||
|
`${createStationData.common.latitude}, ${createStationData.common.longitude}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [createStationData.common.latitude, createStationData.common.longitude]);
|
||||||
|
|
||||||
const handleCreate = async () => {
|
const handleCreate = async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
await stationsStore.createStation(name, systemName, direction);
|
await createStation();
|
||||||
toast.success("Станция успешно создана");
|
toast.success("Станция успешно создана");
|
||||||
navigate("/station");
|
navigate("/station");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error("Error creating station:", error);
|
||||||
toast.error("Ошибка при создании станции");
|
toast.error("Ошибка при создании станции");
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchCities = async () => {
|
||||||
|
await getCities("ru");
|
||||||
|
await getCities("en");
|
||||||
|
await getCities("zh");
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchCities();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher />
|
||||||
@ -47,44 +76,123 @@ export const StationCreatePage = observer(() => {
|
|||||||
Назад
|
Назад
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-10 w-full items-end">
|
<div className="flex flex-col gap-10 w-full items-end">
|
||||||
<div className="flex gap-10 items-center mb-5 max-w-[80%] self-start">
|
<div className="flex gap-10 items-center mb-5 max-w-[80%] self-start">
|
||||||
<h1 className="text-3xl break-words">{name}</h1>
|
<h1 className="text-3xl break-words">Создание станции</h1>
|
||||||
</div>
|
</div>
|
||||||
<TextField
|
<TextField
|
||||||
className="w-full"
|
fullWidth
|
||||||
label="Название"
|
label="Название"
|
||||||
|
value={createStationData[language].name || ""}
|
||||||
required
|
required
|
||||||
value={name}
|
onChange={(e) =>
|
||||||
onChange={(e) => setName(e.target.value)}
|
setLanguageCreateStationData(language, {
|
||||||
/>
|
name: e.target.value,
|
||||||
<TextField
|
})
|
||||||
className="w-full"
|
}
|
||||||
label="Системное название"
|
|
||||||
required
|
|
||||||
value={systemName}
|
|
||||||
onChange={(e) => setSystemName(e.target.value)}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>Направление</InputLabel>
|
<InputLabel id="direction-label">Прямой/обратный маршрут</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={direction}
|
labelId="direction-label"
|
||||||
label="Направление"
|
value={createStationData.common.direction ? "Прямой" : "Обратный"}
|
||||||
onChange={(e) => setDirection(e.target.value)}
|
label="Прямой/обратный маршрут"
|
||||||
required
|
onChange={(e) =>
|
||||||
|
setCreateCommonData({
|
||||||
|
direction: e.target.value === "Прямой",
|
||||||
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<MenuItem value="forward">Прямое</MenuItem>
|
<MenuItem value="Прямой">Прямой</MenuItem>
|
||||||
<MenuItem value="backward">Обратное</MenuItem>
|
<MenuItem value="Обратный">Обратный</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Описание"
|
||||||
|
value={createStationData[language].description || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
setLanguageCreateStationData(language, {
|
||||||
|
description: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Адрес"
|
||||||
|
value={createStationData[language].address || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
setLanguageCreateStationData(language, {
|
||||||
|
address: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Координаты"
|
||||||
|
value={coordinates}
|
||||||
|
onChange={(e) => {
|
||||||
|
const newValue = e.target.value;
|
||||||
|
setCoordinates(newValue);
|
||||||
|
|
||||||
|
const input = newValue.replace(/,/g, " ").trim();
|
||||||
|
const [latStr, lonStr] = input.split(/\s+/);
|
||||||
|
|
||||||
|
const lat = parseFloat(latStr);
|
||||||
|
const lon = parseFloat(lonStr);
|
||||||
|
|
||||||
|
const isValidLat = !isNaN(lat);
|
||||||
|
const isValidLon = !isNaN(lon);
|
||||||
|
|
||||||
|
if (isValidLat && isValidLon) {
|
||||||
|
setCreateCommonData({
|
||||||
|
latitude: lat,
|
||||||
|
longitude: lon,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setCreateCommonData({
|
||||||
|
latitude: 0,
|
||||||
|
longitude: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
placeholder="Введите координаты в формате: широта долгота (можно использовать запятые или пробелы)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormControl fullWidth>
|
||||||
|
<InputLabel>Город</InputLabel>
|
||||||
|
<Select
|
||||||
|
value={createStationData.common.city_id || ""}
|
||||||
|
label="Город"
|
||||||
|
onChange={(e) => {
|
||||||
|
const selectedCity = cities["ru"].data.find(
|
||||||
|
(city) => city.id === e.target.value
|
||||||
|
);
|
||||||
|
setCreateCommonData({
|
||||||
|
city_id: e.target.value as number,
|
||||||
|
city: selectedCity?.name || "",
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{cities["ru"].data.map((city) => (
|
||||||
|
<MenuItem key={city.id} value={city.id}>
|
||||||
|
{city.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
|
||||||
className="w-min flex gap-2 items-center"
|
className="w-min flex gap-2 items-center"
|
||||||
startIcon={<Save size={20} />}
|
startIcon={<Save size={20} />}
|
||||||
onClick={handleCreate}
|
onClick={handleCreate}
|
||||||
disabled={isLoading || !name || !systemName || !direction}
|
disabled={isLoading || !createStationData[language]?.name}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<Loader2 size={20} className="animate-spin" />
|
<Loader2 size={20} className="animate-spin" />
|
||||||
|
@ -29,6 +29,18 @@ export const StationEditPage = observer(() => {
|
|||||||
setLanguageEditStationData,
|
setLanguageEditStationData,
|
||||||
} = stationsStore;
|
} = stationsStore;
|
||||||
const { cities, getCities } = cityStore;
|
const { cities, getCities } = cityStore;
|
||||||
|
const [coordinates, setCoordinates] = useState<string>("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
editStationData.common.latitude !== 0 ||
|
||||||
|
editStationData.common.longitude !== 0
|
||||||
|
) {
|
||||||
|
setCoordinates(
|
||||||
|
`${editStationData.common.latitude}, ${editStationData.common.longitude}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [editStationData.common.latitude, editStationData.common.longitude]);
|
||||||
|
|
||||||
const handleEdit = async () => {
|
const handleEdit = async () => {
|
||||||
try {
|
try {
|
||||||
@ -71,7 +83,7 @@ export const StationEditPage = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-10 w-full items-end">
|
<div className="flex flex-col gap-10 w-full items-end">
|
||||||
<div className="flex gap-10 items-center mb-5 max-w-[80%]">
|
<div className="flex gap-10 items-center mb-5 max-w-[80%] self-start">
|
||||||
<h1 className="text-3xl break-words">{editStationData.ru.name}</h1>
|
<h1 className="text-3xl break-words">{editStationData.ru.name}</h1>
|
||||||
</div>
|
</div>
|
||||||
<TextField
|
<TextField
|
||||||
@ -128,16 +140,33 @@ export const StationEditPage = observer(() => {
|
|||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
label="Координаты"
|
label="Координаты"
|
||||||
value={`${editStationData.common.latitude} ${editStationData.common.longitude}`}
|
value={coordinates}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const [latitude, longitude] = e.target.value.split(" ").map(Number);
|
const newValue = e.target.value;
|
||||||
if (!isNaN(latitude) && !isNaN(longitude)) {
|
setCoordinates(newValue);
|
||||||
|
|
||||||
|
const input = newValue.replace(/,/g, " ").trim();
|
||||||
|
const [latStr, lonStr] = input.split(/\s+/);
|
||||||
|
|
||||||
|
const lat = parseFloat(latStr);
|
||||||
|
const lon = parseFloat(lonStr);
|
||||||
|
|
||||||
|
const isValidLat = !isNaN(lat);
|
||||||
|
const isValidLon = !isNaN(lon);
|
||||||
|
|
||||||
|
if (isValidLat && isValidLon) {
|
||||||
setEditCommonData({
|
setEditCommonData({
|
||||||
latitude: latitude,
|
latitude: lat,
|
||||||
longitude: longitude,
|
longitude: lon,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setEditCommonData({
|
||||||
|
latitude: 0,
|
||||||
|
longitude: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
placeholder="Введите координаты в формате: широта долгота (можно использовать запятые или пробелы)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
@ -146,7 +175,7 @@ export const StationEditPage = observer(() => {
|
|||||||
value={editStationData.common.city_id || ""}
|
value={editStationData.common.city_id || ""}
|
||||||
label="Город"
|
label="Город"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const selectedCity = cities[language].data.find(
|
const selectedCity = cities["ru"].data.find(
|
||||||
(city) => city.id === e.target.value
|
(city) => city.id === e.target.value
|
||||||
);
|
);
|
||||||
setEditCommonData({
|
setEditCommonData({
|
||||||
@ -155,7 +184,7 @@ export const StationEditPage = observer(() => {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{cities[language].data.map((city) => (
|
{cities["ru"].data.map((city) => (
|
||||||
<MenuItem key={city.id} value={city.id}>
|
<MenuItem key={city.id} value={city.id}>
|
||||||
{city.name}
|
{city.name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
@ -21,7 +21,7 @@ export const StationPreviewPage = observer(() => {
|
|||||||
}, [id, language]);
|
}, [id, language]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper className="w-full h-full p-3 flex flex-col gap-10">
|
<Paper className="w-full p-3 py-5 flex flex-col gap-10">
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher />
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<button
|
<button
|
||||||
|
@ -9,7 +9,7 @@ export const MEDIA_TYPE_LABELS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const MEDIA_TYPE_VALUES = {
|
export const MEDIA_TYPE_VALUES = {
|
||||||
photo: 1,
|
image: 1,
|
||||||
video: 2,
|
video: 2,
|
||||||
icon: 3,
|
icon: 3,
|
||||||
thumbnail: 3,
|
thumbnail: 3,
|
||||||
|
@ -132,7 +132,7 @@ export const PreviewMediaDialog = observer(
|
|||||||
sx={{ width: "50%" }}
|
sx={{ width: "50%" }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box className="flex gap-4">
|
<Box className="flex gap-4 h-[40vh]">
|
||||||
<Paper
|
<Paper
|
||||||
elevation={2}
|
elevation={2}
|
||||||
sx={{
|
sx={{
|
||||||
@ -142,7 +142,6 @@ export const PreviewMediaDialog = observer(
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
}}
|
}}
|
||||||
className="max-h-[40vh]"
|
|
||||||
>
|
>
|
||||||
<MediaViewer
|
<MediaViewer
|
||||||
media={{
|
media={{
|
||||||
@ -150,7 +149,6 @@ export const PreviewMediaDialog = observer(
|
|||||||
media_type: media.media_type,
|
media_type: media.media_type,
|
||||||
filename: media.filename,
|
filename: media.filename,
|
||||||
}}
|
}}
|
||||||
fullHeight
|
|
||||||
/>
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
@ -102,7 +102,6 @@ export const SelectMediaDialog = observer(
|
|||||||
filteredMedia = filteredMedia.filter(
|
filteredMedia = filteredMedia.filter(
|
||||||
(mediaItem) => mediaItem.media_type === mediaType
|
(mediaItem) => mediaItem.media_type === mediaType
|
||||||
);
|
);
|
||||||
console.log(filteredMedia);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -163,7 +162,13 @@ export const SelectMediaDialog = observer(
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ListItemText primary={mediaItem.media_name} />
|
<ListItemText
|
||||||
|
primary={
|
||||||
|
mediaItem.media_name
|
||||||
|
? mediaItem.media_name
|
||||||
|
: mediaItem.filename
|
||||||
|
}
|
||||||
|
/>
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -31,7 +31,7 @@ interface UploadMediaDialogProps {
|
|||||||
media_type: number;
|
media_type: number;
|
||||||
}) => void;
|
}) => void;
|
||||||
afterUploadSight?: (id: string) => void;
|
afterUploadSight?: (id: string) => void;
|
||||||
hardcodeType?: "thumbnail" | "watermark_lu" | "watermark_rd" | null;
|
hardcodeType?: "thumbnail" | "watermark_lu" | "watermark_rd" | "image" | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UploadMediaDialog = observer(
|
export const UploadMediaDialog = observer(
|
||||||
|
@ -64,7 +64,7 @@ class CarrierStore {
|
|||||||
getCarriers = async (language: Language) => {
|
getCarriers = async (language: Language) => {
|
||||||
if (this.carriers[language as keyof Carriers].loaded) return;
|
if (this.carriers[language as keyof Carriers].loaded) return;
|
||||||
|
|
||||||
const response = await authInstance.get("/carrier");
|
const response = await languageInstance(language).get("/carrier");
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.carriers[language as keyof Carriers].data = response.data;
|
this.carriers[language as keyof Carriers].data = response.data;
|
||||||
@ -108,46 +108,94 @@ class CarrierStore {
|
|||||||
return this.carrier[id];
|
return this.carrier[id];
|
||||||
};
|
};
|
||||||
|
|
||||||
createCarrier = async (
|
createCarrierData = {
|
||||||
|
city_id: 0,
|
||||||
|
logo: "",
|
||||||
|
ru: {
|
||||||
|
full_name: "",
|
||||||
|
short_name: "",
|
||||||
|
slogan: "",
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
full_name: "",
|
||||||
|
short_name: "",
|
||||||
|
slogan: "",
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
full_name: "",
|
||||||
|
short_name: "",
|
||||||
|
slogan: "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
setCreateCarrierData = (
|
||||||
fullName: string,
|
fullName: string,
|
||||||
shortName: string,
|
shortName: string,
|
||||||
cityId: number,
|
cityId: number,
|
||||||
slogan: string,
|
slogan: string,
|
||||||
logoId: string
|
logoId: string,
|
||||||
|
language: Language
|
||||||
) => {
|
) => {
|
||||||
const { language } = languageStore;
|
this.createCarrierData.city_id = cityId;
|
||||||
const cityName =
|
this.createCarrierData.logo = logoId;
|
||||||
cityStore.cities[language].data.find((city) => city.id === cityId)
|
this.createCarrierData[language] = {
|
||||||
?.name || "";
|
|
||||||
|
|
||||||
const response = await languageInstance(language).post("/carrier", {
|
|
||||||
full_name: fullName,
|
full_name: fullName,
|
||||||
short_name: shortName,
|
short_name: shortName,
|
||||||
|
slogan: slogan,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
createCarrier = async () => {
|
||||||
|
const { language } = languageStore;
|
||||||
|
const cityName =
|
||||||
|
cityStore.cities[language].data.find(
|
||||||
|
(city) => city.id === this.createCarrierData.city_id
|
||||||
|
)?.name || "";
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
full_name: this.createCarrierData[language].full_name,
|
||||||
|
short_name: this.createCarrierData[language].short_name,
|
||||||
city: cityName,
|
city: cityName,
|
||||||
city_id: cityId,
|
city_id: this.createCarrierData.city_id,
|
||||||
slogan,
|
slogan: this.createCarrierData[language].slogan,
|
||||||
logo: logoId,
|
...(this.createCarrierData.logo
|
||||||
});
|
? { logo: this.createCarrierData.logo }
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await languageInstance(language).post("/carrier", payload);
|
||||||
|
|
||||||
const carrierId = response.data.id;
|
const carrierId = response.data.id;
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.carriers[language].data.push(response.data);
|
||||||
|
});
|
||||||
|
|
||||||
// Create translations for other languages
|
// Create translations for other languages
|
||||||
for (const lang of ["ru", "en", "zh"].filter((l) => l !== language)) {
|
for (const lang of ["ru", "en", "zh"].filter((l) => l !== language)) {
|
||||||
await languageInstance(lang as Language).patch(`/carrier/${carrierId}`, {
|
const patchPayload = {
|
||||||
full_name: fullName,
|
// @ts-ignore
|
||||||
short_name: shortName,
|
full_name: this.createCarrierData[lang as any].full_name as string,
|
||||||
|
// @ts-ignore
|
||||||
|
short_name: this.createCarrierData[lang as any].short_name as string,
|
||||||
city: cityName,
|
city: cityName,
|
||||||
city_id: cityId,
|
city_id: this.createCarrierData.city_id,
|
||||||
slogan,
|
// @ts-ignore
|
||||||
logo: logoId,
|
slogan: this.createCarrierData[lang as any].slogan as string,
|
||||||
});
|
...(this.createCarrierData.logo
|
||||||
}
|
? { logo: this.createCarrierData.logo }
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
await languageInstance(lang as Language).patch(
|
||||||
|
`/carrier/${carrierId}`,
|
||||||
|
patchPayload
|
||||||
|
);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
for (const language of ["ru", "en", "zh"] as const) {
|
this.carriers[lang as keyof Carriers].data.push(response.data);
|
||||||
this.carriers[language].data.push(response.data);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
editCarrierData = {
|
editCarrierData = {
|
||||||
@ -206,31 +254,29 @@ class CarrierStore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
editCarrier = async (id: number) => {
|
editCarrier = async (id: number) => {
|
||||||
|
const { language } = languageStore;
|
||||||
const cityName =
|
const cityName =
|
||||||
cityStore.cities[languageStore.language].data.find(
|
cityStore.cities[language].data.find(
|
||||||
(city) => city.id === this.editCarrierData.city_id
|
(city) => city.id === this.editCarrierData.city_id
|
||||||
)?.name || "";
|
)?.name || "";
|
||||||
|
|
||||||
for (const language of ["ru", "en", "zh"] as const) {
|
for (const lang of ["ru", "en", "zh"] as const) {
|
||||||
const response = await languageInstance(language).patch(
|
const response = await languageInstance(lang).patch(`/carrier/${id}`, {
|
||||||
`/carrier/${id}`,
|
...this.editCarrierData[lang],
|
||||||
{
|
|
||||||
...this.editCarrierData[language],
|
|
||||||
city: cityName,
|
city: cityName,
|
||||||
|
city_id: this.editCarrierData.city_id,
|
||||||
logo: this.editCarrierData.logo,
|
logo: this.editCarrierData.logo,
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
if (this.carrier[id]) {
|
if (this.carrier[id]) {
|
||||||
this.carrier[id][language] = response.data;
|
this.carrier[id][lang] = response.data;
|
||||||
}
|
}
|
||||||
for (const language of ["ru", "en", "zh"] as const) {
|
|
||||||
this.carriers[language].data = this.carriers[language].data.map(
|
this.carriers[lang].data = this.carriers[lang].data.map(
|
||||||
(carrier: Carrier) =>
|
(carrier: Carrier) =>
|
||||||
carrier.id === id ? { ...carrier, ...response.data } : carrier
|
carrier.id === id ? { ...carrier, ...response.data } : carrier
|
||||||
);
|
);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -85,7 +85,7 @@ class CityStore {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await authInstance.get(`/city`);
|
const response = await languageInstance(language).get(`/city`);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.cities[language].data = response.data;
|
this.cities[language].data = response.data;
|
||||||
@ -98,7 +98,7 @@ class CityStore {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await authInstance.get(`/city/${code}`);
|
const response = await languageInstance(language).get(`/city/${code}`);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
if (!this.city[code]) {
|
if (!this.city[code]) {
|
||||||
@ -170,15 +170,20 @@ class CityStore {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Create city in primary language
|
// Create city in primary language
|
||||||
const cityResponse = await languageInstance(language).post("/city", {
|
const cityPayload = {
|
||||||
name,
|
name,
|
||||||
country:
|
country:
|
||||||
countryStore.countries[language as keyof CashedCountries]?.data.find(
|
countryStore.countries[language as keyof CashedCountries]?.data.find(
|
||||||
(c) => c.code === country_code
|
(c) => c.code === country_code
|
||||||
)?.name || "",
|
)?.name || "",
|
||||||
country_code,
|
country_code,
|
||||||
arms: arms || "",
|
...(arms ? { arms } : {}),
|
||||||
});
|
};
|
||||||
|
|
||||||
|
const cityResponse = await languageInstance(language).post(
|
||||||
|
"/city",
|
||||||
|
cityPayload
|
||||||
|
);
|
||||||
|
|
||||||
const cityId = cityResponse.data.id;
|
const cityId = cityResponse.data.id;
|
||||||
|
|
||||||
@ -194,14 +199,16 @@ class CityStore {
|
|||||||
(c) => c.code === country_code
|
(c) => c.code === country_code
|
||||||
)?.name || "";
|
)?.name || "";
|
||||||
|
|
||||||
const patchResponse = await languageInstance(secondaryLanguage).patch(
|
const patchPayload = {
|
||||||
`/city/${cityId}`,
|
|
||||||
{
|
|
||||||
name: secondaryName || "",
|
name: secondaryName || "",
|
||||||
country: countryName,
|
country: countryName,
|
||||||
country_code: country_code || "",
|
country_code: country_code || "",
|
||||||
arms: arms || "",
|
...(arms ? { arms } : {}),
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const patchResponse = await languageInstance(secondaryLanguage).patch(
|
||||||
|
`/city/${cityId}`,
|
||||||
|
patchPayload
|
||||||
);
|
);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
|
@ -91,14 +91,16 @@ class CountryStore {
|
|||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteCountry = async (code: string, language: keyof CashedCountries) => {
|
deleteCountry = async (code: string) => {
|
||||||
await authInstance.delete(`/country/${code}`);
|
await authInstance.delete(`/country/${code}`);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.countries[language].data = this.countries[language].data.filter(
|
for (const lang of ["ru", "en", "zh"]) {
|
||||||
(country) => country.code !== code
|
this.countries[lang as keyof CashedCountries].data = this.countries[
|
||||||
);
|
lang as keyof CashedCountries
|
||||||
this.countries[language].loaded = true;
|
].data.filter((country) => country.code !== code);
|
||||||
|
}
|
||||||
|
|
||||||
this.country[code] = {
|
this.country[code] = {
|
||||||
ru: null,
|
ru: null,
|
||||||
en: null,
|
en: null,
|
||||||
|
@ -64,6 +64,10 @@ type Station = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type CreateStationData = {
|
||||||
|
[key in Language]: StationLanguageData;
|
||||||
|
} & { common: StationCommonData };
|
||||||
|
|
||||||
class StationsStore {
|
class StationsStore {
|
||||||
stations: Station[] = [];
|
stations: Station[] = [];
|
||||||
station: Station | null = null;
|
station: Station | null = null;
|
||||||
@ -139,6 +143,51 @@ class StationsStore {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
createStationData: CreateStationData = {
|
||||||
|
ru: {
|
||||||
|
name: "",
|
||||||
|
system_name: "",
|
||||||
|
description: "",
|
||||||
|
address: "",
|
||||||
|
loaded: false,
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
name: "",
|
||||||
|
system_name: "",
|
||||||
|
description: "",
|
||||||
|
address: "",
|
||||||
|
loaded: false,
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
name: "",
|
||||||
|
system_name: "",
|
||||||
|
description: "",
|
||||||
|
address: "",
|
||||||
|
loaded: false,
|
||||||
|
},
|
||||||
|
common: {
|
||||||
|
city: "",
|
||||||
|
city_id: 0,
|
||||||
|
direction: false,
|
||||||
|
icon: "",
|
||||||
|
latitude: 0,
|
||||||
|
longitude: 0,
|
||||||
|
offset_x: 0,
|
||||||
|
offset_y: 0,
|
||||||
|
transfers: {
|
||||||
|
bus: "",
|
||||||
|
metro_blue: "",
|
||||||
|
metro_green: "",
|
||||||
|
metro_orange: "",
|
||||||
|
metro_purple: "",
|
||||||
|
metro_red: "",
|
||||||
|
train: "",
|
||||||
|
tram: "",
|
||||||
|
trolleybus: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
makeAutoObservable(this);
|
makeAutoObservable(this);
|
||||||
}
|
}
|
||||||
@ -172,9 +221,6 @@ class StationsStore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
getEditStation = async (id: number) => {
|
getEditStation = async (id: number) => {
|
||||||
if (this.editStationData.ru.loaded) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const ruResponse = await languageInstance("ru").get(`/station/${id}`);
|
const ruResponse = await languageInstance("ru").get(`/station/${id}`);
|
||||||
const enResponse = await languageInstance("en").get(`/station/${id}`);
|
const enResponse = await languageInstance("en").get(`/station/${id}`);
|
||||||
const zhResponse = await languageInstance("zh").get(`/station/${id}`);
|
const zhResponse = await languageInstance("zh").get(`/station/${id}`);
|
||||||
@ -336,35 +382,125 @@ class StationsStore {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
createStation = async (
|
setCreateCommonData = (data: Partial<StationCommonData>) => {
|
||||||
name: string,
|
this.createStationData.common = {
|
||||||
systemName: string,
|
...this.createStationData.common,
|
||||||
direction: string
|
...data,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
setLanguageCreateStationData = (
|
||||||
|
language: Language,
|
||||||
|
data: Partial<StationLanguageData>
|
||||||
) => {
|
) => {
|
||||||
const response = await authInstance.post("/station", {
|
this.createStationData[language] = {
|
||||||
station_name: name,
|
...this.createStationData[language],
|
||||||
system_name: systemName,
|
...data,
|
||||||
direction,
|
|
||||||
});
|
|
||||||
runInAction(() => {
|
|
||||||
this.stations.push(response.data);
|
|
||||||
const newStation = response.data as Station;
|
|
||||||
if (!this.stationPreview[newStation.id]) {
|
|
||||||
this.stationPreview[newStation.id] = {
|
|
||||||
ru: { loaded: false, data: newStation },
|
|
||||||
en: { loaded: false, data: newStation },
|
|
||||||
zh: { loaded: false, data: newStation },
|
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
createStation = async () => {
|
||||||
|
const { language } = languageStore;
|
||||||
|
let commonDataPayload: Partial<StationCommonData> = {
|
||||||
|
city_id: this.createStationData.common.city_id,
|
||||||
|
direction: this.createStationData.common.direction,
|
||||||
|
icon: this.createStationData.common.icon,
|
||||||
|
latitude: this.createStationData.common.latitude,
|
||||||
|
longitude: this.createStationData.common.longitude,
|
||||||
|
offset_x: this.createStationData.common.offset_x,
|
||||||
|
offset_y: this.createStationData.common.offset_y,
|
||||||
|
transfers: this.createStationData.common.transfers,
|
||||||
|
city: this.createStationData.common.city,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.createStationData.common.icon === "") {
|
||||||
|
delete commonDataPayload.icon;
|
||||||
}
|
}
|
||||||
this.stationPreview[newStation.id]["ru"] = {
|
|
||||||
loaded: true,
|
// First create station in Russian
|
||||||
data: newStation,
|
const { name, description, address } = this.createStationData[language];
|
||||||
};
|
const response = await languageInstance(language).post("/station", {
|
||||||
this.stationPreview[newStation.id]["en"] = {
|
name: name || "",
|
||||||
loaded: true,
|
system_name: name || "", // system_name is often derived from name
|
||||||
data: newStation,
|
description: description || "",
|
||||||
|
address: address || "",
|
||||||
|
...commonDataPayload,
|
||||||
|
});
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.stationLists[language].data.push(response.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
const stationId = response.data.id;
|
||||||
|
|
||||||
|
// Then update for other languages
|
||||||
|
for (const lang of ["ru", "en", "zh"].filter(
|
||||||
|
(lang) => lang !== language
|
||||||
|
) as Language[]) {
|
||||||
|
const { name, description, address } = this.createStationData[lang];
|
||||||
|
const response = await languageInstance(lang).patch(
|
||||||
|
`/station/${stationId}`,
|
||||||
|
{
|
||||||
|
name: name || "",
|
||||||
|
system_name: name || "", // system_name is often derived from name
|
||||||
|
description: description || "",
|
||||||
|
address: address || "",
|
||||||
|
...commonDataPayload,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.stationLists[lang].data.push(response.data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.createStationData = {
|
||||||
|
ru: {
|
||||||
|
name: "",
|
||||||
|
system_name: "",
|
||||||
|
description: "",
|
||||||
|
address: "",
|
||||||
|
loaded: false,
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
name: "",
|
||||||
|
system_name: "",
|
||||||
|
description: "",
|
||||||
|
address: "",
|
||||||
|
loaded: false,
|
||||||
|
},
|
||||||
|
zh: {
|
||||||
|
name: "",
|
||||||
|
system_name: "",
|
||||||
|
description: "",
|
||||||
|
address: "",
|
||||||
|
loaded: false,
|
||||||
|
},
|
||||||
|
common: {
|
||||||
|
city: "",
|
||||||
|
city_id: 0,
|
||||||
|
direction: false,
|
||||||
|
icon: "",
|
||||||
|
latitude: 0,
|
||||||
|
longitude: 0,
|
||||||
|
offset_x: 0,
|
||||||
|
offset_y: 0,
|
||||||
|
transfers: {
|
||||||
|
bus: "",
|
||||||
|
metro_blue: "",
|
||||||
|
metro_green: "",
|
||||||
|
metro_orange: "",
|
||||||
|
metro_purple: "",
|
||||||
|
metro_red: "",
|
||||||
|
train: "",
|
||||||
|
tram: "",
|
||||||
|
trolleybus: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Reset editStationData when navigating away or after saving
|
// Reset editStationData when navigating away or after saving
|
||||||
|
@ -5,7 +5,7 @@ import { editSightStore } from "@shared";
|
|||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
interface ImageUploadCardProps {
|
interface ImageUploadCardProps {
|
||||||
title: string;
|
title: string;
|
||||||
imageKey?: "thumbnail" | "watermark_lu" | "watermark_rd";
|
imageKey?: "thumbnail" | "watermark_lu" | "watermark_rd" | "image";
|
||||||
imageUrl: string | null | undefined;
|
imageUrl: string | null | undefined;
|
||||||
onImageClick: () => void;
|
onImageClick: () => void;
|
||||||
onDeleteImageClick: () => void;
|
onDeleteImageClick: () => void;
|
||||||
|
Reference in New Issue
Block a user