Files
recall/src/app/notes/[id]/page.tsx
Daniel Arroyo 05b8f3910d feat: MVP-3 Sprint 1 - Usage tracking, smart dashboard, scoring boost
## Registro de Uso
- Nuevo modelo NoteUsage en Prisma
- Tipos de eventos: view, search_click, related_click, link_click, copy_command, copy_snippet
- Funciones: trackNoteUsage, getUsageStats, getRecentlyUsedNotes
- localStorage: recentlyViewed (últimas 10 notas)
- Rastreo de copias en markdown-content.tsx

## Dashboard Rediseñado
- 5 bloques: Recientes, Más usadas, Comandos recientes, Snippets recientes, Según actividad
- Nuevo src/lib/dashboard.ts con getDashboardData()
- Recomendaciones basadas en recentlyViewed

## Scoring con Uso Real
- search.ts: +1 per 5 views (max +3), +2 recency boost
- related.ts: mismo sistema de usage boost
- No eclipsa match textual fuerte

## Tests
- 110 tests pasando (usage, dashboard, related, search)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 16:03:14 -03:00

101 lines
3.3 KiB
TypeScript

import { prisma } from '@/lib/prisma'
import { notFound } from 'next/navigation'
import { RelatedNotes } from '@/components/related-notes'
import { getRelatedNotes } from '@/lib/related'
import { MarkdownContent } from '@/components/markdown-content'
import { DeleteNoteButton } from '@/components/delete-note-button'
import { TrackNoteView } from '@/components/track-note-view'
import Link from 'next/link'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { ArrowLeft, Edit, Heart, Pin } from 'lucide-react'
import { NoteType } from '@/types/note'
const typeColors: Record<string, string> = {
command: 'bg-green-100 text-green-800',
snippet: 'bg-blue-100 text-blue-800',
decision: 'bg-purple-100 text-purple-800',
recipe: 'bg-orange-100 text-orange-800',
procedure: 'bg-yellow-100 text-yellow-800',
inventory: 'bg-gray-100 text-gray-800',
note: 'bg-slate-100 text-slate-800',
}
export default async function NoteDetailPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params
const note = await prisma.note.findUnique({
where: { id },
include: { tags: { include: { tag: true } } },
})
if (!note) {
notFound()
}
const related = await getRelatedNotes(id, 5)
const noteType = note.type as NoteType
return (
<>
<TrackNoteView noteId={note.id} />
<main className="container mx-auto py-8 px-4 max-w-4xl">
<div className="mb-6">
<Link href="/notes">
<Button variant="ghost" size="sm">
<ArrowLeft className="h-4 w-4 mr-1" /> Volver
</Button>
</Link>
</div>
<div className="flex items-start justify-between gap-4 mb-6">
<div>
<h1 className="text-3xl font-bold mb-2">{note.title}</h1>
<div className="flex items-center gap-3">
<Badge className={typeColors[noteType] || typeColors.note}>
{noteType}
</Badge>
{note.isFavorite && <Heart className="h-5 w-5 text-pink-500 fill-pink-500" />}
{note.isPinned && <Pin className="h-5 w-5 text-amber-500" />}
<span className="text-sm text-gray-500">
Actualizada: {new Date(note.updatedAt).toLocaleDateString('en-CA')}
</span>
</div>
</div>
<div className="flex gap-2">
<Link href={`/edit/${note.id}`}>
<Button variant="outline" size="sm">
<Edit className="h-4 w-4 mr-1" /> Editar
</Button>
</Link>
<DeleteNoteButton noteId={note.id} noteTitle={note.title} />
</div>
</div>
{note.tags.length > 0 && (
<div className="flex flex-wrap gap-2 mb-6">
{note.tags.map(({ tag }) => (
<Link key={tag.id} href={`/notes?tag=${tag.name}`}>
<Badge variant="outline" className="cursor-pointer hover:bg-gray-100">
{tag.name}
</Badge>
</Link>
))}
</div>
)}
<div className="mb-8">
<MarkdownContent
content={note.content}
noteType={noteType}
className="bg-gray-50 p-4 rounded-lg border"
/>
</div>
{related.length > 0 && (
<RelatedNotes notes={related} />
)}
</main>
</>
)
}