grouped object parameters

This commit is contained in:
2025-10-24 15:43:39 +09:00
parent 99bce93c86
commit 84a82c38eb
8 changed files with 334 additions and 131 deletions

View File

@ -0,0 +1,109 @@
import TableValue from './TableValue'
import { Text } from '@fluentui/react-components';
import { IObjectValue } from '../../interfaces/objects';
interface ITCBParameterProps {
values: IObjectValue[];
vtable: string;
inactive?: boolean;
name: string;
map_id: string;
}
const GTCBParameter = ({
values,
vtable,
name,
map_id
}: ITCBParameterProps) => {
//Get value
// const { data: tcbValue } = useSWR(
// value ? `/general/params/tcb?id=${value}&vtable=${vtable}` : null,
// (url) => fetcher(url, BASE_URL.ems).then(res => res[0]),
// {
// revalidateOnFocus: false,
// revalidateIfStale: false
// }
// )
const tables = [
'BoilersTemper',
'FuelsParametrs',
'PipesTypes',
'vAddRepairEvent',
'vApartmentTypes',
'vBoilers',
'vBoilersAppointment',
'vBoilersBalance',
'vBoilersCondition',
'vBoilersFuels',
'vBoilersHotWater',
'vBoilersPerimeter',
'vBoilersPeriods',
'vBoilersScheme',
'vBoilersState',
'vBoilersTypes',
'vBuildingManagement',
'vBuildingOwner',
'vCanalization',
'vColdWaterTypes',
'vConditionEquipment',
'vCovering',
'vDensityWater',
'vDryer',
'vElectroSupplyTypes',
'vEquipmentsTypes',
'vFoundation',
'vFuelsFeed',
'vGasSupplyTypes',
'vHeatingTypes',
'vHeatTransfer',
'vHotWaterTypes',
'vMaterialsWall',
'vNormative',
'vPeriodHW',
'vPipeDiameters',
'vPipeOutDiameters',
'vPipesBearingType',
'vPipesCovering',
'vPipesEvent',
'vPipesGround',
'vPipesIsolation',
'vPipesLayer',
'vPipesMaterial',
'vPumpType',
'vRepairEvent',
'vRoof',
'vRPSType',
'vServe',
'vStreets',
'vTechStatus',
'vTrash',
'vTrashStorage',
'vVentilation',
'vValvingType',
'vWallingEquipment',
'tTypes',
]
const TCBValue = (vtable: string) => {
if (tables.includes(vtable)) {
return (
<TableValue map_id={map_id} values={values} name={name} type='select' vtable={vtable} />
)
} else {
return (
<Text>
{JSON.stringify(name)}
{JSON.stringify(Array.isArray(values) && values.length > 0 && values[0].value)}
</Text>
)
}
}
return (
TCBValue(vtable)
)
}
export default GTCBParameter

View File

