Skip to content
5 changes: 5 additions & 0 deletions .cursor-updates
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Cursor Updates

- Added discovery card below Catalog mental health card with Harmony logo and discovery chips for topics and instruments that link to the discovery app (layout matches Catalog card with logo on one side, content on the other)
- Added Harmony logo above match percentage in each match results card with tooltip linking to discovery search using query parameter with both question texts
- Updated URL generation to use getCurrentDomain() and getDiscoveryNextPath() utility functions for proper localhost (port 3222) and production (/search/) URL handling
13 changes: 13 additions & 0 deletions src/components/AppBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import GoogleIcon from "@mui/icons-material/Google";
import GitHubIcon from "@mui/icons-material/GitHub";
import TwitterIcon from "@mui/icons-material/Twitter";
import { useData } from "../contexts/DataContext";
import ComingSoonDialog from "./ComingSoonDialog";
const settings = ["My Harmony", "Logout"];

const SettingsIcons = {
Expand All @@ -34,6 +35,8 @@ function HarmonyAppBar() {
const [apiVersion, setApiVersion] = React.useState(null);
const [allModels, setAllModels] = React.useState();
const [error, setError] = React.useState(null);
const [comingSoonOpen, setComingSoonOpen] = React.useState(false);
const [comingSoonFeature, setComingSoonFeature] = React.useState("");
const {
currentUser,
logout,
Expand Down Expand Up @@ -75,6 +78,11 @@ function HarmonyAppBar() {

const handleUserMenuClick = (menuItem) => {
switch (menuItem) {
case "My Harmony":
handleCloseUserMenu();
setComingSoonFeature("My Harmony");
setComingSoonOpen(true);
break;
case "Logout":
handleCloseUserMenu();
console.log("logging out");
Expand Down Expand Up @@ -267,6 +275,11 @@ function HarmonyAppBar() {
</Menu>
</Box>
</Box>
<ComingSoonDialog
open={comingSoonOpen}
onClose={() => setComingSoonOpen(false)}
featureName={comingSoonFeature}
/>
</AppBar>
);
}
Expand Down
5 changes: 5 additions & 0 deletions src/components/HarmonySidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ export default function HarmonySidebar() {

const handleUserMenuClick = (menuItem) => {
switch (menuItem) {
case "My Harmony":
handleCloseUserMenu();
setComingSoonFeature("My Harmony");
setComingSoonOpen(true);
break;
case "Logout":
handleCloseUserMenu();
console.log("logging out");
Expand Down
87 changes: 86 additions & 1 deletion src/components/MatchUnit.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
Menu,
MenuItem,
ListItemIcon,
Tooltip,
Link,
} from "@mui/material";
import "react-circular-progressbar/dist/styles.css";
import MatchCircle from "./MatchCircle";
Expand All @@ -19,6 +21,55 @@ const MatchUnit = ({ Q1, Q2, percentage, matchUnitMenuAction, selected }) => {
// const [isFlipped, setIsFlipped] = useState(false);
const [menuElement, setMenuElement] = useState(null);

// Get current domain for dynamic links
const getCurrentDomain = () => {
if (typeof window !== "undefined") {
// Handle local development with different ports
if (
window.location.hostname === "localhost" ||
window.location.hostname === "127.0.0.1"
) {
// React app runs on port 3000, DiscoveryNext runs on port 3222
return "http://localhost:3222";
}
return window.location.origin;
}
return "https://harmonydata.ac.uk"; // fallback for SSR
};

// Get the correct path for DiscoveryNext links
const getDiscoveryNextPath = (path) => {
if (typeof window !== "undefined") {
// Handle local development - DiscoveryNext is on root
if (
window.location.hostname === "localhost" ||
window.location.hostname === "127.0.0.1"
) {
return path;
}
// Production - DiscoveryNext is under /search
return `/search${path}`;
}
return `/search${path}`; // fallback for SSR
};

// Build discovery URL with both question texts as query
const getDiscoveryUrl = () => {
const params = new URLSearchParams();

// Combine both question texts for the query
const queryText = [Q1.question_text, Q2.question_text]
.filter(Boolean)
.join(" ");

if (queryText) {
params.set("query", queryText);
}

const queryString = params.toString();
return `${getCurrentDomain()}${getDiscoveryNextPath("/")}${queryString ? `?${queryString}` : ""}`;
};

const openMenu = (event) => {
event.stopPropagation();
setMenuElement(event.currentTarget);
Expand Down Expand Up @@ -72,7 +123,41 @@ const MatchUnit = ({ Q1, Q2, percentage, matchUnitMenuAction, selected }) => {
{Q1.instrument && Q1.instrument.name} - Q{Q1.question_no}
</Typography>
</Box>
<Box sx={{ width: "4em", margin: "0.5rem" }}>
<Box
sx={{
width: "4em",
margin: "0.5rem",
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 0.5,
}}
>
<Tooltip title="Search for studies using these items">
<Link
href={getDiscoveryUrl()}
target="HarmonyDiscovery"
onClick={(e) => {
e.stopPropagation();
}}
sx={{
display: "flex",
alignItems: "center",
justifyContent: "center",
textDecoration: "none",
}}
>
<img
src="/app/harmony.png"
alt="Harmony Logo"
style={{
height: "1.5rem",
width: "auto",
cursor: "pointer",
}}
/>
</Link>
</Tooltip>
<MatchCircle percentage={percentage} />
</Box>
<Box
Expand Down
152 changes: 151 additions & 1 deletion src/components/Results.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState, useMemo, useCallback, useEffect } from "react";
import { Paper, Card, Stack, Box, Link, Typography } from "@mui/material";
import { Paper, Card, Stack, Box, Link, Typography, Chip } from "@mui/material";
import MatchUnit from "./MatchUnit";
import { useParams } from "react-router-dom";
import { useData } from "../contexts/DataContext";
Expand All @@ -9,6 +9,10 @@ import ResultsOptions from "./ResultsOptions";

import { parse, test } from "liqe";

// Discovery's instruments filter needs its own instrument names; Harmony's
// (e.g. "SCARED English (child)") match nothing, so hide the chip for now.
const SHOW_INSTRUMENTS_CHIP = false;

export default function Results({
apiData,
setApiData,
Expand All @@ -29,8 +33,41 @@ export default function Results({
const { getPublicHarmonisations, reportMisMatch } = useData();
const [savedError, setSavedError] = useState(null);
const [topics, setTopics] = useState([]);
const [uniqueInstruments, setUniqueInstruments] = useState([]);
ReactGA.send({ hitType: "pageview", page: "/model", title: "Model" });

// Get current domain for dynamic links
const getCurrentDomain = () => {
if (typeof window !== "undefined") {
// Handle local development with different ports
if (
window.location.hostname === "localhost" ||
window.location.hostname === "127.0.0.1"
) {
// React app runs on port 3000, DiscoveryNext runs on port 3222
return "http://localhost:3222";
}
return window.location.origin;
}
return "https://harmonydata.ac.uk"; // fallback for SSR
};

// Get the correct path for DiscoveryNext links
const getDiscoveryNextPath = (path) => {
if (typeof window !== "undefined") {
// Handle local development - DiscoveryNext is on root
if (
window.location.hostname === "localhost" ||
window.location.hostname === "127.0.0.1"
) {
return path; // e.g., "/" becomes "/"
}
// Production - DiscoveryNext is under /search
return `/search${path}`; // e.g., "/" becomes "/search/"
}
return `/search${path}`; // fallback for SSR
};

const getQuestion = useCallback(
(qidx) => {
return apiData.instruments
Expand Down Expand Up @@ -241,6 +278,30 @@ export default function Results({
.flat()
),
]);

// Extract unique instrument names
const instruments = [
...new Set(
cm
.map((m) => {
const q = getQuestion(m.qi);
const mq = getQuestion(m.mqi);
let inst = [];
if (q.instrument && q.instrument.name) {
inst.push(q.instrument.name);
}
if (mq.instrument && mq.instrument.name) {
inst.push(mq.instrument.name);
}
return inst;
})
.flat()
.filter(Boolean)
),
];
setUniqueInstruments(instruments);
} else {
setUniqueInstruments([]);
}
}, [resultsOptions, apiData, setComputedMatches, getQuestion]);

Expand Down Expand Up @@ -395,6 +456,95 @@ export default function Results({
</Stack>
</Card>
)}
{/* Discovery Card - Harmony Discovery Links */}
{(topics.length > 0 ||
(SHOW_INSTRUMENTS_CHIP && uniqueInstruments.length > 0)) && (
<Card
variant="outlined"
sx={{
display: "flex",
width: "100%",
height: { xs: "10rem", sm: "8rem" },
padding: "0.5rem",
margin: "0 0 1rem 0",
justifyContent: "space-between",
alignItems: "center",
textAlign: "center",
}}
>
<Stack
direction={"row"}
spacing={2}
sx={{
height: "100%",
width: "100%",
justifyContent: "space-around",
alignItems: "center",
}}
>
<img
style={{ height: "4rem", width: "unset" }}
src="/app/harmony.png"
alt="Harmony Logo"
/>

<Box
sx={{
display: "flex",
height: "100%",
flexDirection: "column",
justifyContent: "space-between",
alignItems: "center",
}}
>
<Typography variant="caption" sx={{ fontWeight: 600 }}>
Discover:
</Typography>
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 1, justifyContent: "center" }}>
{topics.length > 0 && (
<Chip
label="Studies with matching topics"
component="a"
href={`${getCurrentDomain()}${getDiscoveryNextPath("/")}?${topics.map(t => `topics=${encodeURIComponent(t)}`).join("&")}`}
target="HarmonyDiscovery"
clickable
sx={{
cursor: "pointer",
"&:hover": {
backgroundColor: "action.hover",
},
}}
onClick={(e) => {
e.stopPropagation();
}}
/>
)}
{SHOW_INSTRUMENTS_CHIP && uniqueInstruments.length > 0 && (
<Chip
label="Studies using the same instruments"
component="a"
href={`${getCurrentDomain()}${getDiscoveryNextPath("/")}?${uniqueInstruments.map(i => `instruments=${encodeURIComponent(i)}`).join("&")}`}
target="HarmonyDiscovery"
clickable
sx={{
cursor: "pointer",
"&:hover": {
backgroundColor: "action.hover",
},
}}
onClick={(e) => {
e.stopPropagation();
}}
/>
)}
</Box>
<Typography variant="caption" sx={{ visibility: "hidden" }}>
Placeholder
</Typography>
</Box>
</Stack>
</Card>
)}
{computedMatches &&
computedMatches.slice(0, 100).map((i) => {
return (
Expand Down
Loading
Loading