fuel; nest api
This commit is contained in:
@ -17,7 +17,7 @@ const ObjectTree = ({
|
||||
|
||||
const { data: existingObjectsList } = useSWR(
|
||||
selectedYear && selectedDistrict ? `/general/objects/list?year=${selectedYear}&city_id=${selectedDistrict}&planning=0` : null,
|
||||
(url) => fetcher(url, BASE_URL.ems).then(res => {
|
||||
(url) => fetcher(url, BASE_URL.nest).then(res => {
|
||||
if (Array.isArray(res)) {
|
||||
let count = 0
|
||||
res.forEach(el => {
|
||||
@ -34,7 +34,7 @@ const ObjectTree = ({
|
||||
|
||||
const { data: planningObjectsList } = useSWR(
|
||||
selectedYear && selectedDistrict ? `/general/objects/list?year=${selectedYear}&city_id=${selectedDistrict}&planning=1` : null,
|
||||
(url) => fetcher(url, BASE_URL.ems).then(res => {
|
||||
(url) => fetcher(url, BASE_URL.nest).then(res => {
|
||||
if (Array.isArray(res)) {
|
||||
let count = 0
|
||||
res.forEach(el => {
|
||||
@ -116,7 +116,7 @@ const ObjectList = ({
|
||||
|
||||
const { data } = useSWR(
|
||||
selectedDistrict && selectedYear ? `/general/objects/list?type=${id}&city_id=${selectedDistrict}&year=${selectedYear}&planning=${planning}` : null,
|
||||
(url) => fetcher(url, BASE_URL.ems),
|
||||
(url) => fetcher(url, BASE_URL.nest),
|
||||
{
|
||||
revalidateOnFocus: false,
|
||||
revalidateIfStale: false
|
||||
|
||||
@ -78,20 +78,20 @@ const MapComponent = ({
|
||||
const mapElement = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
// Get type roles
|
||||
useSWR(`/gis/type-roles`, (url) => fetcher(url, BASE_URL.ems).then(res => {
|
||||
useSWR(`/gis/type-roles`, (url) => fetcher(url, BASE_URL.nest).then(res => {
|
||||
if (Array.isArray(res)) {
|
||||
setTypeRoles(id, res)
|
||||
}
|
||||
return res
|
||||
}), swrOptions)
|
||||
|
||||
const { data: regionsData } = useSWR(`/general/regions`, (url) => fetcher(url, BASE_URL.ems).then(res => {
|
||||
const { data: regionsData } = useSWR(`/general/regions`, (url) => fetcher(url, BASE_URL.nest).then(res => {
|
||||
setRegionsData(res)
|
||||
return res
|
||||
}), swrOptions)
|
||||
|
||||
// Bounds: region
|
||||
const { data: regionBoundsData } = useSWR(`/gis/bounds/region`, (url) => fetcher(url, BASE_URL.ems), swrOptions)
|
||||
const { data: regionBoundsData } = useSWR(`/gis/bounds/region`, (url) => fetcher(url, BASE_URL.nest), swrOptions)
|
||||
|
||||
// Map init
|
||||
useEffect(() => {
|
||||
@ -169,7 +169,7 @@ const MapComponent = ({
|
||||
useEffect(() => {
|
||||
if (selectedDistrict && selectedYear && districtBoundLayer) {
|
||||
const bounds = new VectorSource({
|
||||
url: `${BASE_URL.ems}/gis/bounds/city/${selectedDistrict}`,
|
||||
url: `${BASE_URL.nest}/gis/bounds/city/${selectedDistrict}`,
|
||||
format: new GeoJSON(),
|
||||
})
|
||||
|
||||
@ -279,7 +279,7 @@ const MapComponent = ({
|
||||
}
|
||||
}, [currentObjectId, figuresLayer, linesLayer])
|
||||
|
||||
const { data: districtsData } = useSWR(selectedRegion ? `/general/districts/?region_id=${selectedRegion}` : null, (url) => fetcher(url, BASE_URL.ems).then(res => {
|
||||
const { data: districtsData } = useSWR(selectedRegion ? `/general/districts/?region_id=${selectedRegion}` : null, (url) => fetcher(url, BASE_URL.nest).then(res => {
|
||||
setDistrictsData(res)
|
||||
return res
|
||||
}), swrOptions)
|
||||
@ -287,7 +287,7 @@ const MapComponent = ({
|
||||
const { data: figuresData, isValidating: figuresValidating } = useSWR(
|
||||
selectedDistrict && selectedYear ? `/gis/figures/all?city_id=${selectedDistrict}&year=${selectedYear}&offset=0&limit=${10000}` : null,
|
||||
(url) => axios.get(url, {
|
||||
baseURL: BASE_URL.ems
|
||||
baseURL: BASE_URL.nest
|
||||
}).then((res) => res.data),
|
||||
swrOptions
|
||||
)
|
||||
@ -295,7 +295,7 @@ const MapComponent = ({
|
||||
const { data: linesData, isValidating: linesValidating } = useSWR(
|
||||
!figuresValidating && selectedDistrict && selectedYear ? `/gis/lines/all?city_id=${selectedDistrict}&year=${selectedYear}&offset=0&limit=${10000}` : null,
|
||||
(url) => axios.get(url, {
|
||||
baseURL: BASE_URL.ems
|
||||
baseURL: BASE_URL.nest
|
||||
}).then((res) => res.data),
|
||||
swrOptions
|
||||
)
|
||||
@ -303,7 +303,7 @@ const MapComponent = ({
|
||||
const { data: districtData } = useSWR(
|
||||
selectedDistrict ? `/gis/images/all?city_id=${selectedDistrict}` : null,
|
||||
(url) => axios.get(url, {
|
||||
baseURL: BASE_URL.ems
|
||||
baseURL: BASE_URL.nest
|
||||
}).then((res) => Array.isArray(res.data) ? res.data[0] : null),
|
||||
swrOptions
|
||||
)
|
||||
@ -398,7 +398,7 @@ const MapComponent = ({
|
||||
list.push(district.id as Number)
|
||||
})
|
||||
|
||||
fetch(`${BASE_URL.ems}/gis/bounds/city`, {
|
||||
fetch(`${BASE_URL.nest}/gis/bounds/city`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@ -15,7 +15,7 @@ const MapLegend = ({
|
||||
|
||||
const { data: existingObjectsList } = useSWR(
|
||||
selectedYear && selectedDistrict ? `/general/objects/list?year=${selectedYear}&city_id=${selectedDistrict}&planning=0` : null,
|
||||
(url) => fetcher(url, BASE_URL.ems),
|
||||
(url) => fetcher(url, BASE_URL.nest),
|
||||
{
|
||||
revalidateOnFocus: false
|
||||
}
|
||||
@ -23,7 +23,7 @@ const MapLegend = ({
|
||||
|
||||
const { data: planningObjectsList } = useSWR(
|
||||
selectedYear && selectedDistrict ? `/general/objects/list?year=${selectedYear}&city_id=${selectedDistrict}&planning=1` : null,
|
||||
(url) => fetcher(url, BASE_URL.ems),
|
||||
(url) => fetcher(url, BASE_URL.nest),
|
||||
{
|
||||
revalidateOnFocus: false
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ const MapObjectSearch = ({
|
||||
|
||||
const { data: searchData } = useSWR(
|
||||
throttledSearchObject !== "" && selectedDistrict && selectedYear ? `/general/search/objects?q=${throttledSearchObject}&id_city=${selectedDistrict}&year=${selectedYear}` : null,
|
||||
(url) => fetcher(url, BASE_URL.ems),
|
||||
(url) => fetcher(url, BASE_URL.nest),
|
||||
swrOptions
|
||||
)
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ const MapRegionSelect = ({
|
||||
const { selectedRegion, selectedYear, selectedDistrict } = useObjectsStore().id[map_id]
|
||||
const { regionsData } = useRegionsStore()
|
||||
|
||||
const { data: districtsData } = useSWR(selectedRegion ? `/general/districts/?region_id=${selectedRegion}` : null, (url) => fetcher(url, BASE_URL.ems).then(res => {
|
||||
const { data: districtsData } = useSWR(selectedRegion ? `/general/districts/?region_id=${selectedRegion}` : null, (url) => fetcher(url, BASE_URL.nest).then(res => {
|
||||
setDistrictsData(res)
|
||||
return res
|
||||
}), swrOptions)
|
||||
|
||||
@ -6,7 +6,7 @@ import { BASE_URL } from '../../constants'
|
||||
const ObjectData = (object_data: IObjectData) => {
|
||||
const { data: typeData } = useSWR(
|
||||
object_data.type ? `/general/types` : null,
|
||||
(url) => fetcher(url, BASE_URL.ems),
|
||||
(url) => fetcher(url, BASE_URL.nest),
|
||||
{
|
||||
revalidateOnFocus: false
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ const ObjectParameters = ({
|
||||
|
||||
const { data: valuesData, isValidating: valuesValidating } = useSWR(
|
||||
currentObjectId ? `/general/values/?object_id=${currentObjectId}` : null,
|
||||
(url) => fetcher(url, BASE_URL.ems),
|
||||
(url) => fetcher(url, BASE_URL.nest),
|
||||
{
|
||||
revalidateOnFocus: false,
|
||||
revalidateIfStale: false
|
||||
|
||||
@ -3,7 +3,7 @@ import { BASE_URL } from '../../constants'
|
||||
import { fetcher } from '../../http/axiosInstance'
|
||||
|
||||
const RegionSelect = () => {
|
||||
const { data } = useSWR(`/gis/regions/borders`, (url) => fetcher(url, BASE_URL.ems), {
|
||||
const { data } = useSWR(`/gis/regions/borders`, (url) => fetcher(url, BASE_URL.nest), {
|
||||
revalidateOnFocus: false,
|
||||
revalidateIfStale: false
|
||||
})
|
||||
|
||||
@ -30,7 +30,7 @@ const TableValue = ({
|
||||
|
||||
const { data: tcbAll, isValidating } = useSWR(
|
||||
type === 'select' && selectedDistrict ? `/general/params/tcb?vtable=${vtable}&id_city=${selectedDistrict}` : null,
|
||||
(url) => fetcher(url, BASE_URL.ems).then(res => {
|
||||
(url) => fetcher(url, BASE_URL.nest).then(res => {
|
||||
if (Array.isArray(res)) {
|
||||
return res.map((el) => ({
|
||||
label: el.name || "",
|
||||
|
||||
@ -9,4 +9,5 @@ export const BASE_URL = {
|
||||
fuel: import.meta.env.VITE_API_FUEL_URL,
|
||||
servers: import.meta.env.VITE_API_SERVERS_URL,
|
||||
ems: import.meta.env.VITE_API_EMS_URL,
|
||||
nest: import.meta.env.VITE_API_NEST_URL
|
||||
}
|
||||
@ -3,6 +3,11 @@ export interface IRegion {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface ISeason {
|
||||
name: string
|
||||
year: number
|
||||
}
|
||||
|
||||
export interface ICity {
|
||||
id: number;
|
||||
name: string;
|
||||
|
||||
@ -1,49 +1,117 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Combobox, CompoundButton, Option, Text } from '@fluentui/react-components'
|
||||
import { Dropdown, Option, Spinner } 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'
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
import { ICity, IRegion } from '../../../interfaces/fuel'
|
||||
import BoilersCard from './BoilersCard'
|
||||
|
||||
ModuleRegistry.registerModules([AllCommunityModule])
|
||||
|
||||
function Boilers() {
|
||||
const [, setBoilersPage] = useState(1)
|
||||
const [boilerSearch, setBoilerSearch] = useState("")
|
||||
const [, setDebouncedBoilerSearch] = useState("")
|
||||
// const { boilers } = useBoilers(10, boilersPage, debouncedBoilerSearch)
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
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<number | undefined>(
|
||||
searchParams.get("region_id") ? Number(searchParams.get("region_id")) : undefined
|
||||
);
|
||||
|
||||
const [regionId, setRegionId] = useState<number | undefined>(undefined)
|
||||
const [cityId, setCityId] = useState<number | undefined>(
|
||||
searchParams.get("city_id") ? Number(searchParams.get("city_id")) : 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))
|
||||
// Load regions
|
||||
const { data: regions, isLoading: regionsLoading } = useSWR("/general/regions", () =>
|
||||
axios
|
||||
.get("/general/regions", { baseURL: import.meta.env.VITE_API_NEST_URL })
|
||||
.then((res) => res.data)
|
||||
);
|
||||
|
||||
const [cityId, setCityId] = useState<number | undefined>(undefined)
|
||||
// Load cities when regionId exists
|
||||
const { data: cities, isLoading: citiesLoading } = 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 { 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))
|
||||
// Load boilers when cityId exists
|
||||
const { data: boilers, isLoading: boilersLoading } = useSWR(
|
||||
cityId ? `/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)
|
||||
);
|
||||
|
||||
//
|
||||
// Sync regionId → URL
|
||||
//
|
||||
useEffect(() => {
|
||||
if (!regionId) return;
|
||||
|
||||
setSearchParams((prev) => {
|
||||
const params = new URLSearchParams(prev);
|
||||
params.set("region_id", regionId.toString());
|
||||
params.delete("city_id"); // reset city when region changes
|
||||
return params;
|
||||
});
|
||||
|
||||
}, [regionId]);
|
||||
|
||||
//
|
||||
// Sync cityId → URL
|
||||
//
|
||||
useEffect(() => {
|
||||
if (!cityId) return;
|
||||
|
||||
setSearchParams((prev) => {
|
||||
const params = new URLSearchParams(prev);
|
||||
params.set("city_id", cityId.toString());
|
||||
return params;
|
||||
});
|
||||
}, [cityId]);
|
||||
|
||||
//
|
||||
// Utility: get display values
|
||||
//
|
||||
const selectedRegion = regions?.find((r: IRegion) => r.id === regionId);
|
||||
const selectedCity = cities?.find((c: ICity) => c.id === cityId);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = setTimeout(() => {
|
||||
setDebouncedBoilerSearch(boilerSearch)
|
||||
}, 500)
|
||||
const paramCityId = searchParams.get("city_id");
|
||||
|
||||
return () => {
|
||||
clearTimeout(handler)
|
||||
if (!paramCityId) return;
|
||||
|
||||
const id = Number(paramCityId);
|
||||
|
||||
// If cityId not yet set OR mismatched with URL
|
||||
if (cities && !cityId && cities.some((c: ICity) => c.id === id)) {
|
||||
setCityId(id);
|
||||
}
|
||||
}, [boilerSearch])
|
||||
}, [cities])
|
||||
|
||||
useEffect(() => {
|
||||
setBoilersPage(1)
|
||||
setBoilerSearch("")
|
||||
}, [])
|
||||
const paramRegionId = searchParams.get("region_id");
|
||||
if (!paramRegionId) return;
|
||||
|
||||
const id = Number(paramRegionId);
|
||||
|
||||
if (regions && !regionId && regions.some((r: IRegion) => r.id === id)) {
|
||||
setRegionId(id);
|
||||
}
|
||||
}, [regions]);
|
||||
|
||||
useEffect(() => {
|
||||
setCityId(undefined); // clear stale value before SWR runs
|
||||
}, [regionId]);
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
@ -58,42 +126,51 @@ function Boilers() {
|
||||
</Portal> */}
|
||||
|
||||
<div style={{ display: 'flex', gap: '1rem' }}>
|
||||
<Combobox
|
||||
placeholder="Выберите регион"
|
||||
clearable
|
||||
onOptionSelect={(_, data) => {
|
||||
if (data.optionValue) {
|
||||
setRegionId(Number(data.optionValue))
|
||||
} else {
|
||||
setCityId(undefined)
|
||||
setRegionId(undefined)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{regions && Array.isArray(regions) && regions.map((option) => (
|
||||
<Option key={option.id} text={option.name} value={option.id}>
|
||||
{option.name}
|
||||
</Option>
|
||||
))}
|
||||
</Combobox>
|
||||
{regionsLoading ?
|
||||
<Spinner />
|
||||
:
|
||||
<Dropdown
|
||||
placeholder='Выберите район'
|
||||
value={selectedRegion?.name ?? ""}
|
||||
selectedOptions={regionId ? [regionId.toString()] : []}
|
||||
onOptionSelect={(_, data) => {
|
||||
if (!data.optionValue) {
|
||||
setRegionId(undefined);
|
||||
return;
|
||||
}
|
||||
setRegionId(Number(data.optionValue));
|
||||
}}
|
||||
>
|
||||
{regions && Array.isArray(regions) && regions.map((option) => (
|
||||
<Option key={`region-${option.id}`} text={option.name} value={option.id}>
|
||||
{option.name}
|
||||
</Option>
|
||||
))}
|
||||
</Dropdown>
|
||||
}
|
||||
|
||||
{cities && <Combobox
|
||||
clearable
|
||||
placeholder="Выберите населенный пункт"
|
||||
onOptionSelect={(_, data) => {
|
||||
if (data.optionValue) {
|
||||
setCityId(Number(data.optionValue))
|
||||
} else {
|
||||
setCityId(undefined)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{cities && Array.isArray(cities) && cities.map((option) => (
|
||||
<Option key={option.id} text={option.name} value={option.id}>
|
||||
{option.name}
|
||||
</Option>
|
||||
))}
|
||||
</Combobox>}
|
||||
{citiesLoading ?
|
||||
<Spinner />
|
||||
:
|
||||
<Dropdown
|
||||
placeholder='Выберите населенный пункт'
|
||||
value={selectedCity?.name ?? ""}
|
||||
selectedOptions={cityId ? [cityId.toString()] : []}
|
||||
onOptionSelect={(_, data) => {
|
||||
if (!data.optionValue) {
|
||||
setCityId(undefined);
|
||||
return;
|
||||
}
|
||||
setCityId(Number(data.optionValue));
|
||||
}}
|
||||
>
|
||||
{cities && Array.isArray(cities) && cities.map((option) => (
|
||||
<Option key={`region-${option.id}`} text={option.name} value={option.id}>
|
||||
{option.name}
|
||||
</Option>
|
||||
))}
|
||||
</Dropdown>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
@ -102,10 +179,6 @@ function Boilers() {
|
||||
flexDirection: 'column',
|
||||
gap: '1rem'
|
||||
}}>
|
||||
<Text size={400} weight='medium'>
|
||||
{cityId && cities && Array.isArray(cities) && cities.find(city => city.id === cityId).name}
|
||||
</Text>
|
||||
|
||||
{cityId &&
|
||||
<div style={{ display: 'flex', width: '100%', gap: '1rem', justifyContent: 'space-between' }}>
|
||||
<BoilersCard title='Всего объектов' value={boilers && Array.isArray(boilers) ? boilers.length.toString() : ''} subtitle='' />
|
||||
@ -119,80 +192,48 @@ function Boilers() {
|
||||
}
|
||||
</div>
|
||||
|
||||
<AgGridReact
|
||||
// rowData={[
|
||||
// Object.keys(table.headers).reduce((obj, key) => ({ ...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' }
|
||||
{boilersLoading ?
|
||||
<Spinner />
|
||||
:
|
||||
<AgGridReact
|
||||
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) => (<span>{params.value === true ? 'Да' : 'Нет'}</span>)
|
||||
}
|
||||
//enableCellChangeFlash: true
|
||||
},
|
||||
{
|
||||
field: 'activity',
|
||||
headerName: 'Активный',
|
||||
cellRenderer: (params: CustomCellRendererProps) => (<span>{params.value === true ? 'Да' : 'Нет'}</span>)
|
||||
}
|
||||
]}
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
}}
|
||||
/>
|
||||
]}
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
}}
|
||||
/>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const BoilersCard = ({
|
||||
title,
|
||||
value,
|
||||
subtitle,
|
||||
}: {
|
||||
title: string
|
||||
value: string
|
||||
subtitle: string
|
||||
}) => {
|
||||
return (
|
||||
<CompoundButton
|
||||
onClick={() => { }}
|
||||
style={{ display: 'flex', width: '100%', justifyContent: 'flex-start', flexDirection: 'column', gap: '2rem', alignItems: 'flex-start', cursor: 'pointer', userSelect: 'none' }}
|
||||
//icon={icon}
|
||||
>
|
||||
|
||||
<Text weight='bold' size={300}>
|
||||
{title}
|
||||
</Text>
|
||||
|
||||
<Text weight='bold' size={500}>
|
||||
{value}
|
||||
</Text>
|
||||
|
||||
<Text weight='regular' size={200} style={{ color: 'gray' }}>
|
||||
{subtitle}
|
||||
</Text>
|
||||
</CompoundButton>
|
||||
)
|
||||
}
|
||||
|
||||
export default Boilers
|
||||
34
client/src/pages/fuel/Fuel/BoilersCard.tsx
Normal file
34
client/src/pages/fuel/Fuel/BoilersCard.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import { CompoundButton, Text } from "@fluentui/react-components"
|
||||
|
||||
const BoilersCard = ({
|
||||
title,
|
||||
value,
|
||||
subtitle,
|
||||
}: {
|
||||
title: string
|
||||
value: string
|
||||
subtitle: string
|
||||
}) => {
|
||||
return (
|
||||
<CompoundButton
|
||||
onClick={() => { }}
|
||||
style={{ display: 'flex', width: '100%', justifyContent: 'flex-start', flexDirection: 'column', gap: '2rem', alignItems: 'flex-start', cursor: 'pointer', userSelect: 'none' }}
|
||||
//icon={icon}
|
||||
>
|
||||
|
||||
<Text weight='bold' size={300}>
|
||||
{title}
|
||||
</Text>
|
||||
|
||||
<Text weight='bold' size={500}>
|
||||
{value}
|
||||
</Text>
|
||||
|
||||
<Text weight='regular' size={200} style={{ color: 'gray' }}>
|
||||
{subtitle}
|
||||
</Text>
|
||||
</CompoundButton>
|
||||
)
|
||||
}
|
||||
|
||||
export default BoilersCard
|
||||
@ -16,7 +16,7 @@ export default (params: CustomCellRendererProps) => {
|
||||
<Table style={{ width: '100%' }} size='small'>
|
||||
<TableBody>
|
||||
{Array.isArray(params.value) && params.value.map(fuel => (
|
||||
<TableRow>
|
||||
<TableRow key={`${fuel.id}`}>
|
||||
<TableCell>
|
||||
<TableCellLayout truncate>
|
||||
{fuel.name}
|
||||
|
||||
@ -97,7 +97,7 @@ const FuelsPage = () => {
|
||||
}}
|
||||
>
|
||||
{fuelTypes && Array.isArray(fuelTypes) && fuelTypes.map((option) => (
|
||||
<Option key={option.id} text={option.name} value={option.id}>
|
||||
<Option key={`fuel-type-${option.id}`} text={option.name} value={option.id}>
|
||||
{option.name}
|
||||
</Option>
|
||||
))}
|
||||
@ -131,7 +131,7 @@ const FuelsPage = () => {
|
||||
setSelectedIdFuels(Number(data.value))
|
||||
}}>
|
||||
{fuelTypes && Array.isArray(fuelTypes) && fuelTypes.map((ft: FuelType) => (
|
||||
<Tab id={ft.id.toString()} value={ft.id}>
|
||||
<Tab id={ft.id.toString()} value={ft.id} key={ft.id}>
|
||||
{ft.name}
|
||||
</Tab>
|
||||
))
|
||||
|
||||
@ -1,7 +1,391 @@
|
||||
const LimitsPage = () => {
|
||||
return (
|
||||
<div>LimitsPage</div>
|
||||
)
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Button, CompoundButton, Dropdown, Option, Spinner, Text } from '@fluentui/react-components'
|
||||
import useSWR from 'swr'
|
||||
import axios from 'axios'
|
||||
import { AgGridReact } from 'ag-grid-react'
|
||||
import { AllCommunityModule, ModuleRegistry } from 'ag-grid-community'
|
||||
import { ICity, IRegion, ISeason } from '../../interfaces/fuel'
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
import LimitAddForm from './Limits/LimitEditForm'
|
||||
|
||||
ModuleRegistry.registerModules([AllCommunityModule])
|
||||
|
||||
function LimitsPage() {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [year, setYear] = useState<number | undefined>(
|
||||
searchParams.get("year") ? Number(searchParams.get("year")) : undefined
|
||||
)
|
||||
|
||||
const [regionId, setRegionId] = useState<number | undefined>(
|
||||
searchParams.get("region_id") ? Number(searchParams.get("region_id")) : undefined
|
||||
);
|
||||
|
||||
const [cityId, setCityId] = useState<number | undefined>(
|
||||
searchParams.get("city_id") ? Number(searchParams.get("city_id")) : undefined
|
||||
)
|
||||
|
||||
// Load regions
|
||||
const { data: regions, isLoading: regionsLoading } = useSWR("/general/regions", () =>
|
||||
axios
|
||||
.get("/general/regions", { baseURL: import.meta.env.VITE_API_NEST_URL })
|
||||
.then((res) => res.data)
|
||||
);
|
||||
|
||||
// Load cities when regionId exists
|
||||
const { data: cities, isLoading: citiesLoading } = 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)
|
||||
);
|
||||
|
||||
// Load seasons
|
||||
const { data: seasons } = useSWR(
|
||||
`/fuel/seasons`,
|
||||
() => [
|
||||
{
|
||||
name: 'Отопительный сезон 2025-2026',
|
||||
year: 2025
|
||||
}
|
||||
] as ISeason[]
|
||||
);
|
||||
|
||||
// Load boilers when cityId exists
|
||||
const { data: limits, isLoading: limitsLoading } = useSWR(
|
||||
cityId && year ? `/fuel/limits?city_id=${cityId}&year=${year}&offset=0&limit=100` : null,
|
||||
() =>
|
||||
axios
|
||||
.get(`/fuel/limits?city_id=${cityId}&year=${year}&offset=0&limit=100`, {
|
||||
baseURL: import.meta.env.VITE_API_NEST_URL,
|
||||
})
|
||||
.then((res) => res.data)
|
||||
);
|
||||
|
||||
//
|
||||
// Sync regionId → URL
|
||||
//
|
||||
useEffect(() => {
|
||||
if (!regionId) return;
|
||||
|
||||
setSearchParams((prev) => {
|
||||
const params = new URLSearchParams(prev);
|
||||
params.set("region_id", regionId.toString());
|
||||
params.delete("city_id"); // reset city when region changes
|
||||
return params;
|
||||
});
|
||||
|
||||
}, [regionId]);
|
||||
|
||||
//
|
||||
// Sync cityId → URL
|
||||
//
|
||||
useEffect(() => {
|
||||
if (!cityId) return;
|
||||
|
||||
setSearchParams((prev) => {
|
||||
const params = new URLSearchParams(prev);
|
||||
params.set("city_id", cityId.toString());
|
||||
return params;
|
||||
});
|
||||
}, [cityId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!year) return;
|
||||
|
||||
setSearchParams((prev) => {
|
||||
const params = new URLSearchParams(prev);
|
||||
params.set("year", year.toString());
|
||||
return params;
|
||||
});
|
||||
}, [year]);
|
||||
|
||||
//
|
||||
// Utility: get display values
|
||||
//
|
||||
const selectedRegion = regions?.find((r: IRegion) => r.id === regionId);
|
||||
const selectedCity = cities?.find((c: ICity) => c.id === cityId);
|
||||
const selectedSeason = seasons?.find((c: ISeason) => c.year === year);
|
||||
|
||||
useEffect(() => {
|
||||
const paramCityId = searchParams.get("city_id");
|
||||
|
||||
if (!paramCityId) return;
|
||||
|
||||
const id = Number(paramCityId);
|
||||
|
||||
// If cityId not yet set OR mismatched with URL
|
||||
if (cities && !cityId && cities.some((c: ICity) => c.id === id)) {
|
||||
setCityId(id);
|
||||
}
|
||||
}, [cities])
|
||||
|
||||
useEffect(() => {
|
||||
const paramRegionId = searchParams.get("region_id");
|
||||
if (!paramRegionId) return;
|
||||
|
||||
const id = Number(paramRegionId);
|
||||
|
||||
if (regions && !regionId && regions.some((r: IRegion) => r.id === id)) {
|
||||
setRegionId(id);
|
||||
}
|
||||
}, [regions]);
|
||||
|
||||
useEffect(() => {
|
||||
const paramYear = searchParams.get("year");
|
||||
if (!paramYear) return;
|
||||
|
||||
const year = Number(paramYear);
|
||||
|
||||
if (year && seasons) {
|
||||
setYear(year);
|
||||
}
|
||||
}, [seasons]);
|
||||
|
||||
useEffect(() => {
|
||||
setCityId(undefined); // clear stale value before SWR runs
|
||||
setYear(undefined)
|
||||
}, [regionId])
|
||||
|
||||
const [limitAddFormOpen, setLimitAddFormOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: '1rem',
|
||||
width: '100%',
|
||||
gap: '1rem'
|
||||
}}>
|
||||
{/* <Portal mountNode={document.querySelector('#header-portal')}>
|
||||
|
||||
</Portal> */}
|
||||
|
||||
<div style={{ display: 'flex', gap: '1rem' }}>
|
||||
{regionsLoading ?
|
||||
<Spinner />
|
||||
:
|
||||
<Dropdown
|
||||
placeholder='Выберите район'
|
||||
value={selectedRegion?.name ?? ""}
|
||||
selectedOptions={regionId ? [regionId.toString()] : []}
|
||||
onOptionSelect={(_, data) => {
|
||||
if (!data.optionValue) {
|
||||
setRegionId(undefined);
|
||||
return;
|
||||
}
|
||||
setRegionId(Number(data.optionValue));
|
||||
}}
|
||||
>
|
||||
{regions && Array.isArray(regions) && regions.map((option) => (
|
||||
<Option key={`region-${option.id}`} text={option.name} value={option.id}>
|
||||
{option.name}
|
||||
</Option>
|
||||
))}
|
||||
</Dropdown>
|
||||
}
|
||||
|
||||
{citiesLoading ?
|
||||
<Spinner />
|
||||
:
|
||||
<Dropdown
|
||||
placeholder='Выберите населенный пункт'
|
||||
value={selectedCity?.name ?? ""}
|
||||
selectedOptions={cityId ? [cityId.toString()] : []}
|
||||
onOptionSelect={(_, data) => {
|
||||
if (!data.optionValue) {
|
||||
setCityId(undefined);
|
||||
return;
|
||||
}
|
||||
setCityId(Number(data.optionValue));
|
||||
}}
|
||||
>
|
||||
{cities && Array.isArray(cities) && cities.map((option) => (
|
||||
<Option key={`region-${option.id}`} text={option.name} value={option.id}>
|
||||
{option.name}
|
||||
</Option>
|
||||
))}
|
||||
</Dropdown>
|
||||
}
|
||||
|
||||
<Dropdown
|
||||
placeholder='Выберите отопительный сезон'
|
||||
value={selectedSeason?.name ?? ""}
|
||||
selectedOptions={year ? [year.toString()] : []}
|
||||
onOptionSelect={(_, data) => {
|
||||
if (!data.optionValue) {
|
||||
setYear(undefined);
|
||||
return;
|
||||
}
|
||||
setYear(Number(data.optionValue));
|
||||
}}
|
||||
>
|
||||
{seasons && Array.isArray(seasons) && seasons.map((option) => (
|
||||
<Option key={`region-${option.year}`} text={option.name} value={option.year.toString()}>
|
||||
{option.name}
|
||||
</Option>
|
||||
))}
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
flexDirection: 'column',
|
||||
gap: '1rem'
|
||||
}}>
|
||||
<Text size={400} weight='medium'>
|
||||
{cityId && cities && Array.isArray(cities) && cities.find(city => city.id === cityId).name}
|
||||
</Text>
|
||||
|
||||
{cityId &&
|
||||
<div style={{ display: 'flex', width: '100%', gap: '1rem', justifyContent: 'space-between' }}>
|
||||
<BoilersCard title='Всего объектов' value={limits && Array.isArray(limits) ? limits.length.toString() : ''} subtitle='' />
|
||||
|
||||
<BoilersCard title='Общий остаток' value={''} subtitle='' />
|
||||
|
||||
<BoilersCard title='Лимит на сезон' value={''} subtitle='' />
|
||||
|
||||
<BoilersCard title='Требуют внимания' value={''} subtitle='' />
|
||||
</div>
|
||||
}
|
||||
|
||||
<Button appearance='primary' style={{ width: 'min-content' }} onClick={() => setLimitAddFormOpen(true)}>
|
||||
Добавить
|
||||
</Button>
|
||||
|
||||
<LimitAddForm cityId={cityId} open={limitAddFormOpen} setOpen={setLimitAddFormOpen} />
|
||||
</div>
|
||||
|
||||
<AgGridReact
|
||||
key={`boilers-${cityId}`}
|
||||
loading={limitsLoading}
|
||||
overlayLoadingTemplate='Загрузка...'
|
||||
overlayNoRowsTemplate='Нет данных'
|
||||
rowData={limits}
|
||||
onRowClicked={(e) => {
|
||||
console.log(e.data)
|
||||
}
|
||||
}
|
||||
columnDefs={[
|
||||
{
|
||||
field: 'boiler_name',
|
||||
headerName: 'Наименование'
|
||||
},
|
||||
{
|
||||
field: 'fuel_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) => (<span>{params.value === true ? 'Да' : 'Нет'}</span>)
|
||||
// }
|
||||
{
|
||||
field: 'jul',
|
||||
headerName: 'Июль'
|
||||
},
|
||||
{
|
||||
field: 'aug',
|
||||
headerName: 'Август'
|
||||
},
|
||||
{
|
||||
field: 'sep',
|
||||
headerName: 'Сентябрь'
|
||||
},
|
||||
{
|
||||
field: 'oct',
|
||||
headerName: 'Октябрь'
|
||||
},
|
||||
{
|
||||
field: 'nov',
|
||||
headerName: 'Ноябрь'
|
||||
},
|
||||
{
|
||||
field: 'dec',
|
||||
headerName: 'Декабрь'
|
||||
},
|
||||
{
|
||||
field: 'jan',
|
||||
headerName: 'Январь'
|
||||
},
|
||||
{
|
||||
field: 'feb',
|
||||
headerName: 'Февраль'
|
||||
},
|
||||
{
|
||||
field: 'mar',
|
||||
headerName: 'Март'
|
||||
},
|
||||
{
|
||||
field: 'apr',
|
||||
headerName: 'Апрель'
|
||||
},
|
||||
{
|
||||
field: 'may',
|
||||
headerName: 'Май'
|
||||
},
|
||||
{
|
||||
field: 'jun',
|
||||
headerName: 'Июнь'
|
||||
}
|
||||
]}
|
||||
defaultColDef={{
|
||||
flex: 1,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const BoilersCard = ({
|
||||
title,
|
||||
value,
|
||||
subtitle,
|
||||
}: {
|
||||
title: string
|
||||
value: string
|
||||
subtitle: string
|
||||
}) => {
|
||||
return (
|
||||
<CompoundButton
|
||||
onClick={() => { }}
|
||||
style={{ display: 'flex', width: '100%', justifyContent: 'flex-start', flexDirection: 'column', gap: '2rem', alignItems: 'flex-start', cursor: 'pointer', userSelect: 'none' }}
|
||||
//icon={icon}
|
||||
>
|
||||
|
||||
<Text weight='bold' size={300}>
|
||||
{title}
|
||||
</Text>
|
||||
|
||||
<Text weight='bold' size={500}>
|
||||
{value}
|
||||
</Text>
|
||||
|
||||
<Text weight='regular' size={200} style={{ color: 'gray' }}>
|
||||
{subtitle}
|
||||
</Text>
|
||||
</CompoundButton>
|
||||
)
|
||||
}
|
||||
|
||||
export default LimitsPage
|
||||
287
client/src/pages/fuel/Limits/LimitEditForm.tsx
Normal file
287
client/src/pages/fuel/Limits/LimitEditForm.tsx
Normal file
@ -0,0 +1,287 @@
|
||||
import { Button, Dialog, DialogBody, DialogContent, DialogSurface, DialogTitle, Field, Input, ProgressBar, Spinner } from '@fluentui/react-components'
|
||||
import { DataPieColor } from '@fluentui/react-icons'
|
||||
import axios from 'axios'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Controller, SubmitHandler, useFieldArray, useForm, useWatch } from 'react-hook-form'
|
||||
import useSWR from 'swr'
|
||||
|
||||
type Month = {
|
||||
month: number
|
||||
value: number
|
||||
}
|
||||
|
||||
type Inputs = {
|
||||
id_boiler: string
|
||||
id_fuel: string
|
||||
year: number
|
||||
months: Month[]
|
||||
}
|
||||
|
||||
const months = [
|
||||
{
|
||||
id: 7,
|
||||
month: 'jul',
|
||||
label: 'Июль'
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
month: 'aug',
|
||||
label: 'Август'
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
month: 'sep',
|
||||
label: 'Сентябрь'
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
month: 'nov',
|
||||
label: 'Октябрь'
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
month: 'oct',
|
||||
label: 'Ноябрь'
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
month: 'dec',
|
||||
label: 'Декабрь'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
month: 'jan',
|
||||
label: 'Январь'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
month: 'feb',
|
||||
label: 'Февраль'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
month: 'mar',
|
||||
label: 'Март'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
month: 'apr',
|
||||
label: 'Апрель'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
month: 'may',
|
||||
label: 'Май'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
month: 'jun',
|
||||
label: 'Июнь'
|
||||
},
|
||||
]
|
||||
|
||||
const LimitAddForm = ({
|
||||
cityId,
|
||||
open,
|
||||
setOpen,
|
||||
percentage
|
||||
}: {
|
||||
cityId: number | undefined
|
||||
open: boolean
|
||||
setOpen: (open: boolean) => void
|
||||
percentage?: boolean
|
||||
}) => {
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
setValue,
|
||||
formState: { isSubmitting },
|
||||
} = useForm<Inputs>({
|
||||
defaultValues: {
|
||||
months: Array.from({ length: 12 }, (_, i) => ({
|
||||
month: i + 1,
|
||||
value: 0,
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
const { fields } = useFieldArray({
|
||||
control,
|
||||
name: 'months'
|
||||
})
|
||||
|
||||
const onSubmit: SubmitHandler<Inputs> = async (data) => {
|
||||
await axios.post(`/fuel/limits`, {
|
||||
id_boiler: '06407B1C-C23F-44C8-BADF-4653060EB784',
|
||||
id_fuel: 3,
|
||||
year: 2025,
|
||||
months: data.months
|
||||
}, {
|
||||
baseURL: import.meta.env.VITE_API_NEST_URL,
|
||||
})
|
||||
//mutateFuels([...fuels, data])
|
||||
|
||||
// setTimeout(() => {
|
||||
// console.log("done")
|
||||
// }, 1000)
|
||||
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(() => resolve(console.log("done")), 1000);
|
||||
})
|
||||
}
|
||||
|
||||
const [overallLimit, setOverallLimit] = useState(0)
|
||||
|
||||
const watchedMonths = useWatch({
|
||||
control,
|
||||
name: "months",
|
||||
})
|
||||
|
||||
const { data: citySettings } = useSWR(
|
||||
cityId ? `/fuel/city-settings?city_id=${cityId}` : null,
|
||||
() =>
|
||||
axios
|
||||
.get(`/fuel/city-settings?city_id=${cityId}`, {
|
||||
baseURL: import.meta.env.VITE_API_NEST_URL,
|
||||
})
|
||||
.then((res) => res.data)
|
||||
)
|
||||
|
||||
const handlePartition = () => {
|
||||
if (citySettings && Array.isArray(citySettings) && citySettings.length > 0) {
|
||||
citySettings.map(s => {
|
||||
setValue(`months.${Number(s.month - 1)}.value`, Number((Number((overallLimit / 100).toFixed(3)) * s.procent).toFixed(3)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (watchedMonths) {
|
||||
let sum = 0
|
||||
watchedMonths.map(wm => sum = sum + wm.value)
|
||||
setOverallLimit(Number(sum.toFixed(3)))
|
||||
}
|
||||
}, [watchedMonths])
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(_, data) => setOpen(data.open)}>
|
||||
<DialogSurface>
|
||||
<DialogBody>
|
||||
{isSubmitting &&
|
||||
<div style={{ position: 'absolute', inset: 0, zIndex: '1', background: '#00000030', display: 'flex', justifyContent: 'center', alignItems: 'center', width: '100%', height: '100%' }}>
|
||||
<Spinner />
|
||||
</div>
|
||||
}
|
||||
<DialogTitle>Распределение лимитов</DialogTitle>
|
||||
|
||||
<DialogContent
|
||||
style={{
|
||||
display: 'flex', flexDirection: 'column', gap: '1rem',
|
||||
minWidth: 'fit-content', minHeight: 'fit-content'
|
||||
}}
|
||||
>
|
||||
<form onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
handlePartition()
|
||||
}} style={{ display: 'flex', width: '100%', gap: '0.25rem', justifyContent: 'flex-end' }}>
|
||||
<Field style={{ display: 'flex' }} label='Лимит расхода котельного топлива за сезон' orientation='horizontal'>
|
||||
<Input type='number' value={overallLimit.toString()} onChange={(_, data) => setOverallLimit(Number(data.value))} />
|
||||
</Field>
|
||||
|
||||
<Button title='Распределить' type='submit' icon={<DataPieColor />}></Button>
|
||||
</form>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', gap: '1rem', width: '100%' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', width: '50%', gap: '0.25rem' }}>
|
||||
{fields.map((f, index) => {
|
||||
if (f.month >= 7 && f.month <= 12) {
|
||||
return (
|
||||
<Controller
|
||||
key={f.month}
|
||||
name={`months.${index}.value`}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Field style={{ gridTemplateColumns: '1fr auto' }} key={f.month} validationMessage={percentage ? `${(field.value / (overallLimit / 100)).toFixed(1)}%` : undefined} validationState='none' label={months.find(m => m.id === f.month)?.label} orientation='horizontal'>
|
||||
<Input value={field.value.toString()} onChange={(_, data) => field.onChange(Number(data.value))} />
|
||||
{percentage && <ProgressBar value={field.value / (overallLimit / 100) * 0.01} />}
|
||||
</Field>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
})}
|
||||
|
||||
|
||||
<Field style={{ gridTemplateColumns: '1fr auto' }} validationMessage={percentage ? `${(watchedMonths.filter(month => month.month >= 7 && month.month <= 12)
|
||||
.reduce((sum, month) => {
|
||||
const value = Number(month.value) || 0;
|
||||
return sum + value;
|
||||
}, 0) / (overallLimit / 100)).toFixed(1)}%` : undefined} validationState='none' label={'2 полугодие'} orientation='horizontal'>
|
||||
<Input disabled value={watchedMonths.filter(month => month.month >= 7 && month.month <= 12)
|
||||
.reduce((sum, month) => {
|
||||
const value = Number(month.value) || 0;
|
||||
return sum + value;
|
||||
}, 0).toFixed(3)} />
|
||||
|
||||
{percentage && <ProgressBar value={watchedMonths.filter(month => month.month >= 7 && month.month <= 12)
|
||||
.reduce((sum, month) => {
|
||||
const value = Number(month.value) || 0;
|
||||
return sum + value;
|
||||
}, 0) / (overallLimit / 100) * 0.01} />}
|
||||
</Field>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', width: '50%', gap: '0.25rem' }}>
|
||||
{fields.map((f, index) => {
|
||||
if (f.month >= 1 && f.month <= 6) {
|
||||
return (
|
||||
<Controller
|
||||
key={f.month}
|
||||
name={`months.${index}.value`}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Field style={{ gridTemplateColumns: '1fr auto' }} key={f.month} validationMessage={percentage ? `${(field.value / (overallLimit / 100)).toFixed(1)}%` : undefined} validationState='none' label={months.find(m => m.id === f.month)?.label} orientation='horizontal'>
|
||||
<Input value={field.value.toString()} onChange={(_, data) => field.onChange(Number(data.value))} />
|
||||
{percentage && <ProgressBar value={field.value / (overallLimit / 100) * 0.01} />}
|
||||
</Field>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
})}
|
||||
|
||||
<Field style={{ gridTemplateColumns: '1fr auto' }} validationMessage={percentage ? `${(watchedMonths.filter(month => month.month >= 1 && month.month <= 6)
|
||||
.reduce((sum, month) => {
|
||||
const value = Number(month.value) || 0;
|
||||
return sum + value;
|
||||
}, 0) / (overallLimit / 100)).toFixed(1)}%` : undefined} validationState='none' label={'1 полугодие'} orientation='horizontal'>
|
||||
<Input disabled value={watchedMonths.filter(month => month.month >= 1 && month.month <= 6)
|
||||
.reduce((sum, month) => {
|
||||
const value = Number(month.value) || 0;
|
||||
return sum + value;
|
||||
}, 0).toFixed(3)} />
|
||||
{percentage && <ProgressBar value={watchedMonths.filter(month => month.month >= 1 && month.month <= 6)
|
||||
.reduce((sum, month) => {
|
||||
const value = Number(month.value) || 0;
|
||||
return sum + value;
|
||||
}, 0) / (overallLimit / 100) * 0.01} />}
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', marginTop: '1rem', justifyContent: 'flex-end' }}>
|
||||
<Button type='submit' appearance='primary'>
|
||||
Добавить
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</DialogBody>
|
||||
</DialogSurface>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export default LimitAddForm
|
||||
19
client/src/store/fuel.ts
Normal file
19
client/src/store/fuel.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { create } from 'zustand'
|
||||
|
||||
export interface FuelState {
|
||||
regionId: number | undefined
|
||||
cityId: number | undefined
|
||||
}
|
||||
|
||||
export const useFuelStore = create<FuelState>(() => {
|
||||
return {
|
||||
regionId: undefined,
|
||||
cityId: undefined
|
||||
}
|
||||
})
|
||||
|
||||
export const getRegionId = () => useFuelStore.getState().regionId
|
||||
export const setRegionId = (id: number | undefined) => useFuelStore.setState(() => ({ regionId: id }))
|
||||
|
||||
export const getCityId = () => useFuelStore.getState().cityId
|
||||
export const setCityId = (id: number | undefined) => useFuelStore.setState(() => ({ cityId: id }))
|
||||
Reference in New Issue
Block a user