|
| 1 | +'use client'; |
| 2 | + |
| 3 | +import BreadCrumbs from '@/components/BreadCrumbs'; |
| 4 | +import { Icons } from '@/components/icons'; |
| 5 | +import JsonLd from '@/components/JsonLd'; |
| 6 | +import { Loading } from '@/components/loading'; |
| 7 | +import { graphql } from '@/gql'; |
| 8 | +import { TypeCollaborative } from '@/gql/generated/graphql'; |
| 9 | +import { GraphQLPublic } from '@/lib/api'; |
| 10 | +import { formatDate, generateJsonLd } from '@/lib/utils'; |
| 11 | +import { useQuery } from '@tanstack/react-query'; |
| 12 | +import Image from 'next/image'; |
| 13 | +import { Button, Card, Icon, Text } from 'opub-ui'; |
| 14 | +import { useState } from 'react'; |
| 15 | + |
| 16 | +const PublishedCollaboratives = graphql(` |
| 17 | + query PublishedCollaboratives { |
| 18 | + publishedCollaboratives { |
| 19 | + id |
| 20 | + title |
| 21 | + summary |
| 22 | + slug |
| 23 | + created |
| 24 | + startedOn |
| 25 | + completedOn |
| 26 | + status |
| 27 | + isIndividualCollaborative |
| 28 | + user { |
| 29 | + fullName |
| 30 | + id |
| 31 | + profilePicture { |
| 32 | + url |
| 33 | + } |
| 34 | + } |
| 35 | + organization { |
| 36 | + name |
| 37 | + slug |
| 38 | + id |
| 39 | + logo { |
| 40 | + url |
| 41 | + } |
| 42 | + } |
| 43 | + logo { |
| 44 | + name |
| 45 | + path |
| 46 | + } |
| 47 | + tags { |
| 48 | + id |
| 49 | + value |
| 50 | + } |
| 51 | + sectors { |
| 52 | + id |
| 53 | + name |
| 54 | + } |
| 55 | + sdgs { |
| 56 | + id |
| 57 | + code |
| 58 | + name |
| 59 | + } |
| 60 | + datasetCount |
| 61 | + metadata { |
| 62 | + metadataItem { |
| 63 | + id |
| 64 | + label |
| 65 | + dataType |
| 66 | + } |
| 67 | + id |
| 68 | + value |
| 69 | + } |
| 70 | + } |
| 71 | + } |
| 72 | +`); |
| 73 | + |
| 74 | +const CollaborativesListingClient = () => { |
| 75 | + const [searchTerm, setSearchTerm] = useState(''); |
| 76 | + const [selectedSector, setSelectedSector] = useState(''); |
| 77 | + |
| 78 | + const { |
| 79 | + data: collaborativesData, |
| 80 | + isLoading, |
| 81 | + error, |
| 82 | + } = useQuery<{ publishedCollaboratives: TypeCollaborative[] }>( |
| 83 | + ['fetch_published_collaboratives'], |
| 84 | + async () => { |
| 85 | + console.log('Fetching collaboratives...'); |
| 86 | + try { |
| 87 | + // @ts-expect-error - Query has no variables |
| 88 | + const result = await GraphQLPublic( |
| 89 | + PublishedCollaboratives as any, |
| 90 | + {} |
| 91 | + ); |
| 92 | + console.log('Collaboratives result:', result); |
| 93 | + return result as { publishedCollaboratives: TypeCollaborative[] }; |
| 94 | + } catch (err) { |
| 95 | + console.error('Error fetching collaboratives:', err); |
| 96 | + throw err; |
| 97 | + } |
| 98 | + }, |
| 99 | + { |
| 100 | + refetchOnMount: true, |
| 101 | + refetchOnReconnect: true, |
| 102 | + retry: (failureCount) => { |
| 103 | + return failureCount < 3; |
| 104 | + }, |
| 105 | + } |
| 106 | + ); |
| 107 | + |
| 108 | + const collaboratives = collaborativesData?.publishedCollaboratives || []; |
| 109 | + |
| 110 | + // Filter collaboratives based on search term and sector |
| 111 | + const filteredCollaboratives = collaboratives.filter((collaborative) => { |
| 112 | + const matchesSearch = collaborative.title?.toLowerCase().includes(searchTerm.toLowerCase()) || |
| 113 | + collaborative.summary?.toLowerCase().includes(searchTerm.toLowerCase()); |
| 114 | + const matchesSector = !selectedSector || |
| 115 | + collaborative.sectors?.some(sector => sector.name === selectedSector); |
| 116 | + return matchesSearch && matchesSector; |
| 117 | + }); |
| 118 | + |
| 119 | + // Get unique sectors for filter dropdown |
| 120 | + const allSectors = collaboratives.flatMap((collaborative: TypeCollaborative) => |
| 121 | + collaborative.sectors?.map((sector: any) => sector.name) || [] |
| 122 | + ); |
| 123 | + const uniqueSectors = [...new Set(allSectors)]; |
| 124 | + const jsonLd = generateJsonLd({ |
| 125 | + '@context': 'https://schema.org', |
| 126 | + '@type': 'WebPage', |
| 127 | + name: 'CivicDataLab', |
| 128 | + url: `${process.env.NEXT_PUBLIC_PLATFORM_URL}/collaboratives`, |
| 129 | + description: |
| 130 | + 'Explore collaborative data initiatives and partnerships that bring organizations together to create impactful solutions.', |
| 131 | + }); |
| 132 | + return ( |
| 133 | + <main> |
| 134 | + <JsonLd json={jsonLd} /> |
| 135 | + <BreadCrumbs |
| 136 | + data={[ |
| 137 | + { href: '/', label: 'Home' }, |
| 138 | + { href: '/collaboratives', label: 'Collaboratives' }, |
| 139 | + ]} |
| 140 | + /> |
| 141 | + <> |
| 142 | + <> |
| 143 | + <div className="w-full"> |
| 144 | + <div className=" bg-primaryBlue"> |
| 145 | + <div className=" container flex flex-col-reverse items-center gap-8 py-10 lg:flex-row "> |
| 146 | + <div className="flex flex-col gap-5 lg:w-1/2"> |
| 147 | + <Text |
| 148 | + variant="heading2xl" |
| 149 | + fontWeight="bold" |
| 150 | + color="onBgDefault" |
| 151 | + > |
| 152 | + Our Collaboratives |
| 153 | + </Text> |
| 154 | + <Text |
| 155 | + variant="headingLg" |
| 156 | + color="onBgDefault" |
| 157 | + fontWeight="regular" |
| 158 | + className="leading-3 lg:leading-5" |
| 159 | + > |
| 160 | + By Collaboratives we mean a collective effort by several organisations |
| 161 | + in any specific sectors that can be applied to address some of the |
| 162 | + most pressing concerns from hyper-local to the global level simultaneously. |
| 163 | + </Text> |
| 164 | + </div> |
| 165 | + <div className="flex w-full items-center justify-center lg:w-1/2"> |
| 166 | + <Image |
| 167 | + src={'/collaborative.png'} |
| 168 | + alt={'collaborative'} |
| 169 | + width={1700} |
| 170 | + height={800} |
| 171 | + className="h-auto w-full object-contain" |
| 172 | + /> |
| 173 | + </div> |
| 174 | + </div> |
| 175 | + </div> |
| 176 | + </div> |
| 177 | + </> |
| 178 | + </> |
| 179 | + |
| 180 | + <div className="bg-onSurfaceDefault"> |
| 181 | + <div className="container py-8 lg:py-14"> |
| 182 | + {/* Header Section */} |
| 183 | + <div className="mb-8"> |
| 184 | + |
| 185 | + {/* Search and Filter Section */} |
| 186 | + <div className="flex flex-col gap-4 md:flex-row md:items-center md:gap-6"> |
| 187 | + <div className="flex-1"> |
| 188 | + <input |
| 189 | + type="text" |
| 190 | + placeholder="Search collaboratives..." |
| 191 | + value={searchTerm} |
| 192 | + onChange={(e) => setSearchTerm(e.target.value)} |
| 193 | + className="w-full rounded-lg border border-greyExtralight px-4 py-2 focus:border-primaryBlue focus:outline-none" |
| 194 | + /> |
| 195 | + </div> |
| 196 | + <div className="md:w-48"> |
| 197 | + <select |
| 198 | + value={selectedSector} |
| 199 | + onChange={(e) => setSelectedSector(e.target.value)} |
| 200 | + className="w-full rounded-lg border border-greyExtralight px-4 py-2 focus:border-primaryBlue focus:outline-none" |
| 201 | + > |
| 202 | + <option value="">All Sectors</option> |
| 203 | + {uniqueSectors.map((sector: string) => ( |
| 204 | + <option key={sector} value={sector}> |
| 205 | + {sector} |
| 206 | + </option> |
| 207 | + ))} |
| 208 | + </select> |
| 209 | + </div> |
| 210 | + </div> |
| 211 | + </div> |
| 212 | + |
| 213 | + {isLoading? ( |
| 214 | + <div className="flex justify-center p-10"> |
| 215 | + <Loading /> |
| 216 | + </div> |
| 217 | + ):error?( |
| 218 | + <div className="flex flex-col items-center justify-center gap-4 py-10"> |
| 219 | + <Text variant="headingXl" color="critical"> |
| 220 | + Error Loading Collaboratives |
| 221 | + </Text> |
| 222 | + <Text variant="bodyLg"> |
| 223 | + Failed to load collaboratives. Please try again later. |
| 224 | + </Text> |
| 225 | + </div> |
| 226 | + ):null} |
| 227 | + |
| 228 | + {/* Results Section */} |
| 229 | + {!isLoading && !error && ( |
| 230 | + <> |
| 231 | + <div className="mb-6 flex items-center justify-between"> |
| 232 | + <Text variant="headingLg"> |
| 233 | + {filteredCollaboratives.length} Collaborative{filteredCollaboratives.length !== 1 ? 's' : ''} Found |
| 234 | + </Text> |
| 235 | + </div> |
| 236 | + |
| 237 | + {/* Collaboratives Grid */} |
| 238 | + {filteredCollaboratives.length > 0 ? ( |
| 239 | + <div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3"> |
| 240 | + {filteredCollaboratives.map((collaborative: TypeCollaborative) => ( |
| 241 | + <Card |
| 242 | + key={collaborative.id} |
| 243 | + title={collaborative.title || ''} |
| 244 | + variation="collapsed" |
| 245 | + iconColor="warning" |
| 246 | + metadataContent={[ |
| 247 | + { |
| 248 | + icon: Icons.calendar, |
| 249 | + label: 'Started', |
| 250 | + value: formatDate(collaborative.startedOn), |
| 251 | + }, |
| 252 | + { |
| 253 | + icon: Icons.dataset, |
| 254 | + label: 'Datasets', |
| 255 | + value: collaborative.datasetCount?.toString() || '0', |
| 256 | + }, |
| 257 | + { |
| 258 | + icon: Icons.globe, |
| 259 | + label: 'Geography', |
| 260 | + value: |
| 261 | + collaborative.metadata?.find( |
| 262 | + (meta: any) => |
| 263 | + meta.metadataItem?.label === 'Geography' |
| 264 | + )?.value || 'N/A', |
| 265 | + }, |
| 266 | + ]} |
| 267 | + href={`/collaboratives/${collaborative.slug}`} |
| 268 | + footerContent={[ |
| 269 | + { |
| 270 | + icon: collaborative.sectors?.[0]?.name |
| 271 | + ? `/Sectors/${collaborative.sectors[0].name}.svg` |
| 272 | + : '/Sectors/default.svg', |
| 273 | + label: 'Sectors', |
| 274 | + }, |
| 275 | + { |
| 276 | + icon: collaborative.isIndividualCollaborative |
| 277 | + ? collaborative?.user?.profilePicture |
| 278 | + ? `${process.env.NEXT_PUBLIC_BACKEND_URL}/${collaborative.user.profilePicture.url}` |
| 279 | + : '/profile.png' |
| 280 | + : collaborative?.organization?.logo |
| 281 | + ? `${process.env.NEXT_PUBLIC_BACKEND_URL}/${collaborative.organization.logo.url}` |
| 282 | + : '/org.png', |
| 283 | + label: 'Published by', |
| 284 | + }, |
| 285 | + ]} |
| 286 | + description={collaborative.summary || ''} |
| 287 | + /> |
| 288 | + ))} |
| 289 | + </div> |
| 290 | + ) : ( |
| 291 | + <div className="flex flex-col items-center justify-center gap-4 py-20"> |
| 292 | + <Icon source={Icons.search} size={48} color="subdued" /> |
| 293 | + <Text variant="headingLg" color="subdued"> |
| 294 | + No Collaboratives Found |
| 295 | + </Text> |
| 296 | + <Text variant="bodyLg" color="subdued"> |
| 297 | + Try adjusting your search terms or filters. |
| 298 | + </Text> |
| 299 | + {(searchTerm || selectedSector) && ( |
| 300 | + <Button |
| 301 | + onClick={() => { |
| 302 | + setSearchTerm(''); |
| 303 | + setSelectedSector(''); |
| 304 | + }} |
| 305 | + kind="secondary" |
| 306 | + > |
| 307 | + Clear Filters |
| 308 | + </Button> |
| 309 | + )} |
| 310 | + </div> |
| 311 | + )} |
| 312 | + </> |
| 313 | + )} |
| 314 | + </div> |
| 315 | + </div> |
| 316 | + </main> |
| 317 | + ); |
| 318 | +}; |
| 319 | + |
| 320 | +export default CollaborativesListingClient; |
0 commit comments