feat: Add edit/create/list
sight page
This commit is contained in:
@ -1,73 +1,264 @@
|
||||
import { BackButton, TabPanel } from "@shared";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
List,
|
||||
ListItemButton,
|
||||
ListItemText,
|
||||
Paper,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { BackButton, Sight, TabPanel } from "@shared";
|
||||
import { SightEdit } from "@widgets";
|
||||
import { Plus } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
// Мокап данных для списка блоков правого виджета
|
||||
const mockRightWidgetBlocks = [
|
||||
{ id: "preview_media", name: "Превью-медиа", type: "special" }, // Особый блок?
|
||||
{ id: "article_1", name: "1. История", type: "article" },
|
||||
{ id: "article_2", name: "2. Факты", type: "article" },
|
||||
{
|
||||
id: "article_3",
|
||||
name: "3. Блокада (Пример длинного названия)",
|
||||
type: "article",
|
||||
},
|
||||
];
|
||||
|
||||
// Мокап данных для выбранного блока для редактирования
|
||||
// В реальности это будет объект Article из API
|
||||
const mockSelectedBlockData = {
|
||||
id: "article_1",
|
||||
heading: "История основания Санкт-Петербурга",
|
||||
body: "## Начало\nГород был основан 27 мая 1703 года Петром I...",
|
||||
media: [
|
||||
// Предполагаем, что у статьи может быть несколько медиа
|
||||
// { id: "media_1", url: "https://via.placeholder.com/300x200.png?text=History+Image+1", type: "image" }
|
||||
],
|
||||
};
|
||||
|
||||
export const RightWidgetTab = ({
|
||||
value,
|
||||
index,
|
||||
data,
|
||||
}: {
|
||||
value: number;
|
||||
index: number;
|
||||
data?: Sight;
|
||||
}) => {
|
||||
const [rightWidgetBlocks, setRightWidgetBlocks] = useState(
|
||||
mockRightWidgetBlocks
|
||||
);
|
||||
const [selectedBlockId, setSelectedBlockId] = useState<string | null>(
|
||||
mockRightWidgetBlocks[1]?.id || null
|
||||
); // Выбираем первый "article" по умолчанию
|
||||
|
||||
const handleSelectBlock = (blockId: string) => {
|
||||
setSelectedBlockId(blockId);
|
||||
// Здесь будет логика загрузки данных для выбранного блока, если они не загружены
|
||||
console.log("Selected block:", blockId);
|
||||
};
|
||||
|
||||
const handleAddBlock = () => {
|
||||
// Логика открытия модала/формы для создания нового блока/статьи
|
||||
// или выбора существующей статьи для привязки
|
||||
console.log("Add new block");
|
||||
const newBlockId = `article_${Date.now()}`;
|
||||
setRightWidgetBlocks([
|
||||
...rightWidgetBlocks,
|
||||
{
|
||||
id: newBlockId,
|
||||
name: `${
|
||||
rightWidgetBlocks.filter((b) => b.type === "article").length + 1
|
||||
}. Новый блок`,
|
||||
type: "article",
|
||||
},
|
||||
]);
|
||||
setSelectedBlockId(newBlockId);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
console.log("Saving right widget...");
|
||||
};
|
||||
|
||||
// Находим данные для редактирования на основе selectedBlockId
|
||||
// В реальном приложении эти данные будут приходить из store или загружаться по API
|
||||
const currentBlockToEdit =
|
||||
selectedBlockId === mockSelectedBlockData.id
|
||||
? mockSelectedBlockData
|
||||
: selectedBlockId
|
||||
? {
|
||||
id: selectedBlockId,
|
||||
heading:
|
||||
rightWidgetBlocks.find((b) => b.id === selectedBlockId)?.name ||
|
||||
"Заголовок...",
|
||||
body: "Содержимое...",
|
||||
media: [],
|
||||
}
|
||||
: null;
|
||||
|
||||
return (
|
||||
<TabPanel value={value} index={index}>
|
||||
{/* Ensure the main container takes full height and uses flexbox for layout */}
|
||||
<div className="flex flex-col h-full min-h-[600px]">
|
||||
{/* Content area with back button and main layout */}
|
||||
<div className="flex-1 flex flex-col gap-6 p-4">
|
||||
{" "}
|
||||
{/* Added padding for better spacing */}
|
||||
<BackButton />
|
||||
<div className="flex flex-1 gap-6">
|
||||
{" "}
|
||||
{/* flex-1 allows this div to take remaining height */}
|
||||
{/* Left sidebar */}
|
||||
<div className="flex flex-col justify-between w-[240px] shrink-0 bg-gray-500 rounded-lg p-3">
|
||||
{" "}
|
||||
{/* Added background and padding */}
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="border rounded-lg p-3 bg-white font-medium shadow-sm">
|
||||
{" "}
|
||||
{/* Adjusted background and added shadow */}
|
||||
Превью медиа
|
||||
</div>
|
||||
<div className="border rounded-lg p-3 bg-white hover:bg-gray-100 transition-colors cursor-pointer shadow-sm">
|
||||
{" "}
|
||||
{/* Adjusted background and added shadow */}1 История
|
||||
</div>
|
||||
<div className="border rounded-lg p-3 bg-white hover:bg-gray-100 transition-colors cursor-pointer shadow-sm">
|
||||
{" "}
|
||||
{/* Adjusted background and added shadow */}2 Факты
|
||||
</div>
|
||||
</div>
|
||||
<button className="w-10 h-10 rounded-full bg-blue-500 hover:bg-blue-600 text-white p-3transition-colors flex items-center justify-center">
|
||||
{" "}
|
||||
{/* Added margin-top */}
|
||||
<Plus />
|
||||
</button>
|
||||
</div>
|
||||
{/* Main content area */}
|
||||
<div className="flex-1 border rounded-lg p-6 bg-white shadow-md">
|
||||
{" "}
|
||||
{/* Added shadow for depth */}
|
||||
{/* Content within the main area */}
|
||||
<SightEdit />
|
||||
{/* Replaced '1' with more descriptive content */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
minHeight: "calc(100vh - 200px)",
|
||||
gap: 2,
|
||||
paddingBottom: "70px",
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
<BackButton />
|
||||
|
||||
{/* Save button at the bottom, aligned to the right */}
|
||||
<div className="flex justify-end p-4">
|
||||
{" "}
|
||||
{/* Wrapper for save button, added padding */}
|
||||
<button className="bg-green-500 hover:bg-green-600 text-white py-2.5 px-6 rounded-lg transition-colors font-medium shadow-md">
|
||||
{" "}
|
||||
{/* Added shadow */}
|
||||
<Box sx={{ display: "flex", flexGrow: 1, gap: 2.5 }}>
|
||||
{/* Левая колонка: Список блоков/статей */}
|
||||
<Paper
|
||||
elevation={2}
|
||||
sx={{
|
||||
width: 260, // Ширина как на макете
|
||||
minWidth: 240,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
padding: 1.5,
|
||||
borderRadius: 2,
|
||||
border: "1px solid",
|
||||
borderColor: "divider",
|
||||
}}
|
||||
>
|
||||
<List
|
||||
dense
|
||||
sx={{
|
||||
overflowY: "auto",
|
||||
flexGrow: 1,
|
||||
maxHeight:
|
||||
"calc(100% - 60px)" /* Adjust based on button size */,
|
||||
}}
|
||||
>
|
||||
{rightWidgetBlocks.map((block) => (
|
||||
<ListItemButton
|
||||
key={block.id}
|
||||
selected={selectedBlockId === block.id}
|
||||
onClick={() => handleSelectBlock(block.id)}
|
||||
sx={{
|
||||
borderRadius: 1,
|
||||
mb: 0.5,
|
||||
backgroundColor:
|
||||
selectedBlockId === block.id
|
||||
? "primary.light"
|
||||
: "transparent",
|
||||
"&.Mui-selected": {
|
||||
backgroundColor: "primary.main",
|
||||
color: "primary.contrastText",
|
||||
"&:hover": {
|
||||
backgroundColor: "primary.dark",
|
||||
},
|
||||
},
|
||||
"&:hover": {
|
||||
backgroundColor:
|
||||
selectedBlockId !== block.id
|
||||
? "action.hover"
|
||||
: undefined,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ListItemText
|
||||
primary={block.name}
|
||||
primaryTypographyProps={{
|
||||
fontWeight:
|
||||
selectedBlockId === block.id ? "bold" : "normal",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
/>
|
||||
</ListItemButton>
|
||||
))}
|
||||
</List>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
pt: 1.5,
|
||||
borderTop: "1px solid",
|
||||
borderColor: "divider",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleAddBlock}
|
||||
startIcon={<Plus />}
|
||||
fullWidth
|
||||
>
|
||||
Добавить блок
|
||||
</Button>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{/* Правая колонка: Редактор выбранного блока (SightEdit) */}
|
||||
<Paper
|
||||
elevation={2}
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
padding: 2.5,
|
||||
borderRadius: 2,
|
||||
border: "1px solid",
|
||||
borderColor: "divider",
|
||||
overflowY: "auto", // Если контент будет больше
|
||||
}}
|
||||
>
|
||||
{currentBlockToEdit ? (
|
||||
<>
|
||||
<SightEdit
|
||||
onUnlink={() => console.log("Unlink block:", selectedBlockId)}
|
||||
onDelete={() => {
|
||||
console.log("Delete block:", selectedBlockId);
|
||||
setRightWidgetBlocks((blocks) =>
|
||||
blocks.filter((b) => b.id !== selectedBlockId)
|
||||
);
|
||||
setSelectedBlockId(null);
|
||||
}}
|
||||
/>
|
||||
<Paper elevation={1} sx={{ padding: 2, mt: 1, width: "75%" }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
МЕДИА
|
||||
</Typography>
|
||||
{/* Здесь будет UI для управления медиа статьи */}
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: 100,
|
||||
backgroundColor: "grey.100",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
borderRadius: 1,
|
||||
mb: 1,
|
||||
border: "2px dashed",
|
||||
borderColor: "grey.300",
|
||||
}}
|
||||
>
|
||||
<Typography color="text.secondary">Нет медиа</Typography>
|
||||
</Box>
|
||||
|
||||
<Button variant="contained">Выбрать/Загрузить медиа</Button>
|
||||
</Paper>
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Paper>
|
||||
</Box>
|
||||
|
||||
{/* Блок МЕДИА для статьи */}
|
||||
|
||||
<Box sx={{ position: "absolute", bottom: 0, right: 0, padding: 2 }}>
|
||||
<Button variant="contained" color="success" onClick={handleSave}>
|
||||
Сохранить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</TabPanel>
|
||||
);
|
||||
};
|
||||
|
Reference in New Issue
Block a user