fuel; nest api

This commit is contained in:
2025-12-23 09:53:04 +09:00
parent fa516b3a20
commit 04ce74d320
23 changed files with 1125 additions and 183 deletions

View File

@ -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