Files
quietthanks/src/components/WeeklyReflection.tsx
Gemini Agent 5555c1e6b5 Initial commit: Quiet Thanks gratitude app
A calm, private gratitude and mood log built with Next.js 16, TypeScript,
Tailwind CSS, and SQLite/Drizzle ORM.

Features:
- Quick check-in with autosave (800ms debounce)
- Optional mood selector (5 levels) with accessibility labels
- Optional tags with tap-to-add from recent
- Timeline with weekly reflection card
- Filters by mood, tag, and rough day
- Export to Markdown and JSON
- Dark mode default
- Delete with undo toast
- Docker deployment ready

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 01:57:20 +00:00

66 lines
1.9 KiB
TypeScript

"use client";
import type { EntryWithTags } from "@/lib/types";
import { isWithinWeek } from "@/lib/utils/date";
interface WeeklyReflectionProps {
entries: EntryWithTags[];
}
export function WeeklyReflection({ entries }: WeeklyReflectionProps) {
// Filter to last 7 days
const weekEntries = entries.filter((e) => isWithinWeek(e.date));
if (weekEntries.length === 0) {
return null;
}
// Count tag occurrences
const tagCounts = new Map<string, { name: string; count: number }>();
for (const entry of weekEntries) {
for (const tag of entry.tags) {
const existing = tagCounts.get(tag.id);
if (existing) {
existing.count++;
} else {
tagCounts.set(tag.id, { name: tag.name, count: 1 });
}
}
}
// Get top 3 tags
const topTags = Array.from(tagCounts.values())
.sort((a, b) => b.count - a.count)
.slice(0, 3);
// Generate summary
let summary = "";
if (topTags.length > 0) {
const tagNames = topTags.map((t) => capitalize(t.name));
if (tagNames.length === 1) {
summary = `You mentioned: ${tagNames[0]}.`;
} else if (tagNames.length === 2) {
summary = `You mentioned: ${tagNames[0]} and ${tagNames[1]}.`;
} else {
summary = `You mentioned: ${tagNames.slice(0, -1).join(", ")}, and ${tagNames[tagNames.length - 1]}.`;
}
}
return (
<div className="bg-surface border border-border rounded-xl p-4 mb-6">
<h2 className="text-sm font-medium text-muted mb-2">This week</h2>
<div className="flex items-baseline gap-4 mb-2">
<span className="text-2xl font-light">{weekEntries.length}</span>
<span className="text-sm text-muted">
{weekEntries.length === 1 ? "entry" : "entries"}
</span>
</div>
{summary && <p className="text-sm text-muted italic">{summary}</p>}
</div>
);
}
function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}