Add logical toggle for demolished buildings

This commit is contained in:
Maciej Ziarkowski 2021-03-18 19:41:28 +00:00
parent 26a91a229e
commit 4aa35b94dd
11 changed files with 107 additions and 35 deletions

View File

@ -508,6 +508,10 @@
</Rule>
</Style>
<Style name="dynamics_demolished_count">
<Rule>
<Filter>[dynamics_has_demolished_buildings] = false</Filter>
<PolygonSymbolizer fill="#0C7BDC" />
</Rule>
<Rule>
<Filter>[demolished_buildings_count] &gt;= 7</Filter>
<PolygonSymbolizer fill="#bd0026" />

View File

@ -239,6 +239,11 @@ export const dataFieldsConfig = valueType<DataFieldConfig>()({ /* eslint-disable
verify: false,
},
dynamics_has_demolished_buildings: {
edit: true,
verify: true
},
demolished_buildings: {
edit: true,
verify: false,
@ -246,3 +251,6 @@ export const dataFieldsConfig = valueType<DataFieldConfig>()({ /* eslint-disable
sqlCast: 'jsonb',
},
});
export type Building = { [k in keyof typeof dataFieldsConfig]: any };
export type BuildingUpdate = Partial<Building>;

View File

@ -1,6 +1,7 @@
import * as _ from 'lodash';
import { hasAnyOwnProperty } from '../../../helpers';
import { Building, BuildingUpdate } from '../../config/dataFields';
import { getBuildingData } from '../../dataAccess/building';
import { ArgumentError } from '../../errors/general';
@ -36,10 +37,30 @@ async function processCurrentLandUseClassifications(buildingId: number, building
/**
* Process Dynamics data - sort past buildings by construction date
* Process Dynamics data - check field relationships and sort demolished buildings by construction date
*/
async function processDynamicsPastBuildings(buildingId: number, buildingUpdate: any): Promise<any> {
buildingUpdate.demolished_buildings = buildingUpdate.demolished_buildings.sort((a, b) => b.year_constructed.min - a.year_constructed.min);
async function processDynamicsDemolishedBuildings(buildingId: number, buildingUpdate: BuildingUpdate): Promise<BuildingUpdate> {
const currentBuildingData = await getBuildingData(buildingId);
const afterUpdate: Building = Object.assign({}, currentBuildingData, buildingUpdate);
const hasDemolished: boolean = afterUpdate.dynamics_has_demolished_buildings;
const demolishedList: any[] = afterUpdate.demolished_buildings;
if(currentBuildingData.date_year == undefined) {
throw new ArgumentError('Cannot edit demolished buildings data if data on current building age is missing', 'buildingUpdate');
}
if(hasDemolished === false || hasDemolished == undefined) {
if(demolishedList.length > 0) {
throw new ArgumentError('Inconsistent data on whether there were any other buildings on this site', 'buildingUpdate');
}
}
if(buildingUpdate.demolished_buildings != undefined) {
buildingUpdate.demolished_buildings = buildingUpdate.demolished_buildings.sort((a, b) => b.year_constructed.min - a.year_constructed.min);
}
return buildingUpdate;
}
@ -47,12 +68,12 @@ async function processDynamicsPastBuildings(buildingId: number, buildingUpdate:
/**
* Define any custom processing logic for specific building attributes
*/
export async function processBuildingUpdate(buildingId: number, buildingUpdate: any): Promise<any> {
export async function processBuildingUpdate(buildingId: number, buildingUpdate: BuildingUpdate): Promise<any> {
if(hasAnyOwnProperty(buildingUpdate, ['current_landuse_group'])) {
buildingUpdate = await processCurrentLandUseClassifications(buildingId, buildingUpdate);
}
if('demolished_buildings' in buildingUpdate) {
buildingUpdate = await processDynamicsPastBuildings(buildingId, buildingUpdate);
if(hasAnyOwnProperty(buildingUpdate, ['demolished_buildings', 'dynamics_has_demolished_buildings'])) {
buildingUpdate = await processDynamicsDemolishedBuildings(buildingId, buildingUpdate);
}
return buildingUpdate;

View File

@ -11,6 +11,7 @@ import { NumberRangeDataEntry } from './number-range-data-entry';
import './dynamics-data-entry.css';
import { CloseIcon } from '../../../components/icons';
import DataTitle, { DataTitleCopyable } from '../../data-components/data-title';
type DemolishedBuilding = (BuildingAttributes['demolished_buildings'][number]);
@ -147,6 +148,7 @@ const DynamicsDataRow: React.FC<DynamicsDataRowProps> = ({
};
interface DynamicsDataEntryProps extends BaseDataEntryProps {
title: string;
value: DemolishedBuilding[];
editableEntries: boolean;
maxYear: number;
@ -206,11 +208,13 @@ export const DynamicsDataEntry: React.FC<DynamicsDataEntryProps> = (props) => {
<>
<div>
{
isEditing &&
isEditing ?
<>
<h6 className="h6">Existing records for demolished buildings</h6>
<label>Please supply sources for any edits of existing records</label>
</>
</> :
<DataTitleCopyable slug={props.slug} title={props.title} tooltip={null}/>
}
<ul className="data-entry-list">
{

View File

@ -13,6 +13,7 @@ import NumericDataEntry from '../../data-components/numeric-data-entry';
import withCopyEdit from '../../data-container';
import { CategoryViewProps } from '../category-view-props';
import { LogicalDataEntry } from '../../data-components/logical-data-entry/logical-data-entry';
/**
* Dynamics view/edit section
@ -60,35 +61,51 @@ const DynamicsView: React.FunctionComponent<CategoryViewProps> = (props) => {
</FieldRow>
</DynamicsBuildingPane>
{
currentBuildingConstructionYear != undefined ?
currentBuildingConstructionYear == undefined ?
<InfoBox>To add historical records, fill in the <Link to={ageLinkUrl}>Age</Link> data first.</InfoBox> :
<>
<DynamicsDataEntry
/*
Will clear the edits and new record data upon navigating to another building.
Should get a better way to do this, plus a way to actually keep unsaved edits.
*/
key={building.building_id}
value={building.demolished_buildings}
editableEntries={true}
slug='demolished_buildings'
title={undefined}
mode={props.mode}
<LogicalDataEntry
slug='dynamics_has_demolished_buildings'
title={dataFields.dynamics_has_demolished_buildings.title}
value={building.dynamics_has_demolished_buildings}
disallowFalse={(building.demolished_buildings?.length ?? 0) > 0}
disallowNull={(building.demolished_buildings?.length ?? 0) > 0}
onChange={props.onChange}
onSaveAdd={props.onSaveAdd}
hasEdits={props.edited}
maxYear={currentBuildingConstructionYear}
minYear={50}
mode={props.mode}
copy={props.copy}
/>
{
props.mode === 'view' &&
<InfoBox>Switch to edit mode to add/edit past building records</InfoBox>
building.dynamics_has_demolished_buildings &&
<>
<DynamicsDataEntry
/*
Will clear the edits and new record data upon navigating to another building.
Should get a better way to do this, plus a way to actually keep unsaved edits.
*/
key={building.building_id}
value={building.demolished_buildings}
editableEntries={true}
slug='demolished_buildings'
title={dataFields.demolished_buildings.title}
mode={props.mode}
onChange={props.onChange}
onSaveAdd={props.onSaveAdd}
hasEdits={props.edited}
maxYear={currentBuildingConstructionYear}
minYear={50}
/>
{
props.mode === 'view' &&
<InfoBox>Switch to edit mode to add/edit past building records</InfoBox>
}
</>
}
</> :
<InfoBox>To add historical records, fill in the <Link to={ageLinkUrl}>Age</Link> data first.</InfoBox>
</>
}
</DataEntryGroup>
<DataEntryGroup name="Future planned data collection" collapsed={false} showCount={false}>

View File

@ -188,7 +188,7 @@ export const categoryMapsConfig: {[key in Category]: CategoryMapDefinition} = {
mapStyle: 'dynamics_demolished_count',
legend: {
title: 'Dynamics',
description: 'Past (demolished) buildings on site',
description: 'Demolished buildings on the same site',
elements: [
{
text: '7+',
@ -211,6 +211,9 @@ export const categoryMapsConfig: {[key in Category]: CategoryMapDefinition} = {
}, {
text: '1',
color: '#ffe8a9',
}, {
text: 'None',
color: '#0C7BDC'
}
],
},

View File

@ -445,6 +445,12 @@ export const dataFields = { /* eslint-disable @typescript-eslint/camelcase */
example: 100,
},
dynamics_has_demolished_buildings: {
category: Category.Dynamics,
title: 'Where any other buildings ever built on this site?',
example: true,
},
demolished_buildings: {
category: Category.Dynamics,
title: 'Past (demolished) buildings on this site',

View File

@ -24,7 +24,7 @@ export function parseJsonOrDefault(jsonString: string) {
}
}
export function hasAnyOwnProperty(obj: {}, keys: string[]) {
export function hasAnyOwnProperty<T>(obj: T, keys: (keyof T)[]) {
return keys.some(k => obj.hasOwnProperty(k));
}

View File

@ -136,10 +136,11 @@ const LAYER_QUERIES = {
dynamics_demolished_count: `
SELECT
geometry_id,
jsonb_array_length(demolished_buildings) as demolished_buildings_count
jsonb_array_length(demolished_buildings) as demolished_buildings_count,
dynamics_has_demolished_buildings
FROM
buildings
WHERE jsonb_array_length(demolished_buildings) > 0`,
WHERE jsonb_array_length(demolished_buildings) > 0 OR dynamics_has_demolished_buildings = FALSE`,
};
const GEOMETRY_FIELD = 'geometry_geom';

View File

@ -0,0 +1 @@
ALTER TABLE buildings DROP COLUMN IF EXISTS dynamics_has_demolished_buildings;

View File

@ -0,0 +1,7 @@
ALTER TABLE buildings
ADD COLUMN dynamics_has_demolished_buildings BOOLEAN;
UPDATE buildings
SET dynamics_has_demolished_buildings = TRUE
WHERE jsonb_array_length(demolished_buildings) > 0;