Removed mantine libraries; Removed mandatory authentication

This commit is contained in:
2025-09-22 09:38:21 +09:00
parent c8caec7351
commit 037c0b7cf1
18 changed files with 686 additions and 1069 deletions

View File

@ -1,17 +1,30 @@
import { BrowserRouter as Router, Route, Routes, Navigate } from "react-router-dom"
import { BrowserRouter as Router, Route, Routes } from "react-router-dom"
//import { Navigate } from "react-router-dom"
import NotFound from "./pages/NotFound"
import MainLayout from "./layouts/MainLayout"
import { initAuth, useAuthStore } from "./store/auth"
import { useEffect, useState } from "react"
import DashboardLayout from "./layouts/DashboardLayout"
import { pages } from "./constants/app"
import { Spinner } from "@fluentui/react-components"
import { FluentProvider, Spinner, webDarkTheme, webLightTheme } from "@fluentui/react-components"
import { setColorScheme, useAppStore } from "./store/app"
function App() {
const auth = useAuthStore()
const { colorScheme } = useAppStore()
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
const localColorScheme = localStorage.getItem('colorScheme');
if (localColorScheme === 'light') {
setColorScheme('light')
} else if (localColorScheme === 'dark') {
setColorScheme('dark')
} else if (localColorScheme === 'auto') {
setColorScheme('auto')
}
initAuth()
}, [])
@ -28,27 +41,30 @@ function App() {
)
} else {
return (
<div style={{
width: '100%',
height: '100vh'
}}>
<Router>
<Routes>
<Route element={<MainLayout />}>
{pages.filter((page) => !page.dashboard).filter((page) => page.enabled).map((page, index) => (
<Route key={`ml-${index}`} path={page.path} element={page.component} />
))}
</Route>
<FluentProvider theme={colorScheme === 'light' ? webLightTheme : webDarkTheme}>
<div style={{
width: '100%',
height: '100vh'
}}>
<Router>
<Routes>
<Route element={<MainLayout />}>
{pages.filter((page) => !page.dashboard).filter((page) => page.enabled).map((page, index) => (
<Route key={`ml-${index}`} path={page.path} element={page.component} />
))}
</Route>
<Route element={auth.isAuthenticated ? <DashboardLayout></DashboardLayout> : <Navigate to={"/auth/signin"} />}>
{pages.filter((page) => page.dashboard).filter((page) => page.enabled).map((page, index) => (
<Route key={`dl-${index}`} path={page.path} element={page.component} />
))}
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
</Router>
</div>
{/* <Route element={auth.isAuthenticated || pages.find(page => page.path === '/auth/signin')?.enabled === false ? <DashboardLayout></DashboardLayout> : <Navigate to={"/auth/signin"} />}> */}
<Route element={<DashboardLayout></DashboardLayout>}>
{pages.filter((page) => page.dashboard).filter((page) => page.enabled).map((page, index) => (
<Route key={`dl-${index}`} path={page.path} element={page.component} />
))}
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
</Router>
</div>
</FluentProvider>
)
}
}

View File

