Skip to content

Commit 73f7236

Browse files
committed
feat: implement SectorsListing and SectorDetailsClient components with metadata generation
1 parent ecf8e41 commit 73f7236

File tree

4 files changed

+352
-272
lines changed

4 files changed

+352
-272
lines changed
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
'use client';
2+
3+
import { useEffect, useState } from 'react';
4+
import Image from 'next/image';
5+
import Link from 'next/link';
6+
import { graphql } from '@/gql';
7+
import {
8+
Ordering,
9+
SectorOrder,
10+
SectorsListsQuery,
11+
} from '@/gql/generated/graphql';
12+
import { useQuery } from '@tanstack/react-query';
13+
import { Divider, SearchInput, Select, Spinner, Text } from 'opub-ui';
14+
15+
import { GraphQL } from '@/lib/api';
16+
import { cn } from '@/lib/utils';
17+
import BreadCrumbs from '@/components/BreadCrumbs';
18+
import { ErrorPage } from '@/components/error';
19+
import Styles from '../datasets/dataset.module.scss';
20+
21+
const sectorsListQueryDoc: any = graphql(`
22+
query SectorsLists($order: SectorOrder, $filters: SectorFilter) {
23+
activeSectors(order: $order, filters: $filters) {
24+
id
25+
name
26+
description
27+
slug
28+
datasetCount
29+
}
30+
}
31+
`);
32+
33+
const SectorsListing = () => {
34+
const [sort, setSort] = useState<SectorOrder>({ name: Ordering.Asc });
35+
const [searchText, setSearchText] = useState('');
36+
37+
const { data, isLoading, isError, refetch } = useQuery<SectorsListsQuery>(
38+
['sectors_list_page', sort],
39+
() =>
40+
GraphQL(
41+
sectorsListQueryDoc,
42+
{},
43+
{ filters: searchText ? { search: searchText } : {}, order: sort }
44+
) as Promise<SectorsListsQuery>
45+
);
46+
47+
function capitalizeWords(name: any) {
48+
return name
49+
.split('-')
50+
.map((word: any) => word.charAt(0).toUpperCase() + word.slice(1))
51+
.join('+');
52+
}
53+
54+
useEffect(() => {
55+
refetch();
56+
}, [searchText]);
57+
58+
const handleSortChange = (e: string) => {
59+
const [field, direction] = e.split('_');
60+
const formattedSort: SectorOrder = {
61+
[field]: direction.toUpperCase() as Ordering,
62+
};
63+
setSort(formattedSort);
64+
};
65+
66+
return (
67+
<main >
68+
<BreadCrumbs
69+
data={[
70+
{ href: '/', label: 'Home' },
71+
{ href: '#', label: 'Sectors' },
72+
]}
73+
/>
74+
<>
75+
<>
76+
<div className="w-full">
77+
<div className=" bg-primaryBlue">
78+
<div className=" container flex flex-col-reverse items-center gap-8 py-10 lg:flex-row ">
79+
<div className="flex flex-col gap-5 ">
80+
<Text
81+
variant="heading2xl"
82+
fontWeight="bold"
83+
color="onBgDefault"
84+
>
85+
Our Sectors
86+
</Text>
87+
<Text
88+
variant="headingLg"
89+
color="onBgDefault"
90+
fontWeight="regular"
91+
className="leading-3 lg:leading-5"
92+
>
93+
Browse our thematic sectors - from disaster risk reduction
94+
to open budgets - to discover curated datasets and use cases
95+
driving data-informed decisions across fields.
96+
</Text>
97+
</div>
98+
<div className="flex items-center justify-center gap-2 px-3 ">
99+
<Image
100+
src={'/s2.png'}
101+
alt={'s1'}
102+
width={130}
103+
height={100}
104+
className="h-auto w-[80px] sm:w-[100px] md:w-[120px] lg:w-[130px]"
105+
priority
106+
/>
107+
<Image
108+
src={'/s3.png'}
109+
alt={'s1'}
110+
width={230}
111+
height={100}
112+
className="h-auto w-[140px] sm:w-[180px] md:w-[200px] lg:w-[230px]"
113+
priority
114+
/>
115+
<Image
116+
src={'/s1.png'}
117+
alt={'s1'}
118+
width={130}
119+
height={100}
120+
className="h-auto w-[80px] sm:w-[100px] md:w-[120px] lg:w-[130px]"
121+
priority
122+
/>
123+
</div>
124+
</div>
125+
</div>
126+
<div className="container flex flex-col gap-5 pb-20 pt-10 lg:gap-10">
127+
<div>
128+
<Text variant="heading2xl" fontWeight="bold">
129+
Explore Sectors
130+
</Text>
131+
<div className="mt-6 flex w-full flex-col justify-center gap-6">
132+
<div className="flex flex-wrap gap-6 lg:flex-nowrap">
133+
<SearchInput
134+
label={''}
135+
className={cn('w-full', Styles.Search)}
136+
onSubmit={(e) => {
137+
setSearchText(e);
138+
}}
139+
onClear={() => {
140+
setSearchText('');
141+
}}
142+
name={'Start typing to search for any sector'}
143+
/>
144+
<div className="flex items-center gap-2">
145+
<Text
146+
variant="bodyLg"
147+
fontWeight="semibold"
148+
className="whitespace-nowrap text-secondaryOrange"
149+
>
150+
Sort :
151+
</Text>
152+
<Select
153+
label=""
154+
labelInline
155+
name="sort-select"
156+
options={[
157+
{
158+
label: 'Name Asc',
159+
value: 'name_asc',
160+
},
161+
{
162+
label: 'Name Desc',
163+
value: 'name_desc',
164+
},
165+
{
166+
label: 'Dataset Count Asc',
167+
value: 'datasetCount_asc',
168+
},
169+
{
170+
label: 'Dataset Count Desc',
171+
value: 'datasetCount_desc',
172+
},
173+
]}
174+
onChange={(e: any) => {
175+
handleSortChange(e);
176+
}}
177+
/>
178+
</div>
179+
</div>
180+
</div>
181+
</div>
182+
{isLoading ? (
183+
<div className="m-4 flex justify-center">
184+
<Spinner />
185+
</div>
186+
) : data && data?.activeSectors?.length > 0 ? (
187+
<>
188+
<div className="grid w-full grid-cols-1 gap-10 md:grid-cols-2 lg:grid-cols-3">
189+
{data?.activeSectors.map((sectors: any) => (
190+
<Link
191+
href={`/sectors/${sectors.slug}?sectors=${capitalizeWords(sectors.slug)}`}
192+
key={sectors.id}
193+
>
194+
<div className="flex w-full items-center gap-5 rounded-4 bg-surfaceDefault p-7 shadow-card">
195+
<div className="flex gap-4">
196+
<Image
197+
src={`/Sectors/${sectors.name}.svg`}
198+
width={80}
199+
height={80}
200+
alt={'Sectors Logo'}
201+
/>
202+
</div>
203+
<div className="flex w-full flex-col gap-3">
204+
<div className="flex flex-col gap-2">
205+
<Text variant="headingLg" fontWeight="semibold">
206+
{sectors.name}
207+
</Text>
208+
<Divider className=" h-[2px] bg-greyExtralight" />
209+
</div>
210+
<div className="flex gap-1">
211+
<Text
212+
variant="bodyMd"
213+
fontWeight="bold"
214+
className=" text-primaryBlue"
215+
>
216+
{sectors.datasetCount}
217+
</Text>
218+
<Text variant="bodyMd">Datasets</Text>
219+
</div>
220+
</div>
221+
</div>
222+
</Link>
223+
))}
224+
</div>
225+
</>
226+
) : isError ? (
227+
<ErrorPage />
228+
) : (
229+
<></>
230+
)}
231+
</div>
232+
</div>
233+
</>
234+
</>
235+
</main>
236+
);
237+
};
238+
239+
export default SectorsListing;
240+
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
'use client';
2+
3+
import React from 'react';
4+
import { TypeSector } from '@/gql/generated/graphql';
5+
6+
import { ErrorPage } from '@/components/error';
7+
import ListingComponent from '../../components/ListingComponent';
8+
9+
const SectorDetailsClient = ({ sector }: { sector: TypeSector }) => {
10+
if (!sector) return <ErrorPage />;
11+
12+
const breadcrumbData = [
13+
{ href: '/', label: 'Home' },
14+
{ href: '/sectors', label: 'Sectors' },
15+
{ href: '#', label: sector.name },
16+
];
17+
18+
return (
19+
<ListingComponent
20+
type="dataset"
21+
breadcrumbData={breadcrumbData}
22+
categoryName={sector.name}
23+
categoryDescription={sector.description ?? undefined}
24+
categoryImage={`/Sectors/${sector.name}.svg`}
25+
redirectionURL={`/datasets`}
26+
placeholder="Start typing to search for any Dataset"
27+
/>
28+
);
29+
};
30+
31+
export default SectorDetailsClient;
Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
1-
'use client';
2-
31
import React from 'react';
4-
import { fetchDatasets } from '@/fetch';
52
import { graphql } from '@/gql';
6-
import { useQuery } from '@tanstack/react-query';
73

