- general.continue/send - structuredViews.tagApplied/filterDone/filterTodo/propertyStatus - wizard.taskA/taskB - richTextEditor.preview*Tip (7 clés SlashPreview) - wizard.* au niveau racine (48 clés FR + 48 EN) - Total: 0 clé manquante pour FR et EN - 0 erreur TypeScript
131 lines
4.2 KiB
TypeScript
131 lines
4.2 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import type { Note } from '@/lib/types'
|
|
import type { NotebookSchemaPayload, NotePropertyValues } from '@/lib/structured-views/types'
|
|
import { formatPropertyDisplay } from '@/lib/structured-views/property-utils'
|
|
import { getNoteDisplayTitle, getNoteFeedImage } from '@/lib/note-preview'
|
|
import { useLanguage } from '@/lib/i18n'
|
|
import { sanitizeIllustrationSvg } from '@/lib/sanitize-content'
|
|
|
|
type NotesGalleryViewProps = {
|
|
notes: Note[]
|
|
schema: NotebookSchemaPayload
|
|
noteValues: Record<string, NotePropertyValues>
|
|
notebookColor?: string | null
|
|
onOpen: (note: Note) => void
|
|
}
|
|
|
|
export function NotesGalleryView({
|
|
notes,
|
|
schema,
|
|
noteValues,
|
|
notebookColor,
|
|
onOpen,
|
|
}: NotesGalleryViewProps) {
|
|
const { t } = useLanguage()
|
|
const untitled = t('notes.untitled')
|
|
const previewProps = schema.properties.slice(0, 2)
|
|
|
|
return (
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 max-w-7xl mx-auto">
|
|
{notes.map((note) => (
|
|
<GalleryCard
|
|
key={note.id}
|
|
note={note}
|
|
title={getNoteDisplayTitle(note, untitled)}
|
|
image={getNoteFeedImage(note)}
|
|
notebookColor={notebookColor}
|
|
previewProps={previewProps}
|
|
allProps={schema.properties}
|
|
values={noteValues[note.id] ?? {}}
|
|
onOpen={() => onOpen(note)}
|
|
/>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function GalleryCard({
|
|
note,
|
|
title,
|
|
image,
|
|
notebookColor,
|
|
previewProps,
|
|
allProps,
|
|
values,
|
|
onOpen,
|
|
}: {
|
|
note: Note
|
|
title: string
|
|
image: string | null
|
|
notebookColor?: string | null
|
|
previewProps: NotebookSchemaPayload['properties']
|
|
allProps: NotebookSchemaPayload['properties']
|
|
values: NotePropertyValues
|
|
onOpen: () => void
|
|
}) {
|
|
const [hover, setHover] = useState(false)
|
|
const accent = notebookColor || '#A47148'
|
|
|
|
return (
|
|
<button
|
|
type="button"
|
|
onClick={onOpen}
|
|
onMouseEnter={() => setHover(true)}
|
|
onMouseLeave={() => setHover(false)}
|
|
className="text-left rounded-2xl border border-border/40 bg-card/40 overflow-hidden shadow-sm hover:shadow-md hover:border-border transition-all group"
|
|
>
|
|
<div
|
|
className="aspect-[4/3] relative overflow-hidden"
|
|
style={{ backgroundColor: `${accent}18` }}
|
|
>
|
|
{image ? (
|
|
<img src={image} alt="" className="w-full h-full object-cover grayscale group-hover:grayscale-0 transition-all duration-500" />
|
|
) : note.illustrationSvg ? (
|
|
<div
|
|
className="w-full h-full p-4 [&_svg]:w-full [&_svg]:h-full opacity-80"
|
|
dangerouslySetInnerHTML={{ __html: sanitizeIllustrationSvg(note.illustrationSvg) }}
|
|
/>
|
|
) : (
|
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
<span
|
|
className="font-memento-serif text-4xl opacity-20"
|
|
style={{ color: accent }}
|
|
>
|
|
{title.charAt(0).toUpperCase()}
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="p-4 space-y-2">
|
|
<h3 className="font-memento-serif text-[15px] font-medium line-clamp-2 group-hover:text-brand-accent transition-colors">
|
|
{title}
|
|
</h3>
|
|
{!hover && previewProps.length > 0 && (
|
|
<div className="space-y-1">
|
|
{previewProps.map((p) => (
|
|
<div key={p.id} className="flex gap-2 text-[11px]">
|
|
<span className="text-muted-foreground shrink-0">{p.name}:</span>
|
|
<span className="truncate">{formatPropertyDisplay(p.type, values[p.id])}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
{hover && allProps.length > 0 ? (
|
|
<div className="space-y-1.5 pt-1 border-t border-border/30">
|
|
{allProps.map((p) => (
|
|
<div key={p.id} className="flex justify-between gap-2 text-[11px]">
|
|
<span className="text-muted-foreground">{p.name}</span>
|
|
<span className="font-medium text-right truncate max-w-[55%]">
|
|
{formatPropertyDisplay(p.type, values[p.id])}
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</button>
|
|
)
|
|
}
|