diff --git a/app/[locale]/(user)/collaboratives/CollaborativesListingClient.tsx b/app/[locale]/(user)/collaboratives/CollaborativesListingClient.tsx index 6474ac41..4c4cc91f 100644 --- a/app/[locale]/(user)/collaboratives/CollaborativesListingClient.tsx +++ b/app/[locale]/(user)/collaboratives/CollaborativesListingClient.tsx @@ -218,7 +218,7 @@ const CollaborativesListingClient = () => {
{'collaborative'} { const hasPartnerOrganizations = CollaborativeDetailsData?.collaborativeBySlug?.partnerOrganizations && CollaborativeDetailsData?.collaborativeBySlug?.partnerOrganizations?.length > 0; - const hasContributors = - CollaborativeDetailsData?.collaborativeBySlug?.contributors && - CollaborativeDetailsData?.collaborativeBySlug?.contributors?.length > 0; + const jsonLd = generateJsonLd({ '@context': 'https://schema.org', @@ -321,7 +331,7 @@ const CollaborativeDetailClient = () => { {hasSupportingOrganizations && (
- Supported by + Supporters
{CollaborativeDetailsData?.collaborativeBySlug?.supportingOrganizations?.map( @@ -348,7 +358,7 @@ const CollaborativeDetailClient = () => { {hasPartnerOrganizations && (
- Partnered by + Partners
{CollaborativeDetailsData?.collaborativeBySlug?.partnerOrganizations?.map( @@ -396,9 +406,9 @@ const CollaborativeDetailClient = () => { ? `${process.env.NEXT_PUBLIC_BACKEND_URL}/${useCase.organization.logo.url}` : '/org.png'; - const Geography = useCase.metadata?.find( - (meta: any) => meta.metadataItem?.label === 'Geography' - )?.value; + const Geography = useCase.geographies && useCase.geographies.length > 0 + ? useCase.geographies.map((geo: any) => geo.name).join(', ') + : null; const MetadataContent = [ { @@ -492,10 +502,9 @@ const CollaborativeDetailClient = () => { icon: Icons.globe, label: 'Geography', value: - dataset.metadata?.find( - (meta: any) => - meta.metadataItem?.label === 'Geography' - )?.value || '', + dataset.geographies && dataset.geographies.length > 0 + ? dataset.geographies.map((geo: any) => geo.name).join(', ') + : '', }, ]} href={`/datasets/${dataset.id}`} diff --git a/app/[locale]/(user)/collaboratives/components/Details.tsx b/app/[locale]/(user)/collaboratives/components/Details.tsx index 79bd59c4..67c3d045 100644 --- a/app/[locale]/(user)/collaboratives/components/Details.tsx +++ b/app/[locale]/(user)/collaboratives/components/Details.tsx @@ -1,8 +1,8 @@ 'use client'; -import React, { useState } from 'react'; import Image from 'next/image'; import { Button, Icon, Spinner, Tag, Text, Tray } from 'opub-ui'; +import { useState } from 'react'; import ReactMarkdown from 'react-markdown'; import rehypeRaw from 'rehype-raw'; import remarkGfm from 'remark-gfm'; @@ -142,9 +142,6 @@ const PrimaryDetails = ({ data, isLoading }: { data: any; isLoading: any }) => {
)}
- - Summary -
{ { icon: Icons.calendar, label: 'Date', - value: '19 July 2024', + value: new Date(item.modified).toLocaleDateString('en-US', { + day: 'numeric', + month: 'long', + year: 'numeric', + }), }, { icon: Icons.download, @@ -112,7 +116,9 @@ const Datasets = () => { { icon: Icons.globe, label: 'Geography', - value: 'India', + value: item.geographies?.length > 0 + ? item.geographies.join(', ') + : 'Not specified', }, ]} tag={item.tags} diff --git a/app/[locale]/(user)/components/ListingComponent.tsx b/app/[locale]/(user)/components/ListingComponent.tsx index 0d191c38..152a7875 100644 --- a/app/[locale]/(user)/components/ListingComponent.tsx +++ b/app/[locale]/(user)/components/ListingComponent.tsx @@ -528,9 +528,14 @@ const ListingComponent: React.FC = ({ : item?.organization?.logo ? `${process.env.NEXT_PUBLIC_BACKEND_URL}/${item.organization.logo}` : '/org.png'; - const Geography = item.metadata.filter( - (item: any) => item.metadata_item.label === 'Geography' - )[0]?.value; + + const geographies = item.geographies && item.geographies.length > 0 + ? item.geographies + : null; + + const sdgs = item.sdgs && item.sdgs.length > 0 + ? item.sdgs + : null; const MetadataContent = [ { @@ -550,12 +555,27 @@ const ListingComponent: React.FC = ({ }); } - if (Geography) { + if (geographies && geographies.length > 0) { + // Format geographies hierarchically for display + const geoDisplay = geographies.join(', '); + MetadataContent.push({ icon: Icons.globe, label: 'Geography', - value: Geography, - tooltip: 'Geography', + value: geoDisplay, + tooltip: geoDisplay, + }); + } + + if (sdgs && sdgs.length > 0) { + // Format SDGs for display + const sdgDisplay = sdgs.map((sdg: any) => `${sdg.code} - ${sdg.name}`).join(', '); + + MetadataContent.push({ + icon: Icons.target, + label: 'SDG Goals', + value: sdgDisplay, + tooltip: sdgDisplay, }); } diff --git a/app/[locale]/(user)/components/UseCases.tsx b/app/[locale]/(user)/components/UseCases.tsx index f8b738bf..f748671c 100644 --- a/app/[locale]/(user)/components/UseCases.tsx +++ b/app/[locale]/(user)/components/UseCases.tsx @@ -47,6 +47,11 @@ const useCasesListDoc: any = graphql(` logo { path } + geographies { + id + name + code + } metadata { metadataItem { id @@ -150,10 +155,9 @@ const UseCasesListingPage = () => { { icon: Icons.globe, label: 'Geography', - value: item.metadata?.find( - (meta: any) => - meta.metadataItem?.label === 'Geography' - )?.value, + value: item.geographies?.length > 0 + ? item.geographies.map((geo: any) => geo.name).join(', ') + : 'Not specified', }, ]} footerContent={[ diff --git a/app/[locale]/(user)/datasets/[datasetIdentifier]/DatasetDetailsPage.tsx b/app/[locale]/(user)/datasets/[datasetIdentifier]/DatasetDetailsPage.tsx index 6e4ff8d4..f89c5ef6 100644 --- a/app/[locale]/(user)/datasets/[datasetIdentifier]/DatasetDetailsPage.tsx +++ b/app/[locale]/(user)/datasets/[datasetIdentifier]/DatasetDetailsPage.tsx @@ -1,14 +1,13 @@ 'use client'; -import { useEffect, useState } from 'react'; import { graphql } from '@/gql'; import { useQuery } from '@tanstack/react-query'; import { Spinner } from 'opub-ui'; -import { GraphQL } from '@/lib/api'; -import { generateJsonLd } from '@/lib/utils'; import BreadCrumbs from '@/components/BreadCrumbs'; import JsonLd from '@/components/JsonLd'; +import { GraphQL } from '@/lib/api'; +import { generateJsonLd } from '@/lib/utils'; import Details from './components/Details'; import Metadata from './components/Metadata'; import PrimaryData from './components/PrimaryData'; @@ -64,6 +63,17 @@ const datasetQuery: any = graphql(` sectors { name } + geographies { + id + name + code + type + parentId { + id + name + type + } + } formats } } diff --git a/app/[locale]/(user)/datasets/[datasetIdentifier]/components/Metadata/index.tsx b/app/[locale]/(user)/datasets/[datasetIdentifier]/components/Metadata/index.tsx index fbeec857..225213da 100644 --- a/app/[locale]/(user)/datasets/[datasetIdentifier]/components/Metadata/index.tsx +++ b/app/[locale]/(user)/datasets/[datasetIdentifier]/components/Metadata/index.tsx @@ -1,10 +1,10 @@ -import React, { useEffect, useState } from 'react'; import Image from 'next/image'; import Link from 'next/link'; import { Button, Divider, Icon, Tag, Text, Tooltip } from 'opub-ui'; +import React, { useEffect, useState } from 'react'; -import { cn, formatDate, getWebsiteTitle } from '@/lib/utils'; import { Icons } from '@/components/icons'; +import { cn, formatDate, getWebsiteTitle } from '@/lib/utils'; import Styles from '../../../dataset.module.scss'; interface MetadataProps { @@ -13,14 +13,14 @@ interface MetadataProps { } const MetadataComponent: React.FC = ({ data, setOpen }) => { - const Metadata = data.metadata.map((item: any) => ({ + const Metadata = (data.metadata || []).map((item: any) => ({ label: item.metadataItem.label, value: item.value, type: item.metadataItem.dataType, })); - const [isexpanded, setIsexpanded] = useState(false); - const toggleDescription = () => setIsexpanded(!isexpanded); + // const [isexpanded, setIsexpanded] = useState(false); + // const toggleDescription = () => setIsexpanded(!isexpanded); const licenseOptions = [ { @@ -55,7 +55,7 @@ const MetadataComponent: React.FC = ({ data, setOpen }) => { useEffect(() => { const fetchTitle = async () => { try { - const urlItem = data.metadata.find( + const urlItem = (data.metadata || []).find( (item: any) => item.metadataItem?.dataType === 'URL' ); @@ -177,6 +177,25 @@ const MetadataComponent: React.FC = ({ data, setOpen }) => { )}
+ {data.geographies && data.geographies.length > 0 && ( +
+ + Geography + +
+ {data.geographies.map((geo: any, index: number) => ( + + {geo.name} + + ))} +
+
+ )} {Metadata.map((item: any, index: any) => (
{ { icon: Icons.globe, label: 'Geography', - value: 'India', + value: item.geographies.join(', '), }, ]} tag={item.tags} diff --git a/app/[locale]/(user)/datasets/components/FIlter/Filter.tsx b/app/[locale]/(user)/datasets/components/FIlter/Filter.tsx index 273b9d6e..84a390e9 100644 --- a/app/[locale]/(user)/datasets/components/FIlter/Filter.tsx +++ b/app/[locale]/(user)/datasets/components/FIlter/Filter.tsx @@ -12,6 +12,7 @@ import { import { toTitleCase } from '@/lib/utils'; import { Icons } from '@/components/icons'; +import GeographyFilter from './GeographyFilter'; interface FilterProps { setOpen?: (isOpen: boolean) => void; @@ -60,42 +61,54 @@ const Filter: React.FC = ({ )}
- {Object.entries(options).map(([category, data], index) => ( -
- - + setSelectedOptions('geographies', geographies) + } + geographyOptions={options['geographies'] || []} + /> + + {/* Other Filters */} + {Object.entries(options) + .filter(([category]) => category !== 'geographies') + .map(([category, data], index) => ( +
+ - - {toTitleCase(category)} - - - { - setSelectedOptions(category, values as string[]); + + {toTitleCase(category)} + + - - - -
- ))} + > + { + setSelectedOptions(category, values as string[]); + }} + /> + +
+
+
+ ))}
); diff --git a/app/[locale]/(user)/datasets/components/FIlter/GeographyFilter.tsx b/app/[locale]/(user)/datasets/components/FIlter/GeographyFilter.tsx new file mode 100644 index 00000000..841569ba --- /dev/null +++ b/app/[locale]/(user)/datasets/components/FIlter/GeographyFilter.tsx @@ -0,0 +1,196 @@ +import React, { useEffect, useState } from 'react'; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, + Text, +} from 'opub-ui'; + +import { toTitleCase } from '@/lib/utils'; +import { TreeView } from '@/components/ui/tree-view'; + +interface Geography { + id: number; + name: string; + code: string; + type: string; + parentId: { + id: number; + name: string; + type: string; + } | null; +} + +interface GeographyNode extends Geography { + children: GeographyNode[]; +} + +interface GeographyFilterProps { + selectedGeographies: string[]; + onGeographyChange: (geographies: string[]) => void; + geographyOptions?: { label: string; value: string }[]; +} + +const GeographyFilter: React.FC = ({ + selectedGeographies, + onGeographyChange, + geographyOptions = [], +}) => { + const [geographies, setGeographies] = useState([]); + const [loading, setLoading] = useState(true); + const [expandedItems, setExpandedItems] = useState([]); + + useEffect(() => { + const fetchGeographies = async () => { + setLoading(true); + try { + // Always try to fetch from GraphQL for full hierarchy + const response = await fetch( + `${process.env.NEXT_PUBLIC_BACKEND_URL}/api/graphql`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + query: ` + query { + geographies { + id + name + code + type + parentId { + id + name + type + } + } + } + `, + }), + } + ); + + const { data } = await response.json(); + + if (data && data.geographies && data.geographies.length > 0) { + const hierarchicalData = buildHierarchy(data.geographies); + setGeographies(hierarchicalData); + } else if (geographyOptions && geographyOptions.length > 0) { + // Fallback to aggregations if GraphQL fails + const flatGeographies: GeographyNode[] = geographyOptions.map((opt, idx) => ({ + id: idx, + name: opt.label, + code: '', + type: '', + parentId: null, + children: [], + })); + setGeographies(flatGeographies); + } + } catch (error) { + console.error('Error fetching geographies:', error); + // Use aggregations as fallback on error + if (geographyOptions && geographyOptions.length > 0) { + const flatGeographies: GeographyNode[] = geographyOptions.map((opt, idx) => ({ + id: idx, + name: opt.label, + code: '', + type: '', + parentId: null, + children: [], + })); + setGeographies(flatGeographies); + } + } finally { + setLoading(false); + } + }; + + fetchGeographies(); + }, []); // Only run once on mount + + const buildHierarchy = (flatList: Geography[]): GeographyNode[] => { + const map = new Map(); + const roots: GeographyNode[] = []; + + // Initialize all nodes + flatList.forEach((geo) => { + map.set(geo.id, { ...geo, children: [] }); + }); + + // Build hierarchy + flatList.forEach((geo) => { + const node = map.get(geo.id)!; + if (geo.parentId && geo.parentId.id) { + const parent = map.get(geo.parentId.id); + if (parent) { + parent.children.push(node); + } else { + roots.push(node); + } + } else { + roots.push(node); + } + }); + + return roots; + }; + + // Convert GeographyNode to TreeView format + const convertToTreeData = (nodes: GeographyNode[]): any[] => { + return nodes.map((node) => ({ + id: node.name, + name: node.name, + children: node.children.length > 0 ? convertToTreeData(node.children) : undefined, + })); + }; + + const treeData = convertToTreeData(geographies); + + if (loading) { + return ( +
+ + Loading geographies... + +
+ ); + } + + if (geographies.length === 0) { + return null; + } + + return ( + + + + {toTitleCase('geographies')} + + +
+ +
+
+
+
+ ); +}; + +export default GeographyFilter; diff --git a/app/[locale]/(user)/usecases/[useCaseSlug]/UsecaseDetailsClient.tsx b/app/[locale]/(user)/usecases/[useCaseSlug]/UsecaseDetailsClient.tsx index ff52b638..56f2692d 100644 --- a/app/[locale]/(user)/usecases/[useCaseSlug]/UsecaseDetailsClient.tsx +++ b/app/[locale]/(user)/usecases/[useCaseSlug]/UsecaseDetailsClient.tsx @@ -60,6 +60,22 @@ const UseCasedetails = graphql(` id name } + geographies { + id + name + code + type + parentId { + id + name + type + } + } + sdgs { + id + code + name + } runningStatus tags { id @@ -108,6 +124,12 @@ const UseCasedetails = graphql(` sectors { name } + geographies { + id + name + code + type + } modified } contactEmail @@ -283,10 +305,9 @@ const UseCaseDetailClient = () => { icon: Icons.globe, label: 'Geography', value: - dataset.metadata?.find( - (meta: any) => - meta.metadataItem?.label === 'Geography' - )?.value || '', + dataset.geographies && dataset.geographies.length > 0 + ? dataset.geographies.map((geo: any) => geo.name).join(', ') + : '', }, ]} href={`/datasets/${dataset.id}`} diff --git a/app/[locale]/(user)/usecases/components/Details.tsx b/app/[locale]/(user)/usecases/components/Details.tsx index 31a55452..602c69d7 100644 --- a/app/[locale]/(user)/usecases/components/Details.tsx +++ b/app/[locale]/(user)/usecases/components/Details.tsx @@ -74,24 +74,24 @@ const PrimaryDetails = ({ data, isLoading }: { data: any; isLoading: any }) => { />
-
- Geographies -
- - { - data.useCase.metadata?.find( - (meta: any) => meta.metadataItem?.label === 'Geography' - )?.value - } - + {data.useCase.geographies && data.useCase.geographies.length > 0 && ( +
+ Geographies +
+ {data.useCase.geographies.map((geo: any, index: number) => ( + + {geo.name} + + ))} +
-
+ )}
- Summary
{ defaultVal['sdgs'] = data?.sdgs?.map((sdg: any) => { + const num = String(sdg.number || 0).padStart(2, '0'); return { - label: `${sdg.code} - ${sdg.name}`, + label: `${num}. ${sdg.name}`, value: sdg.id, }; }) || []; @@ -433,10 +436,13 @@ const Metadata = () => { label="SDG Goals *" name="sdgs" list={ - getSDGsList?.data.sdgs?.map((item: any) => ({ - label: `${item.code} - ${item.name}`, - value: item.id, - })) || [] + getSDGsList?.data?.sdgs?.map((item: any) => { + const num = String(item.number || 0).padStart(2, '0'); + return { + label: `${num}. ${item.name}`, + value: item.id, + }; + }) || [] } selectedValue={formData.sdgs} onChange={(value) => { diff --git a/app/[locale]/dashboard/[entityType]/[entitySlug]/dataset/[id]/edit/components/EditMetadata.tsx b/app/[locale]/dashboard/[entityType]/[entitySlug]/dataset/[id]/edit/components/EditMetadata.tsx index 60f756d7..0abdf21b 100644 --- a/app/[locale]/dashboard/[entityType]/[entitySlug]/dataset/[id]/edit/components/EditMetadata.tsx +++ b/app/[locale]/dashboard/[entityType]/[entitySlug]/dataset/[id]/edit/components/EditMetadata.tsx @@ -24,7 +24,6 @@ import { } from 'opub-ui'; import { GraphQL } from '@/lib/api'; -import { Loading } from '@/components/loading'; import DatasetLoading from '../../../components/loading-dataset'; import { useDatasetEditStatus } from '../context'; @@ -46,6 +45,21 @@ const tagsListQueryDoc: any = graphql(` } `); +const geographiesListQueryDoc: any = graphql(` + query GeographiesList { + geographies { + id + name + code + type + parentId { + id + name + } + } + } +`); + const datasetMetadataQueryDoc: any = graphql(` query MetadataValues($filters: DatasetFilter) { datasets(filters: $filters) { @@ -60,6 +74,12 @@ const datasetMetadataQueryDoc: any = graphql(` id name } + geographies { + id + name + code + type + } license metadata { metadataItem { @@ -116,6 +136,12 @@ const updateMetadataMutationDoc: any = graphql(` id name } + geographies { + id + name + code + type + } license accessType metadata { @@ -188,6 +214,17 @@ export function EditMetadata({ id }: { id: string }) { ) ); + const getGeographiesList: { data: any; isLoading: boolean; error: any } = + useQuery([`geographies_list_query`], () => + GraphQL( + geographiesListQueryDoc, + { + [params.entityType]: params.entitySlug, + }, + [] + ) + ); + const getMetaDataListQuery: { data: any; isLoading: boolean; @@ -250,13 +287,24 @@ export function EditMetadata({ id }: { id: string }) { } ); - const defaultValuesPrepFn = (dataset: TypeDataset) => { + const defaultValuesPrepFn = (dataset?: TypeDataset) => { let defaultVal: { [key: string]: any; } = {}; - dataset?.metadata.length > 0 && - dataset?.metadata?.map((field) => { + if (!dataset) { + return { + description: '', + sectors: [], + license: null, + tags: [], + geographies: [], + isPublic: true, + }; + } + + (dataset?.metadata || []).length > 0 && + (dataset?.metadata || []).map((field) => { if ( field.metadataItem.dataType === 'MULTISELECT' && field.value !== '' @@ -294,13 +342,21 @@ export function EditMetadata({ id }: { id: string }) { }; }) || []; + defaultVal['geographies'] = + dataset?.geographies?.map((geo: any) => { + return { + label: geo.name, + value: geo.id, + }; + }) || []; + defaultVal['isPublic'] = true; return defaultVal; }; const [formData, setFormData] = useState( - defaultValuesPrepFn(getDatasetMetadata?.data?.datasets[0]) + defaultValuesPrepFn(getDatasetMetadata?.data?.datasets?.[0] || {} as TypeDataset) ); const [previousFormData, setPreviousFormData] = useState(formData); @@ -372,6 +428,7 @@ export function EditMetadata({ id }: { id: string }) { 'sectors', 'description', 'tags', + 'geographies', 'isPublic', 'license', ].includes(key) && transformedValues[key] !== '' @@ -393,6 +450,9 @@ export function EditMetadata({ id }: { id: string }) { ...(changedFields.sectors && { sectors: changedFields.sectors.map((item: any) => item.value), }), + ...(changedFields.geographies && { + geographies: changedFields.geographies.map((item: any) => parseInt(item.value, 10)), + }), }, }); }; @@ -530,6 +590,7 @@ export function EditMetadata({ id }: { id: string }) { <> {!getTagsList?.isLoading && !getSectorsList?.isLoading && + !getGeographiesList?.isLoading && !getDatasetMetadata.isLoading ? (
+ ({ + label: `${item.name}${item.parentId ? ` (${item.parentId.name})` : ''}`, + value: item.id, + })) || [] + } + selectedValue={formData.geographies} + onChange={(value) => { + handleChange('geographies', value); + handleSave({ ...formData, geographies: value }); + }} + />
{getMetaDataListQuery?.data?.metadata diff --git a/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/metadata/page.tsx b/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/metadata/page.tsx index 7c0bfc0f..705ecf84 100644 --- a/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/metadata/page.tsx +++ b/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/metadata/page.tsx @@ -1,18 +1,17 @@ 'use client'; -import { useEffect, useState } from 'react'; -import { useParams } from 'next/navigation'; import { graphql } from '@/gql'; import { MetadataModels, TypeMetadata, TypeSector, TypeTag, - TypeUseCase, UpdateUseCaseMetadataInput, } from '@/gql/generated/graphql'; import { useMutation, useQuery } from '@tanstack/react-query'; +import { useParams } from 'next/navigation'; import { Combobox, Spinner, toast } from 'opub-ui'; +import { useEffect, useState } from 'react'; import { GraphQL } from '@/lib/api'; import { useEditStatus } from '../../context'; @@ -30,39 +29,62 @@ const FetchUseCasedetails: any = graphql(` id value } + tags { + id + value + } sectors { id name } - tags { + geographies { id - value + name + code + type + } + sdgs { + id + code + name + number } } } `); const UpdateUseCaseMetadataMutation: any = graphql(` - mutation updateUsecase($updateMetadataInput: UpdateUseCaseMetadataInput!) { + mutation addUpdateUsecaseMetadata($updateMetadataInput: UpdateUseCaseMetadataInput!) { addUpdateUsecaseMetadata(updateMetadataInput: $updateMetadataInput) { ... on TypeUseCase { - id - metadata { - metadataItem { - id - label - dataType - } - id - value - } - sectors { + id + metadata { + metadataItem { id - name + label + dataType } - tags { - id - value + id + value + } + tags { + id + value + } + sectors { + id + name + } + geographies { + id + name + code + type + } + sdgs { + id + code + name } } } @@ -95,6 +117,21 @@ const sectorsListQueryDoc: any = graphql(` } `); +const geographiesListQueryDoc: any = graphql(` + query GeographiesList { + geographies { + id + name + code + type + parentId { + id + name + } + } + } +`); + const tagsListQueryDoc: any = graphql(` query TagsList { tags { @@ -104,6 +141,17 @@ const tagsListQueryDoc: any = graphql(` } `); +const sdgsListQueryDoc: any = graphql(` + query SDGList { + sdgs { + id + code + name + number + } + } +`); + const Metadata = () => { const params = useParams<{ entityType: string; @@ -128,7 +176,7 @@ const Metadata = () => { refetchOnReconnect: true, } ); - const { data: metadataFields, isLoading: isMetadataFieldsLoading } = useQuery( + const { data: metadataFields } = useQuery( [`metadata_fields_USECASE_${params.id}`], () => GraphQL( @@ -145,12 +193,21 @@ const Metadata = () => { ) ); - const defaultValuesPrepFn = (data: TypeUseCase) => { + const defaultValuesPrepFn = (data: any) => { let defaultVal: { [key: string]: any; } = {}; - data?.metadata?.map((field) => { + if (!data) { + return { + sectors: [], + geographies: [], + tags: [], + sdgs: [], + }; + } + + data?.metadata?.map((field: any) => { if (field.metadataItem.dataType === 'MULTISELECT' && field.value !== '') { defaultVal[field.metadataItem.id] = field.value .split(', ') @@ -173,6 +230,23 @@ const Metadata = () => { }; }) || []; + defaultVal['geographies'] = + data?.geographies?.map((geo: any) => { + return { + label: geo.name, + value: geo.id, + }; + }) || []; + + defaultVal['sdgs'] = + data?.sdgs?.map((sdg: any) => { + const num = String(sdg.number || 0).padStart(2, '0'); + return { + label: `${num}. ${sdg.name}`, + value: sdg.id, + }; + }) || []; + defaultVal['tags'] = data?.tags?.map((tag: TypeTag) => { return { @@ -185,7 +259,7 @@ const Metadata = () => { }; const [formData, setFormData] = useState( - defaultValuesPrepFn(useCaseData?.data?.useCases[0]) + defaultValuesPrepFn(useCaseData?.data?.useCases?.[0] || {}) ); const [previousFormData, setPreviousFormData] = useState(formData); @@ -208,6 +282,28 @@ const Metadata = () => { ) ); + const getGeographiesList: { data: any; isLoading: boolean; error: any } = + useQuery([`geographies_list_query`], () => + GraphQL( + geographiesListQueryDoc, + { + [params.entityType]: params.entitySlug, + }, + [] + ) + ); + + const getSDGsList: { data: any; isLoading: boolean; error: any } = + useQuery([`sdgs_list_query`], () => + GraphQL( + sdgsListQueryDoc, + { + [params.entityType]: params.entitySlug, + }, + [] + ) + ); + const getTagsList: { data: any; isLoading: boolean; @@ -279,7 +375,7 @@ const Metadata = () => { ...Object.keys(transformedValues) .filter( (valueItem) => - !['sectors', 'tags'].includes(valueItem) && + !['sectors', 'tags', 'geographies', 'sdgs'].includes(valueItem) && transformedValues[valueItem] !== '' ) .map((key) => { @@ -291,6 +387,8 @@ const Metadata = () => { ], sectors: updatedData.sectors?.map((item: any) => item.value) || [], tags: updatedData.tags?.map((item: any) => item.label) || [], + sdgs: updatedData.sdgs?.map((item: any) => item.value) || [], + geographies: updatedData.geographies?.map((item: any) => parseInt(item.value, 10)) || [], }, }); } @@ -299,6 +397,8 @@ const Metadata = () => { if ( getSectorsList.isLoading || getTagsList.isLoading || + getSDGsList.isLoading || + getGeographiesList.isLoading || useCaseData.isLoading ) { return ( @@ -358,6 +458,27 @@ const Metadata = () => {
+
+ { + const num = String(item.number || 0).padStart(2, '0'); + return { + label: `${num}. ${item.name}`, + value: item.id, + }; + }) || [] + } + selectedValue={formData.sdgs} + onChange={(value) => { + handleChange('sdgs', value); + handleSave({ ...formData, sdgs: value }); + }} + /> +
{ handleSave({ ...formData, sectors: value }); }} /> + ({ + label: `${item.name}${item.parentId ? ` (${item.parentId.name})` : ''}`, + value: item.id, + })) || [] + } + selectedValue={formData.geographies} + onChange={(value) => { + handleChange('geographies', value); + handleSave({ ...formData, geographies: value }); + }} + />
diff --git a/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/publish/Details.tsx b/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/publish/Details.tsx index ac096633..95d780a4 100644 --- a/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/publish/Details.tsx +++ b/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/publish/Details.tsx @@ -27,7 +27,7 @@ const Details = ({ data }: { data: any }) => { } else { fetchTitle(); } - }, [data?.useCases[0]?.platformUrl]); + }, [data?.useCases, data?.useCases[0]?.platformUrl]); const PrimaryDetails = [ { label: 'Use Case Name', value: data?.useCases[0]?.title }, @@ -42,6 +42,8 @@ const Details = ({ data }: { data: any }) => { value: data?.useCases[0]?.completedOn, }, { label: 'Sector', value: data?.useCases[0]?.sectors[0]?.name }, + { label: 'Geography', value: data?.useCases[0]?.geographies?.map((geo: any) => geo.name).join(', ') }, + { label: 'SDG Goals', value: data?.useCases[0]?.sdgs?.map((sdg: any) => `${sdg.code} - ${sdg.name}`).join(', ') }, { label: 'Tags', value: data?.useCases[0]?.tags[0]?.value }, ...(data?.useCases[0]?.metadata?.map((meta: any) => ({ label: meta.metadataItem?.label, @@ -71,14 +73,18 @@ const Details = ({ data }: { data: any }) => { Platform URL:
- - - {platformTitle?.trim() ? platformTitle : 'Visit Platform'} - - + {data.useCases[0].platformUrl ? ( + + + {platformTitle?.trim() ? platformTitle : 'Visit Platform'} + + + ) : ( + Not provided + )}
diff --git a/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/publish/page.tsx b/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/publish/page.tsx index 41a36310..dc7aaeef 100644 --- a/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/publish/page.tsx +++ b/app/[locale]/dashboard/[entityType]/[entitySlug]/usecases/edit/[id]/publish/page.tsx @@ -43,6 +43,17 @@ const UseCaseDetails: any = graphql(` id name } + geographies { + id + name + code + type + } + sdgs { + id + code + name + } runningStatus tags { id @@ -142,7 +153,7 @@ const Publish = () => { [params.entityType]: params.entitySlug, }, { useCaseId: params.id }), { - onSuccess: (data: any) => { + onSuccess: () => { toast('UseCase Published Successfully'); router.push( `/dashboard/${params.entityType}/${params.entitySlug}/usecases` @@ -161,7 +172,7 @@ const Publish = () => { error: UseCaseData.data?.useCases[0]?.sectors.length === 0 || UseCaseData.data?.useCases[0]?.summary.length === 0 || - UseCaseData.data?.useCases[0]?.metadata.length === 0 || + UseCaseData.data?.useCases[0]?.sdgs.length === 0 || UseCaseData.data?.useCases[0]?.logo === null ? 'Summary or SDG or Sectors or Logo is missing. Please add to continue.' : '', @@ -194,7 +205,7 @@ const Publish = () => { const hasRequiredMetadata = useCase.sectors.length > 0 && useCase?.summary.length > 0 && - useCase?.metadata.length > 0 && + useCase?.sdgs.length > 0 && useCase?.logo !== null; // No datasets assigned diff --git a/app/[locale]/dashboard/[entityType]/page.tsx b/app/[locale]/dashboard/[entityType]/page.tsx index 54d4e469..c25d6386 100644 --- a/app/[locale]/dashboard/[entityType]/page.tsx +++ b/app/[locale]/dashboard/[entityType]/page.tsx @@ -55,7 +55,7 @@ const Page = () => { const [formData, setFormData] = useState(initialFormData); - const { mutate, isLoading: editMutationLoading } = useMutation( + const { mutate } = useMutation( (input: { input: OrganizationInput }) => GraphQL(organizationCreationMutation, {}, input), { diff --git a/app/[locale]/dashboard/components/GraphqlPagination/footer.tsx b/app/[locale]/dashboard/components/GraphqlPagination/footer.tsx index 9c4b5a0f..86a96714 100644 --- a/app/[locale]/dashboard/components/GraphqlPagination/footer.tsx +++ b/app/[locale]/dashboard/components/GraphqlPagination/footer.tsx @@ -39,13 +39,6 @@ const Footer: React.FC = ({ } }; - const handleJumpToPage = (event: React.ChangeEvent) => { - const pageNumber = parseInt(event.target.value); - if (!isNaN(pageNumber) && pageNumber >= 1 && pageNumber <= totalPages) { - onPageChange(pageNumber); - } - }; - const handlePageSizeChange = (event: any) => { const newSize = parseInt(event as string); if (!isNaN(newSize) && newSize > 0) { diff --git a/app/[locale]/dashboard/components/main-nav.tsx b/app/[locale]/dashboard/components/main-nav.tsx index 5735fd9b..b0a7b139 100644 --- a/app/[locale]/dashboard/components/main-nav.tsx +++ b/app/[locale]/dashboard/components/main-nav.tsx @@ -88,7 +88,7 @@ export function MainNav({ hideSearch = false }) { }; fetchData(); - }, [session, hasFetched]); + }, [session, hasFetched, setUserDetails, setAllEntityDetails]); if (isLoggingOut) { return ; diff --git a/app/[locale]/dashboard/page.tsx b/app/[locale]/dashboard/page.tsx index 530e9e1f..89db6085 100644 --- a/app/[locale]/dashboard/page.tsx +++ b/app/[locale]/dashboard/page.tsx @@ -9,7 +9,7 @@ import { Loading } from '@/components/loading'; import { useDashboardStore } from '@/config/store'; const UserDashboard = () => { - const { userDetails, allEntityDetails } = useDashboardStore(); + const { userDetails } = useDashboardStore(); const list = [ { label: 'My Dashboard', diff --git a/app/api/auth/[...nextauth]/options.ts b/app/api/auth/[...nextauth]/options.ts index 58755cde..4810ca17 100644 --- a/app/api/auth/[...nextauth]/options.ts +++ b/app/api/auth/[...nextauth]/options.ts @@ -4,8 +4,6 @@ import { Account, AuthOptions, Session } from 'next-auth'; import { JWT } from 'next-auth/jwt'; import KeycloakProvider from 'next-auth/providers/keycloak'; -import { encrypt } from '@/lib/encryption'; - // this will refresh an expired access token, when needed async function refreshAccessToken(token: JWT) { const urlObj: Record = { diff --git a/components/SessionGuard.tsx b/components/SessionGuard.tsx index fa9622cd..397b7a16 100644 --- a/components/SessionGuard.tsx +++ b/components/SessionGuard.tsx @@ -16,7 +16,7 @@ export default function SessionGuard({ children }: { children: ReactNode }) { ) { signIn('keycloak'); } - }, [data]); + }, [data, pathname]); return <>{children}; } diff --git a/components/ui/tree-view.tsx b/components/ui/tree-view.tsx new file mode 100644 index 00000000..cc5c6a4f --- /dev/null +++ b/components/ui/tree-view.tsx @@ -0,0 +1,149 @@ +import * as React from "react" +import { ChevronRight } from "lucide-react" +import { cn } from "@/lib/utils" + +interface TreeDataItem { + id: string + name: string + children?: TreeDataItem[] + [key: string]: any +} + +interface TreeViewProps { + data: TreeDataItem[] + selectedItems?: string[] + onSelectChange?: (items: string[]) => void + expandedItems?: string[] + onExpandedChange?: (items: string[]) => void + className?: string +} + +interface TreeItemProps { + item: TreeDataItem + level: number + selectedItems: string[] + expandedItems: string[] + onSelectChange: (items: string[]) => void + onExpandedChange: (items: string[]) => void +} + +const TreeItem: React.FC = ({ + item, + level, + selectedItems, + expandedItems, + onSelectChange, + onExpandedChange, +}) => { + const hasChildren = item.children && item.children.length > 0 + const isExpanded = expandedItems.includes(item.id) + const isSelected = selectedItems.includes(item.id) + + const handleToggle = () => { + if (hasChildren) { + if (isExpanded) { + onExpandedChange(expandedItems.filter((id) => id !== item.id)) + } else { + onExpandedChange([...expandedItems, item.id]) + } + } + } + + const handleSelect = (e: React.ChangeEvent) => { + e.stopPropagation() + if (isSelected) { + onSelectChange(selectedItems.filter((id) => id !== item.id)) + } else { + onSelectChange([...selectedItems, item.id]) + } + } + + return ( +
+
+
+ {hasChildren ? ( + + ) : ( +
+ )} + + e.stopPropagation()} + /> + + + {item.name} + +
+
+ + {hasChildren && isExpanded && ( +
+ {item.children!.map((child) => ( + + ))} +
+ )} +
+ ) +} + +export const TreeView: React.FC = ({ + data, + selectedItems = [], + onSelectChange = () => {}, + expandedItems = [], + onExpandedChange = () => {}, + className, +}) => { + return ( +
+ {data.map((item) => ( + + ))} +
+ ) +} diff --git a/package-lock.json b/package-lock.json index 8997dbfb..1b52d6e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "gtag": "^1.0.1", "holy-loader": "^2.2.10", "jwt-decode": "^4.0.0", + "lucide-react": "^0.544.0", "next": "^14.0.4", "next-auth": "^4.24.7", "next-intl": "^3.4.0", @@ -17154,6 +17155,14 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.544.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.544.0.tgz", + "integrity": "sha512-t5tS44bqd825zAW45UQxpG2CvcC4urOwn2TrwSH8u+MjeE+1NnWl6QqeQ/6NdjMqdOygyiT9p3Ev0p1NJykxjw==", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", diff --git a/package.json b/package.json index 23177209..289dcbb9 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "gtag": "^1.0.1", "holy-loader": "^2.2.10", "jwt-decode": "^4.0.0", + "lucide-react": "^0.544.0", "next": "^14.0.4", "next-auth": "^4.24.7", "next-intl": "^3.4.0", diff --git a/public/collaborative.svg b/public/collaborative.svg new file mode 100644 index 00000000..8e466388 --- /dev/null +++ b/public/collaborative.svg @@ -0,0 +1,9 @@ + + + + + + + + +