init: Init React Application
This commit is contained in:
206
src/widgets/DevicesTable/index.tsx
Normal file
206
src/widgets/DevicesTable/index.tsx
Normal file
@ -0,0 +1,206 @@
|
||||
import Table from "@mui/material/Table";
|
||||
import TableBody from "@mui/material/TableBody";
|
||||
import TableCell from "@mui/material/TableCell";
|
||||
import TableContainer from "@mui/material/TableContainer";
|
||||
import TableHead from "@mui/material/TableHead";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import { Check, RotateCcw, Send, X } from "lucide-react";
|
||||
import { devicesStore, Modal, snapshotStore, vehicleStore } from "@shared";
|
||||
import { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Button, Checkbox } from "@mui/material";
|
||||
|
||||
const formatDate = (dateString: string | undefined) => {
|
||||
if (!dateString) return "Нет данных";
|
||||
|
||||
try {
|
||||
const date = new Date(dateString);
|
||||
return new Intl.DateTimeFormat("ru-RU", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
hour12: false,
|
||||
}).format(date);
|
||||
} catch (error) {
|
||||
console.error("Error formatting date:", error);
|
||||
return "Некорректная дата";
|
||||
}
|
||||
};
|
||||
|
||||
function createData(
|
||||
uuid: string,
|
||||
online: boolean,
|
||||
lastUpdate: string,
|
||||
gps: boolean,
|
||||
media: boolean,
|
||||
connection: boolean
|
||||
) {
|
||||
return { uuid, online, lastUpdate, gps, media, connection };
|
||||
}
|
||||
|
||||
const rows = (devices: any[], vehicles: any[]) => {
|
||||
return devices.map((device) => {
|
||||
const { device_status } = vehicles.find(
|
||||
(v) => v?.device_status?.device_uuid === device
|
||||
);
|
||||
const findVehicle = vehicles.find((v) => v?.vehicle?.uuid === device);
|
||||
|
||||
console.log(findVehicle);
|
||||
return createData(
|
||||
findVehicle?.vehicle?.tail_number ?? "1243000",
|
||||
device_status?.online,
|
||||
device_status?.last_update,
|
||||
device_status?.gps_ok,
|
||||
device_status?.media_service_ok,
|
||||
device_status?.is_connected
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const DevicesTable = observer(() => {
|
||||
const {
|
||||
devices,
|
||||
getDevices,
|
||||
uuid,
|
||||
setSelectedDevice,
|
||||
sendSnapshotModalOpen,
|
||||
toggleSendSnapshotModal,
|
||||
} = devicesStore;
|
||||
const { snapshots, getSnapshots } = snapshotStore;
|
||||
const { vehicles, getVehicles } = vehicleStore;
|
||||
const [selectedDevices, setSelectedDevices] = useState<string[]>([]);
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
await getVehicles();
|
||||
await getDevices();
|
||||
await getSnapshots();
|
||||
};
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const handleSendSnapshot = (uuid: string[]) => {
|
||||
setSelectedDevice(uuid);
|
||||
toggleSendSnapshotModal();
|
||||
};
|
||||
|
||||
const handleReloadStatus = (uuid: string) => {
|
||||
setSelectedDevice(uuid);
|
||||
};
|
||||
|
||||
const handleSelectDevice = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.checked) {
|
||||
setSelectedDevices([...selectedDevices, event.target.value]);
|
||||
} else {
|
||||
setSelectedDevices(
|
||||
selectedDevices.filter((device) => device !== event.target.value)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableContainer component={Paper}>
|
||||
<div className="flex justify-end p-3 gap-5">
|
||||
<Button variant="contained" color="primary">
|
||||
Выбрать все
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={selectedDevices.length === 0}
|
||||
className="ml-auto"
|
||||
onClick={() => handleSendSnapshot(selectedDevices)}
|
||||
>
|
||||
Отправить снапшот
|
||||
</Button>
|
||||
</div>
|
||||
<Table sx={{ minWidth: 650 }} aria-label="simple table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell align="center"></TableCell>
|
||||
<TableCell align="center">Бортовой номер</TableCell>
|
||||
<TableCell align="center">Онлайн</TableCell>
|
||||
<TableCell align="center">Последнее обновление</TableCell>
|
||||
<TableCell align="center">ГПС</TableCell>
|
||||
<TableCell align="center">Медиа-данные</TableCell>
|
||||
<TableCell align="center">Подключение</TableCell>
|
||||
<TableCell align="center">Перезапросить</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{rows(devices, vehicles).map((row) => (
|
||||
<TableRow
|
||||
key={row?.uuid}
|
||||
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
|
||||
className="flex items-center"
|
||||
>
|
||||
<TableCell align="center">
|
||||
<Checkbox
|
||||
className="h-full"
|
||||
onChange={handleSelectDevice}
|
||||
value={row?.uuid}
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="center" component="th" scope="row">
|
||||
{row?.uuid}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{row?.online ? (
|
||||
<Check className="m-auto text-green-500" />
|
||||
) : (
|
||||
<X className="m-auto text-red-500" />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{formatDate(row?.lastUpdate)}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{row?.gps ? (
|
||||
<Check className="m-auto text-green-500" />
|
||||
) : (
|
||||
<X className="m-auto text-red-500" />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{row?.media ? (
|
||||
<Check className="m-auto text-green-500" />
|
||||
) : (
|
||||
<X className="m-auto text-red-500" />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{row?.connection ? (
|
||||
<Check className="m-auto text-green-500" />
|
||||
) : (
|
||||
<X className="m-auto text-red-500" />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="center" className="flex justify-center">
|
||||
<button onClick={() => handleReloadStatus(row?.uuid ?? "")}>
|
||||
<RotateCcw className="m-auto" />
|
||||
</button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<Modal open={sendSnapshotModalOpen} onClose={toggleSendSnapshotModal}>
|
||||
<p>Выбрать снапшот</p>
|
||||
<div className="mt-5 flex flex-col gap-2 max-h-[300px] overflow-y-auto">
|
||||
{snapshots &&
|
||||
snapshots.map((snapshot) => (
|
||||
<button className="p-2 rounded-xl bg-slate-100" key={snapshot.id}>
|
||||
{snapshot.Name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
});
|
29
src/widgets/LanguageSwitcher/index.tsx
Normal file
29
src/widgets/LanguageSwitcher/index.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import { languageStore } from "@shared";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
export const LanguageSwitcher = observer(() => {
|
||||
const { language, setLanguage } = languageStore;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<button
|
||||
className={`p-3 ${language === "ru" ? "bg-blue-500" : ""}`}
|
||||
onClick={() => setLanguage("ru")}
|
||||
>
|
||||
RU
|
||||
</button>
|
||||
<button
|
||||
className={`p-3 ${language === "en" ? "bg-blue-500" : ""}`}
|
||||
onClick={() => setLanguage("en")}
|
||||
>
|
||||
EN
|
||||
</button>
|
||||
<button
|
||||
className={`p-3 ${language === "zh" ? "bg-blue-500" : ""}`}
|
||||
onClick={() => setLanguage("zh")}
|
||||
>
|
||||
zh
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
});
|
67
src/widgets/Layout/index.tsx
Normal file
67
src/widgets/Layout/index.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
import * as React from "react";
|
||||
import Box from "@mui/material/Box";
|
||||
import Toolbar from "@mui/material/Toolbar";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { Menu, ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { AppBar } from "./ui/AppBar";
|
||||
import { Drawer } from "./ui/Drawer";
|
||||
import { DrawerHeader } from "./ui/DrawerHeader";
|
||||
import { NavigationList } from "@features";
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Layout: React.FC<LayoutProps> = ({ children }) => {
|
||||
const theme = useTheme();
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
const handleDrawerOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const handleDrawerClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<AppBar position="fixed" open={open}>
|
||||
<Toolbar>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
aria-label="open drawer"
|
||||
onClick={handleDrawerOpen}
|
||||
edge="start"
|
||||
sx={[
|
||||
{
|
||||
marginRight: 5,
|
||||
},
|
||||
open && { display: "none" },
|
||||
]}
|
||||
>
|
||||
<Menu />
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<Drawer variant="permanent" open={open}>
|
||||
<DrawerHeader>
|
||||
<IconButton onClick={handleDrawerClose}>
|
||||
{theme.direction === "rtl" ? (
|
||||
<ChevronRightIcon />
|
||||
) : (
|
||||
<ChevronLeftIcon />
|
||||
)}
|
||||
</IconButton>
|
||||
</DrawerHeader>
|
||||
<NavigationList open={open} />
|
||||
</Drawer>
|
||||
<Box component="main" sx={{ flexGrow: 1, p: 3 }}>
|
||||
<DrawerHeader />
|
||||
{children}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
27
src/widgets/Layout/ui/AppBar.tsx
Normal file
27
src/widgets/Layout/ui/AppBar.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { styled } from "@mui/material/styles";
|
||||
import MuiAppBar, {
|
||||
type AppBarProps as MuiAppBarProps,
|
||||
} from "@mui/material/AppBar";
|
||||
import { DRAWER_WIDTH } from "@shared";
|
||||
|
||||
interface AppBarProps extends MuiAppBarProps {
|
||||
open?: boolean;
|
||||
}
|
||||
|
||||
export const AppBar = styled(MuiAppBar, {
|
||||
shouldForwardProp: (prop) => prop !== "open",
|
||||
})<AppBarProps>(({ theme, open }) => ({
|
||||
zIndex: theme.zIndex.drawer + 1,
|
||||
transition: theme.transitions.create(["width", "margin"], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
...(open && {
|
||||
marginLeft: DRAWER_WIDTH,
|
||||
width: `calc(100% - ${DRAWER_WIDTH}px)`,
|
||||
transition: theme.transitions.create(["width", "margin"], {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
}),
|
||||
}));
|
42
src/widgets/Layout/ui/Drawer.tsx
Normal file
42
src/widgets/Layout/ui/Drawer.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import { styled } from "@mui/material/styles";
|
||||
import MuiDrawer from "@mui/material/Drawer";
|
||||
import type { Theme, CSSObject } from "@mui/material/styles";
|
||||
import { DRAWER_WIDTH } from "@shared";
|
||||
|
||||
const openedMixin = (theme: Theme): CSSObject => ({
|
||||
width: DRAWER_WIDTH,
|
||||
transition: theme.transitions.create("width", {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.enteringScreen,
|
||||
}),
|
||||
overflowX: "hidden",
|
||||
});
|
||||
|
||||
const closedMixin = (theme: Theme): CSSObject => ({
|
||||
transition: theme.transitions.create("width", {
|
||||
easing: theme.transitions.easing.sharp,
|
||||
duration: theme.transitions.duration.leavingScreen,
|
||||
}),
|
||||
overflowX: "hidden",
|
||||
width: `calc(${theme.spacing(7)} + 1px)`,
|
||||
[theme.breakpoints.up("sm")]: {
|
||||
width: `calc(${theme.spacing(8)} + 1px)`,
|
||||
},
|
||||
});
|
||||
|
||||
export const Drawer = styled(MuiDrawer, {
|
||||
shouldForwardProp: (prop) => prop !== "open",
|
||||
})(({ theme, open }) => ({
|
||||
width: DRAWER_WIDTH,
|
||||
flexShrink: 0,
|
||||
whiteSpace: "nowrap",
|
||||
boxSizing: "border-box",
|
||||
...(open && {
|
||||
...openedMixin(theme),
|
||||
"& .MuiDrawer-paper": openedMixin(theme),
|
||||
}),
|
||||
...(!open && {
|
||||
...closedMixin(theme),
|
||||
"& .MuiDrawer-paper": closedMixin(theme),
|
||||
}),
|
||||
}));
|
10
src/widgets/Layout/ui/DrawerHeader.tsx
Normal file
10
src/widgets/Layout/ui/DrawerHeader.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { styled } from "@mui/material/styles";
|
||||
import type { Theme } from "@mui/material/styles";
|
||||
|
||||
export const DrawerHeader = styled("div")(({ theme }: { theme: Theme }) => ({
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
padding: theme.spacing(0, 1),
|
||||
...theme.mixins.toolbar,
|
||||
}));
|
50
src/widgets/ReactMarkdown/index.tsx
Normal file
50
src/widgets/ReactMarkdown/index.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import { Box } from "@mui/material";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import rehypeRaw from "rehype-raw";
|
||||
|
||||
export const ReactMarkdownComponent = ({ value }: { value: string }) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
"& img": {
|
||||
maxWidth: "100%",
|
||||
height: "auto",
|
||||
borderRadius: 1,
|
||||
},
|
||||
"& h1, & h2, & h3, & h4, & h5, & h6": {
|
||||
color: "primary.main",
|
||||
mt: 2,
|
||||
mb: 1,
|
||||
},
|
||||
"& p": {
|
||||
mb: 2,
|
||||
color: (theme) =>
|
||||
theme.palette.mode === "dark" ? "grey.300" : "grey.800",
|
||||
},
|
||||
"& a": {
|
||||
color: "primary.main",
|
||||
textDecoration: "none",
|
||||
"&:hover": {
|
||||
textDecoration: "underline",
|
||||
},
|
||||
},
|
||||
"& blockquote": {
|
||||
borderLeft: "4px solid",
|
||||
borderColor: "primary.main",
|
||||
pl: 2,
|
||||
my: 2,
|
||||
color: "text.secondary",
|
||||
},
|
||||
"& code": {
|
||||
bgcolor: (theme) =>
|
||||
theme.palette.mode === "dark" ? "grey.900" : "grey.100",
|
||||
p: 0.5,
|
||||
borderRadius: 0.5,
|
||||
color: "primary.main",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ReactMarkdown rehypePlugins={[rehypeRaw]}>{value}</ReactMarkdown>
|
||||
</Box>
|
||||
);
|
||||
};
|
109
src/widgets/ReactMarkdownEditor/index.tsx
Normal file
109
src/widgets/ReactMarkdownEditor/index.tsx
Normal file
@ -0,0 +1,109 @@
|
||||
import { styled } from "@mui/material/styles";
|
||||
import SimpleMDE, { SimpleMDEReactProps } from "react-simplemde-editor";
|
||||
import "easymde/dist/easymde.min.css";
|
||||
|
||||
const StyledMarkdownEditor = styled("div")(({ theme }) => ({
|
||||
"& .editor-toolbar": {
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
borderColor: theme.palette.divider,
|
||||
},
|
||||
"& .editor-toolbar button": {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
"& .editor-toolbar button:hover": {
|
||||
backgroundColor: theme.palette.action.hover,
|
||||
},
|
||||
"& .editor-toolbar button:active, & .editor-toolbar button.active": {
|
||||
backgroundColor: theme.palette.action.selected,
|
||||
},
|
||||
"& .editor-statusbar": {
|
||||
display: "none",
|
||||
},
|
||||
// Стили для самого редактора
|
||||
"& .CodeMirror": {
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
color: theme.palette.text.primary,
|
||||
borderColor: theme.palette.divider,
|
||||
},
|
||||
// Стили для текста в редакторе
|
||||
"& .CodeMirror-selected": {
|
||||
backgroundColor: `${theme.palette.action.selected} !important`,
|
||||
},
|
||||
"& .CodeMirror-cursor": {
|
||||
borderLeftColor: theme.palette.text.primary,
|
||||
},
|
||||
// Стили для markdown разметки
|
||||
"& .cm-header": {
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
"& .cm-quote": {
|
||||
color: theme.palette.text.secondary,
|
||||
fontStyle: "italic",
|
||||
},
|
||||
"& .cm-link": {
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
"& .cm-url": {
|
||||
color: theme.palette.secondary.main,
|
||||
},
|
||||
"& .cm-formatting": {
|
||||
color: theme.palette.text.secondary,
|
||||
},
|
||||
|
||||
"& .CodeMirror .editor-preview-full": {
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
color: theme.palette.text.primary,
|
||||
borderColor: theme.palette.divider,
|
||||
},
|
||||
|
||||
"& .EasyMDEContainer": {
|
||||
position: "relative",
|
||||
zIndex: 1000,
|
||||
},
|
||||
"& .guide": {
|
||||
display: "none",
|
||||
},
|
||||
}));
|
||||
|
||||
export const ReactMarkdownEditor = (props: SimpleMDEReactProps) => {
|
||||
if (props.options)
|
||||
props.options.toolbar = [
|
||||
"bold",
|
||||
"italic",
|
||||
"strikethrough",
|
||||
{
|
||||
name: "Underline",
|
||||
action: (editor: any) => {
|
||||
const cm = editor.codemirror;
|
||||
let output = "";
|
||||
const selectedText = cm.getSelection();
|
||||
const text = selectedText ?? "placeholder";
|
||||
|
||||
output = "<u>" + text + "</u>";
|
||||
cm.replaceSelection(output);
|
||||
},
|
||||
className: "fa fa-underline", // Look for a suitable icon
|
||||
title: "Underline (Ctrl/Cmd-Alt-U)",
|
||||
},
|
||||
"heading",
|
||||
"quote",
|
||||
"unordered-list",
|
||||
"ordered-list",
|
||||
"link",
|
||||
"image",
|
||||
"code",
|
||||
"table",
|
||||
"horizontal-rule",
|
||||
"preview",
|
||||
"fullscreen",
|
||||
"guide",
|
||||
];
|
||||
return (
|
||||
<StyledMarkdownEditor
|
||||
className="my-markdown-editor"
|
||||
sx={{ marginTop: 1.5, marginBottom: 3 }}
|
||||
>
|
||||
<SimpleMDE {...props} />
|
||||
</StyledMarkdownEditor>
|
||||
);
|
||||
};
|
33
src/widgets/SightEdit/index.tsx
Normal file
33
src/widgets/SightEdit/index.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { Unlink } from "lucide-react";
|
||||
|
||||
import { Trash2 } from "lucide-react";
|
||||
import { TextField } from "@mui/material";
|
||||
import { ReactMarkdownEditor } from "@widgets";
|
||||
|
||||
export const SightEdit = () => {
|
||||
return (
|
||||
<div className="flex gap-3">
|
||||
<div className="flex flex-1 flex-col gap-3">
|
||||
<div className="flex items-center gap-2 justify-end">
|
||||
<button className="flex items-center gap-2 border border-gray-300 bg-blue-100 rounded-md px-2 py-1">
|
||||
Открепить
|
||||
<Unlink />
|
||||
</button>
|
||||
<button className="flex items-center gap-2 border border-gray-300 bg-red-100 rounded-md px-2 py-1">
|
||||
Удалить
|
||||
<Trash2 />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<TextField label="Заголовок" />
|
||||
<ReactMarkdownEditor />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-3 w-[350px]">
|
||||
<p>Превью</p>
|
||||
|
||||
<div className=" w-full bg-red-500">1</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
1
src/widgets/SightHeader/index.ts
Normal file
1
src/widgets/SightHeader/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./ui";
|
3
src/widgets/SightHeader/ui/index.tsx
Normal file
3
src/widgets/SightHeader/ui/index.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export const SightHeader = () => {
|
||||
return <div>SightHeader</div>;
|
||||
};
|
48
src/widgets/SightTabs/InformationTab/index.tsx
Normal file
48
src/widgets/SightTabs/InformationTab/index.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import { TextField } from "@mui/material";
|
||||
import { BackButton, TabPanel } from "@shared";
|
||||
import { LanguageSwitcher } from "@widgets";
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
60
src/widgets/SightTabs/LeftWidgetTab/index.tsx
Normal file
60
src/widgets/SightTabs/LeftWidgetTab/index.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
import { TextField } from "@mui/material";
|
||||
import { BackButton, TabPanel } from "@shared";
|
||||
import { ReactMarkdownComponent, ReactMarkdownEditor } from "@widgets";
|
||||
import { Trash2 } from "lucide-react";
|
||||
import { Unlink } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
|
||||
export const LeftWidgetTab = ({
|
||||
value,
|
||||
index,
|
||||
}: {
|
||||
value: number;
|
||||
index: number;
|
||||
}) => {
|
||||
const [leftArticleData, setLeftArticleData] = useState(" ");
|
||||
return (
|
||||
<TabPanel value={value} index={index}>
|
||||
<div className="flex flex-col gap-5">
|
||||
<BackButton />
|
||||
|
||||
<div className="flex items-center justify-between px-5 h-14 rounded-md border">
|
||||
<p className="text-2xl">Левая статья</p>
|
||||
<div className="flex items-center gap-5">
|
||||
<button className="flex items-center gap-2 border border-gray-300 bg-blue-100 rounded-md px-2 py-1">
|
||||
Открепить
|
||||
<Unlink />
|
||||
</button>
|
||||
<button className="flex items-center gap-2 border border-gray-300 bg-red-100 rounded-md px-2 py-1">
|
||||
Удалить
|
||||
<Trash2 />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-5">
|
||||
<div className="flex flex-col gap-5 flex-1">
|
||||
<TextField sx={{ width: "30%" }} label="Название" />
|
||||
|
||||
<ReactMarkdownEditor
|
||||
value={leftArticleData}
|
||||
onChange={setLeftArticleData}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<p>Предпросмотр</p>
|
||||
<div className="bg-yellow-200 w-[350px] h-full">
|
||||
<div className="bg-red-100 w-full h-[200px]"></div>
|
||||
<div className="bg-blue-100 w-full text-lg p-3"></div>
|
||||
<div className="bg-green-100 p-3 prose max-w-none">
|
||||
<ReactMarkdownComponent value={leftArticleData} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button className="bg-green-400 w-min ml-auto text-white py-2 rounded-2xl px-4">
|
||||
Сохранить
|
||||
</button>
|
||||
</div>
|
||||
</TabPanel>
|
||||
);
|
||||
};
|
73
src/widgets/SightTabs/RightWidgetTab/index.tsx
Normal file
73
src/widgets/SightTabs/RightWidgetTab/index.tsx
Normal file
@ -0,0 +1,73 @@
|
||||
import { BackButton, TabPanel } from "@shared";
|
||||
import { SightEdit } from "@widgets";
|
||||
import { Plus } from "lucide-react";
|
||||
|
||||
export const RightWidgetTab = ({
|
||||
value,
|
||||
index,
|
||||
}: {
|
||||
value: number;
|
||||
index: number;
|
||||
}) => {
|
||||
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>
|
||||
|
||||
{/* 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 */}
|
||||
Сохранить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</TabPanel>
|
||||
);
|
||||
};
|
3
src/widgets/SightTabs/index.ts
Normal file
3
src/widgets/SightTabs/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from "./InformationTab";
|
||||
export * from "./LeftWidgetTab";
|
||||
export * from "./RightWidgetTab";
|
8
src/widgets/index.ts
Normal file
8
src/widgets/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export * from "./Layout";
|
||||
export * from "./SightHeader";
|
||||
export * from "./SightTabs";
|
||||
export * from "./ReactMarkdown";
|
||||
export * from "./ReactMarkdownEditor";
|
||||
export * from "./SightEdit";
|
||||
export * from "./LanguageSwitcher";
|
||||
export * from "./DevicesTable";
|
Reference in New Issue
Block a user