Skip to content

Commit 0ac5b61

Browse files
Add district comments panel
1 parent 4c10c14 commit 0ac5b61

5 files changed

Lines changed: 162 additions & 1 deletion

File tree

app/src/app/components/sidebar/DataPanelUtils.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import PopulationPanel from '@components/sidebar/PopulationPanel';
22
import {MapStore} from '@/app/store/mapStore';
33
import {MapValidation} from './MapValidation/MapValidation';
44
import {SummaryPanel} from './SummaryPanel';
5+
import { MetadataPanel } from './MetadataPanel';
56

67
export interface DataPanelSpec {
78
title: MapStore['sidebarPanels'][number];
@@ -37,4 +38,9 @@ export const defaultPanels: DataPanelSpec[] = [
3738
label: 'Map validation',
3839
content: <MapValidation />,
3940
},
41+
{
42+
title: 'metadata',
43+
label: 'Map Comments and Metadata',
44+
content: <MetadataPanel />,
45+
},
4046
];
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import {useMapMetadata} from '@/app/hooks/useMapMetadata';
2+
import {useMapStore} from '@/app/store/mapStore';
3+
import {saveMap} from '@/app/utils/api/apiHandlers/saveMap';
4+
import {DocumentMetadata} from '@/app/utils/api/apiHandlers/types';
5+
import {DEFAULT_MAP_METADATA} from '@/app/utils/language';
6+
import {Button, Flex, Select, Text, TextArea} from '@radix-ui/themes';
7+
import {Input} from 'postcss';
8+
import {useEffect, useState} from 'react';
9+
10+
export const MetadataPanel = () => {
11+
const mapMetadata = useMapMetadata();
12+
const numDistricts = useMapStore(state => state.mapDocument?.num_districts);
13+
const [innerFormState, setInnerFormState] = useState<DocumentMetadata>(
14+
mapMetadata ?? DEFAULT_MAP_METADATA
15+
);
16+
const missingDistrictComments = Array.from({length: numDistricts || 0}, (_, i) => i + 1).filter(
17+
i => !innerFormState.district_comments?.[i]
18+
);
19+
20+
useEffect(() => {
21+
setInnerFormState(mapMetadata ?? DEFAULT_MAP_METADATA);
22+
}, [mapMetadata]);
23+
24+
const handleMetadataChange = async (updates: Partial<DocumentMetadata>) => {
25+
await saveMap({
26+
...(mapMetadata || DEFAULT_MAP_METADATA),
27+
...updates,
28+
});
29+
};
30+
console.log('!!!', innerFormState);
31+
32+
return (
33+
<Flex direction="column" gap="2">
34+
<Flex direction="column" gap="2">
35+
<Text>Plan Comments</Text>
36+
<TextArea
37+
value={innerFormState?.description || ''}
38+
onChange={e => setInnerFormState({...innerFormState, district_comments: e.target.value})}
39+
/>
40+
</Flex>
41+
<Flex direction="row" gap="2">
42+
<Button
43+
onClick={() =>
44+
handleMetadataChange({
45+
description: innerFormState.description,
46+
district_comments: innerFormState.district_comments,
47+
})
48+
}
49+
color="green"
50+
>
51+
Save
52+
</Button>
53+
<Button onClick={() => setInnerFormState(mapMetadata ?? DEFAULT_MAP_METADATA)} color="red">
54+
Reset
55+
</Button>
56+
</Flex>
57+
<Flex direction="column" gap="2">
58+
<Text>District Comments</Text>
59+
{Object.keys(innerFormState.district_comments || {}).map(district => (
60+
<DistrictCommentRow
61+
key={district}
62+
district={Number(district)}
63+
innerFormState={innerFormState}
64+
setInnerFormState={setInnerFormState}
65+
numDistricts={numDistricts || 0}
66+
/>
67+
))}
68+
{missingDistrictComments.length > 0 && (
69+
<Button
70+
onClick={() =>
71+
setInnerFormState({
72+
...innerFormState,
73+
district_comments: {
74+
...innerFormState.district_comments,
75+
[missingDistrictComments[0]]: `District ${missingDistrictComments[0]} comments`,
76+
},
77+
})
78+
}
79+
>
80+
Add District Comments
81+
</Button>
82+
)}
83+
</Flex>
84+
</Flex>
85+
);
86+
};
87+
88+
const DistrictCommentRow: React.FC<{
89+
district: number;
90+
innerFormState: DocumentMetadata;
91+
setInnerFormState: (state: DocumentMetadata) => void;
92+
numDistricts: number;
93+
}> = ({district, innerFormState, setInnerFormState, numDistricts}) => {
94+
const handleChangeDistrictNumber = (value: string) => {
95+
const content = innerFormState.district_comments?.[Number(district)];
96+
const previousContent = {...innerFormState.district_comments};
97+
delete previousContent[district];
98+
setInnerFormState({
99+
...innerFormState,
100+
district_comments: {...previousContent, [Number(value)]: content || ''},
101+
});
102+
};
103+
104+
const handleChangeText = (value: string) => {
105+
setInnerFormState({
106+
...innerFormState,
107+
district_comments: {...innerFormState.district_comments, [Number(district)]: value},
108+
});
109+
};
110+
const handleRemoveDistrict = () => {
111+
const previousContent = {...innerFormState.district_comments};
112+
delete previousContent[district];
113+
setInnerFormState({...innerFormState, district_comments: previousContent});
114+
};
115+
116+
return (
117+
<Flex direction="row" gap="2" align="center">
118+
<Select.Root
119+
value={district.toString()}
120+
onValueChange={value =>
121+
value === 'remove' ? handleRemoveDistrict() : handleChangeDistrictNumber(value)
122+
}
123+
>
124+
<Select.Trigger>
125+
<Text>{district}</Text>
126+
</Select.Trigger>
127+
<Select.Content>
128+
<Select.Item key="remove" value="remove">
129+
Remove
130+
</Select.Item>
131+
{Array.from({length: numDistricts || 0}, (_, i) => (
132+
<Select.Item key={i} value={(i + 1).toString()}>
133+
{i + 1}
134+
</Select.Item>
135+
))}
136+
</Select.Content>
137+
</Select.Root>
138+
<TextArea
139+
value={innerFormState.district_comments?.[Number(district)] || ''}
140+
className="flex-grow"
141+
onChange={e => handleChangeText(e.target.value)}
142+
/>
143+
</Flex>
144+
);
145+
};

