Files
recall/src/app/notes/page.tsx
T
darroyo e66a678160 feat: MVP-5 P2 - Export/Import, Settings, Tests y Validaciones
- Ticket 10: Navegación completa de listas por teclado (↑↓ Enter E F P)
- Ticket 13: Historial de navegación contextual con recent-context-list
- Ticket 17: Exportación mejorada a Markdown con frontmatter
- Ticket 18: Exportación HTML simple y legible
- Ticket 19: Importador Markdown mejorado con frontmatter, tags, wiki links
- Ticket 20: Importador Obsidian-compatible (wiki links, #tags inline)
- Ticket 21: Centro de respaldo y portabilidad en Settings
- Ticket 22: Configuración visible de feature flags
- Ticket 24: Tests de command palette y captura externa
- Ticket 25: Harden de validaciones y límites (50MB backup, 10K notas, etc)
2026-03-22 19:39:55 -03:00

94 lines
2.8 KiB
TypeScript

import { prisma } from '@/lib/prisma'
import { KeyboardNavigableNoteList } from '@/components/keyboard-navigable-note-list'
import { KeyboardHint } from '@/components/keyboard-hint'
import { SearchBar } from '@/components/search-bar'
import { TagFilter } from '@/components/tag-filter'
import { NoteType } from '@/types/note'
const NOTE_TYPES: NoteType[] = ['command', 'snippet', 'decision', 'recipe', 'procedure', 'inventory', 'note']
interface SearchParams {
q?: string
type?: string
tag?: string
}
async function searchNotes(searchParams: SearchParams) {
const where: Record<string, unknown> = {}
if (searchParams.q) {
where.OR = [
{ title: { contains: searchParams.q } },
{ content: { contains: searchParams.q } },
]
}
if (searchParams.type && NOTE_TYPES.includes(searchParams.type as NoteType)) {
where.type = searchParams.type
}
if (searchParams.tag) {
where.tags = {
some: {
tag: { name: searchParams.tag },
},
}
}
const notes = await prisma.note.findMany({
where,
include: { tags: { include: { tag: true } } },
orderBy: [{ isPinned: 'desc' }, { updatedAt: 'desc' }],
})
return notes
}
async function getAllTags() {
const tags = await prisma.tag.findMany({
orderBy: { name: 'asc' },
})
return tags.map((t) => t.name)
}
export default async function NotesPage({ searchParams }: { searchParams: Promise<SearchParams> }) {
const params = await searchParams
const [notes, tags] = await Promise.all([searchNotes(params), getAllTags()])
const notesWithTags = notes.map(note => ({
...note,
type: note.type as NoteType,
createdAt: note.createdAt.toISOString(),
updatedAt: note.updatedAt.toISOString(),
tags: note.tags.map(nt => ({ tag: nt.tag })),
}))
const hasFilters = params.q || params.type || params.tag
return (
<main className="container mx-auto py-8 px-4">
<div className="flex flex-col sm:flex-row gap-4 items-start sm:items-center justify-between mb-6">
<h1 className="text-2xl font-bold">
{hasFilters ? 'Resultados de búsqueda' : 'Todas las notas'}
</h1>
<div className="flex flex-col sm:flex-row gap-2 items-stretch sm:items-center w-full sm:w-auto">
<div className="w-full sm:w-auto">
<SearchBar />
</div>
<TagFilter tags={tags} selectedTag={params.tag || null} />
</div>
</div>
{hasFilters && (
<div className="flex flex-wrap gap-2 mb-4">
{params.q && <span className="text-sm">Búsqueda: &quot;{params.q}&quot;</span>}
{params.type && <span className="text-sm">Tipo: {params.type}</span>}
{params.tag && <span className="text-sm">Tag: {params.tag}</span>}
</div>
)}
<KeyboardNavigableNoteList notes={notesWithTags} />
<KeyboardHint />
</main>
)
}