Skip to content

Commit 342cd15

Browse files
committed
Start using new geography field in dataset listing page. Add the new fields to eiit flow
1 parent 66c7ee4 commit 342cd15

File tree

9 files changed

+634
-78
lines changed

9 files changed

+634
-78
lines changed

app/[locale]/(user)/components/ListingComponent.tsx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -528,9 +528,11 @@ const ListingComponent: React.FC<ListingProps> = ({
528528
: item?.organization?.logo
529529
? `${process.env.NEXT_PUBLIC_BACKEND_URL}/${item.organization.logo}`
530530
: '/org.png';
531-
const Geography = item.metadata.filter(
532-
(item: any) => item.metadata_item.label === 'Geography'
533-
)[0]?.value;
531+
532+
// Get geographies from the geographies field (new approach)
533+
const geographies = item.geographies && item.geographies.length > 0
534+
? item.geographies
535+
: null;
534536

535537
const MetadataContent = [
536538
{
@@ -550,12 +552,20 @@ const ListingComponent: React.FC<ListingProps> = ({
550552
});
551553
}
552554

553-
if (Geography) {
555+
if (geographies && geographies.length > 0) {
556+
// Format geographies hierarchically for display
557+
const geoDisplay = geographies.map((geo: any) => {
558+
if (geo.parentId) {
559+
return `${geo.name} (${geo.parentId.name})`;
560+
}
561+
return geo.name;
562+
}).join(', ');
563+
554564
MetadataContent.push({
555565
icon: Icons.globe,
556566
label: 'Geography',
557-
value: Geography,
558-
tooltip: 'Geography',
567+
value: geoDisplay,
568+
tooltip: geoDisplay,
559569
});
560570
}
561571

app/[locale]/(user)/datasets/components/FIlter/Filter.tsx

Lines changed: 46 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212

1313
import { toTitleCase } from '@/lib/utils';
1414
import { Icons } from '@/components/icons';
15+
import GeographyFilter from './GeographyFilter';
1516

1617
interface FilterProps {
1718
setOpen?: (isOpen: boolean) => void;
@@ -60,42 +61,54 @@ const Filter: React.FC<FilterProps> = ({
6061
)}
6162
</div>
6263
<div className="flex flex-col gap-5">
63-
{Object.entries(options).map(([category, data], index) => (
64-
<div key={index}>
65-
<Accordion
66-
type="single"
67-
collapsible
68-
defaultValue={category}
69-
className="w-full"
70-
>
71-
<AccordionItem
72-
value={category}
73-
className=" border-surfaceDefault"
64+
{/* Geography Filter - Hierarchical */}
65+
<GeographyFilter
66+
selectedGeographies={selectedOptions['geographies'] || []}
67+
onGeographyChange={(geographies) =>
68+
setSelectedOptions('geographies', geographies)
69+
}
70+
geographyOptions={options['geographies'] || []}
71+
/>
72+
73+
{/* Other Filters */}
74+
{Object.entries(options)
75+
.filter(([category]) => category !== 'geographies')
76+
.map(([category, data], index) => (
77+
<div key={index}>
78+
<Accordion
79+
type="single"
80+
collapsible
81+
defaultValue={category}
82+
className="w-full"
7483
>
75-
<AccordionTrigger className="flex w-full flex-wrap items-center gap-2 rounded-1 bg-[#1F5F8D1A] py-[10px] px-3 hover:no-underline">
76-
<Text fontWeight="medium">{toTitleCase(category)}</Text>
77-
</AccordionTrigger>
78-
<AccordionContent
79-
className="flex w-full flex-col px-3 pb-0 pt-2"
80-
style={{
81-
backgroundColor: 'var(--base-pure-white)',
82-
outline: '1px solid var(--base-pure-white)',
83-
}}
84+
<AccordionItem
85+
value={category}
86+
className=" border-surfaceDefault"
8487
>
85-
<CheckboxGroup
86-
name={category}
87-
options={data}
88-
title={undefined}
89-
value={selectedOptions[category] || []}
90-
onChange={(values) => {
91-
setSelectedOptions(category, values as string[]);
88+
<AccordionTrigger className="flex w-full flex-wrap items-center gap-2 rounded-1 bg-[#1F5F8D1A] py-[10px] px-3 hover:no-underline">
89+
<Text fontWeight="medium">{toTitleCase(category)}</Text>
90+
</AccordionTrigger>
91+
<AccordionContent
92+
className="flex w-full flex-col px-3 pb-0 pt-2"
93+
style={{
94+
backgroundColor: 'var(--base-pure-white)',
95+
outline: '1px solid var(--base-pure-white)',
9296
}}
93-
/>
94-
</AccordionContent>
95-
</AccordionItem>
96-
</Accordion>
97-
</div>
98-
))}
97+
>
98+
<CheckboxGroup
99+
name={category}
100+
options={data}
101+
title={undefined}
102+
value={selectedOptions[category] || []}
103+
onChange={(values) => {
104+
setSelectedOptions(category, values as string[]);
105+
}}
106+
/>
107+
</AccordionContent>
108+
</AccordionItem>
109+
</Accordion>
110+
</div>
111+
))}
99112
</div>
100113
</div>
101114
);
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
import React, { useEffect, useState } from 'react';
2+
import {
3+
Accordion,
4+
AccordionContent,
5+
AccordionItem,
6+
AccordionTrigger,
7+
Text,
8+
} from 'opub-ui';
9+
10+
import { toTitleCase } from '@/lib/utils';
11+
import { TreeView } from '@/components/ui/tree-view';
12+
13+
interface Geography {
14+
id: number;
15+
name: string;
16+
code: string;
17+
type: string;
18+
parentId: {
19+
id: number;
20+
name: string;
21+
type: string;
22+
} | null;
23+
}
24+
25+
interface GeographyNode extends Geography {
26+
children: GeographyNode[];
27+
}
28+
29+
interface GeographyFilterProps {
30+
selectedGeographies: string[];
31+
onGeographyChange: (geographies: string[]) => void;
32+
geographyOptions?: { label: string; value: string }[];
33+
}
34+
35+
const GeographyFilter: React.FC<GeographyFilterProps> = ({
36+
selectedGeographies,
37+
onGeographyChange,
38+
geographyOptions = [],
39+
}) => {
40+
const [geographies, setGeographies] = useState<GeographyNode[]>([]);
41+
const [loading, setLoading] = useState(true);
42+
const [expandedItems, setExpandedItems] = useState<string[]>([]);
43+
44+
useEffect(() => {
45+
const fetchGeographies = async () => {
46+
setLoading(true);
47+
try {
48+
// Always try to fetch from GraphQL for full hierarchy
49+
const response = await fetch(
50+
`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/graphql`,
51+
{
52+
method: 'POST',
53+
headers: {
54+
'Content-Type': 'application/json',
55+
},
56+
body: JSON.stringify({
57+
query: `
58+
query {
59+
geographies {
60+
id
61+
name
62+
code
63+
type
64+
parentId {
65+
id
66+
name
67+
type
68+
}
69+
}
70+
}
71+
`,
72+
}),
73+
}
74+
);
75+
76+
const { data } = await response.json();
77+
78+
if (data && data.geographies && data.geographies.length > 0) {
79+
const hierarchicalData = buildHierarchy(data.geographies);
80+
setGeographies(hierarchicalData);
81+
} else if (geographyOptions && geographyOptions.length > 0) {
82+
// Fallback to aggregations if GraphQL fails
83+
const flatGeographies: GeographyNode[] = geographyOptions.map((opt, idx) => ({
84+
id: idx,
85+
name: opt.label,
86+
code: '',
87+
type: '',
88+
parentId: null,
89+
children: [],
90+
}));
91+
setGeographies(flatGeographies);
92+
}
93+
} catch (error) {
94+
console.error('Error fetching geographies:', error);
95+
// Use aggregations as fallback on error
96+
if (geographyOptions && geographyOptions.length > 0) {
97+
const flatGeographies: GeographyNode[] = geographyOptions.map((opt, idx) => ({
98+
id: idx,
99+
name: opt.label,
100+
code: '',
101+
type: '',
102+
parentId: null,
103+
children: [],
104+
}));
105+
setGeographies(flatGeographies);
106+
}
107+
} finally {
108+
setLoading(false);
109+
}
110+
};
111+
112+
fetchGeographies();
113+
}, []); // Only run once on mount
114+
115+
const buildHierarchy = (flatList: Geography[]): GeographyNode[] => {
116+
const map = new Map<number, GeographyNode>();
117+
const roots: GeographyNode[] = [];
118+
119+
// Initialize all nodes
120+
flatList.forEach((geo) => {
121+
map.set(geo.id, { ...geo, children: [] });
122+
});
123+
124+
// Build hierarchy
125+
flatList.forEach((geo) => {
126+
const node = map.get(geo.id)!;
127+
if (geo.parentId && geo.parentId.id) {
128+
const parent = map.get(geo.parentId.id);
129+
if (parent) {
130+
parent.children.push(node);
131+
} else {
132+
roots.push(node);
133+
}
134+
} else {
135+
roots.push(node);
136+
}
137+
});
138+
139+
return roots;
140+
};
141+
142+
// Convert GeographyNode to TreeView format
143+
const convertToTreeData = (nodes: GeographyNode[]): any[] => {
144+
return nodes.map((node) => ({
145+
id: node.name,
146+
name: node.name,
147+
children: node.children.length > 0 ? convertToTreeData(node.children) : undefined,
148+
}));
149+
};
150+
151+
const treeData = convertToTreeData(geographies);
152+
153+
if (loading) {
154+
return (
155+
<div className="py-2">
156+
<Text variant="bodySm" className="text-secondaryText">
157+
Loading geographies...
158+
</Text>
159+
</div>
160+
);
161+
}
162+
163+
if (geographies.length === 0) {
164+
return null;
165+
}
166+
167+
return (
168+
<Accordion type="single" collapsible defaultValue="geographies" className="w-full">
169+
<AccordionItem value="geographies" className="border-surfaceDefault">
170+
<AccordionTrigger className="flex w-full flex-wrap items-center gap-2 rounded-1 bg-[#1F5F8D1A] py-[10px] px-3 hover:no-underline">
171+
<Text fontWeight="medium">{toTitleCase('geographies')}</Text>
172+
</AccordionTrigger>
173+
<AccordionContent
174+
className="flex w-full flex-col px-2 pb-2 pt-2"
175+
style={{
176+
backgroundColor: 'var(--base-pure-white)',
177+
outline: '1px solid var(--base-pure-white)',
178+
maxHeight: 'none',
179+
}}
180+
>
181+
<div style={{ maxHeight: '400px' }} className="overflow-y-auto pr-1">
182+
<TreeView
183+
data={treeData}
184+
selectedItems={selectedGeographies}
185+
onSelectChange={onGeographyChange}
186+
expandedItems={expandedItems}
187+
onExpandedChange={setExpandedItems}
188+
/>
189+
</div>
190+
</AccordionContent>
191+
</AccordionItem>
192+
</Accordion>
193+
);
194+
};
195+
196+
export default GeographyFilter;

app/[locale]/(user)/usecases/components/Details.tsx

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -74,22 +74,40 @@ const PrimaryDetails = ({ data, isLoading }: { data: any; isLoading: any }) => {
7474
/>
7575
</div>
7676
<div className=" lg:pr-4">
77-
<div className="mt-6 lg:mt-10">
78-
<Text variant="headingXl">Geographies</Text>
79-
<div className="mt-4">
80-
<Tag
81-
fillColor="var(--orange-secondary-color)"
82-
borderColor="var(--orange-secondary-text)"
83-
textColor="black"
84-
>
85-
{
86-
data.useCase.metadata?.find(
87-
(meta: any) => meta.metadataItem?.label === 'Geography'
88-
)?.value
89-
}
90-
</Tag>
77+
{data.useCase.geographies && data.useCase.geographies.length > 0 && (
78+
<div className="mt-6 lg:mt-10">
79+
<Text variant="headingXl">Geographies</Text>
80+
<div className="mt-4 flex flex-wrap gap-2">
81+
{data.useCase.geographies.map((geo: any, index: number) => (
82+
<Tag
83+
key={index}
84+
fillColor="var(--orange-secondary-color)"
85+
borderColor="var(--orange-secondary-text)"
86+
textColor="black"
87+
>
88+
{geo.name}
89+
</Tag>
90+
))}
91+
</div>
9192
</div>
92-
</div>
93+
)}
94+
{data.useCase.sdgs && data.useCase.sdgs.length > 0 && (
95+
<div className="mt-6 lg:mt-10">
96+
<Text variant="headingXl">SDG Goals</Text>
97+
<div className="mt-4 flex flex-wrap gap-2">
98+
{data.useCase.sdgs.map((sdg: any, index: number) => (
99+
<Tag
100+
key={index}
101+
fillColor="var(--blue-secondary-color)"
102+
borderColor="var(--blue-secondary-text)"
103+
textColor="black"
104+
>
105+
{sdg.code} - {sdg.name}
106+
</Tag>
107+
))}
108+
</div>
109+
</div>
110+
)}
93111
<div className="mt-6 lg:mt-10">
94112
<Text variant="headingXl">Summary</Text>
95113
<div className="prose-h1:text-3xl prose-h2:text-2xl prose-h3:text-xl prose-p:leading-relaxed prose-a:text-blue-600 hover:prose-a:text-blue-700 prose-code:bg-gray-200 prose-code:rounded prose-pre:bg-gray-100 prose-pre:border prose-pre:border-gray-300 prose-blockquote:border-l-blue-500 prose-th:bg-gray-100 prose-img:rounded-lg prose prose-lg mt-4 max-w-none prose-headings:text-gray-900 prose-p:text-gray-800 prose-a:underline prose-blockquote:text-gray-700 prose-strong:text-gray-900 prose-em:text-gray-800 prose-code:px-1 prose-code:py-0.5 prose-code:text-gray-900 prose-code:before:content-none prose-code:after:content-none prose-pre:text-gray-900 prose-ol:text-gray-800 prose-ul:text-gray-800 prose-li:text-gray-800 prose-li:marker:text-gray-600 prose-table:text-gray-800 prose-thead:border-gray-300 prose-tr:border-gray-300 prose-th:border-gray-300 prose-th:text-gray-900 prose-td:border-gray-300 prose-td:text-gray-800 prose-hr:border-gray-300">

0 commit comments

Comments
 (0)