Files
study-bible-project/src/pages/InsightsPage.jsx
T
nmemmertandClaude Sonnet 5.5 eccc5e509d Studying: daily five minutes, reading list, plan-day studies use your template
- Home: "Your five minutes": a rotating Observe/Interpret/Apply prompt on the
  passage whose matching note is thinnest in your own words, a 5:00 clock and a
  box that appends to that note. Pasting is blocked; sessions show in Insights.
- Study: a Reading list tab (R) for the study: books and articles to consult,
  with a note and a tick; merges across devices.
- Starting a study from a reading-plan day now uses the notes template you last
  picked when starting a study by hand (logic moved to utils/planStudy.js).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 13:51:49 -04:00

108 lines
6.0 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Insights: your studies seen together — this week's own work, themes (tags), words you keep studying,
// what slipped in recalls, and follow-ups still open.
import { useMemo } from 'react';
import { useApp } from '../context/AppContext.js';
import PageShell from '../components/study/PageShell.jsx';
import { loadProjectById } from '../utils/projectStorage.js';
import { followUpList, slippedList, themeIndex, weeklySummary, wordIndex } from '../utils/insights.js';
const card = 'rounded-2xl border border-[var(--r-line)] bg-[var(--r-bar)] p-5';
const open = 'text-xs text-[var(--r-accent)] hover:underline';
function Stat({ label, value }) {
return (
<div className="rounded-xl border border-[var(--r-line)] bg-[var(--r-bg)] px-4 py-3">
<p className="text-2xl font-medium tabular-nums">{value}</p>
<p className="text-xs text-[var(--r-sub)]">{label}</p>
</div>
);
}
export default function InsightsPage() {
const { projectIndex, openStudyChunk, goHome } = useApp();
const data = useMemo(() => {
const projects = projectIndex.map((e) => loadProjectById(e.id)).filter(Boolean);
return { week: weeklySummary(projects), themes: themeIndex(projects), words: wordIndex(projects), slipped: slippedList(projects), followUps: followUpList(projects) };
}, [projectIndex]);
const { week, themes, words, slipped, followUps } = data;
const link = (item) => <button type="button" onClick={() => openStudyChunk(item.projectId, item.chunkId)} className={open}>{item.reference} · {item.projectTitle}</button>;
return (
<PageShell title="Insights" active="studies" onBack={goHome} maxWidth="max-w-4xl">
<main className="mx-auto max-w-4xl space-y-5 px-4 pb-10">
<section aria-label="This week" className={card}>
<h2 className="text-sm font-medium">This week</h2>
<p className="mb-3 mt-0.5 text-xs text-[var(--r-sub)]">Counted over the chunks you edited in the last 7 days. Pasted text isn’t counted as yours.</p>
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
<Stat label="Chunks worked on" value={week.chunksTouched} />
<Stat label="Words of your own" value={week.ownWords} />
<Stat label="Your own words" value={week.ownShare === null ? '–' : `${week.ownShare}%`} />
<Stat label="Recalls done" value={week.recalls} />
</div>
{(week.teachBacks > 0 || week.fiveMinutes > 0) && <p className="mt-2 text-xs text-[var(--r-sub)]">{[week.fiveMinutes > 0 && `${week.fiveMinutes} five-minute session${week.fiveMinutes === 1 ? '' : 's'}`, week.teachBacks > 0 && `${week.teachBacks} teach-it-back summar${week.teachBacks === 1 ? 'y' : 'ies'}`].filter(Boolean).join(' · ')} this week.</p>}
</section>
<section aria-label="Open follow-ups" className={card}>
<h2 className="text-sm font-medium">Follow-ups</h2>
{followUps.length === 0 ? <p className="mt-1 text-xs text-[var(--r-sub)]">None yet. Add them under “More” in the Study stage.</p> : (
<ul className="mt-2 space-y-1.5 text-sm">
{followUps.map((f) => (
<li key={f.id} className="flex flex-wrap items-baseline gap-x-3">
<span className={f.done ? 'text-[var(--r-sub)] line-through' : ''}>{f.text}</span>
{link(f)}
</li>
))}
</ul>
)}
</section>
<section aria-label="What slipped" className={card}>
<h2 className="text-sm font-medium">What slipped in recalls</h2>
{slipped.length === 0 ? <p className="mt-1 text-xs text-[var(--r-sub)]">Nothing yet. After a weekly recall, note what you missed.</p> : (
<ul className="mt-2 space-y-3 text-sm">
{slipped.map((r) => (
<li key={`${r.chunkId}-${r.at}`}>
<p className="text-xs text-[var(--r-sub)]">{new Date(r.at).toLocaleDateString()} · {link(r)}</p>
<ul className="ml-4 list-disc">{r.items.map((i) => <li key={i}>{i}</li>)}</ul>
</li>
))}
</ul>
)}
</section>
<section aria-label="Themes" className={card}>
<h2 className="text-sm font-medium">Themes</h2>
{themes.length === 0 ? <p className="mt-1 text-xs text-[var(--r-sub)]">Tag a chunk (under “More”) and it appears here with every other chunk on the same theme.</p> : (
<div className="mt-2 space-y-3">
{themes.map((t) => (
<details key={t.tag} className="rounded-xl border border-[var(--r-line)] bg-[var(--r-bg)] px-3 py-2">
<summary className="cursor-pointer text-sm"><span className="font-medium">{t.tag}</span> <span className="text-xs text-[var(--r-sub)]">· {t.count} chunk{t.count === 1 ? '' : 's'}</span></summary>
<ul className="mt-2 space-y-2">
{t.items.map((i) => (
<li key={`${i.chunkId}`} className="text-sm">{link(i)}{i.snippet && <p className="font-serif text-[13px] text-[var(--r-sub)]">{i.snippet}</p>}</li>
))}
</ul>
</details>
))}
</div>
)}
</section>
<section aria-label="Words you keep studying" className={card}>
<h2 className="text-sm font-medium">Words you keep studying</h2>
{words.length === 0 ? <p className="mt-1 text-xs text-[var(--r-sub)]">Words you study in two or more places show up here.</p> : (
<ul className="mt-2 space-y-2 text-sm">
{words.map((w) => (
<li key={w.key}>
<span className="font-serif text-base">{w.lexeme}</span> <span className="text-xs text-[var(--r-sub)]">{[w.transliteration, w.strong, w.gloss].filter(Boolean).join(' · ')} · {w.count} places</span>
<span className="mt-0.5 flex flex-wrap gap-x-3">{w.places.map((p) => <span key={p.chunkId}>{link(p)}</span>)}</span>
</li>
))}
</ul>
)}
</section>
</main>
</PageShell>
);
}