From 83110200d5dd7090f94e7eb93de14620a728b5b0 Mon Sep 17 00:00:00 2001 From: Antigravity Date: Sun, 14 Jun 2026 17:56:54 +0000 Subject: [PATCH] feat: calculs tableaux Structured Views + Link Preview + fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Calculs en pied de tableau : Somme/Moyenne/Min/Max/Compte, cliquable pour changer - Link Preview : métadonnées persistées + texte indexable pour recherche/embeddings - Fix: bg-memento-paper → bg-card (dark mode) sur dialogs Structured Views - Fix: bouton Ajouter un champ → brand-accent au lieu de primary - Calendar view retiré du sélecteur (non pertinent) --- .../structured-views/add-property-dialog.tsx | 4 +- .../notes-structured-table.tsx | 82 ++++++++++++++++++- .../structured-views-wizard.tsx | 2 +- memento-note/locales/en.json | 2 + memento-note/locales/fr.json | 2 + 5 files changed, 88 insertions(+), 4 deletions(-) diff --git a/memento-note/components/structured-views/add-property-dialog.tsx b/memento-note/components/structured-views/add-property-dialog.tsx index 3f7475f..5f6b2cf 100644 --- a/memento-note/components/structured-views/add-property-dialog.tsx +++ b/memento-note/components/structured-views/add-property-dialog.tsx @@ -50,7 +50,7 @@ export function AddPropertyDialog({ open, onClose, onSubmit }: AddPropertyDialog

{t('structuredViews.addPropertyTitle')}

@@ -117,7 +117,7 @@ export function AddPropertyDialog({ open, onClose, onSubmit }: AddPropertyDialog -
diff --git a/memento-note/components/structured-views/notes-structured-table.tsx b/memento-note/components/structured-views/notes-structured-table.tsx index f1f3daa..813ed62 100644 --- a/memento-note/components/structured-views/notes-structured-table.tsx +++ b/memento-note/components/structured-views/notes-structured-table.tsx @@ -28,7 +28,7 @@ import { getNoteDisplayTitle } from '@/lib/note-preview' import { useLanguage } from '@/lib/i18n' import { formatAbsoluteDateLocalized } from '@/lib/utils/format-localized-date' import { enUS, fr, faIR } from 'date-fns/locale' -import { ChevronDown, ChevronUp, Filter, Trash2, Sparkles, Brain, Loader2, ArrowUpRight, Link2 } from 'lucide-react' +import { ChevronDown, ChevronUp, Filter, Trash2, Sparkles, Brain, Loader2, ArrowUpRight, Link2, Sigma } from 'lucide-react' import { cn } from '@/lib/utils' import { openNotePeek } from '@/lib/note-peek-sync' @@ -88,6 +88,7 @@ export function NotesStructuredTable({ const [filterValue, setFilterValue] = useState('') const [propertyToDelete, setPropertyToDelete] = useState<{ id: string; name: string } | null>(null) const [deletingProperty, setDeletingProperty] = useState(false) + const [calcTypes, setCalcTypes] = useState>({}) // Memory Echo states const { requestAiConsent } = useAiConsent() @@ -189,6 +190,53 @@ export function NotesStructuredTable({ return sortNotesWithProperties(filtered, noteValues, sort, schema.properties) }, [notes, noteValues, filters, sort, schema.properties]) + const numberProperties = schema.properties.filter(p => p.type === 'number') + + const calculations = useMemo(() => { + const result: Record = {} + for (const prop of schema.properties) { + const calcType = calcTypes[prop.id] || (prop.type === 'number' ? 'sum' : 'count') + if (calcType === 'none') { result[prop.id] = { value: null, type: 'none' }; continue } + + const values = displayed + .map(n => noteValues[n.id]?.[prop.id]) + .filter(v => v !== null && v !== undefined && v !== '') + + if (prop.type === 'number') { + const nums = values.map(v => parseFloat(String(v))).filter(n => !isNaN(n)) + if (nums.length === 0) { result[prop.id] = { value: null, type: calcType }; continue } + if (calcType === 'sum') result[prop.id] = { value: formatNum(nums.reduce((a, b) => a + b, 0)), type: 'sum' } + else if (calcType === 'avg') result[prop.id] = { value: formatNum(nums.reduce((a, b) => a + b, 0) / nums.length), type: 'avg' } + else if (calcType === 'min') result[prop.id] = { value: formatNum(Math.min(...nums)), type: 'min' } + else if (calcType === 'max') result[prop.id] = { value: formatNum(Math.max(...nums)), type: 'max' } + else if (calcType === 'count') result[prop.id] = { value: String(nums.length), type: 'count' } + } else { + result[prop.id] = { value: calcType === 'count' ? String(values.length) : null, type: calcType } + } + } + return result + }, [displayed, noteValues, schema.properties, calcTypes]) + + function formatNum(n: number): string { + return Number.isInteger(n) ? String(n) : n.toFixed(2) + } + + function cycleCalcType(propId: string, propType: string) { + const current = calcTypes[propId] || (propType === 'number' ? 'sum' : 'count') + const numberCycle: Array = ['sum', 'avg', 'min', 'max', 'count', 'none'] + const otherCycle: Array = ['count', 'none'] + const cycle = propType === 'number' ? numberCycle : otherCycle + const nextIdx = (cycle.indexOf(current) + 1) % cycle.length + setCalcTypes(prev => ({ ...prev, [propId]: cycle[nextIdx] })) + } + + const calcLabel = (type: string): string => { + const labels: Record = { + sum: 'Σ', avg: 'x̄', min: 'min', max: 'max', count: '#', none: '' + } + return labels[type] || '' + } + const toggleSort = (propertyId: ColumnSort['propertyId']) => { setSort((prev) => prev.propertyId === propertyId @@ -429,6 +477,38 @@ export function NotesStructuredTable({ ) })} + {numberProperties.length > 0 && ( + + + + + + {t('structuredViews.calculations')} + + + {schema.properties.map((p) => { + const calc = calculations[p.id] + if (!calc || calc.value === null) { + return — + } + return ( + + + + ) + })} + + + + )} {displayed.length === 0 && (

{t('structuredViews.noMatchingNotes')}

diff --git a/memento-note/components/structured-views/structured-views-wizard.tsx b/memento-note/components/structured-views/structured-views-wizard.tsx index 4714db5..d1ed388 100644 --- a/memento-note/components/structured-views/structured-views-wizard.tsx +++ b/memento-note/components/structured-views/structured-views-wizard.tsx @@ -140,7 +140,7 @@ export function StructuredViewsWizard({
diff --git a/memento-note/locales/en.json b/memento-note/locales/en.json index 710d917..14af15e 100644 --- a/memento-note/locales/en.json +++ b/memento-note/locales/en.json @@ -2647,6 +2647,8 @@ "deleteProperty": "Delete field", "deletePropertyTitle": "Delete this field?", "deletePropertyConfirm": "The field \"{name}\" and all its values on notes in this notebook will be removed. This cannot be undone.", + "calculations": "Calculations", + "calcClickToChange": "Click to change calculation type (Sum, Average, Min, Max, Count, None)", "deletePropertySuccess": "Field deleted", "cellEmpty": "—", "multiselectPick": "Choose…", diff --git a/memento-note/locales/fr.json b/memento-note/locales/fr.json index fe2d463..f7a1295 100644 --- a/memento-note/locales/fr.json +++ b/memento-note/locales/fr.json @@ -2651,6 +2651,8 @@ "deleteProperty": "Supprimer le champ", "deletePropertyTitle": "Supprimer ce champ ?", "deletePropertyConfirm": "Le champ « {name} » et toutes ses valeurs sur les notes de ce carnet seront supprimés. Cette action est irréversible.", + "calculations": "Calculs", + "calcClickToChange": "Cliquer pour changer le type de calcul (Somme, Moyenne, Min, Max, Compte, Aucun)", "deletePropertySuccess": "Champ supprimé", "cellEmpty": "—", "multiselectPick": "Choisir…",