diff --git a/client/src/components/CustomTable.tsx b/client/src/components/CustomTable.tsx index 93fea4e..15055de 100644 --- a/client/src/components/CustomTable.tsx +++ b/client/src/components/CustomTable.tsx @@ -3,7 +3,7 @@ import { IconPlus } from '@tabler/icons-react'; import { CreateField } from '../interfaces/create'; import { AxiosResponse } from 'axios'; import FormFields from './FormFields'; -import { Badge, Button, createTableColumn, DataGrid, DataGridBody, DataGridCell, DataGridHeader, DataGridHeaderCell, DataGridRow, Dialog, DialogSurface, DialogTitle, DialogTrigger, Input, Select, TableCellLayout, TableColumnDefinition } from '@fluentui/react-components'; +import { Badge, Button, createTableColumn, DataGrid, DataGridBody, DataGridCell, DataGridHeader, DataGridHeaderCell, DataGridRow, Dialog, DialogSurface, DialogTitle, DialogTrigger, Input, Select, Spinner, TableCellLayout, TableColumnDefinition } from '@fluentui/react-components'; import { IColumn } from '../interfaces/DataGrid/columns'; type CustomTableProps = { @@ -13,6 +13,7 @@ type CustomTableProps = { submitHandler?: (data: T) => Promise onEditCell?: (rowId: number, columnId: string, value: any) => any searchable?: boolean + isLoading?: boolean } const CustomTable = ({ @@ -20,7 +21,8 @@ const CustomTable = ({ columns, createFields, submitHandler, - searchable = false + searchable = false, + isLoading = false }: CustomTableProps) => { const [data, setData] = useState<(T & { id: number })[]>(initialData); const [searchText, setSearchText] = useState(''); @@ -38,7 +40,7 @@ const CustomTable = ({ ) } - const columnDefinitions: TableColumnDefinition[] = columns.map(column => ( + const columnDefinitions: TableColumnDefinition[] = columns.filter(column => column.hidden !== true).map(column => ( createTableColumn({ columnId: column.name, renderHeaderCell: () => column.header, @@ -170,27 +172,33 @@ const CustomTable = ({ } - - - - {({ renderHeaderCell }) => ( - {renderHeaderCell()} - )} - - - - {({ item, rowId }) => ( - - {({ renderCell }) => {renderCell(item)}} + {isLoading ? + + : + item.name} + focusMode="cell" + > + + + {({ renderHeaderCell }) => ( + {renderHeaderCell()} + )} - )} - - + + >> + {({ item, rowId }) => ( + + {({ renderCell }) => {renderCell(item)}} + + )} + + + } ); }; diff --git a/client/src/constants/app.tsx b/client/src/constants/app.tsx index 02a9832..33b5282 100644 --- a/client/src/constants/app.tsx +++ b/client/src/constants/app.tsx @@ -2,7 +2,6 @@ import { IconComponents, IconDeviceDesktopAnalytics, IconFlame, IconLogin, IconL import SignIn from "../pages/auth/SignIn"; import SignUp from "../pages/auth/SignUp"; import PasswordReset from "../pages/auth/PasswordReset"; -import ComponentTest from "../pages/ComponentTest"; import MonitorPage from "../pages/MonitorPage"; import Settings from "../pages/Settings"; import Users from "../pages/Users"; @@ -10,13 +9,16 @@ import Roles from "../pages/Roles"; import Documents from "../pages/Documents"; import Reports from "../pages/Reports"; import Servers from "../pages/Servers"; -import Boilers from "../pages/Boilers"; +import Boilers from "../pages/fuel/Fuel/Boilers"; import MapTest from "../pages/MapTest"; import PrintReport from "../pages/PrintReport"; import DBManager from "../pages/DBManager"; import MapLineTest from "../components/map/MapLineTest"; import FuelPage from "../pages/Fuel"; -import { Building24Color, Cloud24Color, Database24Color, Document24Color, Form24Color, Map24Filled, Map24Regular, PeopleList24Color, Shield24Color } from "@fluentui/react-icons" +import { Building24Color, Cloud24Color, Database24Color, Diversity24Color, Document24Color, Form24Color, Gauge24Color, Map24Filled, Map24Regular, PeopleList24Color, Shield24Color } from "@fluentui/react-icons" +import LimitsPage from "../pages/fuel/Limits"; +import ReportsPage from "../pages/fuel/Reports"; +import FuelsPage from "../pages/fuel/Fuel/Fuels"; // Определение страниц с путями и компонентом для рендера @@ -115,13 +117,40 @@ const pages = [ }, { label: "Котельные", - path: "/boilers", + path: "/fuel/boilers", icon: , component: , drawer: true, dashboard: true, enabled: true, }, + { + label: "Виды топлива", + path: "/fuel/fuels", + icon: , + component: , + drawer: true, + dashboard: true, + enabled: true, + }, + { + label: "Лимиты", + path: "/fuel/limits", + icon: , + component: , + drawer: true, + dashboard: true, + enabled: true, + }, + { + label: "Отчеты", + path: "/fuel/reports", + icon: , + component: , + drawer: true, + dashboard: true, + enabled: true, + }, // { // label: "ИКС", // path: "/map-test", @@ -149,15 +178,6 @@ const pages = [ dashboard: true, enabled: false, }, - { - label: "Component test", - path: "/component-test", - icon: , - component: , - drawer: true, - dashboard: true, - enabled: false, - }, { label: "Print report test", path: "/print-report-test", diff --git a/client/src/interfaces/fuel.ts b/client/src/interfaces/fuel.ts index 5e33465..039fa22 100644 --- a/client/src/interfaces/fuel.ts +++ b/client/src/interfaces/fuel.ts @@ -14,4 +14,9 @@ export interface IBoiler { boiler_code: string; id_city: number; activity: boolean; +} + +export type FuelType = { + id: number + name: string } \ No newline at end of file diff --git a/client/src/interfaces/objects.ts b/client/src/interfaces/objects.ts index 247ebd5..90a144c 100644 --- a/client/src/interfaces/objects.ts +++ b/client/src/interfaces/objects.ts @@ -24,6 +24,13 @@ export interface IObjectValue { date_po: string | null } +export type IObjectParamFormat = + 'bit' | + 'int' | 'smallint' | 'bigint' | 'tinyint' | + 'smalldatetime' | 'calculate' | + 'TCB' | 'GTCB' | + 'year' | 'uniqueidentifier' | 'group' | 'groupcalculate' | 'array' | string + export interface IObjectParam { id_object: string id_param: number @@ -31,7 +38,7 @@ export interface IObjectParam { date_s: string | null date_po: string | null id_user: number - format: string + format: IObjectParamFormat vtable: string exact_format: string | null unit: string | null diff --git a/client/src/pages/Fuel.tsx b/client/src/pages/Fuel.tsx index 6f919e3..e8dbb08 100644 --- a/client/src/pages/Fuel.tsx +++ b/client/src/pages/Fuel.tsx @@ -2,14 +2,13 @@ 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 { useState } from "react"; import { SubmitHandler, useForm } from "react-hook-form"; import { AgGridReact } from "ag-grid-react"; import { AllCommunityModule, ColDef, ModuleRegistry } from 'ag-grid-community' 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,16 +93,6 @@ export default function FuelPage() { const { data, isLoading } = useSWR(currentTab.get, () => fetcher(currentTab.get), { revalidateOnFocus: false }) - const { colorScheme } = useAppStore() - - useEffect(() => { - if (colorScheme === 'dark') { - document.body.dataset.agThemeMode = 'dark' - } else { - document.body.dataset.agThemeMode = 'light' - } - }, [colorScheme]) - return ( <>
@@ -137,7 +126,6 @@ export default function FuelPage() {
{tables.map((table, index) => { if (table.value === currentTab.value) { @@ -147,12 +135,11 @@ export default function FuelPage() {
: -
+
({ ...obj, [key]: 'test' }), {}), + // Object.keys(table.headers).reduce((obj, key) => ({ ...obj, [key]: 'test1' }), {}), // Object.keys(table.headers).reduce((obj, key) => ({ ...obj, [key]: 'test' }), {}) // ]} rowData={data} diff --git a/client/src/pages/fuel/Fuel/Boilers.tsx b/client/src/pages/fuel/Fuel/Boilers.tsx new file mode 100644 index 0000000..3a50e4e --- /dev/null +++ b/client/src/pages/fuel/Fuel/Boilers.tsx @@ -0,0 +1,198 @@ +import { useEffect, useState } from 'react' +import { Combobox, CompoundButton, Option, Text } from '@fluentui/react-components' +import useSWR from 'swr' +import axios from 'axios' +import { AgGridReact, CustomCellRendererProps } from 'ag-grid-react' +import { AllCommunityModule, ModuleRegistry } from 'ag-grid-community' +import FuelRenderer from './FuelRenderer' + +ModuleRegistry.registerModules([AllCommunityModule]) + +function Boilers() { + const [, setBoilersPage] = useState(1) + const [boilerSearch, setBoilerSearch] = useState("") + const [, setDebouncedBoilerSearch] = useState("") + // const { boilers } = useBoilers(10, boilersPage, debouncedBoilerSearch) + + const { data: regions } = useSWR('/general/regions', () => axios.get('/general/regions', { + baseURL: import.meta.env.VITE_API_NEST_URL + }).then(res => res.data)) + + const [regionId, setRegionId] = useState(undefined) + + const { data: cities } = useSWR(regionId ? `/general/cities?region_id=${regionId}` : null, () => axios.get(`/general/cities?region_id=${regionId}`, { + baseURL: import.meta.env.VITE_API_NEST_URL + }).then(res => res.data)) + + const [cityId, setCityId] = useState(undefined) + + const { data: boilers, isLoading: boilersLoading } = useSWR(cityId !== undefined ? `/fuel/boilers?city_id=${cityId}&offset=0&limit=100` : null, () => axios.get(`/fuel/boilers?city_id=${cityId}&offset=0&limit=100`, { + baseURL: import.meta.env.VITE_API_NEST_URL + }).then(res => res.data)) + + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedBoilerSearch(boilerSearch) + }, 500) + + return () => { + clearTimeout(handler) + } + }, [boilerSearch]) + + useEffect(() => { + setBoilersPage(1) + setBoilerSearch("") + }, []) + + return ( +
+ {/* + + */} + +
+ { + if (data.optionValue) { + setRegionId(Number(data.optionValue)) + } else { + setCityId(undefined) + setRegionId(undefined) + } + }} + > + {regions && Array.isArray(regions) && regions.map((option) => ( + + ))} + + + {cities && { + if (data.optionValue) { + setCityId(Number(data.optionValue)) + } else { + setCityId(undefined) + } + }} + > + {cities && Array.isArray(cities) && cities.map((option) => ( + + ))} + } +
+ +
+ + {cityId && cities && Array.isArray(cities) && cities.find(city => city.id === cityId).name} + + + {cityId && +
+ + + + + + + +
+ } +
+ + ({ ...obj, [key]: 'test1' }), {}), + // Object.keys(table.headers).reduce((obj, key) => ({ ...obj, [key]: 'test' }), {}) + // ]} + key={`boilers-${cityId}`} + loading={boilersLoading} + overlayLoadingTemplate='Загрузка...' + overlayNoRowsTemplate='Нет данных' + rowData={boilers} + columnDefs={[ + { + field: 'boiler_name', + headerName: 'Наименование' + }, + { + field: 'boiler_code', + headerName: 'Идент. код' + }, + { + field: 'id_fuels', + headerName: 'Вид топлива', + //editable: true, + //cellEditor: FuelTypeEditor, + autoHeight: true, + cellRenderer: FuelRenderer, + cellStyle: (_) => { + return { padding: '1px' } + } + //enableCellChangeFlash: true + }, + { + field: 'activity', + headerName: 'Активный', + cellRenderer: (params: CustomCellRendererProps) => ({params.value === true ? 'Да' : 'Нет'}) + } + ]} + defaultColDef={{ + flex: 1, + }} + /> +
+ ) +} + +const BoilersCard = ({ + title, + value, + subtitle, +}: { + title: string + value: string + subtitle: string +}) => { + return ( + { }} + style={{ display: 'flex', width: '100%', justifyContent: 'flex-start', flexDirection: 'column', gap: '2rem', alignItems: 'flex-start', cursor: 'pointer', userSelect: 'none' }} + //icon={icon} + > + + + {title} + + + + {value} + + + + {subtitle} + + + ) +} + +export default Boilers \ No newline at end of file diff --git a/client/src/pages/fuel/Fuel/Flow.tsx b/client/src/pages/fuel/Fuel/Flow.tsx new file mode 100644 index 0000000..279397b --- /dev/null +++ b/client/src/pages/fuel/Fuel/Flow.tsx @@ -0,0 +1,47 @@ +import { Text } from '@fluentui/react-components' + +const FlowPage = () => { + return ( +
+ {/* + + */} + +
+ + Приход и расход топлива + + + + + + + + {/* {cityId && +
+ + + + + + + +
+ } */} +
+
+ ) +} + +export default FlowPage \ No newline at end of file diff --git a/client/src/pages/fuel/Fuel/FuelRenderer.tsx b/client/src/pages/fuel/Fuel/FuelRenderer.tsx new file mode 100644 index 0000000..f414dfb --- /dev/null +++ b/client/src/pages/fuel/Fuel/FuelRenderer.tsx @@ -0,0 +1,53 @@ +import { Button, Spinner, Table, TableBody, TableCell, TableCellActions, TableCellLayout, TableRow } from '@fluentui/react-components'; +import { Add12Regular, DeleteRegular, EditRegular } from '@fluentui/react-icons'; +import type { CustomCellRendererProps } from 'ag-grid-react'; +import axios from 'axios'; +import useSWR from 'swr'; + +export default (params: CustomCellRendererProps) => { + const { data: fuelTypes } = useSWR('/fuel/fuel-types', () => axios.get(`/fuel/fuel-types`, { + baseURL: import.meta.env.VITE_API_NEST_URL + }).then(res => res.data)) + + if (fuelTypes) { + return ( +
+ {Array.isArray(params.value) && params.value.length > 0 && + + + {Array.isArray(params.value) && params.value.map(fuel => ( + + + + {fuel.name} + + + + + {fuelTypes && Array.isArray(fuelTypes) && + fuelTypes.find(ft => ft.id === fuel.id_fuels).name + } + + + +
+ } + + +
+ ); + } else { + return ( + + ) + } +} \ No newline at end of file diff --git a/client/src/pages/fuel/Fuel/FuelTypeEditor.tsx b/client/src/pages/fuel/Fuel/FuelTypeEditor.tsx new file mode 100644 index 0000000..a44936d --- /dev/null +++ b/client/src/pages/fuel/Fuel/FuelTypeEditor.tsx @@ -0,0 +1,43 @@ +import { Combobox, Option, Spinner } from "@fluentui/react-components"; +import { CustomCellEditorProps } from "ag-grid-react"; +import axios from "axios"; +import useSWR from "swr"; + +export default ({ value, onValueChange }: CustomCellEditorProps) => { + const { data: fuelTypes } = useSWR('/fuel/fuel-types', () => axios.get(`/fuel/fuel-types`, { + baseURL: import.meta.env.VITE_API_NEST_URL + }).then(res => res.data)) + + if (fuelTypes) { + return ( + f.id === value).name} + style={{ + width: '100%', + height: '100%', + marginTop: '-4px' + }} + size='small' + onOptionSelect={(_, data) => { + onValueChange(data.optionValue === '' || data.optionValue === undefined ? null : data.optionValue) + }} + > + {fuelTypes && Array.isArray(fuelTypes) && fuelTypes.map((option) => ( + + ))} + + ); + } else { + return ( + + ) + } +} \ No newline at end of file diff --git a/client/src/pages/fuel/Fuel/FuelTypeRenderer.tsx b/client/src/pages/fuel/Fuel/FuelTypeRenderer.tsx new file mode 100644 index 0000000..9be746d --- /dev/null +++ b/client/src/pages/fuel/Fuel/FuelTypeRenderer.tsx @@ -0,0 +1,25 @@ +import { Spinner } from '@fluentui/react-components'; +import type { CustomCellRendererProps } from 'ag-grid-react'; +import axios from 'axios'; +import useSWR from 'swr'; + +export default (params: CustomCellRendererProps) => { + const { data: fuelTypes } = useSWR('/fuel/fuel-types', () => axios.get(`/fuel/fuel-types`, { + baseURL: import.meta.env.VITE_API_NEST_URL + }).then(res => res.data)) + + if (fuelTypes) { + return ( + + {fuelTypes.find((t: { + id: number + name: string + }) => t.id === params.value).name} + + ); + } else { + return ( + + ) + } +} \ No newline at end of file diff --git a/client/src/pages/fuel/Fuel/FuelTypes.tsx b/client/src/pages/fuel/Fuel/FuelTypes.tsx new file mode 100644 index 0000000..c029371 --- /dev/null +++ b/client/src/pages/fuel/Fuel/FuelTypes.tsx @@ -0,0 +1,34 @@ +import { Text } from '@fluentui/react-components' + +const FuelTypesPage = () => { + return ( +
+ {/* + + */} + +
+ + Виды топлива + + + + + +
+
+ ) +} + +export default FuelTypesPage \ No newline at end of file diff --git a/client/src/pages/fuel/Fuel/Fuels.tsx b/client/src/pages/fuel/Fuel/Fuels.tsx new file mode 100644 index 0000000..636df42 --- /dev/null +++ b/client/src/pages/fuel/Fuel/Fuels.tsx @@ -0,0 +1,185 @@ +import { Button, Combobox, Dialog, DialogActions, DialogBody, DialogContent, DialogSurface, DialogTitle, DialogTrigger, Field, Input, Option, Tab, TabList, Text } from '@fluentui/react-components' +import useSWR from 'swr' +import axios from 'axios' +import { AgGridReact } from 'ag-grid-react' +import { Controller, SubmitHandler, useForm } from 'react-hook-form' +import { useState } from 'react' +import { FuelType } from '../../../interfaces/fuel' + +type Inputs = { + id_fuel_type: string + name: string +} + +const FuelsPage = () => { + const [selectedIdFuels, setSelectedIdFuels] = useState(null) + + const { data: fuelTypes } = useSWR('/fuel/fuel-types', () => axios.get(`/fuel/fuel-types`, { + baseURL: import.meta.env.VITE_API_NEST_URL + }).then(res => { + setSelectedIdFuels(res.data[0].id) + return res.data + })) + + const { data: fuels, mutate: mutateFuels, isLoading: fuelsLoading } = useSWR(selectedIdFuels ? `/fuel/fuels?id_fuels=${selectedIdFuels}` : null, () => axios.get(`/fuel/fuels?id_fuels=${selectedIdFuels}`, { + baseURL: import.meta.env.VITE_API_NEST_URL + }).then(res => res.data)) + + const { + register, + handleSubmit, + control, + //formState: { errors, isSubmitting }, + } = useForm() + + const onSubmit: SubmitHandler = (data) => { + console.log(data) + mutateFuels([...fuels, data]) + + setTimeout(() => { + console.log("done") + }, 1000) + } + + return ( +
+
+ + Виды топлива + + + + + + + + + {fuelTypes && +
+ + Добавление вида топлива + + + { + return ( + + f.id.toString() == value)?.name || ''} + size='medium' + onOptionSelect={(_, data) => { + onChange(data.optionValue) + //onValueChange(data.optionValue === '' || data.optionValue === undefined ? null : data.optionValue) + }} + > + {fuelTypes && Array.isArray(fuelTypes) && fuelTypes.map((option) => ( + + ))} + + + + ); + }} + /> + + + + + + + + + + +
+ } + +
+
+
+ + {fuelTypes && Array.isArray(fuelTypes) && + { + setSelectedIdFuels(Number(data.value)) + }}> + {fuelTypes && Array.isArray(fuelTypes) && fuelTypes.map((ft: FuelType) => ( + + {ft.name} + + )) + } + } + + +
+ ) +} + +export default FuelsPage \ No newline at end of file diff --git a/client/src/pages/fuel/Limits.tsx b/client/src/pages/fuel/Limits.tsx new file mode 100644 index 0000000..c38f040 --- /dev/null +++ b/client/src/pages/fuel/Limits.tsx @@ -0,0 +1,7 @@ +const LimitsPage = () => { + return ( +
LimitsPage
+ ) +} + +export default LimitsPage \ No newline at end of file diff --git a/client/src/pages/fuel/Reports.tsx b/client/src/pages/fuel/Reports.tsx new file mode 100644 index 0000000..6dc531a --- /dev/null +++ b/client/src/pages/fuel/Reports.tsx @@ -0,0 +1,69 @@ +import { Button, CompoundButton, Text } from '@fluentui/react-components' + +const ReportsPage = () => { + return ( +
+ {/* + + */} + +
+ + Отчеты + + + + + + + { }} + style={{ display: 'flex', width: '100%', justifyContent: 'flex-start', flexDirection: 'column', gap: '2rem', alignItems: 'flex-start', cursor: 'pointer', userSelect: 'none' }} + //icon={icon} + > + + + Параметры отчета + + + + + + + + + + + + + + {/* {cityId && +
+ + + + + + + +
+ } */} +
+
+ ) +} + +export default ReportsPage \ No newline at end of file diff --git a/client/src/pages/fuel/Transport/Drivers.tsx b/client/src/pages/fuel/Transport/Drivers.tsx new file mode 100644 index 0000000..ecd5ae9 --- /dev/null +++ b/client/src/pages/fuel/Transport/Drivers.tsx @@ -0,0 +1,9 @@ +const DriversPage = () => { + return ( +
+ +
+ ) +} + +export default DriversPage \ No newline at end of file diff --git a/client/src/pages/fuel/Transport/Routes.tsx b/client/src/pages/fuel/Transport/Routes.tsx new file mode 100644 index 0000000..aa47a54 --- /dev/null +++ b/client/src/pages/fuel/Transport/Routes.tsx @@ -0,0 +1,7 @@ +const RoutesPage = () => { + return ( +
RoutesPage
+ ) +} + +export default RoutesPage \ No newline at end of file diff --git a/client/src/pages/fuel/Transport/Transport.tsx b/client/src/pages/fuel/Transport/Transport.tsx new file mode 100644 index 0000000..9e5390c --- /dev/null +++ b/client/src/pages/fuel/Transport/Transport.tsx @@ -0,0 +1,7 @@ +const TransportPage = () => { + return ( +
TransportPage
+ ) +} + +export default TransportPage \ No newline at end of file diff --git a/client/src/pages/fuel/Transport/Waybills.tsx b/client/src/pages/fuel/Transport/Waybills.tsx new file mode 100644 index 0000000..51e07da --- /dev/null +++ b/client/src/pages/fuel/Transport/Waybills.tsx @@ -0,0 +1,69 @@ +import { Button, CompoundButton, Text } from '@fluentui/react-components' + +const WaybillsPage = () => { + return ( +
+ {/* + + */} + +
+ + Путевые листы + + + + Управление маршрутами и учет расхода топлива + + + { }} + style={{ display: 'flex', width: '100%', justifyContent: 'flex-start', flexDirection: 'column', gap: '2rem', alignItems: 'flex-start', cursor: 'pointer', userSelect: 'none' }} + //icon={icon} + > + + + Параметры отчета + + + + + + + + + + + + + + {/* {cityId && +
+ + + + + + + +
+ } */} +
+
+ ) +} + +export default WaybillsPage \ No newline at end of file