84
import { GraphQL } from '@/lib/api';
9-
import { ErrorPage } from '@/components/error';
10-
import { Loading } from '@/components/loading';
11-
import ListingComponent from '../../components/ListingComponent';
5+
import { generatePageMetadata } from '@/lib/utils';
6+
import SectorDetailsClient from './SectorDetailsClient';
127

138
const sectorQueryDoc = graphql(`
149
query CategoryDetails($filters: SectorFilter) {
@@ -22,36 +17,51 @@ const sectorQueryDoc = graphql(`
2217
}
2318
`);
2419

25-
const SectorDetailsPage = ({ params }: { params: { sectorSlug: string } }) => {
26-
const { data, isLoading, isError } = useQuery(
27-
[`get_category_details_${params.sectorSlug}`],
28-
() => GraphQL(sectorQueryDoc, {}, { filters: { slug: params.sectorSlug } })
20+
export async function generateMetadata({
21+
params,
22+
}: {
23+
params: { sectorSlug: string };
24+
}) {
25+
const data = await GraphQL(
26+
sectorQueryDoc,
27+
{},
28+
{ filters: { slug: params.sectorSlug } }
2929
);
30+
const sector = data?.sectors?.[0];
3031

31-
if (isError) return <ErrorPage />;
32-
if (isLoading) return <Loading />;
33-
34-
const sector = data?.sectors.filter(
35-
(item) => item.slug === params.sectorSlug
36-
)[0];
32+
return generatePageMetadata({
33+
title: `${sector?.name} | Sector Data | CivicDataSpace`,
34+
description:
35+
sector?.description ||
36+
`Explore open data and curated datasets in the ${sector?.name} sector.`,
37+
keywords: [sector?.name, 'CivicDataSpace', 'Open Data', 'Sector Data'],
38+
openGraph: {
39+
type: 'article',
40+
locale: 'en_US',
41+
url: `${process.env.NEXT_PUBLIC_PLATFORM_URL}/sectors/${params.sectorSlug}`,
42+
title: `${sector?.name} | Sector Data | CivicDataSpace`,
43+
description:
44+
sector?.description ||
45+
`Explore open data and curated datasets in the ${sector?.name} sector.`,
46+
siteName: 'CivicDataSpace',
47+
image: `${process.env.NEXT_PUBLIC_PLATFORM_URL}/og.png`,
48+
},
49+
});
50+
}
3751

38-
const breadcrumbData = [
39-
{ href: '/', label: 'Home' },
40-
{ href: '/sectors', label: 'Sectors' },
41-
{ href: '#', label: sector?.name || params.sectorSlug },
42-
];
43-
44-
return (
45-
<ListingComponent
46-
fetchDatasets={fetchDatasets}
47-
breadcrumbData={breadcrumbData}
48-
categoryName={sector?.name}
49-
categoryDescription={sector?.description ?? undefined}
50-
categoryImage={`/Sectors/${sector.name}.svg`}
51-
redirectionURL={`/datasets`}
52-
placeholder="Start typing to search for any Dataset"
53-
/>
52+
const SectorDetailsPage = async ({
53+
params,
54+
}: {
55+
params: { sectorSlug: string };
56+
}) => {
57+
const data = await GraphQL(
58+
sectorQueryDoc,
59+
{},
60+
{ filters: { slug: params.sectorSlug } }
5461
);
62+
const sector = data?.sectors?.[0];
63+
64+
return <SectorDetailsClient sector={sector} />;
5565
};
5666

5767
export default SectorDetailsPage;

0 commit comments

Comments
 (0)