app/src/app/store/mapStore.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,9 @@ export interface MapStore {
221221
focusFeatures: Array<MapFeatureInfo>;
222222
mapOptions: MapOptions & DistrictrMapOptions;
223223
setMapOptions: (options: Partial<MapStore['mapOptions']>) => void;
224-
sidebarPanels: Array<'layers' | 'population' | 'demography' | 'election' | 'mapValidation'>;
224+
sidebarPanels: Array<
225+
'layers' | 'population' | 'demography' | 'election' | 'mapValidation' | 'metadata'
226+
>;
225227
setSidebarPanels: (panels: MapStore['sidebarPanels']) => void;
226228
// HIGHLIGHT
227229
activeTool: ActiveTool;

app/src/app/utils/api/apiHandlers/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ export interface DocumentMetadata {
4343
description: string | null;
4444
eventId: string | null;
4545
draft_status: DraftStatus | null;
46+
district_comments: Record<number, string> | null;
47+
location_comments: Array<{
48+
lat: number;
49+
lng: number;
50+
comment: string;
51+
}> | null;
4652
}
4753

4854
export interface DocumentObject extends StatusObject {

backend/app/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ class DocumentMetadata(BaseModel):
132132
description: str | None = None
133133
event_id: str | None = None
134134
draft_status: DocumentDraftStatus | None = DocumentDraftStatus.scratch
135+
district_comments: dict[int, str] | None = None
136+
location_comments: list[dict[str, str]] | None = None
135137

136138

137139
class Document(TimeStampMixin, SQLModel, table=True):

0 commit comments

Comments
 (0)