feat: update transfers
This commit is contained in:
7
.env
7
.env
@@ -1,3 +1,4 @@
|
||||
VITE_API_URL='https://wn.krbl.ru'
|
||||
VITE_REACT_APP ='https://wn.krbl.ru'
|
||||
VITE_KRBL_MEDIA='https://wn.krbl.ru/media/'
|
||||
VITE_API_URL='https://content.wn.polygon.unprism.ru'
|
||||
VITE_REACT_APP ='https://content.wn.polygon.unprism.ru'
|
||||
VITE_KRBL_MEDIA='https://content.wn.polygon.unprism.ru//media/'
|
||||
VITE_NEED_AUTH='false'
|
||||
@@ -51,7 +51,9 @@ import {
|
||||
|
||||
const PublicRoute = ({ children }: { children: React.ReactNode }) => {
|
||||
const { isAuthenticated } = authStore;
|
||||
if (isAuthenticated) {
|
||||
const need_auth = import.meta.env.VITE_NEED_AUTH == "true";
|
||||
|
||||
if (isAuthenticated || !need_auth) {
|
||||
return <Navigate to="/map" replace />;
|
||||
}
|
||||
return <>{children}</>;
|
||||
@@ -59,13 +61,18 @@ const PublicRoute = ({ children }: { children: React.ReactNode }) => {
|
||||
|
||||
const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
|
||||
const { isAuthenticated } = authStore;
|
||||
const need_auth = import.meta.env.VITE_NEED_AUTH == "true";
|
||||
|
||||
const location = useLocation();
|
||||
if (!isAuthenticated) {
|
||||
|
||||
if (!isAuthenticated && need_auth) {
|
||||
return <Navigate to="/login" replace />;
|
||||
}
|
||||
|
||||
if (location.pathname === "/") {
|
||||
return <Navigate to="/map" replace />;
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
|
||||
@@ -33,8 +33,10 @@ export const NavigationItemComponent: React.FC<NavigationItemProps> = ({
|
||||
const [isExpanded, setIsExpanded] = React.useState(false);
|
||||
const { payload } = authStore;
|
||||
|
||||
const need_auth = import.meta.env.VITE_NEED_AUTH == "true";
|
||||
|
||||
// @ts-ignore
|
||||
const isAdmin = payload?.is_admin || false;
|
||||
const isAdmin = payload?.is_admin || false || !need_auth;
|
||||
|
||||
const isActive = item.path ? location.pathname.startsWith(item.path) : false;
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
languageStore,
|
||||
routeStore,
|
||||
selectedCityStore,
|
||||
stationsStore,
|
||||
} from "@shared";
|
||||
import { EditStationModal } from "../../widgets/modals/EditStationModal";
|
||||
|
||||
@@ -185,6 +186,19 @@ const LinkedItemsContentsInner = <
|
||||
setPosition(linkedItems.length + 1);
|
||||
}, [linkedItems.length]);
|
||||
|
||||
const getStationTransfers = (stationId: number, fallbackTransfers?: any) => {
|
||||
const { stationLists } = stationsStore;
|
||||
for (const lang of ["ru", "en", "zh"] as const) {
|
||||
const station = stationLists[lang].data.find(
|
||||
(s: any) => s.id === stationId
|
||||
);
|
||||
if (station?.transfers) {
|
||||
return station.transfers;
|
||||
}
|
||||
}
|
||||
return fallbackTransfers;
|
||||
};
|
||||
|
||||
const onDragEnd = (result: DropResult) => {
|
||||
if (!result.destination) return;
|
||||
|
||||
@@ -198,7 +212,14 @@ const LinkedItemsContentsInner = <
|
||||
|
||||
authInstance
|
||||
.post(`/${parentResource}/${parentId}/${childResource}`, {
|
||||
stations: reorderedItems.map((item) => ({ id: item.id })),
|
||||
stations: reorderedItems.map((item) => {
|
||||
const stationData: any = { id: item.id };
|
||||
const transfers = getStationTransfers(item.id, item.transfers);
|
||||
if (transfers) {
|
||||
stationData.transfers = transfers;
|
||||
}
|
||||
return stationData;
|
||||
}),
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error updating station order:", error);
|
||||
@@ -245,11 +266,29 @@ const LinkedItemsContentsInner = <
|
||||
const linkItem = () => {
|
||||
if (selectedItemId !== null) {
|
||||
setError(null);
|
||||
const selectedItem = allItems.find((item) => item.id === selectedItemId);
|
||||
const requestData = {
|
||||
stations: insertAtPosition(
|
||||
linkedItems.map((item) => ({ id: item.id })),
|
||||
linkedItems.map((item) => {
|
||||
const stationData: any = { id: item.id };
|
||||
const transfers = getStationTransfers(item.id, item.transfers);
|
||||
if (transfers) {
|
||||
stationData.transfers = transfers;
|
||||
}
|
||||
return stationData;
|
||||
}),
|
||||
position,
|
||||
{ id: selectedItemId }
|
||||
(() => {
|
||||
const newStationData: any = { id: selectedItemId };
|
||||
const transfers = getStationTransfers(
|
||||
selectedItemId,
|
||||
selectedItem?.transfers
|
||||
);
|
||||
if (transfers) {
|
||||
newStationData.transfers = transfers;
|
||||
}
|
||||
return newStationData;
|
||||
})()
|
||||
),
|
||||
};
|
||||
|
||||
@@ -331,10 +370,25 @@ const LinkedItemsContentsInner = <
|
||||
|
||||
setError(null);
|
||||
setIsLinkingBulk(true);
|
||||
const selectedStations = Array.from(selectedItems).map((id) => ({ id }));
|
||||
const selectedStations = Array.from(selectedItems).map((id) => {
|
||||
const item = allItems.find((item) => item.id === id);
|
||||
const stationData: any = { id };
|
||||
const transfers = getStationTransfers(id, item?.transfers);
|
||||
if (transfers) {
|
||||
stationData.transfers = transfers;
|
||||
}
|
||||
return stationData;
|
||||
});
|
||||
const requestData = {
|
||||
stations: [
|
||||
...linkedItems.map((item) => ({ id: item.id })),
|
||||
...linkedItems.map((item) => {
|
||||
const stationData: any = { id: item.id };
|
||||
const transfers = getStationTransfers(item.id, item.transfers);
|
||||
if (transfers) {
|
||||
stationData.transfers = transfers;
|
||||
}
|
||||
return stationData;
|
||||
}),
|
||||
...selectedStations,
|
||||
],
|
||||
};
|
||||
|
||||
@@ -8,9 +8,14 @@ import {
|
||||
} from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Eye, Pencil, Trash2, Minus } from "lucide-react";
|
||||
import { Eye, Pencil, Trash2, Minus, Route } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { CreateButton, DeleteModal, LanguageSwitcher } from "@widgets";
|
||||
import {
|
||||
CreateButton,
|
||||
DeleteModal,
|
||||
LanguageSwitcher,
|
||||
EditStationTransfersModal,
|
||||
} from "@widgets";
|
||||
import { Box, CircularProgress } from "@mui/material";
|
||||
|
||||
export const StationListPage = observer(() => {
|
||||
@@ -18,7 +23,11 @@ export const StationListPage = observer(() => {
|
||||
const navigate = useNavigate();
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [isBulkDeleteModalOpen, setIsBulkDeleteModalOpen] = useState(false);
|
||||
const [isTransfersModalOpen, setIsTransfersModalOpen] = useState(false);
|
||||
const [rowId, setRowId] = useState<number | null>(null);
|
||||
const [selectedStationId, setSelectedStationId] = useState<number | null>(
|
||||
null
|
||||
);
|
||||
const [ids, setIds] = useState<number[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { language } = languageStore;
|
||||
@@ -88,7 +97,7 @@ export const StationListPage = observer(() => {
|
||||
{
|
||||
field: "actions",
|
||||
headerName: "Действия",
|
||||
width: 140,
|
||||
width: 200,
|
||||
align: "center",
|
||||
headerAlign: "center",
|
||||
sortable: false,
|
||||
@@ -102,6 +111,15 @@ export const StationListPage = observer(() => {
|
||||
<button onClick={() => navigate(`/station/${params.row.id}`)}>
|
||||
<Eye size={20} className="text-green-500" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setSelectedStationId(params.row.id);
|
||||
setIsTransfersModalOpen(true);
|
||||
}}
|
||||
title="Редактировать пересадки"
|
||||
>
|
||||
<Route size={20} className="text-purple-500" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsDeleteModalOpen(true);
|
||||
@@ -205,6 +223,15 @@ export const StationListPage = observer(() => {
|
||||
setIsBulkDeleteModalOpen(false);
|
||||
}}
|
||||
/>
|
||||
|
||||
<EditStationTransfersModal
|
||||
open={isTransfersModalOpen}
|
||||
onClose={() => {
|
||||
setIsTransfersModalOpen(false);
|
||||
setSelectedStationId(null);
|
||||
}}
|
||||
stationId={selectedStationId}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { makeAutoObservable, runInAction } from "mobx";
|
||||
import { authInstance } from "@shared";
|
||||
import { authInstance, languageInstance, languageStore } from "@shared";
|
||||
|
||||
export type Route = {
|
||||
route_name: string;
|
||||
@@ -89,11 +89,43 @@ class RouteStore {
|
||||
};
|
||||
|
||||
saveRouteStations = async (routeId: number, stationId: number) => {
|
||||
await authInstance.patch(`/route/${routeId}/station`, {
|
||||
...this.routeStations[routeId]?.find(
|
||||
(station) => station.id === stationId
|
||||
),
|
||||
const { language } = languageStore;
|
||||
|
||||
// Получаем актуальные данные станции с сервера
|
||||
const stationResponse = await languageInstance(language).get(
|
||||
`/station/${stationId}`
|
||||
);
|
||||
const fullStationData = stationResponse.data;
|
||||
|
||||
// Получаем отредактированные данные из локального кеша
|
||||
const editedStationData = this.routeStations[routeId]?.find(
|
||||
(station) => station.id === stationId
|
||||
);
|
||||
|
||||
// Формируем данные для отправки: все поля станции + отредактированные offset
|
||||
const dataToSend: any = {
|
||||
station_id: stationId,
|
||||
offset_x: editedStationData?.offset_x ?? fullStationData.offset_x ?? 0,
|
||||
offset_y: editedStationData?.offset_y ?? fullStationData.offset_y ?? 0,
|
||||
align: editedStationData?.align ?? fullStationData.align ?? 0,
|
||||
transfers: fullStationData.transfers || {},
|
||||
};
|
||||
|
||||
await authInstance.patch(`/route/${routeId}/station`, dataToSend);
|
||||
|
||||
// Обновляем локальный кеш после успешного сохранения
|
||||
runInAction(() => {
|
||||
if (this.routeStations[routeId]) {
|
||||
this.routeStations[routeId] = this.routeStations[routeId].map(
|
||||
(station) =>
|
||||
station.id === stationId
|
||||
? {
|
||||
...station,
|
||||
...dataToSend,
|
||||
}
|
||||
: station
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { authInstance, languageInstance, languageStore } from "@shared";
|
||||
import { makeAutoObservable, runInAction } from "mobx";
|
||||
import { routeStore } from "../RouteStore";
|
||||
|
||||
type Language = "ru" | "en" | "zh";
|
||||
|
||||
@@ -546,6 +547,99 @@ class StationsStore {
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
updateStationTransfers = async (
|
||||
id: number,
|
||||
transfers: {
|
||||
bus: string;
|
||||
metro_blue: string;
|
||||
metro_green: string;
|
||||
metro_orange: string;
|
||||
metro_purple: string;
|
||||
metro_red: string;
|
||||
train: string;
|
||||
tram: string;
|
||||
trolleybus: string;
|
||||
}
|
||||
) => {
|
||||
const { language } = languageStore;
|
||||
|
||||
// Получаем данные станции для текущего языка
|
||||
const response = await languageInstance(language).get(`/station/${id}`);
|
||||
const stationData = response.data as Station;
|
||||
|
||||
if (!stationData) {
|
||||
throw new Error("Station not found");
|
||||
}
|
||||
|
||||
// Формируем commonDataPayload как в editStation, с обновленными transfers
|
||||
const commonDataPayload = {
|
||||
city_id: stationData.city_id,
|
||||
direction: stationData.direction,
|
||||
latitude: stationData.latitude,
|
||||
longitude: stationData.longitude,
|
||||
offset_x: stationData.offset_x,
|
||||
offset_y: stationData.offset_y,
|
||||
transfers: transfers,
|
||||
city: stationData.city || "",
|
||||
};
|
||||
|
||||
// Отправляем один PATCH запрос, так как пересадки общие для всех языков
|
||||
const patchResponse = await languageInstance(language).patch(
|
||||
`/station/${id}`,
|
||||
{
|
||||
name: stationData.name || "",
|
||||
system_name: stationData.system_name || "",
|
||||
description: stationData.description || "",
|
||||
address: stationData.address || "",
|
||||
...commonDataPayload,
|
||||
}
|
||||
);
|
||||
|
||||
// Обновляем данные для всех языков в локальном состоянии
|
||||
runInAction(() => {
|
||||
const updatedTransfers = patchResponse.data.transfers;
|
||||
|
||||
for (const lang of ["ru", "en", "zh"] as const) {
|
||||
if (this.stationPreview[id]) {
|
||||
this.stationPreview[id][lang] = {
|
||||
...this.stationPreview[id][lang],
|
||||
data: {
|
||||
...this.stationPreview[id][lang].data,
|
||||
transfers: updatedTransfers,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (this.stationLists[lang].data) {
|
||||
this.stationLists[lang].data = this.stationLists[lang].data.map(
|
||||
(station: Station) =>
|
||||
station.id === id
|
||||
? {
|
||||
...station,
|
||||
transfers: updatedTransfers,
|
||||
}
|
||||
: station
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем пересадки в RouteStore.routeStations для всех маршрутов
|
||||
if (routeStore?.routeStations) {
|
||||
for (const routeId in routeStore.routeStations) {
|
||||
routeStore.routeStations[routeId] = routeStore.routeStations[
|
||||
routeId
|
||||
].map((station: any) =>
|
||||
station.id === id
|
||||
? {
|
||||
...station,
|
||||
transfers: updatedTransfers,
|
||||
}
|
||||
: station
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export const stationsStore = new StationsStore();
|
||||
|
||||
@@ -117,6 +117,7 @@ export function MediaViewer({
|
||||
}/download?token=${token}`}
|
||||
alt={media?.filename}
|
||||
style={{
|
||||
width: "100%",
|
||||
objectFit: "cover",
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -20,18 +20,6 @@ interface EditStationModalProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const transferFields = [
|
||||
{ key: "bus", label: "Автобус" },
|
||||
{ key: "metro_blue", label: "Метро (синяя)" },
|
||||
{ key: "metro_green", label: "Метро (зеленая)" },
|
||||
{ key: "metro_orange", label: "Метро (оранжевая)" },
|
||||
{ key: "metro_purple", label: "Метро (фиолетовая)" },
|
||||
{ key: "metro_red", label: "Метро (красная)" },
|
||||
{ key: "train", label: "Электричка" },
|
||||
{ key: "tram", label: "Трамвай" },
|
||||
{ key: "trolleybus", label: "Троллейбус" },
|
||||
];
|
||||
|
||||
export const EditStationModal = observer(
|
||||
({ open, onClose }: EditStationModalProps) => {
|
||||
const { id: routeId } = useParams<{ id: string }>();
|
||||
@@ -95,37 +83,6 @@ export const EditStationModal = observer(
|
||||
defaultValue={station?.offset_y}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ mt: 4 }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Пересадки
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(3, 1fr)",
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
{transferFields.map(({ key, label }) => (
|
||||
<TextField
|
||||
key={key}
|
||||
label={label}
|
||||
name={key}
|
||||
fullWidth
|
||||
defaultValue={station?.transfers?.[key]}
|
||||
onChange={(e) => {
|
||||
setRouteStations(Number(routeId), selectedStationId, {
|
||||
...station,
|
||||
transfers: {
|
||||
...station?.transfers,
|
||||
[key]: e.target.value,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ p: 3, justifyContent: "flex-end" }}>
|
||||
<Button onClick={handleSave} variant="contained" color="primary">
|
||||
|
||||
168
src/widgets/modals/EditStationTransfersModal/index.tsx
Normal file
168
src/widgets/modals/EditStationTransfersModal/index.tsx
Normal file
@@ -0,0 +1,168 @@
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
TextField,
|
||||
Typography,
|
||||
IconButton,
|
||||
Box,
|
||||
} from "@mui/material";
|
||||
import { stationsStore, languageStore } from "@shared";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import { toast } from "react-toastify";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
interface EditStationTransfersModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
stationId: number | null;
|
||||
}
|
||||
|
||||
const transferFields = [
|
||||
{ key: "bus", label: "Автобус" },
|
||||
{ key: "metro_blue", label: "Метро (синяя)" },
|
||||
{ key: "metro_green", label: "Метро (зеленая)" },
|
||||
{ key: "metro_orange", label: "Метро (оранжевая)" },
|
||||
{ key: "metro_purple", label: "Метро (фиолетовая)" },
|
||||
{ key: "metro_red", label: "Метро (красная)" },
|
||||
{ key: "train", label: "Электричка" },
|
||||
{ key: "tram", label: "Трамвай" },
|
||||
{ key: "trolleybus", label: "Троллейбус" },
|
||||
];
|
||||
|
||||
export const EditStationTransfersModal = observer(
|
||||
({ open, onClose, stationId }: EditStationTransfersModalProps) => {
|
||||
const { stationLists, updateStationTransfers } = stationsStore;
|
||||
const { language } = languageStore;
|
||||
const [transfers, setTransfers] = useState<{
|
||||
bus: string;
|
||||
metro_blue: string;
|
||||
metro_green: string;
|
||||
metro_orange: string;
|
||||
metro_purple: string;
|
||||
metro_red: string;
|
||||
train: string;
|
||||
tram: string;
|
||||
trolleybus: string;
|
||||
}>({
|
||||
bus: "",
|
||||
metro_blue: "",
|
||||
metro_green: "",
|
||||
metro_orange: "",
|
||||
metro_purple: "",
|
||||
metro_red: "",
|
||||
train: "",
|
||||
tram: "",
|
||||
trolleybus: "",
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (open && stationId) {
|
||||
let station = stationLists[language].data.find(
|
||||
(s: any) => s.id === stationId
|
||||
);
|
||||
if (!station?.transfers) {
|
||||
for (const lang of ["ru", "en", "zh"] as const) {
|
||||
const foundStation = stationLists[lang].data.find(
|
||||
(s: any) => s.id === stationId
|
||||
);
|
||||
if (foundStation?.transfers) {
|
||||
station = foundStation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (station?.transfers) {
|
||||
setTransfers(station.transfers);
|
||||
}
|
||||
}
|
||||
}, [open, stationId, stationLists]);
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!stationId) return;
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await updateStationTransfers(stationId, transfers);
|
||||
toast.success("Пересадки успешно обновлены");
|
||||
const { getStationList } = stationsStore;
|
||||
await getStationList();
|
||||
onClose();
|
||||
} catch (error) {
|
||||
console.error("Error updating transfers:", error);
|
||||
toast.error("Ошибка при обновлении пересадок");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTransferChange = (key: string, value: string) => {
|
||||
setTransfers((prev) => ({
|
||||
...prev,
|
||||
[key]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="md" fullWidth>
|
||||
<DialogTitle>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<IconButton onClick={onClose}>
|
||||
<ArrowLeft />
|
||||
</IconButton>
|
||||
<Box>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Станции / Редактировать пересадки
|
||||
</Typography>
|
||||
<Typography variant="h6">Редактирование пересадок</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Пересадки
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(3, 1fr)",
|
||||
gap: 2,
|
||||
mt: 2,
|
||||
}}
|
||||
>
|
||||
{transferFields.map(({ key, label }) => (
|
||||
<TextField
|
||||
key={key}
|
||||
label={label}
|
||||
name={key}
|
||||
fullWidth
|
||||
value={transfers[key as keyof typeof transfers] || ""}
|
||||
onChange={(e) => handleTransferChange(key, e.target.value)}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ p: 3, justifyContent: "flex-end" }}>
|
||||
<Button onClick={onClose} variant="outlined">
|
||||
Отмена
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? "Сохранение..." : "Сохранить"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./SelectArticleDialog";
|
||||
export * from "./EditStationModal";
|
||||
export * from "./EditStationTransfersModal";
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user