@ -156,7 +156,7 @@ const MapComponent = ({
marker.setStyle(new Style({
image: new Icon({
src: 'public/map_pin_icon.svg',
src: '/map_pin_icon.svg',
scale: 0.2
})
}))

View File

@ -1,10 +1,7 @@
import useSWR from 'swr'
import { fetcher } from '../../http/axiosInstance'
import { BASE_URL } from '../../constants'
import { IObjectParam, IParam } from '../../interfaces/objects'
import { IObjectParam, IObjectValue } from '../../interfaces/objects'
import TCBParameter from './TCBParameter'
import TableValue from './TableValue'
import { TableCell, TableCellLayout, TableRow } from '@fluentui/react-components'
import { Accordion, AccordionHeader, AccordionItem, AccordionPanel, Table, TableBody, TableCell, TableCellLayout, TableRow, Text } from '@fluentui/react-components'
interface ObjectParameterProps {
showLabel?: boolean;
@ -16,61 +13,73 @@ const ObjectParameter = ({
param,
map_id
}: ObjectParameterProps) => {
const { data: paramData } = useSWR(
`/general/params/?param_id=${param.id_param}`,
(url) => fetcher(url, BASE_URL.ems).then(res => res[0] as IParam),
{
revalidateOnFocus: false,
revalidateIfStale: false
}
)
const Parameter = (type: string, name: string, value: unknown, vtable: string, unit: string | null) => {
const Parameter = (type: string, name: string, values: IObjectValue[], vtable: string, unit: string | null) => {
switch (type) {
case 'bit':
return (
<TableValue map_id={map_id} value={value} name={name} type='boolean' />
<TableValue map_id={map_id} values={values} name={name} type='boolean' />
)
case 'int':
return (
<TableValue map_id={map_id} value={value} name={name} type='number' />
<TableValue map_id={map_id} values={values} name={name} type='number' />
)
case 'smallint':
return (
<TableValue map_id={map_id} values={values} name={name} type='number' />
)
case 'bigint':
return (
<TableValue map_id={map_id} value={value} name={name} type='number' />
<TableValue map_id={map_id} values={values} name={name} type='number' />
)
case 'tinyint':
return (
<TableValue map_id={map_id} value={value} name={name} type='number' />
<TableValue map_id={map_id} values={values} name={name} type='number' />
)
case 'smalldatetime':
return (
<TableValue map_id={map_id} values={values} name={name} type='number' />
)
// TODO: Calculate from calc procedures
case 'calculate':
return (
<TableValue map_id={map_id} value={value} name={name} type='value' />
<TableValue map_id={map_id} values={values} name={name} type='value' />
)
case 'GTCB':
return (
<TCBParameter map_id={map_id} value={value as string} vtable={vtable} name={name} />
<TCBParameter map_id={map_id} values={values} vtable={vtable} name={name} />
)
case 'TCB':
return (
<TCBParameter map_id={map_id} value={value as string} vtable={vtable} name={name} />
<TCBParameter map_id={map_id} values={values} vtable={vtable} name={name} />
)
case type.match(/varchar\((\d+)\)/)?.input:
return (
<TableValue map_id={map_id} value={value} name={name} type='string' />
<TableValue map_id={map_id} values={values} name={name} type='string' />
)
case type.match(/numeric\((\d+),(\d+)\)/)?.input:
return (
<TableValue map_id={map_id} value={value} name={name} type='number' unit={unit} />
<TableValue map_id={map_id} values={values} name={name} type='number' unit={unit} />
)
case 'year':
return (
<TableValue map_id={map_id} value={value} name={name} type='number' />
<TableValue map_id={map_id} values={values} name={name} type='number' />
)
case 'uniqueidentifier':
return (
<TableValue map_id={map_id} value={value} name={name} type='value' />
<TableValue map_id={map_id} values={values} name={name} type='value' />
)
case 'group':
return (
<TableValue map_id={map_id} values={values} name={name} type='value' />
)
case 'groupcalculate':
return (
<TableValue map_id={map_id} values={values} name={name} type='value' />
)
case 'array':
return (
<TableValue map_id={map_id} values={values} name={name} type='value' />
)
default:
return (
@ -83,7 +92,7 @@ const ObjectParameter = ({
<TableCell>
<TableCellLayout>
{value as string}
{values[0].value as string}
</TableCellLayout>
</TableCell>
</TableRow>
@ -93,9 +102,37 @@ const ObjectParameter = ({
return (
<>
{paramData &&
Parameter(paramData.format, paramData.name, param.value, paramData.vtable, paramData.unit)
{param && param.parameters && Array.isArray(param.parameters) && param.parameters.length > 0 ?
<TableRow>
<TableCell colSpan={2} style={{ padding: 0 }}>
<TableCellLayout>
<Accordion collapsible defaultOpenItems={param.id_param} style={{ width: '100%' }}>
<AccordionItem style={{ width: '100%' }} value={param.id_param}>
<AccordionHeader size='small'>
<Text weight='bold' size={200} style={{ textWrap: 'wrap' }}>{param.name}</Text>
</AccordionHeader>
<AccordionPanel style={{ width: '100%', margin: 0 }}>
<Table size='extra-small'>
<TableBody>
{param.parameters.length > 0 &&
param.parameters.map((p, index) => (
<ObjectParameter key={`child-param-${param.id_param}-${index}`} param={p} map_id={map_id} />
))
}
</TableBody>
</Table>
</AccordionPanel>
</AccordionItem>
</Accordion>
</TableCellLayout>
</TableCell>
</TableRow>
:
param && Parameter(param.format, param.name, param.values, param.vtable, param.unit)
}
</>
)
}

View File

@ -4,7 +4,7 @@ import useSWR from 'swr';
import { BASE_URL } from '../../../constants';
import { fetcher } from '../../../http/axiosInstance';
import { useObjectsStore } from '../../../store/objects';
import { Spinner, Table, TableBody } from '@fluentui/react-components';
import { Spinner, Table, TableBody, TableHeader, TableHeaderCell, TableRow } from '@fluentui/react-components';
const ObjectParameters = ({
map_id
@ -41,38 +41,20 @@ const ObjectParameters = ({
</div>
)}
<Table size='small'>
<Table size='extra-small'>
<TableHeader>
<TableRow appearance='neutral'>
<TableHeaderCell>Параметр</TableHeaderCell>
<TableHeaderCell>Значение</TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
{Array.isArray(valuesData) &&
Object.entries(
valuesData.reduce((acc, param) => {
if (!acc[param.id_param]) {
acc[param.id_param] = [];
}
acc[param.id_param].push(param);
return acc;
}, {} as Record<string, IObjectParam[]>)
).map(([id_param, params]) => {
// Step 1: Sort the parameters by date_s (start date) and date_po (end date)
const sortedParams = (params as IObjectParam[]).sort((b, a) => {
const dateA = new Date(a.date_s || 0);
const dateB = new Date(b.date_s || 0);
return dateA.getTime() - dateB.getTime();
});
return sortedParams.length > 1 ? (
sortedParams.map((param: IObjectParam) => {
if (param.date_po == null) {
return (
<ObjectParameter map_id={map_id} key={id_param} param={param} showLabel={false} />
)
}
}
)
) : (
<ObjectParameter map_id={map_id} key={id_param} param={sortedParams[0]} />
);
})
valuesData.map((param: IObjectParam) => (
<ObjectParameter map_id={map_id} key={param.id_param} param={param} />
))
}
</TableBody>
</Table>

View File

@ -1,11 +1,9 @@
import useSWR from 'swr'
import { fetcher } from '../../http/axiosInstance'
import { BASE_URL } from '../../constants'
import TableValue from './TableValue'
import { Text } from '@fluentui/react-components';
import { IObjectValue } from '../../interfaces/objects';
interface ITCBParameterProps {
value: string;
values: IObjectValue[];
vtable: string;
inactive?: boolean;
name: string;
@ -13,27 +11,27 @@ interface ITCBParameterProps {
}
const TCBParameter = ({
value,
values,
vtable,
name,
map_id
}: ITCBParameterProps) => {
//Get value
const { data: tcbValue } = useSWR(
`/general/params/tcb?id=${value}&vtable=${vtable}`,
(url) => fetcher(url, BASE_URL.ems).then(res => res[0]),
{
revalidateOnFocus: false,
revalidateIfStale: false
}
)
// const { data: tcbValue } = useSWR(
// value ? `/general/params/tcb?id=${value}&vtable=${vtable}` : null,
// (url) => fetcher(url, BASE_URL.ems).then(res => res[0]),
// {
// revalidateOnFocus: false,
// revalidateIfStale: false
// }
// )
const tables = [
'BoilersTemper',
'FuelsParametrs',
'PipesTypes',
'vAddRepairEvent',
'vApartmentTypes',
'vBoilers',
'vBoilersAppointment',
'vBoilersBalance',
@ -51,6 +49,7 @@ const TCBParameter = ({
'vColdWaterTypes',
'vConditionEquipment',
'vCovering',
'vDensityWater',
'vDryer',
'vElectroSupplyTypes',
'vEquipmentsTypes',
@ -62,21 +61,27 @@ const TCBParameter = ({
'vHotWaterTypes',
'vMaterialsWall',
'vNormative',
'vPeriodHW',
'vPipeDiameters',
'vPipeOutDiameters',
'vPipesBearingType',
'vPipesCovering',
'vPipesEvent',
'vPipesGround',
'vPipesIsolation',
'vPipesLayer',
'vPipesMaterial',
'vPumpType',
'vRepairEvent',
'vRoof',
'vRPSType',
'vServe',
'vStreets',
'vTechStatus',
'vTrash',
'vTrashStorage',
'vVentilation',
'vValvingType',
'vWallingEquipment',
'tTypes',
]
@ -84,13 +89,13 @@ const TCBParameter = ({
const TCBValue = (vtable: string) => {
if (tables.includes(vtable)) {
return (
<TableValue map_id={map_id} value={tcbValue?.id} name={name} type='select' vtable={vtable} />
<TableValue map_id={map_id} values={values} name={name} type='select' vtable={vtable} />
)
} else {
return (
<Text>
{JSON.stringify(name)}
{JSON.stringify(tcbValue)}
{JSON.stringify(Array.isArray(values) && values.length > 0 && values[0].value)}
</Text>
)
}

View File

@ -2,11 +2,13 @@ import useSWR from 'swr';
import { fetcher } from '../../http/axiosInstance';
import { BASE_URL } from '../../constants';
import { useObjectsStore } from '../../store/objects';
import { Checkbox, Input, Select, TableCell, TableCellLayout, TableRow, Text } from '@fluentui/react-components';
import { Input, Select, TableCell, TableCellLayout, TableRow, Text } from '@fluentui/react-components';
import { useState } from 'react';
import { IObjectValue } from '../../interfaces/objects';
interface TableValueProps {
name: string;
value: unknown;
values: IObjectValue[];
type: 'value' | 'boolean' | 'number' | 'select' | 'string';
unit?: string | null | undefined;
vtable?: string;
@ -15,14 +17,17 @@ interface TableValueProps {
const TableValue = ({
name,
value,
values,
type,
unit,
vtable,
map_id
}: TableValueProps) => {
const { selectedDistrict } = useObjectsStore().id[map_id]
//Get available values
const [value] = useState(Array.isArray(values) && values.length > 0 ? values[0].value : null)
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 => {
@ -42,22 +47,22 @@ const TableValue = ({
return (
<TableRow>
<TableCell>
<TableCellLayout truncate>
<Text size={200} style={{ textWrap: 'wrap' }}>{name as string}</Text>
<TableCellLayout truncate title={name as string}>
{name as string}
</TableCellLayout>
</TableCell>
<TableCell>
<TableCell style={{ padding: '0.25rem' }}>
<div style={{ display: 'flex' }}>
{type === 'boolean' ?
// <Select style={{ display: 'flex', width: '100%' }} size='small' defaultChecked={value as boolean}>
// {[true, false].map(tcb => (
// <option key={JSON.stringify(tcb)} value={JSON.stringify(tcb)}>
// {tcb === true ? 'Да' : 'Нет'}
// </option>
// ))}
// </Select>
<Checkbox defaultChecked={value as boolean} />
<Select style={{ display: 'flex', width: '100%' }} size='small' defaultChecked={value as boolean}>
{[true, false].map(tcb => (
<option key={JSON.stringify(tcb)} value={JSON.stringify(tcb)}>
{tcb === true ? 'Да' : 'Нет'}
</option>
))}
</Select>
// <Checkbox defaultChecked={value as boolean} />
:
type === 'number' ?
<Input
@ -65,7 +70,7 @@ const TableValue = ({
style={{ display: 'flex', width: '100%' }}
defaultValue={value as string}
onChange={() => { }}
contentAfter={unit ? ` ${unit}` : ''}
contentAfter={unit ? <Text size={200} wrap={false}>{unit}</Text> : undefined}
//displayValue={unit ? ` ${unit}` : ''}
/>
:
@ -81,7 +86,10 @@ const TableValue = ({
type === 'string' ?
<Input style={{ display: 'flex', width: '100%' }} size='small' value={value as string} />
:
<Text size={200}>{value as string}</Text>
type === 'value' ?
<Input style={{ display: 'flex', width: '100%' }} size='small' value={value as string} />
:
<Text size={200}>{value as string}</Text>
}
</div>
</TableCell>

View File

@ -1,44 +1,45 @@
export interface IObjectList {
id: number,
name: string,
id: number
name: string
count: number
}
export interface IObjectData {
object_id: string,
id_city: number,
year: number,
id_parent: number | null,
type: number,
planning: boolean,
activity: boolean,
kvr: string | null,
jur: string | null,
fuel: string | null,
object_id: string
id_city: number
year: number
id_parent: number | null
type: number
planning: boolean
activity: boolean
kvr: string | null
jur: string | null
fuel: string | null
boiler_id: string | null
}
export interface IObjectParam {
id_object: string,
id_param: number,
value: string,
date_s: string | null,
date_po: string | null,
id_user: number
export interface IObjectValue {
value: any
date_s: string | null
date_po: string | null
}
export interface IParam {
id: number,
id_group: number | null,
name: string,
format: string,
vtable: string,
unit: string | null,
exact_format: string | null,
inHistory: string | null
export interface IObjectParam {
id_object: string
id_param: number
name: string
date_s: string | null
date_po: string | null
id_user: number
format: string
vtable: string
exact_format: string | null
unit: string | null
values: IObjectValue[]
parameters?: IObjectParam[]
}
export interface IObjectType {
id: number,
id: number
name: string
}

View File

@ -181,20 +181,81 @@ export class GeneralService {
}
async getValuesByObjectId(object_id: string): Promise<any[]> {
const generalDatabase = 'nGeneral'
const generalDatabase = 'nGeneral';
const result = await this.dataSource.query(`
SELECT id_object, id_param, CAST(v.value AS varchar(max)) AS value,
date_s,
date_po,
id_user
FROM ${generalDatabase}..tValues v
JOIN ${generalDatabase}..tParameters p ON v.id_param = p.id
const parameters = await this.dataSource.query(`
SELECT
pt.id_param,
p.name AS name,
p.id_group,
p.format,
p.vtable,
p.unit,
p.exact_format
FROM ${generalDatabase}..ParametersType pt
JOIN ${generalDatabase}..tParameters p
ON pt.id_param = p.id
WHERE pt.id_type = (
SELECT TOP 1 CAST(value AS int)
FROM ${generalDatabase}..tValues
WHERE id_object = '${object_id}'
`)
return result
AND id_param = 3
ORDER BY date_s DESC
)
ORDER BY pt.id_order;
`);
const values = await this.dataSource.query(`
SELECT
v.id_param,
CAST(v.value AS varchar(max)) AS value,
v.date_s,
v.date_po,
v.id_user
FROM ${generalDatabase}..tValues v
WHERE v.id_object = '${object_id}';
`);
const valuesByParam = values.reduce((acc, v) => {
if (!acc[v.id_param]) acc[v.id_param] = [];
acc[v.id_param].push({
value: v.value,
date_s: v.date_s,
date_po: v.date_po,
id_user: v.id_user
});
return acc;
}, {} as Record<number, any[]>);
const grouped = parameters
.filter(p => p.id_group == null)
.map(parent => ({
id_param: parent.id_param,
name: parent.name,
format: parent.format,
vtable: parent.vtable,
unit: parent.unit,
exact_format: parent.exact_format,
values: valuesByParam[parent.id_param] || [],
parameters: parameters
.filter(child => child.id_group === parent.id_param)
.map(child => ({
id_param: child.id_param,
name: child.name,
id_group: child.id_group,
format: child.format,
vtable: child.vtable,
unit: child.unit,
exact_format: child.exact_format,
values: valuesByParam[child.id_param] || []
}))
}));
return grouped;
}
async getParamsById(param_id: number): Promise<any[]> {
const generalDatabase = 'nGeneral'