feat: implement dashboard layout with sidebar navigation, finance page, and supporting UI architecture

This commit is contained in:
Poyraz Avsever
2026-05-08 14:12:26 +03:00
parent e0a2dace49
commit a21c64172e
7 changed files with 921 additions and 422 deletions
+134 -228
View File
@@ -1,245 +1,151 @@
"use client";
import { useState } from "react";
import { useLiveQuery } from "dexie-react-hooks";
import { v4 as uuidv4 } from "uuid";
import { Plus, Search, Filter, Brain, PenTool, Calendar, AlignLeft, Tag, Lock, Sparkles, Image as ImageIcon } from "lucide-react";
import { analyzeJournalWithLocalAI } from "@/lib/ai";
import { db } from "@/lib/db";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
const moods = [
{ id: "happy", label: "Mutlu", emoji: "😊" },
{ id: "neutral", label: "Nötr", emoji: "😐" },
{ id: "sad", label: "Üzgün", emoji: "😔" },
{ id: "angry", label: "Sinirli", emoji: "😠" },
// Mock Data
const journalEntries = [
{
id: 1,
title: "Breakthrough in System Design",
preview: "Today we finally cracked the database architecture. By moving to a distributed model...",
date: "Today, 10:45 AM",
tags: ["Work", "Idea", "Win"],
sentiment: "Positive"
},
{
id: 2,
title: "Feeling a bit overwhelmed",
preview: "The Q3 deadlines are approaching fast. I need to make sure I'm prioritizing...",
date: "Yesterday",
tags: ["Mental Health", "Vent"],
sentiment: "Anxious"
},
{
id: 3,
title: "Morning Routine Reflection",
preview: "Woke up at 6 AM. Did 20 minutes of meditation. It really sets the tone for...",
date: "May 06, 2026",
tags: ["Habits", "Morning"],
sentiment: "Calm"
}
];
export default function JournalPage() {
const [content, setContent] = useState("");
const [mood, setMood] = useState("happy");
const [energy, setEnergy] = useState("3");
const [isSubmitting, setIsSubmitting] = useState(false);
// Dexie.js üzerinden günlükleri tarih sırasına göre çekiyoruz (en yeni en üstte).
const journals = useLiveQuery(() =>
db.journals.orderBy("date").reverse().toArray(),
);
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
if (!content.trim()) return;
setIsSubmitting(true);
const now = new Date().toISOString();
const entryId = uuidv4();
const currentContent = content;
try {
// Veriyi önce kaydet, AI analizini daha sonra arka planda tamamla.
await db.journals.add({
id: entryId,
date: now.split("T")[0],
mood,
energy: parseInt(energy, 10),
content: currentContent,
created_at: now,
updated_at: now,
});
setContent("");
setEnergy("3");
setMood("happy");
try {
const aiResult = await analyzeJournalWithLocalAI(currentContent);
if (aiResult) {
await db.journals.update(entryId, {
ai_tags: aiResult.ai_tags,
ai_sentiment_score: aiResult.ai_sentiment_score,
ai_summary: aiResult.ai_summary,
});
if (aiResult.suggested_tasks?.length) {
for (const taskTitle of aiResult.suggested_tasks) {
await db.tasks.add({
id: uuidv4(),
journal_id: entryId,
title: `AI Önerisi: ${taskTitle}`,
status: "todo",
ai_generated: true,
date: now.split("T")[0],
created_at: now,
});
}
}
}
} catch (aiError) {
console.error("Arka plan AI analizi hatası:", aiError);
}
} catch (error) {
console.error("Günlük kaydedilemedi:", error);
} finally {
setIsSubmitting(false);
}
};
const handleDelete = async (id: string) => {
if (confirm("Bu günlüğü silmek istediğinden emin misin?")) {
await db.journals.delete(id);
}
};
return (
<div className="mx-auto max-w-4xl animate-in space-y-6 fade-in slide-in-from-bottom-4 duration-500">
<div>
<h1 className="text-3xl font-bold tracking-tight">Günlük</h1>
<p className="text-muted-foreground">
Zihnini boşalt, hislerini kaydet. Verilerin sadece cihazında kalır.
</p>
<div className="mx-auto max-w-7xl animate-in fade-in slide-in-from-bottom-4 duration-500 h-full flex flex-col text-foreground font-sans space-y-6">
{/* Top Header */}
<div className="flex items-center justify-between pb-4 border-b border-white/5 mt-4">
<h1 className="text-lg font-medium text-muted-foreground">
<span className="text-foreground">Personal</span> / Daily Journal
</h1>
<div className="flex items-center gap-3">
<div className="bg-[#150F1D] border border-white/5 rounded-sm px-3 py-1.5 flex items-center gap-2">
<Search className="h-4 w-4 text-muted-foreground" />
<input
type="text"
placeholder="Search memories..."
className="bg-transparent border-none outline-none text-xs w-48 placeholder:text-muted-foreground/50 text-foreground"
/>
</div>
<button className="bg-primary hover:bg-primary/90 text-primary-foreground border border-primary/20 px-4 py-1.5 rounded-sm text-xs font-semibold flex items-center gap-2 transition-colors">
<Plus className="h-4 w-4" />
NEW ENTRY
</button>
</div>
</div>
<Card>
<CardHeader>
<CardTitle>Yeni Kayıt</CardTitle>
<CardDescription>Bugün nasıl hissediyorsun?</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label>Ruh Hali</Label>
<div className="flex gap-2">
{moods.map((currentMood) => (
<button
key={currentMood.id}
type="button"
onClick={() => setMood(currentMood.id)}
className={`flex flex-1 items-center justify-center rounded-md border px-1 py-2 text-xl transition-colors ${
mood === currentMood.id
? "border-primary bg-primary/20"
: "border-border bg-card hover:bg-muted"
}`}
title={currentMood.label}
>
{currentMood.emoji}
</button>
))}
</div>
</div>
{/* Main Layout */}
<div className="flex flex-col lg:flex-row gap-6 flex-1 min-h-0">
{/* Left Panel: Entry List & AI */}
<div className="w-full lg:w-80 flex flex-col gap-6 shrink-0">
{/* AI Sentiment Analysis */}
<div className="rounded-sm border border-primary/20 bg-[linear-gradient(135deg,rgba(108,91,176,0.1)_0%,rgba(10,7,16,0)_100%)] p-5">
<h3 className="text-sm font-semibold text-primary mb-2 flex items-center gap-2">
<Brain className="h-4 w-4" /> AI Reflection Insight
</h3>
<p className="text-[11px] text-foreground/80 leading-relaxed mb-3">
Over the last 14 days, your entries tagged with <strong>"Work"</strong> show a 30% increase in stress vocabulary. However, entries after <strong>Morning Meditation</strong> are consistently highly positive.
</p>
<button className="bg-primary/20 text-primary hover:bg-primary/30 border border-primary/30 px-3 py-1.5 rounded-sm text-[10px] font-bold uppercase tracking-wider transition-colors w-full">
Generate Weekly Summary
</button>
</div>
<div className="space-y-2">
<Label>Enerji Seviyesi (1-5): {energy}</Label>
<Input
type="range"
min="1"
max="5"
step="1"
value={energy}
onChange={(event) => setEnergy(event.target.value)}
className="w-full"
/>
</div>
{/* Entries List */}
<div className="flex-1 rounded-sm border border-white/5 bg-[#0A0710] flex flex-col overflow-hidden">
<div className="p-4 border-b border-white/5 flex justify-between items-center">
<span className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">Recent Entries</span>
<Filter className="h-4 w-4 text-muted-foreground hover:text-foreground cursor-pointer" />
</div>
<div className="space-y-2">
<Label htmlFor="content">Düşüncelerini buraya dök...</Label>
<Textarea
id="content"
value={content}
onChange={(event) => setContent(event.target.value)}
className="min-h-[150px] resize-y"
placeholder="Örneğin: Bugün toplantıda işler ters gitti..."
/>
</div>
<Button
type="submit"
disabled={isSubmitting}
className="w-full md:w-auto"
>
{isSubmitting ? "Kaydediliyor..." : "Kaydet"}
</Button>
</form>
</CardContent>
</Card>
<div className="space-y-4 pt-4">
<h2 className="text-xl font-bold">Geçmiş Kayıtlar</h2>
{!journals ? (
<p className="text-sm text-muted-foreground">Yükleniyor...</p>
) : journals.length === 0 ? (
<p className="text-sm text-muted-foreground">
Henüz kayıt bulunmuyor.
</p>
) : (
<div className="grid gap-4">
{journals.map((journal) => (
<Card key={journal.id}>
<CardHeader className="pb-2">
<div className="flex items-center justify-between">
<CardTitle className="flex items-center gap-2 text-base">
<span>
{moods.find((currentMood) => currentMood.id === journal.mood)
?.emoji ?? "📝"}
</span>
<span>{journal.date}</span>
</CardTitle>
<span className="rounded-md bg-muted px-2 py-1 text-xs font-medium text-muted-foreground">
Enerji: {journal.energy}/5
<div className="flex-1 overflow-y-auto tiny-scrollbar p-2 space-y-1">
{journalEntries.map((entry, idx) => (
<div key={entry.id} className={`p-3 rounded-sm cursor-pointer transition-colors ${idx === 0 ? 'bg-[#1F172B] border border-primary/30' : 'hover:bg-white/5 border border-transparent'}`}>
<h4 className="text-sm font-semibold text-foreground mb-1 truncate">{entry.title}</h4>
<p className="text-[11px] text-muted-foreground line-clamp-2 leading-relaxed mb-2">
{entry.preview}
</p>
<div className="flex justify-between items-center text-[10px]">
<span className="text-primary font-medium">{entry.date}</span>
<span className="flex gap-1">
{entry.tags.map(tag => (
<span key={tag} className="bg-white/10 px-1.5 py-0.5 rounded-sm">{tag}</span>
))}
</span>
</div>
</CardHeader>
<CardContent className="pt-2">
<p className="whitespace-pre-wrap text-sm leading-relaxed">
{journal.content}
</p>
{journal.ai_tags && (
<div className="mt-4 flex flex-wrap gap-2">
{journal.ai_tags.map((tag) => (
<span
key={tag}
className="rounded-md bg-primary/10 px-2 py-1 text-xs text-primary"
>
{tag}
</span>
))}
</div>
)}
{journal.ai_summary && (
<div className="mt-3 rounded-lg border border-l-2 border-l-primary bg-muted/40 p-3 text-sm italic text-muted-foreground">
&ldquo;{journal.ai_summary}&rdquo;
</div>
)}
<div className="mt-4 flex justify-end">
<Button
variant="ghost"
size="sm"
className="text-red-500 hover:bg-red-500/10 hover:text-red-600"
onClick={() => handleDelete(journal.id)}
>
Sil
</Button>
</div>
</CardContent>
</Card>
))}
</div>
))}
</div>
</div>
)}
</div>
{/* Right Panel: Editor Mock */}
<div className="flex-1 rounded-sm border border-white/5 bg-[#0A0710] flex flex-col relative overflow-hidden group">
{/* Editor Header */}
<div className="p-4 border-b border-white/5 flex justify-between items-center bg-[#0F0B15]/50">
<div className="flex items-center gap-4 text-xs text-muted-foreground">
<span className="flex items-center gap-1.5"><Calendar className="h-4 w-4" /> Today, 10:45 AM</span>
<span className="flex items-center gap-1.5"><Tag className="h-4 w-4" /> 3 Tags</span>
<span className="flex items-center gap-1.5"><Lock className="h-4 w-4" /> Private</span>
</div>
<div className="flex items-center gap-2">
<button className="p-1.5 hover:bg-white/10 rounded-sm text-muted-foreground transition-colors"><AlignLeft className="h-4 w-4" /></button>
<button className="p-1.5 hover:bg-white/10 rounded-sm text-muted-foreground transition-colors"><ImageIcon className="h-4 w-4" /></button>
<button className="p-1.5 hover:bg-primary/20 bg-primary/10 rounded-sm text-primary transition-colors flex items-center gap-1">
<Sparkles className="h-4 w-4" /> <span className="text-[10px] font-bold">Ask AI</span>
</button>
</div>
</div>
{/* Editor Body */}
<div className="flex-1 p-8 md:px-12 md:py-10 overflow-y-auto tiny-scrollbar">
<input
type="text"
value="Breakthrough in System Design"
readOnly
className="w-full bg-transparent border-none outline-none text-3xl font-bold text-foreground mb-6"
/>
<div className="text-foreground/80 leading-loose space-y-6 text-sm">
<p>
Today we finally cracked the database architecture. By moving to a distributed model, we've essentially solved the latency issues we were seeing during peak load. It feels incredibly satisfying to see weeks of research finally click into place.
</p>
<p>
I was discussing this with Alex earlier, and he pointed out that this structure directly mirrors the microservices split we planned for Q4. This means we are actually ahead of schedule!
</p>
<div className="pl-4 border-l-2 border-primary text-primary italic">
AI Note: You've mentioned "latency issues" in 3 previous entries. Consider documenting this final solution in the Engineering Wiki for future reference.
</div>
<p>
Gonna take the rest of the evening off to recharge. Need to keep this momentum going without burning out.
</p>
</div>
</div>
</div>
</div>
</div>
);