Each card shows the Team Health stage and the Meeting Effectiveness response rate for{" "}
{scope === "all"
? `the entire study (Wk 1\u2013${currentWeek}, ${currentWeek} week${currentWeek > 1 ? "s" : ""})`
: scope === currentWeek
? "this week"
: `Wk ${scope}`}
. Open a project for QR codes and outstanding people.
Meeting response
{teams.map(t => (
onPickProject(t.id)}/>
))}
{/* Response activity log */}
Response activity
Submissions in the selected range — {fmtMD(scopeStartDate)} – {fmtMDY(scopeEndDate)}. Anonymous — we never see who submitted.
{scope === "all" ? "All study" : `Week ${scope}`}
{/* Footer note */}
Every survey is anonymous. We only see counts and team-level summaries — never an individual's answers.
Health scores and qualitative findings are reviewed by the TAMU research team and shared back at study milestones.
);
}
// ---------- Team Health cohort timeline ----------
// Two data points per project: Baseline (kickoff) and Post-Health (~6mo in).
// Renders one row per project with two milestones — done dot vs open ring,
// connected by a horizontal time rail. People-level submission count on the right.
function TeamHealthCohortTimeline({ teams, totalPeople, baselinePeopleSubmitted, postHealthPeopleSubmitted, nextPostOpens }) {
const fmt = (iso) => {
if (!iso) return "";
const d = new Date(iso + "T00:00:00");
return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
};
const fmtLong = (iso) => {
if (!iso) return "";
const d = new Date(iso + "T00:00:00");
return d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
};
// Sort: baseline-pending first, then by kickoff date
const ordered = [...teams].sort((a, b) => {
if (a.preDone !== b.preDone) return a.preDone ? 1 : -1;
return a.kickoffDate.localeCompare(b.kickoffDate);
});
const baselineDone = teams.filter(t => t.preDone).length;
const postDone = teams.filter(t => t.postDone).length;
const [expanded, setExpanded] = useStatePM(false);
const baselinePct = totalPeople ? Math.round((baselinePeopleSubmitted / totalPeople) * 100) : 0;
const postPct = totalPeople ? Math.round((postHealthPeopleSubmitted / totalPeople) * 100) : 0;
return (
{/* HEADER — always visible (expansion no longer needed; detail is always open) */}
Team Health · Pre → Post
{teams.length} projects · two waves each
{/* Baseline progress bar only */}
Baseline
{baselinePeopleSubmitted} / {totalPeople}
{baselineDone} of {teams.length} projects complete
{/* Per-project status pip strip — at-a-glance health of all 11 */}
{ordered.map(t => (
))}
{/* DETAIL — full per-project timeline (always shown) */}
Showing latest {rows.length} · all submissions are anonymous.
);
}
function TrendChip({ trend }) {
const pts = Math.round(trend * 100);
if (Math.abs(pts) < 1) {
return (
Steady vs last week
);
}
const up = pts > 0;
const color = up ? "oklch(0.42 0.12 145)" : "oklch(0.5 0.14 28)";
const bg = up ? "oklch(0.96 0.04 145)" : "oklch(0.97 0.03 28)";
return (
{up ? "+" : ""}{pts} pts vs last week
);
}
// Compact stage pill — used in the cohort slim status strip
function StagePill({ count, label, color }) {
return (
{count}{label}
);
}
// Stage funnel cell — used in the cohort Team Health card
function StageCell({ count, total, label, hint, color, last }) {
const pct = total ? Math.round((count / total) * 100) : 0;
return (
{/* color rail */}
{count}of {total}
{label}
{hint}
);
}
// Bottom strip stat (cohort hero footer)
function BottomStat({ label, value, hint }) {
return (
{label}
{value}
{hint &&
{hint}
}
);
}
function StatusRow({ count, total, label, hint, color, last }) {
return (
Suggestions based on Team Health stage and Meeting Effectiveness response trend.
{/* Privacy footer */}
Survey responses are anonymous. We only see counts and team-level averages — never who said what.
Health scores and qualitative findings are reviewed by the TAMU research team and shared back at study milestones.
);
}
// Team Health stage panel — right column on the project detail.
// Shows: current stage, Baseline progress, Post-Health window, what to share via QR.
function TeamHealthStagePanel({ team }) {
const stage = teamHealthStage(team);
const stageC = stageColor(stage.id);
// Use real submission counts from backend
const baselineSubmitted = team.baselineSubmitted || 0;
const postSubmitted = team.postHealthSubmitted || 0;
const stages = [
{ id: "baseline-pending", label: "Baseline pending" },
{ id: "baseline-complete", label: "Baseline complete" },
{ id: "post-window", label: "Post-Health window" },
{ id: "complete", label: "Complete" },
];
// current step index: post-window also implies baseline complete
const currentIdx = stage.id === "complete" ? 3 : stage.id === "post-window" ? 2 : (team.preDone ? 1 : 0);
return (
{stage.id === "baseline-pending" && (
<>Share the Team Health QR at the next huddle. Goal: every team member submits the kickoff survey within the first two weeks.>
)}
{stage.id === "post-window" && (
<>The 6-month window has opened. Re-share the Team Health QR with your team to capture the Post-Health wave.>
)}
{stage.id === "complete" && (
<>Both Team Health waves are submitted. Continue running Meeting Effectiveness weekly until the project closes.>
)}
);
}
function SurveyWaveRow({ waveLabel, sublabel, done, locked, submitted, total, color }) {
const pct = total ? Math.round((submitted / total) * 100) : 0;
return (
{waveLabel}
{sublabel}
{locked ? (
Opens after Baseline
) : (
<>
{submitted}/{total}
{done ? "Submitted" : `${pct}% submitted`}
>
)}
{!locked && (
)}
);
}
function QRHero({ slug, kind, label, sub }) {
return (
);
}
function MiniStat({ label, value, hint }) {
return (
{label}
{value}
{hint &&
{hint}
}
);
}
// Prioritized action cards — covers BOTH instruments:
// Team Health (Baseline + Post-Health, 2 waves) and Meeting Effectiveness (weekly).
// No "send reminder" buttons — the PM's lever is sharing the QR code at huddles.
function PMSimpleNotes({ team, currentWeek, studyStart }) {
const wk = team.weekly.find(w => w.week === currentWeek) || team.weekly[team.weekly.length - 1];
const lastWk = team.weekly.find(w => w.week === currentWeek - 1);
const trend = lastWk ? wk.rate - lastWk.rate : 0;
const allWeeksTrend = team.weekly[team.weekly.length - 1].rate - team.weekly[0].rate;
const avgRate = avg(team.weekly.map(w => w.rate));
const stage = teamHealthStage(team);
const items = [];
// ─── Team Health (2-wave) priorities ─────────────────────────────────────
if (stage.id === "baseline-pending") {
items.push({
kind: "alert",
title: "Baseline survey hasn't been completed yet",
body: `Team Health runs only twice per project — Baseline at kickoff and Post-Health ~6 months later. Share the Team Health QR with the team this week so we have a starting point for the study.`,
});
} else if (stage.id === "post-window") {
items.push({
kind: "neutral",
title: "Post-Health window is open",
body: `It's been ~6 months since kickoff. Re-share the Team Health QR with the team to capture the second wave. Once submitted, Team Health is complete for this project.`,
});
} else if (stage.id === "complete") {
items.push({
kind: "good",
title: "Both Team Health waves are complete",
body: `Baseline and Post-Health are both submitted. The TAMU research team will share the comparison findings at the next milestone review.`,
});
}
// ─── Meeting Effectiveness (weekly) priorities ──────────────────────────
if (wk.rate < 0.9) {
items.push({
kind: "alert",
title: `Meeting Effectiveness response is ${Math.round(wk.rate * 100)}% this week`,
body: `${wk.responses} of ${team.size} submitted this week's pulse. Posting the Meeting Effectiveness QR at the next huddle usually closes the gap.`,
});
} else if (trend < -0.15) {
items.push({
kind: "alert",
title: `Meeting response dropped ${Math.abs(Math.round(trend * 100))} points week-over-week`,
body: `Down from ${Math.round(lastWk.rate * 100)}% last week to ${Math.round(wk.rate * 100)}% this week. Worth a quick check-in to see if anything's changed for the team.`,
});
}
// Trend insights — Meeting Effectiveness
if (trend > 0.1) {
items.push({
kind: "good",
title: `Meeting response up ${Math.round(trend * 100)} points week-over-week`,
body: `Whatever you did, keep it going.`,
});
}
if (allWeeksTrend > 0.15) {
items.push({
kind: "good",
title: "Meeting Effectiveness participation has steadily improved",
body: `Up ${Math.round(allWeeksTrend * 100)} points since week 1. The team is building the weekly habit.`,
});
}
if (allWeeksTrend < -0.15 && wk.rate >= 0.9) {
items.push({
kind: "alert",
title: "Meeting response trending down across the study",
body: `Down ${Math.abs(Math.round(allWeeksTrend * 100))} points since week 1. Consider a team huddle on why.`,
});
}
if (wk.responses === team.size) {
items.push({
kind: "good",
title: "Everyone submitted this week's Meeting pulse",
body: `Full weekly participation — that's the bar. This week's data will be most reliable.`,
});
}
// Fallback
if (items.length === 0) {
items.push({
kind: "good",
title: "On track on both surveys",
body: `Meeting Effectiveness averaging ${Math.round(avgRate * 100)}% across the study, and Team Health is ${stage.label.toLowerCase()}.`,
});
}
return (