@ -12,6 +12,7 @@ type CustomTableProps<T> = {
createFields?: CreateField[];
submitHandler?: (data: T) => Promise<AxiosResponse>
onEditCell?: (rowId: number, columnId: string, value: any) => any
searchable?: boolean
}
const CustomTable = <T extends object>({
@ -19,6 +20,7 @@ const CustomTable = <T extends object>({
columns,
createFields,
submitHandler,
searchable = false
}: CustomTableProps<T>) => {
const [data, setData] = useState<(T & { id: number })[]>(initialData);
const [searchText, setSearchText] = useState('');
@ -138,11 +140,12 @@ const CustomTable = <T extends object>({
display: 'flex',
gap: '1rem'
}}>
<Input
placeholder="Поиск"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
/>
{searchable &&
<Input
placeholder="Поиск"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
/>}
{createFields && submitHandler &&
<Dialog>

View File

@ -156,7 +156,16 @@ export default function FolderViewer() {
if (foldersLoading || documentsLoading) {
return (
<Spinner />
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '100%',
height: '100%',
padding: '1rem',
}}>
<Spinner />
</div>
)
}

View File

@ -14,7 +14,6 @@ import { addInteractions, handleImageDrop, loadFeatures, processFigure, processL
import useSWR, { SWRConfiguration } from 'swr'
import { fetcher } from '../../http/axiosInstance'
import { BASE_URL } from '../../constants'
import { useMantineColorScheme } from '@mantine/core'
import { IconBoxMultiple, IconBoxPadding, IconChevronLeft, IconPlus, IconUpload, } from '@tabler/icons-react'
import { ICitySettings, IFigure, ILine } from '../../interfaces/gis'
import axios from 'axios'
@ -34,8 +33,9 @@ import GisService from '../../services/GisService'
import MapMode from './MapMode'
import { satMapsProviders, schemas } from '../../constants/map'
import MapPrint from './MapPrint/MapPrint'
import { Field, Menu, MenuButton, MenuList, MenuPopover, MenuTrigger, Combobox, Option, Button, Divider, Spinner, Portal } from '@fluentui/react-components'
import { Field, Menu, MenuButton, MenuList, MenuPopover, MenuTrigger, Combobox, Option, Button, Divider, Spinner, Portal, Dropdown } from '@fluentui/react-components'
import { IRegion } from '../../interfaces/fuel'
import { useAppStore } from '../../store/app'
const swrOptions: SWRConfiguration = {
revalidateOnFocus: false
@ -60,8 +60,9 @@ const MapComponent = ({
id: string,
active: boolean,
}) => {
const { colorScheme } = useAppStore()
// Store
const { colorScheme } = useMantineColorScheme()
const { selectedYear, currentObjectId, selectedRegion, selectedDistrict } = useObjectsStore().id[id]
const {
mode, map, currentTool, alignMode, satMapsProvider,
@ -559,7 +560,7 @@ const MapComponent = ({
<Combobox
placeholder="Схема"
clearable
style={{ width: "92px", minWidth: 'auto' }}
style={{ minWidth: 'auto' }}
value={selectedYear ? selectedYear.toString() : ""}
onOptionSelect={(_ev, data) => {
if (data.optionValue) {
@ -585,25 +586,29 @@ const MapComponent = ({
</MenuTrigger>
<MenuPopover>
<MenuList>
<MenuList style={{ padding: '1rem' }}>
<Field>Настройка видимости слоёв</Field>
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
<Combobox
defaultValue={satMapsProviders.find(provider => provider.value === satMapsProvider)?.label ?? ""}
onOptionSelect={(_ev, data) => {
if (data.optionValue) {
setSatMapsProvider(id, data.optionValue as SatelliteMapsProvider);
}
}}
>
{satMapsProviders.map((provider) => (
<Option text={provider.label} key={provider.value} value={provider.value}>
{provider.label}
</Option>
))}
</Combobox>
<Field label="Спутниковые снимки">
<Dropdown
defaultValue={satMapsProviders.find(provider => provider.value === satMapsProvider)?.label}
value={satMapsProviders.find(provider => provider.value === satMapsProvider)?.label}
defaultSelectedOptions={[satMapsProvider]}
selectedOptions={[satMapsProvider]}
onOptionSelect={(_ev, data) => {
if (data.optionValue) {
setSatMapsProvider(id, data.optionValue as SatelliteMapsProvider);
}
}}
>
{satMapsProviders.map((provider) => (
<Option key={provider.value} text={provider.label} value={provider.value}>
{provider.label}
</Option>
))}
</Dropdown>
</Field>
</div>
<div style={{
display: 'flex',
@ -652,7 +657,7 @@ const MapComponent = ({
<div style={{ position: 'absolute', width: '100%', height: '100%' }}>
<div style={{ display: 'flex', flexDirection: 'column', width: '100%', height: '100%' }}>
<div style={{ display: 'flex', width: '100%', height: '94%', padding: '0.5rem', flexGrow: 1 }}>
<div style={{ display: 'flex', height: '94%', padding: '0.5rem', flexGrow: 1 }}>
<div style={{ display: 'flex', flexDirection: 'column', width: '100%', maxWidth: '380px' }}>
<div style={{ display: 'flex', width: '100%', height: '100%', gap: '0.5rem' }}>
{selectedRegion && selectedDistrict && selectedYear &&

View File

@ -1,8 +1,8 @@
import { useMantineColorScheme } from '@mantine/core'
import useSWR from 'swr'
import { fetcher } from '../../../http/axiosInstance'
import { BASE_URL } from '../../../constants'
import { Accordion, AccordionHeader, AccordionItem, AccordionPanel, ColorSwatch, Text } from '@fluentui/react-components'
import { useAppStore } from '../../../store/app'
const MapLegend = ({
selectedDistrict,
@ -11,7 +11,7 @@ const MapLegend = ({
selectedDistrict: number | null,
selectedYear: number | null,
}) => {
const { colorScheme } = useMantineColorScheme()
const { colorScheme } = useAppStore()
const { data: existingObjectsList } = useSWR(
selectedYear && selectedDistrict ? `/general/objects/list?year=${selectedYear}&city_id=${selectedDistrict}&planning=0` : null,

View File

@ -1,49 +1,66 @@
import { useMantineColorScheme } from '@mantine/core'
import { IconArrowBackUp, IconArrowsMove, IconCircle, IconExclamationCircle, IconLine, IconPoint, IconPolygon, IconRuler, IconTransformPoint } from '@tabler/icons-react'
import { getDraw, setCurrentTool, useMapStore } from '../../../store/map';
import { saveFeatures } from '../mapUtils';
import { Button } from '@fluentui/react-components';
import { Button, Tooltip } from '@fluentui/react-components';
import { useAppStore } from '../../../store/app';
const MapToolbar = ({
map_id
}: { map_id: string }) => {
const { currentTool } = useMapStore().id[map_id]
const { colorScheme } = useMantineColorScheme();
const { colorScheme } = useAppStore();
return (
<div style={{ display: 'flex' }}>
<div style={{ display: 'flex', flexDirection: 'column', zIndex: 1, backdropFilter: 'blur(8px)', backgroundColor: colorScheme === 'light' ? '#FFFFFFAA' : '#000000AA', borderRadius: '4px' }}>
<Button icon={<IconExclamationCircle />} appearance='transparent' onClick={() => saveFeatures(map_id)} />
<Button icon={<IconArrowBackUp />} appearance='transparent' onClick={() => getDraw(map_id)?.removeLastPoint()} />
<Tooltip content={"Отмена"} relationship='label' hideDelay={0} showDelay={0} withArrow>
<Button icon={<IconArrowBackUp />} appearance='transparent' onClick={() => getDraw(map_id)?.removeLastPoint()} />
</Tooltip>
<Button icon={<IconTransformPoint />} appearance={currentTool === 'Edit' ? 'primary' : 'transparent'} onClick={() => {
setCurrentTool(map_id, 'Edit')
}} />
<Tooltip content={"Редактировать"} relationship='label' hideDelay={0} showDelay={0} withArrow>
<Button icon={<IconTransformPoint />} appearance={currentTool === 'Edit' ? 'primary' : 'transparent'} onClick={() => {
setCurrentTool(map_id, 'Edit')
}} />
</Tooltip>
<Button icon={<IconPoint />} appearance={currentTool === 'Point' ? 'primary' : 'transparent'} onClick={() => {
setCurrentTool(map_id, 'Point')
}} />
<Tooltip content={"Точка"} relationship='label' hideDelay={0} showDelay={0} withArrow>
<Button icon={<IconPoint />} appearance={currentTool === 'Point' ? 'primary' : 'transparent'} onClick={() => {
setCurrentTool(map_id, 'Point')
}} />
</Tooltip>
<Button icon={<IconLine />} appearance={currentTool === 'LineString' ? 'primary' : 'transparent'} onClick={() => {
setCurrentTool(map_id, 'LineString')
}} />
<Button icon={<IconPolygon />} appearance={currentTool === 'Polygon' ? 'primary' : 'transparent'} onClick={() => {
setCurrentTool(map_id, 'Polygon')
}} />
<Tooltip content={"Линия"} relationship='label' hideDelay={0} showDelay={0} withArrow>
<Button icon={<IconLine />} appearance={currentTool === 'LineString' ? 'primary' : 'transparent'} onClick={() => {
setCurrentTool(map_id, 'LineString')
}} />
</Tooltip>
<Button icon={<IconCircle />} appearance={currentTool === 'Circle' ? 'primary' : 'transparent'} onClick={() => {
setCurrentTool(map_id, 'Circle')
}} />
<Tooltip content={"Многоугольник"} relationship='label' hideDelay={0} showDelay={0} withArrow>
<Button icon={<IconPolygon />} appearance={currentTool === 'Polygon' ? 'primary' : 'transparent'} onClick={() => {
setCurrentTool(map_id, 'Polygon')
}} />
</Tooltip>
<Button icon={<IconArrowsMove />} appearance={currentTool === 'Mover' ? 'primary' : 'transparent'} onClick={() => {
setCurrentTool(map_id, 'Mover')
}} />
<Tooltip content={"Окружность"} relationship='label' hideDelay={0} showDelay={0} withArrow>
<Button icon={<IconCircle />} appearance={currentTool === 'Circle' ? 'primary' : 'transparent'} onClick={() => {
setCurrentTool(map_id, 'Circle')
}} />
</Tooltip>
<Button icon={<IconRuler />} appearance={currentTool === 'Measure' ? 'primary' : 'transparent'} onClick={() => {
setCurrentTool(map_id, 'Measure')
}} />
<Tooltip content={"Перемещение"} relationship='label' hideDelay={0} showDelay={0} withArrow>
<Button icon={<IconArrowsMove />} appearance={currentTool === 'Mover' ? 'primary' : 'transparent'} onClick={() => {
setCurrentTool(map_id, 'Mover')
}} />
</Tooltip>
<Tooltip content={"Линейка"} relationship='label' hideDelay={0} showDelay={0} withArrow>
<Button icon={<IconRuler />} appearance={currentTool === 'Measure' ? 'primary' : 'transparent'} onClick={() => {
setCurrentTool(map_id, 'Measure')
}} />
</Tooltip>
</div>
</div>
)

View File

@ -47,7 +47,7 @@ const pages = [
component: <PasswordReset />,
drawer: false,
dashboard: false,
enabled: true,
enabled: false,
},
{
label: "Настройки",
@ -137,7 +137,7 @@ const pages = [
component: <MapLineTest />,
drawer: true,
dashboard: true,
enabled: true,
enabled: false,
},
{
label: "Монитор",
@ -173,7 +173,7 @@ const pages = [
component: <DBManager />,
drawer: true,
dashboard: true,
enabled: true,
enabled: false,
},
{
label: 'Fuel',

View File

@ -1,11 +1,11 @@
import { useMantineColorScheme } from '@mantine/core';
import { Outlet, useNavigate } from 'react-router-dom';
import { IconLogout, IconSettings, IconMoon, IconSun, IconMenu2, IconUser } from '@tabler/icons-react';
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
import { IconLogout, IconSettings, IconMoon, IconSun, IconMenu2, IconUser, IconLogin } from '@tabler/icons-react';
import { getUserData, logout, useAuthStore } from '../store/auth';
import { useEffect, useState } from 'react';
import { UserData } from '../interfaces/auth';
import { pages } from '../constants/app';
import { Button, Image, makeStyles, Menu, MenuButton, MenuItem, MenuList, MenuPopover, MenuTrigger, Text } from '@fluentui/react-components';
import { setColorScheme, useAppStore } from '../store/app';
const useStyles = makeStyles({
root: {
@ -49,6 +49,8 @@ const useStyles = makeStyles({
function DashboardLayout() {
const navigate = useNavigate()
const location = useLocation()
const { colorScheme } = useAppStore()
const getPageTitle = () => {
const currentPath = location.pathname
@ -69,8 +71,6 @@ function DashboardLayout() {
}
}, [authStore])
const { colorScheme, setColorScheme } = useMantineColorScheme();
const classes = useStyles()
const [navbarOpen, setNavbarOpen] = useState(true)
@ -80,7 +80,6 @@ function DashboardLayout() {
<div className={classes.header}>
<div style={{
display: 'flex',
height: '100%',
width: '100%',
alignItems: 'center',
gap: '0.75rem',
@ -96,29 +95,30 @@ function DashboardLayout() {
</div>
<div style={{ flexShrink: 0 }}>
<Menu positioning={{ autoSize: true }}>
<MenuTrigger>
<MenuButton appearance='transparent' icon={<IconUser />}>{`${userData?.name} ${userData?.surname}`}</MenuButton>
</MenuTrigger>
<Menu positioning={{ autoSize: true }}>
<MenuTrigger>
<MenuButton appearance='transparent' icon={authStore.isAuthenticated ? <IconUser /> : <IconSettings />}>{authStore.isAuthenticated && `${userData?.name} ${userData?.surname}`}</MenuButton>
</MenuTrigger>
<MenuPopover>
<MenuList>
<MenuItem icon={
colorScheme === 'dark' ? <IconMoon /> : <IconSun />
} onClick={() => colorScheme === 'dark' ? setColorScheme('light') : setColorScheme('dark')}>Тема: {colorScheme === 'dark' ? 'тёмная' : 'светлая'}</MenuItem>
<MenuItem icon={<IconSettings />} onClick={() => navigate('/settings')}>Настройки профиля</MenuItem>
<MenuItem icon={<IconLogout />} onClick={() => {
logout()
<MenuPopover>
<MenuList>
{!authStore.isAuthenticated && <MenuItem icon={<IconLogin />} onClick={() => navigate('/auth/signin')}>Войти</MenuItem>}
<MenuItem icon={colorScheme === 'dark' ? <IconMoon /> : <IconSun />} onClick={() => colorScheme === 'dark' ? setColorScheme('light') : setColorScheme('dark')}>Тема: {colorScheme === 'dark' ? 'тёмная' : 'светлая'}</MenuItem>
{authStore.isAuthenticated && <MenuItem icon={<IconSettings />} onClick={() => navigate('/settings')}>Настройки профиля</MenuItem>}
{authStore.isAuthenticated && <MenuItem icon={<IconLogout />} onClick={() => {
logout()
if (pages.find(page => page.path === '/auth/signin')?.enabled) {
navigate("/auth/signin")
}}>Выход</MenuItem>
<MenuItem icon={<Image src={'/logo2.svg'} width={24} />}>
0.1.0
</MenuItem>
</MenuList>
</MenuPopover>
</Menu>
</div>
}
}}>Выход</MenuItem>}
<MenuItem icon={<Image src={'/logo2.svg'} width={24} />}>
0.1.0
</MenuItem>
</MenuList>
</MenuPopover>
</Menu>
</div>
</div>
@ -127,7 +127,7 @@ function DashboardLayout() {
maxWidth: navbarOpen ? '200px' : '2.70rem',
}}>
{pages.filter((page) => page.drawer).filter((page) => page.enabled).map((item) => (
<Button key={item.path} style={{ paddingLeft: '0.5rem', flexShrink: 0, flexWrap: 'nowrap', textWrap: 'nowrap', borderRadius: 0 }} appearance='subtle' onClick={() => navigate(item.path)}>
<Button key={item.path} style={{ paddingLeft: '0.5rem', flexShrink: 0, flexWrap: 'nowrap', textWrap: 'nowrap', borderRadius: 0 }} appearance={location.pathname === item.path ? 'primary' : 'subtle'} onClick={() => navigate(item.path)}>
<div style={{ display: 'flex', }}>
{item.icon}
</div>
@ -143,9 +143,6 @@ function DashboardLayout() {
</div>
</Button>
// <NavItem style={{ flexShrink: 0, flexWrap: 'nowrap', textWrap: 'nowrap' }} onClick={() => navigate(item.path)} icon={item.icon} value={item.path}>
// {item.label}
// </NavItem>
))}
</div>
@ -156,116 +153,6 @@ function DashboardLayout() {
</div>
)
// return (
// <AppShell
// header={{ height: 60 }}
// navbar={{
// width: desktopOpened ? 200 : 50,
// breakpoint: 'sm',
// collapsed: { mobile: !mobileOpened },
// }}
// >
// <AppShell.Header>
// <Flex h="100%" px="md" w='100%' align='center' gap='sm'>
// <Group>
// <Burger opened={mobileOpened} onClick={toggleMobile} hiddenFrom="sm" size="sm" />
// <Burger opened={desktopOpened} onClick={toggleDesktop} visibleFrom="sm" size="sm" />
// </Group>
// <Group w='auto'>
// <Text fw='600'>{getPageTitle()}</Text>
// </Group>
// <Group id='header-portal' w='auto' ml='auto'>
// </Group>
// <Group style={{ flexShrink: 0 }}>
// <Menu
// width={260}
// position="bottom-end"
// transitionProps={{ transition: 'pop-top-right' }}
// withinPortal
// >
// <Menu.Target>
// <Button variant='transparent'>
// <Group gap={7}>
// <Avatar name={`${userData?.name} ${userData?.surname}`} radius="xl" size={30} />
// <Text fw={500} size="sm" lh={1} mr={3}>
// {`${userData?.name} ${userData?.surname}`}
// </Text>
// <IconChevronDown style={{ width: rem(12), height: rem(12) }} stroke={1.5} />
// </Group>
// </Button>
// </Menu.Target>
// <Menu.Dropdown>
// <Menu.Label>{userData?.login}</Menu.Label>
// <Menu.Item
// leftSection={
// colorScheme === 'dark' ? <IconMoon style={{ width: rem(16), height: rem(16) }} stroke={1.5} /> : <IconSun style={{ width: rem(16), height: rem(16) }} stroke={1.5} />
// }
// onClick={() => colorScheme === 'dark' ? setColorScheme('light') : setColorScheme('dark')}
// >
// Тема: {colorScheme === 'dark' ? 'тёмная' : 'светлая'}
// </Menu.Item>
// <Menu.Item
// leftSection={
// <IconSettings style={{ width: rem(16), height: rem(16) }} stroke={1.5} />
// }
// onClick={() => navigate('/settings')}
// >
// Настройки профиля
// </Menu.Item>
// <Menu.Item
// onClick={() => {
// logout()
// navigate("/auth/signin")
// }}
// leftSection={<IconLogout style={{ width: rem(16), height: rem(16) }} stroke={1.5} />}
// >
// Выход
// </Menu.Item>
// <Menu.Item>
// <Flex gap='sm' align='center'>
// <Image src={'/logo2.svg'} w={32} />
// <Text>0.1.0</Text>
// </Flex>
// </Menu.Item>
// </Menu.Dropdown>
// </Menu>
// </Group>
// </Flex>
// </AppShell.Header>
// <AppShell.Navbar style={{ transition: "width 0.2s ease" }}>
// {pages.filter((page) => page.drawer).filter((page) => page.enabled).map((item) => (
// <NavLink
// key={item.path}
// onClick={() => navigate(item.path)}
// label={item.label}
// leftSection={item.icon}
// active={location.pathname === item.path}
// style={{ textWrap: 'nowrap' }}
// // styles={(theme, { active }) => ({
// // root: {
// // color: active ? theme.colors.blue[6] : theme.colors.dark[5],
// // fontWeight: active ? "bold" : "normal",
// // },
// // leftSection: {
// // color: active ? theme.colors.blue[6] : theme.colors.dark[5], // Icon color
// // }
// // })}
// />
// ))}
// </AppShell.Navbar>
// <AppShell.Main>
// <Flex bg={colorScheme === 'dark' ? undefined : '#E8E8E8'} w={{ sm: desktopOpened ? 'calc(100% - 200px)' : 'calc(100% - 50px)', base: '100%' }} h={'calc(100% - 60px)'} style={{ transition: "width 0.2s ease" }} pos={'fixed'}>
// <Outlet />
// </Flex>
// </AppShell.Main>
// </AppShell>
// )
}
export default DashboardLayout

View File

@ -1,44 +1,12 @@
import "@fontsource/inter";
import '@mantine/core/styles.css';
import '@mantine/dates/styles.css';
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import { createTheme, DEFAULT_THEME, MantineProvider, mergeMantineTheme } from '@mantine/core';
import 'dayjs/locale/ru';
import { DatesProvider } from "@mantine/dates";
import { FluentProvider, webLightTheme } from '@fluentui/react-components';
const overrides = createTheme({
// Set this color to `--mantine-color-body` CSS variable
white: '#F0F0F0',
colors: {
// ...
},
})
const theme = mergeMantineTheme(DEFAULT_THEME, overrides);
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<MantineProvider theme={theme}>
<FluentProvider theme={webLightTheme}>
<DatesProvider settings={{ locale: 'ru' }}>
<App />
</DatesProvider>
</FluentProvider>
</MantineProvider>
<App />
</React.StrictMode>,
)
// ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
// <React.StrictMode>
// <MantineProvider theme={theme}>
// <DatesProvider settings={{ locale: 'ru' }}>
// <App />
// </DatesProvider>
// </MantineProvider>
// </React.StrictMode>,
// )
)

View File

@ -1,15 +1,15 @@
import { Modal, useMantineColorScheme } from "@mantine/core";
import { IconMathMax, IconPlus, IconTableMinus } from "@tabler/icons-react";
import { FuelExpenseDtoHeaders, FuelLimitDtoHeaders } from "../dto/fuel/fuel.dto";
import useSWR from "swr";
import { fetcher } from "../http/axiosInstanceNest";
import { useEffect, useState } from "react";
import { DateInput } from '@mantine/dates'
import { SubmitHandler, useForm } from "react-hook-form";
import { AgGridReact } from "ag-grid-react";
import { AllCommunityModule, ColDef, ModuleRegistry } from 'ag-grid-community'
import { Button, Field, Input, Spinner, Tab, TabList } from "@fluentui/react-components";
import { CalendarStrings, DatePicker, defaultDatePickerStrings } from "@fluentui/react-datepicker-compat"
import { Button, Dialog, DialogSurface, DialogTitle, DialogTrigger, Field, Input, Spinner, Tab, TabList } from "@fluentui/react-components";
import { useAppStore } from "../store/app";
ModuleRegistry.registerModules([AllCommunityModule])
@ -94,9 +94,7 @@ export default function FuelPage() {
const { isLoading } = useSWR(currentTab.get, () => fetcher(currentTab.get), { revalidateOnFocus: false })
const [openCreateModel, setOpenCreateModal] = useState(false)
const { colorScheme } = useMantineColorScheme()
const { colorScheme } = useAppStore()
useEffect(() => {
if (colorScheme === 'dark') {
@ -108,8 +106,6 @@ export default function FuelPage() {
return (
<>
<ModalCreate openedCreateModal={openCreateModel} closeCreateModal={() => setOpenCreateModal(false)} currentTab={currentTab} />
<div style={{ display: 'flex', flexDirection: 'column', width: '100%' }}>
<TabList defaultValue={tables[0].value} selectedValue={currentTab.value}>
{tables.map((table, index) => (
@ -120,9 +116,22 @@ export default function FuelPage() {
</TabList>
<div style={{ display: 'flex', padding: '1rem' }}>
<Button appearance='primary' icon={<IconPlus />} onClick={() => setOpenCreateModal(true)}>
Добавить
</Button>
<Dialog>
<DialogTrigger>
<Button
appearance='primary'
icon={<IconPlus />}
style={{ flexShrink: 0 }}
>
Добавить
</Button>
</DialogTrigger>
<DialogSurface>
<DialogTitle>Добавление объекта</DialogTitle>
<ModalCreate currentTab={currentTab} />
</DialogSurface>
</Dialog>
</div>
{tables.map((table, index) => {
@ -133,7 +142,7 @@ export default function FuelPage() {
<Spinner />
</div>
:
<>
<div style={{ width: '100%', height: '100%', padding: '1rem' }}>
<AgGridReact
key={index}
//rowData={data}
@ -148,7 +157,7 @@ export default function FuelPage() {
flex: 1,
}}
/>
</>
</div>
)
}
}
@ -159,12 +168,8 @@ export default function FuelPage() {
}
const ModalCreate = ({
openedCreateModal,
closeCreateModal,
currentTab
}: {
openedCreateModal: boolean
closeCreateModal: () => void
currentTab: ITableSchema
}) => {
const { register, handleSubmit,
@ -177,38 +182,72 @@ const ModalCreate = ({
console.log('Values to submit:', values)
}
return (
<Modal withinPortal opened={openedCreateModal} onClose={closeCreateModal}>
<form style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }} onSubmit={handleSubmit(onSubmit)}>
{currentTab.post_include.map((header, index) => {
switch (header.field_type) {
case 'date':
return (
<DateInput label={header.field} />
)
case 'text':
return (
<Field key={index} label={header.field}>
<Input {...register(header.field, {
required: true
})} />
</Field>
)
default:
return (
<Field key={index} label={header.field}>
<Input {...register(header.field, {
required: true
})} />
</Field>
)
}
})}
const localizedStrings: CalendarStrings = {
...defaultDatePickerStrings,
days: [
'Воскресенье',
'Понедельник',
'Вторник',
'Среда',
'Четверг',
'Пятница',
'Суббота'
],
shortDays: ['Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб'],
months: [
"Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Ноябрь", "Декабрь"
],
shortMonths: [
"Янв",
"Фев",
"Мар",
"Апр",
"Май",
"Июн",
"Июл",
"Авг",
"Сен",
"Ноя",
"Дек",
]
}
<Button style={{ marginTop: '2rem' }} appearance="primary" type='submit'>
Добавить
</Button>
</form>
</Modal>
return (
<form style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }} onSubmit={handleSubmit(onSubmit)}>
{currentTab.post_include.map((header, index) => {
switch (header.field_type) {
case 'date':
return (
<Field key={index} label={header.field}>
<DatePicker
strings={localizedStrings}
{...register(header.field, {
required: true
})} />
</Field>
)
case 'text':
return (
<Field key={index} label={header.field}>
<Input {...register(header.field, {
required: true
})} />
</Field>
)
default:
return (
<Field key={index} label={header.field}>
<Input {...register(header.field, {
required: true
})} />
</Field>
)
}
})}
<Button style={{ marginTop: '2rem' }} appearance="primary" type='submit'>
Добавить
</Button>
</form>
)
}

View File

@ -1,10 +1,10 @@
import { useEffect, useState } from "react"
import createReport, { listCommands } from 'docx-templates'
import { Dropzone, IMAGE_MIME_TYPE } from '@mantine/dropzone'
import { IconFileTypeDocx, IconPlus, IconUpload, IconX } from "@tabler/icons-react"
import { IconPlus, IconX } from "@tabler/icons-react"
import { CommandSummary } from "docx-templates/lib/types"
import { Control, Controller, FieldValues, SubmitHandler, useFieldArray, useForm, UseFormRegister } from "react-hook-form"
import { Button, Field, Input, Text } from "@fluentui/react-components"
import { Button, Field, Input, Text, tokens, useId } from "@fluentui/react-components"
import { ArrowUploadRegular } from "@fluentui/react-icons"
const xslTemplate = `<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
@ -1062,6 +1062,8 @@ const FormLoop = ({
)
}
const IMAGE_MIME_TYPE = ["image/png", "image/jpeg", "image/jpg", "image/webp"];
const renderCommand = (
control: Control<FieldValues, any>,
register: UseFormRegister<FieldValues>,
@ -1080,50 +1082,72 @@ const renderCommand = (
}
if (command.type === 'IMAGE') {
const inputId = useId("file-input");
return (
<div style={{ display: 'flex', flexDirection: 'column' }}>
<Text size={200} weight="semibold">{command.code}</Text>
<Controller
key={key}
name={name}
control={control}
render={({ field: { onChange } }) => (
<Dropzone
accept={IMAGE_MIME_TYPE}
maxSize={5 * 1024 ** 2}
onReject={(files) => console.log('rejected files', files)}
onDrop={(files) => {
console.log(files[0])
files[0].arrayBuffer().then(res => {
onChange({
width: 6,
height: 6,
data: new Uint8Array(res),
extension: files[0]?.path?.match(/\.[^.]+$/)?.[0] || ""
})
})
}}
maxFiles={1}
>
<div style={{ display: 'flex', justifyContent: 'center', gap: '2rem', minHeight: '220px', pointerEvents: 'none' }}>
<Dropzone.Accept>
<IconUpload size={52} color="var(--mantine-color-blue-6)" stroke={1.5} />
</Dropzone.Accept>
<Dropzone.Reject>
<IconX size={52} color="var(--mantine-color-red-6)" stroke={1.5} />
</Dropzone.Reject>
<Dropzone.Idle>
<IconFileTypeDocx size={52} color="var(--mantine-color-dimmed)" stroke={1.5} />
</Dropzone.Idle>
render={({ field: { onChange } }) => {
const handleFiles = (files: FileList | null) => {
if (!files || files.length === 0) return;
const file = files[0];
if (!IMAGE_MIME_TYPE.includes(file.type)) {
console.log("Rejected file:", file);
return;
}
<div>
<Text size={300}>
Перетащите файлы сюда или нажмите, чтобы выбрать их
</Text>
</div>
file.arrayBuffer().then((res) => {
onChange({
width: 6,
height: 6,
data: new Uint8Array(res),
extension: file.name.match(/\.[^.]+$/)?.[0] || "",
});
});
};
const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
handleFiles(e.dataTransfer.files);
};
return (
<div
onDragOver={(e) => e.preventDefault()}
onDrop={handleDrop}
style={{
border: `2px dashed ${tokens.colorNeutralStroke1}`,
borderRadius: tokens.borderRadiusLarge,
minHeight: "220px",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: "1rem",
padding: "1rem",
textAlign: "center",
cursor: "pointer",
}}
onClick={() => document.getElementById(inputId)?.click()}
>
<input
id={inputId}
type="file"
accept={IMAGE_MIME_TYPE.join(",")}
style={{ display: "none" }}
onChange={(e) => handleFiles(e.target.files)}
/>
<ArrowUploadRegular fontSize={40} color={tokens.colorBrandForeground1} />
<Text size={300}>
Перетащите изображение сюда или нажмите, чтобы выбрать
</Text>
</div>
</Dropzone>
)}
);
}}
/>
</div>
)

View File

@ -2,7 +2,7 @@ import { useRoles } from '../hooks/swrHooks'
import { CreateField } from '../interfaces/create'
import RoleService from '../services/RoleService'
import CustomTable from '../components/CustomTable'
import { Spinner } from '@fluentui/react-components'
import { Link, Spinner } from '@fluentui/react-components'
export default function Roles() {
const { roles, isError, isLoading } = useRoles()
@ -12,8 +12,37 @@ export default function Roles() {
{ key: 'description', headerName: 'Описание', type: 'string', required: false, defaultValue: '' },
]
if (isError) return <div>Произошла ошибка при получении данных.</div>
if (isLoading) return <Spinner />
const handleError = (error: any) => {
if (error?.response?.status === 401) {
return (
<Link href="/auth/signin">
Войдите, чтобы продолжить
</Link>
)
} else {
return "Произошла ошибка при получении данных."
}
}
if (isError) return (
<div style={{ padding: '1rem' }}>
{handleError(isError)}
</div>
)
if (isLoading)
return (
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '100%',
height: '100%',
padding: '1rem',
}}>
<Spinner />
</div>
)
return (
<div style={{

View File

@ -4,7 +4,7 @@ import { useEffect, useState } from "react"
import { CreateField } from "../interfaces/create"
import UserService from "../services/UserService"
import CustomTable from "../components/CustomTable"
import { Spinner } from "@fluentui/react-components"
import { Link, Spinner } from "@fluentui/react-components"
import { IUser } from "../interfaces/user"
export default function Users() {
@ -37,15 +37,34 @@ export default function Users() {
{ key: 'password', headerName: 'Пароль', type: 'string', required: true, defaultValue: '' },
]
const handleError = (error: any) => {
if (error?.response?.status === 401) {
return (
<Link href="/auth/signin">
Войдите, чтобы продолжить
</Link>
)
} else {
return "Произошла ошибка при получении данных."
}
}
if (isError) return (
<div>
Произошла ошибка при получении данных.
<div style={{ padding: '1rem' }}>
{handleError(isError)}
</div>
)
if (isLoading) {
return (
<div>
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '100%',
height: '100%',
padding: '1rem',
}}>
<Spinner />
</div>
)

View File

@ -100,7 +100,8 @@ const SignIn = () => {
{pages.find(page => page.path === '/auth/signup')?.enabled &&
<Button as='a' href='/auth/signup' type="button" appearance='subtle'>
Регистрация
</Button>}
</Button>
}
</div>
</form>
</div>

View File

@ -2,7 +2,10 @@ import { create } from 'zustand';
export type Mode = 'edit' | 'view'
export type ColorScheme = 'light' | 'dark' | 'auto'
export interface AppState {
colorScheme: ColorScheme,
mapTab: Record<string, {
year: number | null,
region: number | null,
@ -12,10 +15,20 @@ export interface AppState {
}
export const useAppStore = create<AppState>(() => ({
colorScheme: 'auto',
currentTab: null,
mapTab: {}
}))
const getColorScheme = () => {
useAppStore.getState().colorScheme
}
const setColorScheme = (colorScheme: ColorScheme) => {
useAppStore.setState(() => ({ colorScheme: colorScheme }))
localStorage.setItem('colorScheme', colorScheme.toString())
}
const getCurrentTab = () => useAppStore.getState().currentTab
const setCurrentTab = (id: string | null) => useAppStore.setState(() => ({ currentTab: id }))
@ -39,5 +52,7 @@ export {
deleteMapTab,
getCurrentTab,
setCurrentTab,
setMapTabYear
setMapTabYear,
getColorScheme,
setColorScheme
}