Add Insights, Observe-first, compare, handout layouts, richer search, backup nudge
- Commentary and study aids wait for 15 words of your own Observe notes. - Insights page: this week's own work, themes across studies, words you keep studying, what slipped in recalls, open follow-ups. - Recall can record what slipped; session notes (teach it back, who came, group notes, follow-ups); "Pray about this" from Apply. - Compare any two passages side by side. - Handout layouts: standard, questions only, large print, note-taking. - Search understands "in <book>", "last week/month/year" and a year. - Home shows the last backup age and nudges after two weeks. - Phone: the stage bar scrolls instead of clipping Script and Export. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2ddc4e1ff6
commit
d8e522dd06
24 files changed
+540
-28
No files matched your search
@@ -0,0 +1,107 @@
|
||||
// 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 && <p className="mt-2 text-xs text-[var(--r-sub)]">{week.teachBacks} teach-it-back summar{week.teachBacks === 1 ? 'y' : 'ies'} written.</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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user