From 10f880b757e75dfe66f94e08dae516ba09cd3e33 Mon Sep 17 00:00:00 2001 From: elainefan331 Date: Mon, 20 Oct 2025 11:03:41 -0400 Subject: [PATCH 1/5] refactor: repalced back button with home icon and database path --- src/pages/UpdatedDatasetDetailPage.tsx | 144 +++++++++++++------------ 1 file changed, 75 insertions(+), 69 deletions(-) diff --git a/src/pages/UpdatedDatasetDetailPage.tsx b/src/pages/UpdatedDatasetDetailPage.tsx index e5516b9..2acf5a9 100644 --- a/src/pages/UpdatedDatasetDetailPage.tsx +++ b/src/pages/UpdatedDatasetDetailPage.tsx @@ -682,7 +682,7 @@ const UpdatedDatasetDetailPage: React.FC = () => { return ( <> - + */} + + {/* Breadcrumb Navigation (Home → Database → Dataset) */} + + {/* Home Icon Button */} + + + + » + + + {/* Database Name (Clickable) */} + + + + » + + + {/* Dataset Name (_id field) */} + + {docId} + + { )} - {/* Breadcrumb Navigation (Home → Database → Dataset) */} - - {/* Home Icon Button */} - - - - » - - - {/* Database Name (Clickable) */} - - - - » - - - {/* Dataset Name (_id field) */} - - {docId} - - - {/* ai summary */} {aiSummary && } From 237ae9a055105e3275b1a6cb0ae2d768d64a7e6d Mon Sep 17 00:00:00 2001 From: elainefan331 Date: Mon, 20 Oct 2025 11:47:47 -0400 Subject: [PATCH 2/5] feat: add info tooltip explaining AI summary section --- src/pages/UpdatedDatasetDetailPage.tsx | 61 +++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/src/pages/UpdatedDatasetDetailPage.tsx b/src/pages/UpdatedDatasetDetailPage.tsx index 2acf5a9..cd74143 100644 --- a/src/pages/UpdatedDatasetDetailPage.tsx +++ b/src/pages/UpdatedDatasetDetailPage.tsx @@ -6,6 +6,7 @@ import DescriptionIcon from "@mui/icons-material/Description"; import ExpandLess from "@mui/icons-material/ExpandLess"; import ExpandMore from "@mui/icons-material/ExpandMore"; import HomeIcon from "@mui/icons-material/Home"; +import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; import { Box, Typography, @@ -807,7 +808,65 @@ const UpdatedDatasetDetailPage: React.FC = () => { )} {/* ai summary */} - {aiSummary && } + {aiSummary && ( + <> + + + AI Summary + + + AI Summary is generated using an AI tool that identifies + the related paper and extracts its key content to create a + concise summary. + + } + arrow + placement="right" + slotProps={{ + tooltip: { + sx: { + bgcolor: Colors.white, + border: `1px solid ${Colors.lightGray}`, + boxShadow: 3, + fontSize: "0.875rem", + }, + }, + arrow: { + sx: { + color: Colors.white, + "&::before": { + border: `1px solid ${Colors.lightGray}`, // subtle arrow border + }, + }, + }, + }} + > + + + + + + + )} Date: Mon, 20 Oct 2025 17:45:31 -0400 Subject: [PATCH 3/5] feat: add participants preview table for better readability --- .../DatasetDetailPage/MetaDataPanel.tsx | 25 +++-- .../DatasetDetailPage/ParticipantsPreview.tsx | 97 +++++++++++++++++++ src/components/SearchPage/DatabaseCard.tsx | 2 +- src/pages/UpdatedDatasetDetailPage.tsx | 2 - .../participants.ts | 34 +++++++ 5 files changed, 150 insertions(+), 10 deletions(-) create mode 100644 src/components/DatasetDetailPage/ParticipantsPreview.tsx create mode 100644 src/utils/DatasetDetailPageFunctions/participants.ts diff --git a/src/components/DatasetDetailPage/MetaDataPanel.tsx b/src/components/DatasetDetailPage/MetaDataPanel.tsx index 388dca2..e7c6435 100644 --- a/src/components/DatasetDetailPage/MetaDataPanel.tsx +++ b/src/components/DatasetDetailPage/MetaDataPanel.tsx @@ -1,3 +1,4 @@ +import ParticipantsPreview from "./ParticipantsPreview"; import ArrowCircleRightIcon from "@mui/icons-material/ArrowCircleRight"; import { Box, @@ -190,13 +191,23 @@ const MetaDataPanel: React.FC = ({ })()} - - - Subjects - - - {dbViewInfo?.rows?.[0]?.value?.subj?.length ?? "N/A"} - + + + + Subjects + + + {dbViewInfo?.rows?.[0]?.value?.subj?.length ?? "N/A"} + + + diff --git a/src/components/DatasetDetailPage/ParticipantsPreview.tsx b/src/components/DatasetDetailPage/ParticipantsPreview.tsx new file mode 100644 index 0000000..877d664 --- /dev/null +++ b/src/components/DatasetDetailPage/ParticipantsPreview.tsx @@ -0,0 +1,97 @@ +import { makeParticipantsTable } from "../../utils/DatasetDetailPageFunctions/participants"; +import { + Box, + Button, + Dialog, + DialogContent, + DialogTitle, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, +} from "@mui/material"; +import { Colors } from "design/theme"; +import React, { useMemo, useState } from "react"; + +type Props = { + datasetDocument: any; +}; + +const ParticipantsPreview: React.FC = ({ datasetDocument }) => { + const [open, setOpen] = useState(false); + + const table = useMemo(() => { + const part = datasetDocument?.["participants.tsv"]; + return makeParticipantsTable(part); + }, [datasetDocument]); + + if (!table) return null; // No participants.tsv found + + return ( + <> + + + + + setOpen(false)} + maxWidth="md" + fullWidth + > + participants.tsv + + + + + + {table.columns.map((col) => ( + + {col.replace(/_/g, " ")} + + ))} + + + + {table.rows.map((row, rowIdx) => ( + + {table.columns.map((col) => ( + {row[col]} + ))} + + ))} + +
+
+
+
+ + ); +}; + +export default ParticipantsPreview; diff --git a/src/components/SearchPage/DatabaseCard.tsx b/src/components/SearchPage/DatabaseCard.tsx index 025be91..263c84d 100644 --- a/src/components/SearchPage/DatabaseCard.tsx +++ b/src/components/SearchPage/DatabaseCard.tsx @@ -39,7 +39,7 @@ const DatabaseCard: React.FC = ({ }) => { const dispatch = useAppDispatch(); const dbInfo = useAppSelector((state: RootState) => state.neurojson.dbInfo); - console.log("dbInfo", dbInfo); + // console.log("dbInfo", dbInfo); useEffect(() => { if (dbId) { dispatch(fetchDbInfo(dbId.toLowerCase())); diff --git a/src/pages/UpdatedDatasetDetailPage.tsx b/src/pages/UpdatedDatasetDetailPage.tsx index cd74143..a56e0d0 100644 --- a/src/pages/UpdatedDatasetDetailPage.tsx +++ b/src/pages/UpdatedDatasetDetailPage.tsx @@ -966,8 +966,6 @@ const UpdatedDatasetDetailPage: React.FC = () => { 0 && cols.every((k) => Array.isArray(part[k])); + const arrayCols = Object.keys(part).filter((k) => Array.isArray(part[k])); + + if (arrayCols.length > 0) { + const n = Math.max(...arrayCols.map((k) => part[k].length)); + const rows = Array.from({ length: n }, (_, i) => { + const r: any = { id: i + 1 }; + arrayCols.forEach((k) => (r[k] = part[k][i] ?? "")); + return r; + }); + return { columns: arrayCols, rows }; + } + + // Case B: array-of-objects fallback + if (Array.isArray(part)) { + const allCols = new Set(); + part.forEach((r: any) => + Object.keys(r || {}).forEach((k) => allCols.add(k)) + ); + const columns = Array.from(allCols); + const rows = part.map((r: any, i: number) => ({ id: i + 1, ...(r || {}) })); + return { columns, rows }; + } + + return null; +} From 689f43334273608089904f61c0d38312ef454695 Mon Sep 17 00:00:00 2001 From: elainefan331 Date: Wed, 22 Oct 2025 17:54:44 -0400 Subject: [PATCH 4/5] feat: enhance highlightkeyword to check secondary fields when visible fields don't match --- src/components/SearchPage/DatasetCard.tsx | 120 ++++++++++++++++++++-- src/pages/DatasetDetailPage.tsx | 2 +- 2 files changed, 111 insertions(+), 11 deletions(-) diff --git a/src/components/SearchPage/DatasetCard.tsx b/src/components/SearchPage/DatasetCard.tsx index 110b6fc..19fb758 100644 --- a/src/components/SearchPage/DatasetCard.tsx +++ b/src/components/SearchPage/DatasetCard.tsx @@ -1,6 +1,7 @@ import { Typography, Card, CardContent, Stack, Chip } from "@mui/material"; import { Colors } from "design/theme"; import React from "react"; +import { useMemo } from "react"; import { Link } from "react-router-dom"; import RoutesEnum from "types/routes.enum"; @@ -17,7 +18,9 @@ interface DatasetCardProps { info?: { Authors?: string[]; DatasetDOI?: string; + [k: string]: any; }; + [k: string]: any; }; }; index: number; @@ -25,6 +28,63 @@ interface DatasetCardProps { keyword?: string; // for keyword highlight } +/** ---------- utility helpers ---------- **/ +const normalize = (s: string) => + s + ?.replace(/[\u2018\u2019\u2032]/g, "'") // curly → straight + ?.replace(/[\u201C\u201D\u2033]/g, '"') ?? // curly → straight + ""; + +const containsKeyword = (text?: string, kw?: string) => { + if (!text || !kw) return false; + const t = normalize(text).toLowerCase(); + const k = normalize(kw).toLowerCase(); + return t.includes(k); +}; + +/** Find a short snippet in secondary fields if not already visible */ +function findMatchSnippet( + v: any, + kw?: string +): { label: string; html: string } | null { + if (!kw) return null; + + // Which fields to scan (can add/remove fields here) + const CANDIDATE_FIELDS: Array<[string, (v: any) => string | undefined]> = [ + ["Acknowledgements", (v) => v?.info?.Acknowledgements], + [ + "Funding", + (v) => + Array.isArray(v?.info?.Funding) + ? v.info.Funding.join(" ") + : v?.info?.Funding, + ], + ["ReferencesAndLinks", (v) => v?.info?.ReferencesAndLinks], + ]; + + const k = normalize(kw).toLowerCase(); + + for (const [label, getter] of CANDIDATE_FIELDS) { + const raw = getter(v); // v = parsedJson.value + if (!raw) continue; + const text = normalize(String(raw)); + const i = text.toLowerCase().indexOf(k); // k is the lowercase version of keyword + if (i >= 0) { + const start = Math.max(0, i - 40); + const end = Math.min(text.length, i + k.length + 40); + const before = text.slice(start, i); + const hit = text.slice(i, i + k.length); + const after = text.slice(i + k.length, end); + const html = `${ + start > 0 ? "…" : "" + }${before}${hit}${after}${end < text.length ? "…" : ""}`; + return { label, html }; + } + } + return null; +} +/** ---------- end of helpers ---------- **/ + const DatasetCard: React.FC = ({ dbname, dsname, @@ -40,7 +100,29 @@ const DatasetCard: React.FC = ({ const rawDOI = info?.DatasetDOI?.replace(/^doi:/, ""); const doiLink = rawDOI ? `https://doi.org/${rawDOI}` : null; - // keyword hightlight functional component + // precompute what’s visible & whether it already contains the keyword + const authorsJoined = Array.isArray(info?.Authors) + ? info!.Authors.join(", ") + : typeof info?.Authors === "string" + ? info!.Authors + : ""; + + const visibleHasKeyword = useMemo( + () => + containsKeyword(name, keyword) || + containsKeyword(readme, keyword) || + containsKeyword(authorsJoined, keyword), + [name, readme, authorsJoined, keyword] + ); + + // If not visible, produce a one-line snippet from other fields (for non-visible fields) + const snippet = useMemo( + () => + !visibleHasKeyword ? findMatchSnippet(parsedJson.value, keyword) : null, + [parsedJson.value, keyword, visibleHasKeyword] + ); + + // keyword hightlight functional component (only for visible fields) const highlightKeyword = (text: string, keyword?: string) => { if (!keyword || !text?.toLowerCase().includes(keyword.toLowerCase())) { return text; @@ -99,7 +181,10 @@ const DatasetCard: React.FC = ({ {highlightKeyword(name || "Untitled Dataset", keyword)}
- Database: {dbname}   |   Dataset: {dsname} + {/* Database: {dbname}   |   Dataset: {dsname} */} + Database: {highlightKeyword(dbname, keyword)} + {" "}  |  {" "} + Dataset: {highlightKeyword(dsname, keyword)} @@ -168,20 +253,35 @@ const DatasetCard: React.FC = ({ {info?.Authors && ( Authors:{" "} - {highlightKeyword( - Array.isArray(info.Authors) - ? info.Authors.join(", ") - : typeof info.Authors === "string" - ? info.Authors - : "N/A", - keyword - )} + {highlightKeyword(authorsJoined || "N/A", keyword)} )} )} + {/* show why it matched if not visible in main fields */} + {snippet && ( + + + only + dangerouslySetInnerHTML={{ __html: snippet.html }} + /> + + )} + {doiLink && ( diff --git a/src/pages/DatasetDetailPage.tsx b/src/pages/DatasetDetailPage.tsx index 0999906..c1b5c89 100644 --- a/src/pages/DatasetDetailPage.tsx +++ b/src/pages/DatasetDetailPage.tsx @@ -305,7 +305,7 @@ const DatasetDetailPage: React.FC = () => { useEffect(() => { if (datasetDocument) { // ✅ Extract External Data & Assign `index` - console.log("datasetDocument", datasetDocument); + // console.log("datasetDocument", datasetDocument); const links = extractDataLinks(datasetDocument, "").map( (link, index) => ({ ...link, From 7bc8fc0e62abe36755cb4becd029812a3928dad8 Mon Sep 17 00:00:00 2001 From: elainefan331 Date: Thu, 23 Oct 2025 10:36:19 -0400 Subject: [PATCH 5/5] feat: display readme as summary when ai summary is unavailable --- src/pages/UpdatedDatasetDetailPage.tsx | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/pages/UpdatedDatasetDetailPage.tsx b/src/pages/UpdatedDatasetDetailPage.tsx index a56e0d0..ee24bd0 100644 --- a/src/pages/UpdatedDatasetDetailPage.tsx +++ b/src/pages/UpdatedDatasetDetailPage.tsx @@ -93,6 +93,7 @@ const UpdatedDatasetDetailPage: React.FC = () => { const [copiedKey, setCopiedKey] = useState(null); const copyTimer = useRef(null); const aiSummary = datasetDocument?.[".datainfo"]?.AISummary ?? ""; + const readme = datasetDocument?.["README"] ?? ""; const handleSelectRevision = (newRev?: string | null) => { setSearchParams((prev) => { const p = new URLSearchParams(prev); // copy of the query url @@ -808,7 +809,7 @@ const UpdatedDatasetDetailPage: React.FC = () => { )} {/* ai summary */} - {aiSummary && ( + {aiSummary ? ( <> { + ) : readme ? ( + <> + + + Summary + + + + + + ) : ( + "" )}