mirror of
https://github.com/Tony0410/quietthanks.git
synced 2026-05-25 05:41:38 +08:00
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>
This commit is contained in:
65
src/components/WeeklyReflection.tsx
Normal file
65
src/components/WeeklyReflection.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
"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);
|
||||
}
|
||||
Reference in New Issue
Block a user