diff --git a/memento-note/components/structured-views/property-value-editor.tsx b/memento-note/components/structured-views/property-value-editor.tsx index 387b049..fd76db6 100644 --- a/memento-note/components/structured-views/property-value-editor.tsx +++ b/memento-note/components/structured-views/property-value-editor.tsx @@ -1,10 +1,11 @@ 'use client' -import { useEffect, useRef, useState } from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' import { cn } from '@/lib/utils' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover' import { useLanguage } from '@/lib/i18n' import type { PropertyType, SchemaProperty } from '@/lib/structured-views/types' +import { openNotePeek } from '@/lib/note-peek-sync' type PropertyValueEditorProps = { property: SchemaProperty @@ -78,6 +79,8 @@ export function PropertyValueEditor({ compact={compact} /> ) + case 'relation': + return default: return ( string) { return t(`structuredViews.propertyTypes.${type}`) } + +function RelationEditor({ + value, + onChange, + compact, + className, +}: { + value: string | { id: string; title: string } | null + onChange: (v: unknown) => void + compact?: boolean + className?: string +}) { + const { t } = useLanguage() + const [open, setOpen] = useState(false) + const [search, setSearch] = useState('') + const [results, setResults] = useState>([]) + const [loading, setLoading] = useState(false) + + // Parse value into { id, title } + const parsed = useMemo(() => { + if (!value) return null + if (typeof value === 'object' && value.id) return value as { id: string; title: string } + if (typeof value === 'string') { + try { + const obj = JSON.parse(value) + if (obj.id && obj.title) return obj + } catch {} + return { id: value, title: value } + } + return null + }, [value]) + + // Fetch real title if we only have an ID (old data) + const [resolvedTitle, setResolvedTitle] = useState(null) + useEffect(() => { + if (!parsed || parsed.title !== parsed.id) { setResolvedTitle(null); return } + fetch(`/api/notes?limit=100`) + .then(r => r.json()) + .then(data => { + const note = data.data?.find((n: any) => n.id === parsed.id) + if (note) setResolvedTitle(note.title || t('notes.untitled')) + }) + .catch(() => {}) + }, [parsed, t]) + + const displayTitle = resolvedTitle || parsed?.title || null + + useEffect(() => { + if (!open || !search.trim()) { setResults([]); return } + setLoading(true) + const timer = setTimeout(() => { + fetch(`/api/notes?search=${encodeURIComponent(search.trim())}&limit=10`) + .then(r => r.json()) + .then(data => setResults(data.data ?? [])) + .catch(() => setResults([])) + .finally(() => setLoading(false)) + }, 250) + return () => clearTimeout(timer) + }, [open, search]) + + return ( + + + + + + setSearch(e.target.value)} + placeholder={t('structuredViews.relationSearch') || 'Rechercher une note...'} + autoFocus + className="w-full border-b border-border/40 px-3 py-2 text-sm bg-transparent outline-none" + /> +
+ {loading &&
...
} + {!loading && results.length === 0 && search.trim() && ( +
{t('structuredViews.relationNoResults') || 'Aucune note trouvée'}
+ )} + {results.map((note) => ( + + ))} +
+
+
+ ) +} diff --git a/memento-note/lib/structured-views/property-utils.ts b/memento-note/lib/structured-views/property-utils.ts index f3ed18e..0b94a5c 100644 --- a/memento-note/lib/structured-views/property-utils.ts +++ b/memento-note/lib/structured-views/property-utils.ts @@ -5,6 +5,7 @@ import type { PropertyType, SchemaProperty, } from './types' +import { PROPERTY_TYPES } from './types' export function parsePropertyOptions(raw: string | null | undefined): string[] { if (!raw) return [] @@ -31,6 +32,12 @@ export function serializePropertyValue(type: PropertyType, value: unknown): stri const arr = Array.isArray(value) ? value : [] return JSON.stringify(arr.filter((v) => typeof v === 'string')) } + if (type === 'relation') { + if (typeof value === 'object' && value !== null) { + return JSON.stringify(value) + } + return JSON.stringify(String(value)) + } return JSON.stringify(String(value)) } @@ -38,6 +45,7 @@ export function parseStoredPropertyValue(type: PropertyType, raw: string | null if (raw == null || raw === '') { if (type === 'checkbox') return false if (type === 'multiselect') return [] + if (type === 'relation') return null return null } try { @@ -45,6 +53,7 @@ export function parseStoredPropertyValue(type: PropertyType, raw: string | null if (type === 'checkbox') return Boolean(parsed) if (type === 'number') return typeof parsed === 'number' ? parsed : Number(parsed) if (type === 'multiselect') return Array.isArray(parsed) ? parsed : [] + if (type === 'relation') return typeof parsed === 'object' ? parsed : { id: String(parsed), title: String(parsed) } return parsed } catch { return raw