From c2d9d5451dfb1ded245e24e390fa770578f901c0 Mon Sep 17 00:00:00 2001 From: mohanadft Date: Wed, 3 Jun 2026 16:43:25 +0300 Subject: [PATCH 1/2] feat: add Palestine Datacommons page Interactive dataset catalog at /palestine-data with search, category filters, detail panels, and a cross-reference network view. Wraps the explorer component in the site layout with PageHeader. Excluded from sitemap until linked from navigation. --- astro.config.mjs | 1 + src/components/PalestineDatacommons.tsx | 1230 +++++++++++++++++++++++ src/pages/palestine-data.astro | 25 + 3 files changed, 1256 insertions(+) create mode 100644 src/components/PalestineDatacommons.tsx create mode 100644 src/pages/palestine-data.astro diff --git a/astro.config.mjs b/astro.config.mjs index 5d3d1a19..9e24b6a6 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -64,6 +64,7 @@ export default defineConfig({ "/event-details/", "/404/", "/js/web.js/", + "/palestine-data/", ]; return !exclude.some((path) => page.endsWith(path)); }, diff --git a/src/components/PalestineDatacommons.tsx b/src/components/PalestineDatacommons.tsx new file mode 100644 index 00000000..815dd932 --- /dev/null +++ b/src/components/PalestineDatacommons.tsx @@ -0,0 +1,1230 @@ +import { useState, useRef } from "react"; +import type { CSSProperties } from "react"; + +const DATASETS = [ + { + id: 1, + name: "T4P Daily Casualties", + org: "Tech for Palestine", + category: "casualties", + categoryLabel: "Casualties", + formats: ["JSON", "CSV"], + updateFreq: "Daily", + access: "open", + accessLabel: "REST API", + license: "MIT", + licenseType: "open", + score: 5, + records: "941+ daily reports", + timespan: "Oct 2023 — present", + url: "data.techforpalestine.org", + description: "Daily reports on killed, injured, and demographic breakdowns from the Gaza Ministry of Health. Includes named individual lists, journalist deaths, and West Bank daily counts.", + maintainer: "Tech for Palestine open-source community", + size: "941+ daily records, 40k+ named individuals", + accessDetail: "Static CDN API with versioned JSON/CSV endpoints. No auth required. GitHub repo available for historical tracking.", + licenseDetail: "MIT licence on code. Data sourced from Gaza MoH (public domain). Free for all uses including commercial.", + crossRefs: [2, 3, 6, 7], + crossRefNotes: "Links to UNOSAT damage data on the same time axis. Named records can cross-reference B'Tselem casualty lists. Infrastructure sub-dataset connects to HOT baseline.", + integrationEffort: "~1 day", + caveats: "Official counts are a confirmed floor — MoH excludes indirect deaths (malnutrition, medical collapse). T4P documents this explicitly.", + tags: ["daily updates", "named individuals", "demographics", "press killed", "infrastructure"] + }, + { + id: 2, + name: "UNOSAT Damage Assessments", + org: "UNOSAT / UN-HABITAT", + category: "satellite", + categoryLabel: "Satellite / Infrastructure", + formats: ["GDB", "SHP", "PDF"], + updateFreq: "Per-snapshot", + access: "open", + accessLabel: "HDX download", + license: "CC BY-IGO 3.0", + licenseType: "cc", + score: 5, + records: "163k+ structures", + timespan: "Oct 2023 — Apr 2025", + url: "data.humdata.org", + description: "Satellite imagery-based comprehensive assessments of structural destruction across the full Gaza Strip. Each release compares current imagery against pre-war baselines.", + maintainer: "UNOSAT (UN Satellite Centre)", + size: "163,778+ structures assessed across 5 governorates, 9+ snapshot releases", + accessDetail: "Direct download from HDX. No registration for CSV-level data. GDB/SHP for GIS. Latest release: April 2025.", + licenseDetail: "Creative Commons Attribution for Intergovernmental Organisations. Free to use with attribution.", + crossRefs: [1, 3, 4, 7], + crossRefNotes: "Damage polygons overlay T4P casualty location data and ACLED event coordinates. HOT OSM baseline provides the pre-war building inventory as denominator.", + integrationEffort: "2–3 days", + caveats: "Preliminary analyses not field-validated. Road network damage is a separate dataset. Static snapshots require manual update tracking.", + tags: ["geospatial", "building damage", "satellite imagery", "governorate breakdown"] + }, + { + id: 3, + name: "HOT OSM Buildings Baseline", + org: "Humanitarian OpenStreetMap Team", + category: "satellite", + categoryLabel: "Satellite / Infrastructure", + formats: ["GeoJSON", "SHP", "PBF"], + updateFreq: "Static", + access: "open", + accessLabel: "Direct download", + license: "ODbL 1.0", + licenseType: "open", + score: 4, + records: "All buildings (pre-war)", + timespan: "Baseline: 2019–2023", + url: "data.humdata.org", + description: "Complete digitised inventory of every building footprint in Gaza before October 7, 2023. Essential denominator for calculating percentage of structures destroyed.", + maintainer: "Humanitarian OpenStreetMap Team (global volunteers)", + size: "Full pre-war building footprints, digitized from 2019–2023 imagery", + accessDetail: "Direct download from HDX. No registration. Multiple formats including GeoJSON for web use.", + licenseDetail: "Open Database Licence — full open use, derivative databases must share-alike.", + crossRefs: [2], + crossRefNotes: "Pairs with every UNOSAT release as the 'before' baseline. Enables % destroyed and per-neighbourhood analysis.", + integrationEffort: "~1 day", + caveats: "One-time snapshot — will not update. Intended as pre-war baseline only.", + tags: ["geospatial", "building footprints", "pre-war baseline", "OSM"] + }, + { + id: 4, + name: "ACLED Conflict Events", + org: "ACLED", + category: "events", + categoryLabel: "Conflict Events", + formats: ["CSV", "JSON", "API"], + updateFreq: "Weekly", + access: "restricted", + accessLabel: "Registration required", + license: "Custom EULA", + licenseType: "restrictive", + score: 4, + records: "Event-level data", + timespan: "Oct 2023 — present", + url: "acleddata.com", + description: "Disaggregated political violence and protest events with actor, date, location, fatality counts, and event type. Weekly updates. Aggregated version freely available on HDX.", + maintainer: "ACLED (non-profit, operationally independent)", + size: "Event-level: actor, date, location, fatalities for all political violence in oPt", + accessDetail: "Aggregated CSV free on HDX. Full event data requires myACLED account (free registration). Higher tiers may require fees.", + licenseDetail: "Proprietary EULA. Cannot redistribute raw data. Cannot build a 'functional substitute'. Transformative use permitted with attribution.", + crossRefs: [1, 2, 5], + crossRefNotes: "Event coordinates link to UNOSAT damage polygons. Actor breakdown enables civilian vs. combatant analysis cross-referencing T4P demographic data.", + integrationEffort: "2 days", + caveats: "EULA prohibits building competing platforms. Needs legal review before deep integration. Aggregated HDX version is the legally safe fallback.", + tags: ["event-level", "political violence", "actor data", "weekly updates", "geospatial"] + }, + { + id: 5, + name: "OCHA Humanitarian Situation", + org: "UN OCHA", + category: "humanitarian", + categoryLabel: "Humanitarian", + formats: ["CSV", "API", "PDF"], + updateFreq: "Weekly", + access: "open", + accessLabel: "HDX HAPI", + license: "CC BY-IGO", + licenseType: "cc", + score: 4, + records: "325+ reports", + timespan: "Oct 2023 — present", + url: "data.humdata.org", + description: "Comprehensive humanitarian indicators: displacement, food security, health system status, aid flows, and protection. Weekly situation reports plus structured API indicators.", + maintainer: "UN OCHA", + size: "325+ weekly reports, structured indicators across multiple sectors", + accessDetail: "HDX HAPI REST API for structured indicators (no auth). PDFs for narrative reports. FTS API for financial tracking.", + licenseDetail: "Creative Commons Attribution for Intergovernmental Organisations. Attribution required; otherwise fully open.", + crossRefs: [1, 4], + crossRefNotes: "IDP figures link to UNOSAT damage areas. Food security phases cross-reference casualty timeline. Aid flows connect to infrastructure destruction.", + integrationEffort: "3–4 days", + caveats: "IDP data for Palestine listed as 'unavailable' in 2025 HDX report. Food security and health indicators more complete. PDF reports need parsing.", + tags: ["displacement", "food security", "health", "aid flows", "IPC phases"] + }, + { + id: 6, + name: "B'Tselem Fatalities Database", + org: "B'Tselem", + category: "human-rights", + categoryLabel: "Human Rights", + formats: ["Web", "XLS"], + updateFreq: "Irregular", + access: "restricted", + accessLabel: "Scraping required", + license: "All rights reserved", + licenseType: "restrictive", + score: 3, + records: "Individual verified fatalities", + timespan: "2000 — present", + url: "btselem.org/statistics", + description: "Individually verified fatalities with age, sex, and circumstance — the only continuous record going back to the Second Intifada. Essential historical baseline.", + maintainer: "B'Tselem (Israeli NGO, Jerusalem-based)", + size: "Verified individual fatalities 2000–present, age/sex breakdown", + accessDetail: "No API. Data in web tables. Excel export for some sub-datasets. Scraping or manual download required.", + licenseDetail: "No open licence declared. All rights reserved. Academic papers cite freely under fair use. Redistribution unclear.", + crossRefs: [1], + crossRefNotes: "Only continuous casualty record from 2000–2022. Used as prior in Bayesian mortality models. Cross-references T4P for post-Oct 2023 data.", + integrationEffort: "3–5 days", + caveats: "Most valuable for historical context pre-Oct 2023. No API makes automated refresh unreliable. Direct contact with B'Tselem recommended for licence terms.", + tags: ["historical", "verified individuals", "human rights", "long-term baseline"] + }, + { + id: 7, + name: "Insecurity Insight — Attacks on Health", + org: "Insecurity Insight / SHCC", + category: "human-rights", + categoryLabel: "Human Rights", + formats: ["CSV", "XLS"], + updateFreq: "Monthly", + access: "open", + accessLabel: "HDX download", + license: "CC BY 4.0", + licenseType: "cc", + score: 3, + records: "Event-level attacks", + timespan: "2016 — present", + url: "data.humdata.org", + description: "Attacks on health care, education, food security, and aid operations. Documents IHL violations against protected persons and facilities.", + maintainer: "Insecurity Insight (Swiss NGO) / SHCC", + size: "Event-level: attacks on health, education, food security, aid · 2016–Apr 2025", + accessDetail: "Direct download from HDX. No registration. CSV/XLS. Contact for curated extracts.", + licenseDetail: "CC BY 4.0 — most permissive standard licence. Attribution required, full redistribution allowed.", + crossRefs: [1, 2], + crossRefNotes: "Health facility attacks link to UNOSAT damage polygons. Cross-references T4P aid-worker killed data.", + integrationEffort: "~1 day", + caveats: "Covers all oPt — filter required for Gaza Strip. Updates lag ~1 month. Most valuable for IHL violation documentation.", + tags: ["health care attacks", "education", "IHL violations", "aid workers"] + }, + { + id: 8, + name: "PCBS Population Statistics", + org: "Palestinian Central Bureau of Statistics", + category: "population", + categoryLabel: "Population", + formats: ["XLS", "PDF", "Web"], + updateFreq: "Annual", + access: "restricted", + accessLabel: "Manual download", + license: "Public domain (implicit)", + licenseType: "open", + score: 3, + records: "Population by governorate", + timespan: "Annual series", + url: "pcbs.gov.ps", + description: "Official Palestinian population counts by governorate, civil registration, and wartime demographic estimates. The authoritative denominator for per-capita calculations.", + maintainer: "Palestinian Central Bureau of Statistics (PA)", + size: "Annual population by governorate, civil registration, wartime estimates 2023–2024", + accessDetail: "XLS and PDF downloads. No API. Some education indicators mirrored on UNESCO/HDX.", + licenseDetail: "Published for public use; no explicit open licence. Treated as public domain by academic and UN users.", + crossRefs: [1, 5], + crossRefNotes: "Provides per-capita denominators for casualty rates, displacement ratios, and impact calculations. Used in all Lancet mortality studies.", + integrationEffort: "~1 day", + caveats: "End-2024 estimate: 2.1M in Gaza, down ~160k from 2023. CIA Factbook figure (2.14M) is a pre-war projection — PCBS is the correct source.", + tags: ["demographics", "population", "denominators", "civil registration"] + } +]; + +const CATEGORIES = { + casualties: { color: "#C4503D", bg: "#FDF0ED", label: "Casualties" }, + satellite: { color: "#2D6A9F", bg: "#EBF2F9", label: "Satellite / Infrastructure" }, + events: { color: "#6B52AE", bg: "#F0EDF8", label: "Conflict Events" }, + humanitarian: { color: "#2E7D5B", bg: "#EDF6F1", label: "Humanitarian" }, + "human-rights": { color: "#9E4D6B", bg: "#F6EDF1", label: "Human Rights" }, + population: { color: "#A07628", bg: "#F8F3E8", label: "Population" }, +}; + +const ACCESS_STYLES = { + open: { dot: "#2E7D5B", label: "Open" }, + restricted: { dot: "#C4503D", label: "Restricted" }, +}; + +const LICENSE_STYLES = { + open: { color: "#2E7D5B" }, + cc: { color: "#2D6A9F" }, + restrictive: { color: "#C4503D" }, +}; + +function StarRating({ n }) { + return ( + + {[1, 2, 3, 4, 5].map((i) => ( + + ★ + + ))} + + ); +} + +function FormatPill({ fmt }) { + return ( + + {fmt} + + ); +} + +function CategoryBadge({ cat, small }) { + const c = CATEGORIES[cat]; + return ( + + + {c.label} + + ); +} + +function AccessDot({ type }) { + const s = ACCESS_STYLES[type]; + return ( + + + {s.label} + + ); +} + +function ConnectionLine({ from, to, datasets }) { + const a = datasets.find((d) => d.id === from); + const b = datasets.find((d) => d.id === to); + if (!a || !b) return null; + return ( +
+ + {a.name} + + + + {b.name} + +
+ ); +} + +function DatasetCard({ dataset, isSelected, onClick, index }) { + const cat = CATEGORIES[dataset.category]; + return ( +
+
+
+
+
+ {dataset.name} +
+
+ {dataset.org} +
+
+ +
+

+ {dataset.description} +

+
+ + + + {dataset.updateFreq} + +
+
+ {dataset.formats.map((f) => ( + + ))} + {dataset.crossRefs.length > 0 && ( + + ↔ {dataset.crossRefs.length} connections + + )} +
+
+ ); +} + +function DetailPanel({ dataset, onClose }) { + const cat = CATEGORIES[dataset.category]; + const lic = LICENSE_STYLES[dataset.licenseType]; + const sections = [ + { label: "Maintainer", value: dataset.maintainer }, + { label: "Coverage & size", value: dataset.size }, + { label: "Time span", value: dataset.timespan }, + { label: "Access method", value: dataset.accessDetail }, + { label: "License", value: dataset.licenseDetail, color: lic.color }, + { label: "Integration effort", value: dataset.integrationEffort }, + ]; + + return ( +
+
+
+ +

+ {dataset.name} +

+
+ {dataset.org} + · + + {dataset.url} + +
+
+ +
+ +

+ {dataset.description} +

+ +
+ {dataset.formats.map((f) => ( + + ))} + + {dataset.accessLabel} + + + {dataset.license} + +
+ + {sections.map((s, i) => ( +
+
+ {s.label} +
+
+ {s.value} +
+
+ ))} + +
+
+ Cross-reference connections +
+
+ {dataset.crossRefNotes} +
+ {dataset.crossRefs.map((refId) => { + const ref = DATASETS.find((d) => d.id === refId); + if (!ref) return null; + const rc = CATEGORIES[ref.category]; + return ( +
+ + {ref.name} +
+ ); + })} +
+ + {dataset.caveats && ( +
+
+ Caveats +
+

+ {dataset.caveats} +

+
+ )} + +
+ {dataset.tags.map((t) => ( + + {t} + + ))} +
+
+ ); +} + +function NetworkGraph({ datasets, selectedId, onSelect }) { + const svgRef = useRef(null); + const positions = [ + { x: 160, y: 50 }, + { x: 310, y: 30 }, + { x: 440, y: 65 }, + { x: 90, y: 150 }, + { x: 260, y: 155 }, + { x: 410, y: 155 }, + { x: 520, y: 120 }, + { x: 180, y: 230 }, + ]; + const edges = []; + datasets.forEach((d) => { + d.crossRefs.forEach((refId) => { + if ( + d.id < refId && + !edges.find((e) => e.from === d.id && e.to === refId) + ) { + edges.push({ from: d.id, to: refId }); + } + }); + }); + + return ( + + {edges.map((e, i) => { + const fromPos = positions[e.from - 1]; + const toPos = positions[e.to - 1]; + const isHighlighted = + selectedId && (e.from === selectedId || e.to === selectedId); + return ( + d.id === selectedId).category].color : "#C4C0B8"} + strokeWidth={isHighlighted ? 1.5 : 0.5} + strokeDasharray={isHighlighted ? "none" : "3 3"} + opacity={selectedId ? (isHighlighted ? 0.8 : 0.15) : 0.4} + style={{ transition: "all 0.3s" }} + /> + ); + })} + {datasets.map((d, i) => { + const pos = positions[i]; + const cat = CATEGORIES[d.category]; + const isSelected = selectedId === d.id; + const isConnected = + selectedId && + (datasets + .find((ds) => ds.id === selectedId) + ?.crossRefs.includes(d.id) || + d.id === selectedId); + const dimmed = selectedId && !isConnected; + return ( + onSelect(d.id)} + style={{ cursor: "pointer", transition: "opacity 0.3s" }} + opacity={dimmed ? 0.2 : 1} + > + + + {d.id} + + + {d.name.length > 22 ? d.name.substring(0, 20) + "…" : d.name} + + + ); + })} + + ); +} + +export default function PalestineDatacommons() { + const [selectedId, setSelectedId] = useState(null); + const [filterCat, setFilterCat] = useState("all"); + const [searchTerm, setSearchTerm] = useState(""); + const [view, setView] = useState("grid"); + + const filtered = DATASETS.filter((d) => { + const matchCat = filterCat === "all" || d.category === filterCat; + const matchSearch = + searchTerm === "" || + d.name.toLowerCase().includes(searchTerm.toLowerCase()) || + d.org.toLowerCase().includes(searchTerm.toLowerCase()) || + d.tags.some((t) => t.toLowerCase().includes(searchTerm.toLowerCase())); + return matchCat && matchSearch; + }); + + const selectedDataset = DATASETS.find((d) => d.id === selectedId); + const categories = ["all", ...Object.keys(CATEGORIES)]; + + const cssVars: Record = { + "--ds-bg-primary": "#FAFAF7", + "--ds-bg-secondary": "#F0EFEB", + "--ds-bg-tertiary": "#E8E6E0", + "--ds-text-primary": "#1A1A18", + "--ds-text-secondary": "#6B6960", + "--ds-text-tertiary": "#9E9B90", + "--ds-border": "#DDD9D0", + "--ds-border-light": "#EAE8E2", + }; + + return ( +
+ + +
+
+

+ Palestine Datacommons +

+ + Dataset catalog + +
+

+ Discover, explore, and cross-reference independent datasets documenting + the conflict in Gaza. A single entry point for data that was previously + siloed across separate projects. +

+ +
+
+ setSearchTerm(e.target.value)} + style={{ + width: "100%", + padding: "8px 12px 8px 32px", + border: "1px solid var(--ds-border)", + borderRadius: 8, + fontSize: 12, + background: "var(--ds-bg-primary)", + color: "var(--ds-text-primary)", + outline: "none", + fontFamily: "inherit", + }} + /> + + ⌕ + +
+ +
+ {categories.map((c) => { + const isActive = filterCat === c; + const catData = c === "all" ? null : CATEGORIES[c]; + return ( + + ); + })} +
+ +
+ {["grid", "network"].map((v) => ( + + ))} +
+
+
+ +
+
+ {view === "network" && ( +
+
+ Cross-reference network +
+ setSelectedId(id === selectedId ? null : id)} + /> +

+ Click a node to highlight connections · Lines show datasets that + can be joined or cross-referenced +

+
+ )} + + {view === "grid" && ( +
+
+
+ + {filtered.length} dataset{filtered.length !== 1 && "s"} + {filterCat !== "all" && ` in ${CATEGORIES[filterCat].label}`} + {searchTerm && ` matching "${searchTerm}"`} + + + Sorted by priority score + +
+ +
+ {filtered + .sort((a, b) => b.score - a.score) + .map((d, i) => ( + + setSelectedId(d.id === selectedId ? null : d.id) + } + /> + ))} +
+ + {filtered.length === 0 && ( +
+ No datasets match your filters. +
+ )} +
+ + {selectedId && selectedDataset && ( +
+ setSelectedId(null)} + /> +
+ )} +
+ )} + + {view === "network" && selectedId && selectedDataset && ( +
+ setSelectedId(null)} + /> +
+ )} +
+
+ +
+
+ {[ + { n: DATASETS.length, l: "datasets catalogued" }, + { n: DATASETS.filter((d) => d.access === "open").length, l: "fully open access" }, + { n: DATASETS.filter((d) => ["Daily", "Weekly"].includes(d.updateFreq)).length, l: "live / weekly updates" }, + { + n: new Set( + DATASETS.flatMap((d) => d.crossRefs).concat( + DATASETS.filter((d) => d.crossRefs.length > 0).map((d) => d.id) + ) + ).size, + l: "cross-linked datasets", + }, + ].map((s, i) => ( +
+ + {s.n} + + + {s.l} + +
+ ))} +
+ + Palestine Datacommons · Dataset catalog + +
+
+ ); +} diff --git a/src/pages/palestine-data.astro b/src/pages/palestine-data.astro new file mode 100644 index 00000000..b9a2408a --- /dev/null +++ b/src/pages/palestine-data.astro @@ -0,0 +1,25 @@ +--- +import Layout from "../layouts/Layout.astro"; +import PageHeader from "../components/PageHeader.astro"; +import PalestineDatacommons from "../components/PalestineDatacommons.tsx"; +import "../styles/base.css"; +--- + + + + + + + + From af86a20fd8671741a77e938ba641564a3c836aad Mon Sep 17 00:00:00 2001 From: mohanadft Date: Wed, 3 Jun 2026 23:19:03 +0300 Subject: [PATCH 2/2] fix: add TypeScript types to PalestineDatacommons component --- src/components/PalestineDatacommons.tsx | 84 ++++++++++++++++++------- 1 file changed, 61 insertions(+), 23 deletions(-) diff --git a/src/components/PalestineDatacommons.tsx b/src/components/PalestineDatacommons.tsx index 815dd932..e402a2f8 100644 --- a/src/components/PalestineDatacommons.tsx +++ b/src/components/PalestineDatacommons.tsx @@ -1,7 +1,45 @@ import { useState, useRef } from "react"; import type { CSSProperties } from "react"; -const DATASETS = [ +type CategoryKey = "casualties" | "satellite" | "events" | "humanitarian" | "human-rights" | "population"; +type AccessKey = "open" | "restricted"; +type LicenseKey = "open" | "cc" | "restrictive"; + +interface Dataset { + id: number; + name: string; + org: string; + category: CategoryKey; + categoryLabel: string; + formats: string[]; + updateFreq: string; + access: AccessKey; + accessLabel: string; + license: string; + licenseType: LicenseKey; + score: number; + records: string; + timespan: string; + url: string; + description: string; + maintainer: string; + size: string; + accessDetail: string; + licenseDetail: string; + crossRefs: number[]; + crossRefNotes: string; + integrationEffort: string; + caveats: string; + tags: string[]; +} + +interface CategoryStyle { + color: string; + bg: string; + label: string; +} + +const DATASETS: Dataset[] = [ { id: 1, name: "T4P Daily Casualties", @@ -220,7 +258,7 @@ const DATASETS = [ } ]; -const CATEGORIES = { +const CATEGORIES: Record = { casualties: { color: "#C4503D", bg: "#FDF0ED", label: "Casualties" }, satellite: { color: "#2D6A9F", bg: "#EBF2F9", label: "Satellite / Infrastructure" }, events: { color: "#6B52AE", bg: "#F0EDF8", label: "Conflict Events" }, @@ -229,18 +267,18 @@ const CATEGORIES = { population: { color: "#A07628", bg: "#F8F3E8", label: "Population" }, }; -const ACCESS_STYLES = { +const ACCESS_STYLES: Record = { open: { dot: "#2E7D5B", label: "Open" }, restricted: { dot: "#C4503D", label: "Restricted" }, }; -const LICENSE_STYLES = { +const LICENSE_STYLES: Record = { open: { color: "#2E7D5B" }, cc: { color: "#2D6A9F" }, restrictive: { color: "#C4503D" }, }; -function StarRating({ n }) { +function StarRating({ n }: { n: number }) { return ( {[1, 2, 3, 4, 5].map((i) => ( @@ -252,7 +290,7 @@ function StarRating({ n }) { ); } -function FormatPill({ fmt }) { +function FormatPill({ fmt }: { fmt: string }) { return ( @@ -321,7 +359,7 @@ function AccessDot({ type }) { ); } -function ConnectionLine({ from, to, datasets }) { +function ConnectionLine({ from, to, datasets }: { from: number; to: number; datasets: Dataset[] }) { const a = datasets.find((d) => d.id === from); const b = datasets.find((d) => d.id === to); if (!a || !b) return null; @@ -367,7 +405,7 @@ function ConnectionLine({ from, to, datasets }) { ); } -function DatasetCard({ dataset, isSelected, onClick, index }) { +function DatasetCard({ dataset, isSelected, onClick, index }: { dataset: Dataset; isSelected: boolean; onClick: () => void; index: number }) { const cat = CATEGORIES[dataset.category]; return (
void }) { const cat = CATEGORIES[dataset.category]; const lic = LICENSE_STYLES[dataset.licenseType]; - const sections = [ + const sections: { label: string; value: string; color?: string }[] = [ { label: "Maintainer", value: dataset.maintainer }, { label: "Coverage & size", value: dataset.size }, { label: "Time span", value: dataset.timespan }, @@ -734,9 +772,9 @@ function DetailPanel({ dataset, onClose }) { ); } -function NetworkGraph({ datasets, selectedId, onSelect }) { - const svgRef = useRef(null); - const positions = [ +function NetworkGraph({ datasets, selectedId, onSelect }: { datasets: Dataset[]; selectedId: number | null; onSelect: (id: number) => void }) { + const svgRef = useRef(null); + const positions: { x: number; y: number }[] = [ { x: 160, y: 50 }, { x: 310, y: 30 }, { x: 440, y: 65 }, @@ -746,7 +784,7 @@ function NetworkGraph({ datasets, selectedId, onSelect }) { { x: 520, y: 120 }, { x: 180, y: 230 }, ]; - const edges = []; + const edges: { from: number; to: number }[] = []; datasets.forEach((d) => { d.crossRefs.forEach((refId) => { if ( @@ -776,7 +814,7 @@ function NetworkGraph({ datasets, selectedId, onSelect }) { y1={fromPos.y} x2={toPos.x} y2={toPos.y} - stroke={isHighlighted ? CATEGORIES[datasets.find((d) => d.id === selectedId).category].color : "#C4C0B8"} + stroke={isHighlighted ? CATEGORIES[datasets.find((d) => d.id === selectedId)!.category].color : "#C4C0B8"} strokeWidth={isHighlighted ? 1.5 : 0.5} strokeDasharray={isHighlighted ? "none" : "3 3"} opacity={selectedId ? (isHighlighted ? 0.8 : 0.15) : 0.4} @@ -842,10 +880,10 @@ function NetworkGraph({ datasets, selectedId, onSelect }) { } export default function PalestineDatacommons() { - const [selectedId, setSelectedId] = useState(null); - const [filterCat, setFilterCat] = useState("all"); + const [selectedId, setSelectedId] = useState(null); + const [filterCat, setFilterCat] = useState("all"); const [searchTerm, setSearchTerm] = useState(""); - const [view, setView] = useState("grid"); + const [view, setView] = useState<"grid" | "network">("grid"); const filtered = DATASETS.filter((d) => { const matchCat = filterCat === "all" || d.category === filterCat; @@ -858,7 +896,7 @@ export default function PalestineDatacommons() { }); const selectedDataset = DATASETS.find((d) => d.id === selectedId); - const categories = ["all", ...Object.keys(CATEGORIES)]; + const categories: (CategoryKey | "all")[] = ["all", ...(Object.keys(CATEGORIES) as CategoryKey[])]; const cssVars: Record = { "--ds-bg-primary": "#FAFAF7", @@ -994,7 +1032,7 @@ export default function PalestineDatacommons() { transition: "all 0.15s", }} > - {c === "all" ? `All (${DATASETS.length})` : catData.label} + {c === "all" ? `All (${DATASETS.length})` : catData?.label} ); })} @@ -1009,7 +1047,7 @@ export default function PalestineDatacommons() { marginLeft: "auto", }} > - {["grid", "network"].map((v) => ( + {(["grid", "network"] as const).map((v) => (