feat: Add edit/create/list
sight page
This commit is contained in:
@ -1,48 +1,484 @@
|
||||
import { TextField } from "@mui/material";
|
||||
import { BackButton, TabPanel } from "@shared";
|
||||
import {
|
||||
Button,
|
||||
TextField,
|
||||
Box,
|
||||
Typography,
|
||||
IconButton,
|
||||
Paper,
|
||||
Tooltip,
|
||||
} from "@mui/material";
|
||||
import { BackButton, Sight, sightsStore, TabPanel, Language } from "@shared";
|
||||
import { LanguageSwitcher } from "@widgets";
|
||||
import { ImagePlus, Info } from "lucide-react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
export const InformationTab = ({
|
||||
value,
|
||||
index,
|
||||
}: {
|
||||
value: number;
|
||||
index: number;
|
||||
}) => {
|
||||
return (
|
||||
<TabPanel value={value} index={index}>
|
||||
<div className="flex-1 flex flex-col relative">
|
||||
<div className="flex-1 flex flex-col gap-10">
|
||||
<BackButton />
|
||||
<div className="flex flex-col gap-5 w-1/2">
|
||||
<TextField label="Название" />
|
||||
<TextField label="Адрес" />
|
||||
<TextField label="Город" />
|
||||
<TextField label="Координаты" />
|
||||
|
||||
<div className="flex justify-around w-full mt-20">
|
||||
<div className="flex flex-col gap-2 ">
|
||||
<p>Логотип</p>
|
||||
<button>Выбрать</button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<p>Водяной знак (л.в)</p>
|
||||
<button>Выбрать</button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<p>Водяной знак (п.в)</p>
|
||||
<button>Выбрать</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button className="bg-green-400 w-min ml-auto text-white py-2 rounded-2xl px-4">
|
||||
Сохранить
|
||||
</button>
|
||||
<div className="absolute top-1/2 -translate-y-1/2 right-0">
|
||||
<LanguageSwitcher />
|
||||
</div>
|
||||
</div>
|
||||
</TabPanel>
|
||||
);
|
||||
// Мокап данных для отображения, потом это будет приходить из store/props
|
||||
// Keeping this mock for demonstration, but in a real app,
|
||||
// this would come from the MobX store's 'sight' object.
|
||||
const mockSightData = {
|
||||
name: "Эрмитаж",
|
||||
address: "Дворцовая площадь, 2",
|
||||
city: "Санкт-Петербург", // или city_id, если будет Select
|
||||
coordinates: "59.9398, 30.3146",
|
||||
logo: null, // null или URL/ID медиа
|
||||
watermark_lu: null,
|
||||
watermark_rd: null,
|
||||
};
|
||||
|
||||
// Мокап для всплывающей подсказки
|
||||
const watermarkTooltipText = "При наведении открывается просмотр в поп-апе";
|
||||
const logoTooltipText = "При наведении открывается просмотр логотипа в поп-апе";
|
||||
|
||||
export const InformationTab = observer(
|
||||
({ value, index }: { value: number; index: number }) => {
|
||||
const {
|
||||
sight,
|
||||
cachedMultilingualContent,
|
||||
updateCachedLanguageContent,
|
||||
clearCachedMultilingualContent,
|
||||
// Assuming you'll have an action to update the main sight object
|
||||
updateSight,
|
||||
} = sightsStore;
|
||||
|
||||
// Initialize local states with data from the MobX store's 'sight'
|
||||
const [address, setAddress] = useState(sight?.address ?? "");
|
||||
const [city, setCity] = useState(sight?.city ?? "");
|
||||
const [coordinates, setCoordinates] = useState(
|
||||
sight?.latitude && sight?.longitude
|
||||
? `${sight.latitude}, ${sight.longitude}`
|
||||
: ""
|
||||
);
|
||||
const [currentLanguage, setCurrentLanguage] = useState<Language>("ru");
|
||||
const pathname = useLocation().pathname;
|
||||
|
||||
// Effect to initialize local states when `sight` data becomes available or changes
|
||||
useEffect(() => {
|
||||
if (sight) {
|
||||
setAddress(sight.address ?? "");
|
||||
setCity(sight.city ?? "");
|
||||
setCoordinates(
|
||||
sight.latitude && sight.longitude
|
||||
? `${sight.latitude}, ${sight.longitude}`
|
||||
: ""
|
||||
);
|
||||
// Initialize cached content if not already set
|
||||
if (!cachedMultilingualContent) {
|
||||
sightsStore.setCachedMultilingualContent({
|
||||
ru: { name: sight.name, description: "", address: sight.address },
|
||||
en: { name: "", description: "", address: "" },
|
||||
zh: { name: "", description: "", address: "" },
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [sight, cachedMultilingualContent]); // Add cachedMultilingualContent to dependencies
|
||||
|
||||
// Effect to clear cached content when the route changes
|
||||
useEffect(() => {
|
||||
clearCachedMultilingualContent();
|
||||
}, [pathname, clearCachedMultilingualContent]);
|
||||
|
||||
const handleLanguageChange = (lang: Language) => {
|
||||
setCurrentLanguage(lang);
|
||||
};
|
||||
|
||||
const handleSelectMedia = (
|
||||
type: "logo" | "watermark_lu" | "watermark_rd"
|
||||
) => {
|
||||
// Here will be logic for opening modal window for media selection
|
||||
console.log("Select media for:", type);
|
||||
// In a real application, you might open a dialog here
|
||||
// and update the sight object with the selected media URL/ID.
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
// Parse coordinates back to latitude and longitude
|
||||
let latitude: number | undefined;
|
||||
let longitude: number | undefined;
|
||||
const coordsArray = coordinates
|
||||
.split(",")
|
||||
.map((coord) => parseFloat(coord.trim()));
|
||||
if (
|
||||
coordsArray.length === 2 &&
|
||||
!isNaN(coordsArray[0]) &&
|
||||
!isNaN(coordsArray[1])
|
||||
) {
|
||||
latitude = coordsArray[0];
|
||||
longitude = coordsArray[1];
|
||||
}
|
||||
|
||||
// Prepare the updated sight data
|
||||
const updatedSightData = {
|
||||
...sight, // Keep existing sight data
|
||||
address: address,
|
||||
city: city,
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
// Assuming logo and watermark updates would happen via handleSelectMedia
|
||||
// and then be reflected in the sight object in the store.
|
||||
};
|
||||
|
||||
// Here we would save both the sight data and the multilingual content
|
||||
console.log("Saving general information and multilingual content...", {
|
||||
updatedSightData,
|
||||
multilingualContent: cachedMultilingualContent,
|
||||
});
|
||||
|
||||
// Call an action from your store to save the data
|
||||
// For example:
|
||||
// sightsStore.saveSight({ ...updatedSightData, multilingualContent: cachedMultilingualContent });
|
||||
// You might have a specific action in your store for saving all this data.
|
||||
};
|
||||
|
||||
return (
|
||||
<TabPanel value={value} index={index}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 3,
|
||||
position: "relative",
|
||||
paddingBottom: "70px" /* Space for save button */,
|
||||
}}
|
||||
>
|
||||
<BackButton />
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
|
||||
gap: 4, // Added gap between the two main columns
|
||||
width: "100%",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
{/* Left column with main fields */}
|
||||
<Box
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
display: "flex",
|
||||
width: "80%",
|
||||
flexDirection: "column",
|
||||
gap: 2.5,
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
label={`Название (${currentLanguage.toUpperCase()})`}
|
||||
value={cachedMultilingualContent?.[currentLanguage]?.name ?? ""}
|
||||
onChange={(e) => {
|
||||
updateCachedLanguageContent(currentLanguage, {
|
||||
name: e.target.value,
|
||||
});
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
/>
|
||||
<TextField
|
||||
label={`Описание (${currentLanguage.toUpperCase()})`}
|
||||
value={
|
||||
cachedMultilingualContent?.[currentLanguage]?.description ??
|
||||
""
|
||||
}
|
||||
onChange={(e) => {
|
||||
updateCachedLanguageContent(currentLanguage, {
|
||||
description: e.target.value,
|
||||
});
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
multiline
|
||||
rows={4}
|
||||
/>
|
||||
<TextField
|
||||
label="Адрес"
|
||||
value={address}
|
||||
onChange={(e) => {
|
||||
setAddress(e.target.value);
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
/>
|
||||
<TextField
|
||||
label="Город"
|
||||
value={city}
|
||||
onChange={(e) => {
|
||||
setCity(e.target.value);
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
/>
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||
<TextField
|
||||
label="Координаты"
|
||||
value={coordinates}
|
||||
onChange={(e) => {
|
||||
setCoordinates(e.target.value);
|
||||
}}
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
helperText="Формат: широта, долгота (например, 59.9398, 30.3146)"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Правая колонка для логотипа и водяных знаков */}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
{/* Водяные знаки */}
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-around",
|
||||
width: "80%",
|
||||
gap: 2,
|
||||
flexDirection: { xs: "column", sm: "row" }, // Stack on extra small, side-by-side on small and up
|
||||
}}
|
||||
>
|
||||
<Paper
|
||||
elevation={2}
|
||||
sx={{
|
||||
padding: 2,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: 1,
|
||||
flex: 1,
|
||||
minWidth: 150, // Ensure a minimum width
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
gutterBottom
|
||||
sx={{ mb: 0, mr: 0.5 }}
|
||||
>
|
||||
Логотип
|
||||
</Typography>
|
||||
<Tooltip title={watermarkTooltipText}>
|
||||
<Info
|
||||
size={16}
|
||||
color="gray"
|
||||
style={{ cursor: "pointer" }}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
width: 80,
|
||||
height: 80,
|
||||
backgroundColor: "grey.200",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
borderRadius: 1,
|
||||
mb: 1,
|
||||
cursor: mockSightData.watermark_lu
|
||||
? "pointer"
|
||||
: "default", // Only clickable if there's an image
|
||||
"&:hover": {
|
||||
backgroundColor: mockSightData.watermark_lu
|
||||
? "grey.300"
|
||||
: "grey.200",
|
||||
},
|
||||
}}
|
||||
onClick={() =>
|
||||
mockSightData.watermark_lu &&
|
||||
handleSelectMedia("watermark_lu")
|
||||
}
|
||||
>
|
||||
{mockSightData.watermark_lu ? (
|
||||
<img
|
||||
src={mockSightData.watermark_lu}
|
||||
alt="Знак л.в"
|
||||
style={{ maxWidth: "100%", maxHeight: "100%" }}
|
||||
/>
|
||||
) : (
|
||||
<ImagePlus size={24} color="grey" />
|
||||
)}
|
||||
</Box>
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
onClick={() => handleSelectMedia("watermark_lu")}
|
||||
>
|
||||
Выбрать
|
||||
</Button>
|
||||
</Paper>
|
||||
<Paper
|
||||
elevation={2}
|
||||
sx={{
|
||||
padding: 2,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: 1,
|
||||
flex: 1,
|
||||
minWidth: 150, // Ensure a minimum width
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
gutterBottom
|
||||
sx={{ mb: 0, mr: 0.5 }}
|
||||
>
|
||||
Водяной знак (л.в)
|
||||
</Typography>
|
||||
<Tooltip title={watermarkTooltipText}>
|
||||
<Info
|
||||
size={16}
|
||||
color="gray"
|
||||
style={{ cursor: "pointer" }}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
width: 80,
|
||||
height: 80,
|
||||
backgroundColor: "grey.200",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
borderRadius: 1,
|
||||
mb: 1,
|
||||
cursor: mockSightData.watermark_lu
|
||||
? "pointer"
|
||||
: "default", // Only clickable if there's an image
|
||||
"&:hover": {
|
||||
backgroundColor: mockSightData.watermark_lu
|
||||
? "grey.300"
|
||||
: "grey.200",
|
||||
},
|
||||
}}
|
||||
onClick={() =>
|
||||
mockSightData.watermark_lu &&
|
||||
handleSelectMedia("watermark_lu")
|
||||
}
|
||||
>
|
||||
{mockSightData.watermark_lu ? (
|
||||
<img
|
||||
src={mockSightData.watermark_lu}
|
||||
alt="Знак л.в"
|
||||
style={{ maxWidth: "100%", maxHeight: "100%" }}
|
||||
/>
|
||||
) : (
|
||||
<ImagePlus size={24} color="grey" />
|
||||
)}
|
||||
</Box>
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
onClick={() => handleSelectMedia("watermark_lu")}
|
||||
>
|
||||
Выбрать
|
||||
</Button>
|
||||
</Paper>
|
||||
|
||||
<Paper
|
||||
elevation={2}
|
||||
sx={{
|
||||
padding: 2,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: 1,
|
||||
flex: 1,
|
||||
minWidth: 150, // Ensure a minimum width
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
gutterBottom
|
||||
sx={{ mb: 0, mr: 0.5 }}
|
||||
>
|
||||
Водяной знак (п.в)
|
||||
</Typography>
|
||||
<Tooltip title={watermarkTooltipText}>
|
||||
<Info
|
||||
size={16}
|
||||
color="gray"
|
||||
style={{ cursor: "pointer" }}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
width: 80,
|
||||
height: 80,
|
||||
backgroundColor: "grey.200",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
borderRadius: 1,
|
||||
mb: 1,
|
||||
cursor: mockSightData.watermark_rd
|
||||
? "pointer"
|
||||
: "default", // Only clickable if there's an image
|
||||
"&:hover": {
|
||||
backgroundColor: mockSightData.watermark_rd
|
||||
? "grey.300"
|
||||
: "grey.200",
|
||||
},
|
||||
}}
|
||||
onClick={() =>
|
||||
mockSightData.watermark_rd &&
|
||||
handleSelectMedia("watermark_rd")
|
||||
}
|
||||
>
|
||||
{mockSightData.watermark_rd ? (
|
||||
<img
|
||||
src={mockSightData.watermark_rd}
|
||||
alt="Знак п.в"
|
||||
style={{ maxWidth: "100%", maxHeight: "100%" }}
|
||||
/>
|
||||
) : (
|
||||
<ImagePlus size={24} color="grey" />
|
||||
)}
|
||||
</Box>
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
onClick={() => handleSelectMedia("watermark_rd")}
|
||||
>
|
||||
Выбрать
|
||||
</Button>
|
||||
</Paper>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* LanguageSwitcher positioned at the top right */}
|
||||
<Box sx={{ position: "absolute", top: 0, right: 0, zIndex: 1 }}>
|
||||
<LanguageSwitcher onLanguageChange={handleLanguageChange} />
|
||||
</Box>
|
||||
|
||||
{/* Save Button fixed at the bottom right */}
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
padding: 2,
|
||||
backgroundColor: "background.paper", // To ensure it stands out over content
|
||||
width: "100%", // Take full width to cover content below it
|
||||
display: "flex",
|
||||
justifyContent: "flex-end", // Align to the right
|
||||
}}
|
||||
>
|
||||
<Button variant="contained" color="success" onClick={handleSave}>
|
||||
Сохранить
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</TabPanel>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
Reference in New Issue
Block a user