Skip to content

Commit fd367e4

Browse files
fix keralcare data
1 parent b402435 commit fd367e4

7 files changed

Lines changed: 1934 additions & 26510 deletions

File tree

components/filters/deployment-filters.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,12 @@ export function DeploymentFilters({
108108
/>
109109
</div>
110110

111-
<div className="max-h-[300px] space-y-1 overflow-y-auto pr-1">
111+
<div className="max-h-[300px] space-y-1 overflow-auto pr-1">
112112
{filteredDeployments.map((deployment) => (
113113
<button
114114
key={deployment.id}
115115
onClick={() => onDeploymentClick?.(deployment)}
116-
className={`w-full rounded-md border p-3 text-left transition-colors hover:bg-accent hover:border-primary ${
116+
className={`w-full overflow-hidden rounded-md border p-3 text-left transition-colors hover:bg-accent hover:border-primary ${
117117
highlightedDeploymentId === deployment.id
118118
? "bg-accent border-primary ring-2 ring-primary"
119119
: ""

components/map/deployment-map.tsx

Lines changed: 86 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type { Deployment } from "@/types/deployment";
1010
import maplibregl from "maplibre-gl";
1111
import { useTheme } from "next-themes";
1212
import { useEffect, useRef, useState } from "react";
13-
import Supercluster from "supercluster";
13+
import Supercluster, { ClusterFeature, PointFeature } from "supercluster";
1414

1515
interface DeploymentMapProps {
1616
deployments: Deployment[];
@@ -301,17 +301,44 @@ export function DeploymentMap({
301301
markers.current = [];
302302
markerElementsMap.current.clear();
303303

304-
// Create supercluster index only if clustering is enabled
305-
const cluster = enableClustering
306-
? new Supercluster<Deployment>({
307-
radius: 60,
308-
maxZoom: 16,
309-
minPoints: 2,
310-
})
304+
// Group deployments by program for separate clustering
305+
const deploymentsByProgram = deployments.reduce((acc, deployment) => {
306+
if (!acc[deployment.program]) {
307+
acc[deployment.program] = [];
308+
}
309+
acc[deployment.program].push(deployment);
310+
return acc;
311+
}, {} as Record<string, Deployment[]>);
312+
313+
// Create separate supercluster instances for each program
314+
const clustersByProgram = enableClustering
315+
? Object.entries(deploymentsByProgram).reduce((acc, [program, deps]) => {
316+
const cluster = new Supercluster<Deployment>({
317+
radius: 60,
318+
maxZoom: 16,
319+
minPoints: 2,
320+
});
321+
322+
const features = deps.map((deployment) => ({
323+
type: "Feature" as const,
324+
geometry: {
325+
type: "Point" as const,
326+
coordinates: [
327+
deployment.location.longitude,
328+
deployment.location.latitude,
329+
],
330+
},
331+
properties: deployment,
332+
}));
333+
334+
cluster.load(features);
335+
acc[program] = cluster;
336+
return acc;
337+
}, {} as Record<string, Supercluster<Deployment>>)
311338
: null;
312339

313-
// Convert deployments to GeoJSON features
314-
const features = deployments.map((deployment) => ({
340+
// Convert all deployments to GeoJSON features (for non-clustered mode)
341+
const allFeatures = deployments.map((deployment) => ({
315342
type: "Feature" as const,
316343
geometry: {
317344
type: "Point" as const,
@@ -323,29 +350,44 @@ export function DeploymentMap({
323350
properties: deployment,
324351
}));
325352

326-
if (cluster) {
327-
cluster.load(features);
328-
}
329-
330353
const updateMarkers = () => {
331354
if (!map.current) return;
332355

333356
const bounds = map.current.getBounds();
334357
const zoom = map.current.getZoom();
335358

336359
// Get clusters or individual points based on enableClustering
337-
const clusters =
338-
enableClustering && cluster
339-
? cluster.getClusters(
340-
[
341-
bounds.getWest(),
342-
bounds.getSouth(),
343-
bounds.getEast(),
344-
bounds.getNorth(),
345-
],
346-
Math.floor(zoom)
347-
)
348-
: features; // If clustering disabled, show all features
360+
let allClusters: Array<
361+
PointFeature<Deployment> | ClusterFeature<{ program?: string }>
362+
> = [];
363+
364+
if (enableClustering && clustersByProgram) {
365+
// Get clusters from each program separately
366+
Object.entries(clustersByProgram).forEach(([program, cluster]) => {
367+
const programClusters = cluster.getClusters(
368+
[
369+
bounds.getWest(),
370+
bounds.getSouth(),
371+
bounds.getEast(),
372+
bounds.getNorth(),
373+
],
374+
Math.floor(zoom)
375+
);
376+
// Add program info to each cluster for color coding
377+
programClusters.forEach((c) => {
378+
if (c.properties && "cluster" in c.properties) {
379+
(c.properties as { program?: string; cluster: boolean }).program =
380+
program;
381+
}
382+
});
383+
allClusters.push(...programClusters);
384+
});
385+
} else {
386+
// If clustering disabled, show all features
387+
allClusters = allFeatures as Array<
388+
PointFeature<Deployment> | ClusterFeature<{ program?: string }>
389+
>;
390+
}
349391

350392
// Clear existing markers
351393
markers.current.forEach((marker) => marker.remove());
@@ -357,7 +399,7 @@ export function DeploymentMap({
357399
lngLat: [number, number];
358400
}> = [];
359401

360-
clusters.forEach((clusterPoint) => {
402+
allClusters.forEach((clusterPoint) => {
361403
const [lng, lat] = clusterPoint.geometry.coordinates;
362404
const properties = clusterPoint.properties;
363405

@@ -370,25 +412,29 @@ export function DeploymentMap({
370412
const clusterLeaves =
371413
(properties as { point_count?: number }).point_count || 0;
372414
const clusterId = (properties as { cluster_id?: number }).cluster_id;
415+
const program = (properties as { program?: string }).program;
373416

374-
// For simplicity, use first deployment's color
375-
const firstDeployment = deployments[0];
376-
const color = firstDeployment
377-
? PROGRAM_COLORS[firstDeployment.program]
417+
// Use the program's color for the cluster
418+
const color = program
419+
? PROGRAM_COLORS[program as keyof typeof PROGRAM_COLORS]
378420
: "#6B7280";
379421

380422
const el = createClusterMarkerElement(clusterLeaves, color);
381423

382424
el.addEventListener("click", () => {
383-
if (map.current && clusterId && cluster) {
384-
const expansionZoom = Math.min(
385-
cluster.getClusterExpansionZoom?.(clusterId) || zoom + 2,
386-
20
387-
);
388-
map.current.flyTo({
389-
center: [lng, lat],
390-
zoom: expansionZoom,
391-
});
425+
if (map.current && clusterId && program && clustersByProgram) {
426+
const programCluster = clustersByProgram[program];
427+
if (programCluster) {
428+
const expansionZoom = Math.min(
429+
programCluster.getClusterExpansionZoom?.(clusterId) ||
430+
zoom + 2,
431+
20
432+
);
433+
map.current.flyTo({
434+
center: [lng, lat],
435+
zoom: expansionZoom,
436+
});
437+
}
392438
}
393439
if (!introAnimationComplete) {
394440
setIntroAnimationComplete(true);

components/map/map-popup.tsx

Lines changed: 35 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
Card,
66
CardContent,
77
CardDescription,
8+
CardFooter,
89
CardHeader,
910
CardTitle,
1011
} from "@/components/ui/card";
@@ -20,63 +21,51 @@ interface MapPopupProps {
2021

2122
export function MapPopup({ deployment }: MapPopupProps) {
2223
return (
23-
<Card className="w-full max-w-md min-w-60 border-2 py-4 gap-2">
24-
<CardHeader className="pb-0">
25-
<div className="flex items-start justify-between gap-2">
26-
<div className="flex-1">
27-
<CardTitle className="text-lg">
28-
{deployment.name}{" "}
29-
{deployment.website && (
30-
<Button variant="ghost" size="sm" asChild>
31-
<a href={deployment.website} target="_blank">
32-
<ExternalLink />
33-
</a>
34-
</Button>
35-
)}
36-
</CardTitle>
37-
<CardDescription className="mt-1 flex items-center gap-1 text-sm">
38-
<MapPin className="size-3 shrink-0" />
39-
{deployment.location.address
40-
? `${deployment.location.address.city}, ${deployment.location.address.country}`
41-
: `${deployment.location.latitude.toFixed(
42-
4
43-
)}, ${deployment.location.longitude.toFixed(4)}`}
44-
</CardDescription>
45-
</div>
46-
<div className="flex flex-col gap-2">
47-
<Badge
48-
style={{
49-
backgroundColor: PROGRAM_COLORS[deployment.program],
50-
color: "white",
51-
}}
52-
>
53-
{PROGRAM_LABELS[deployment.program]}
54-
</Badge>
55-
<Badge className="capitalize">{deployment.status || "unknown"}</Badge>
56-
</div>
57-
</div>
24+
<Card className="w-full max-w-md">
25+
<CardHeader>
26+
<CardTitle>{deployment.name}</CardTitle>
27+
<CardDescription>
28+
<MapPin className="size-3 shrink-0 inline mr-2" />
29+
{deployment.location.address
30+
? `${deployment.location.address.city}, ${deployment.location.address.state}, ${deployment.location.address.country}`
31+
: `${deployment.location.latitude.toFixed(
32+
4
33+
)}, ${deployment.location.longitude.toFixed(4)}`}
34+
</CardDescription>
5835
</CardHeader>
5936
<CardContent className="space-y-1">
6037
<p className="text-sm text-muted-foreground line-clamp-3 md:line-clamp-none">
6138
{deployment.description}
6239
</p>
63-
64-
<div className="flex flex-wrap gap-3 text-sm">
40+
</CardContent>
41+
<CardFooter className="sm:flex-row flex-col gap-2">
42+
<div className="flex gap-2">
6543
{deployment.dateDeployed && (
66-
<div className="flex items-center gap-1.5 text-muted-foreground">
67-
<Calendar className="h-4 w-4" />
44+
<div className="flex items-center gap-1.5 text-xs text-muted-foreground">
45+
<Calendar className="size-3 shrink-0" />
6846
<span>{formatDate(deployment.dateDeployed)}</span>
6947
</div>
7048
)}
71-
72-
{deployment.organization && (
73-
<div className="text-muted-foreground">
74-
<span className="font-medium">Org:</span>{" "}
75-
{deployment.organization}
76-
</div>
49+
{deployment.website && (
50+
<Button variant="ghost" size="sm" asChild>
51+
<a href={deployment.website} target="_blank">
52+
<ExternalLink />
53+
</a>
54+
</Button>
7755
)}
7856
</div>
79-
</CardContent>
57+
<div className="flex gap-2">
58+
<Badge
59+
style={{
60+
backgroundColor: PROGRAM_COLORS[deployment.program],
61+
color: "white",
62+
}}
63+
>
64+
{PROGRAM_LABELS[deployment.program]}
65+
</Badge>
66+
<Badge className="capitalize">{deployment.status || "unknown"}</Badge>
67+
</div>
68+
</CardFooter>
8069
</Card>
8170
);
8271
}

0 commit comments

Comments
 (0)