From 843986d5c22cdc769650f5af934ee70f01ec6ff8 Mon Sep 17 00:00:00 2001 From: Corentin Date: Mon, 11 May 2026 09:54:06 +0000 Subject: [PATCH] feat(database-view): admin UI and create-database slash command Adds two new entry points around the bridge-backed database view: - /database admin modal to manage fields (field-admin-modal) - slash command to create a database from the editor (create-database-modal + create-database-slash) Wires the new components into the editor slash menu and the database-view module index. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/field-admin-modal.tsx | 174 ++++++++ .../features/acadenice/database-view/index.ts | 1 + .../renderers/table-renderer.tsx | 123 +++++- .../database-view/services/admin-client.ts | 166 ++++++++ .../database-view/services/bridge-client.ts | 72 +--- .../slash-command/create-database-modal.tsx | 382 ++++++++++++++++++ .../slash-command/create-database-slash.tsx | 99 +++++ .../components/slash-menu/menu-items.ts | 3 +- 8 files changed, 965 insertions(+), 55 deletions(-) create mode 100644 apps/client/src/features/acadenice/database-view/components/field-admin-modal.tsx create mode 100644 apps/client/src/features/acadenice/database-view/services/admin-client.ts create mode 100644 apps/client/src/features/acadenice/database-view/slash-command/create-database-modal.tsx create mode 100644 apps/client/src/features/acadenice/database-view/slash-command/create-database-slash.tsx diff --git a/apps/client/src/features/acadenice/database-view/components/field-admin-modal.tsx b/apps/client/src/features/acadenice/database-view/components/field-admin-modal.tsx new file mode 100644 index 00000000..94cc15b5 --- /dev/null +++ b/apps/client/src/features/acadenice/database-view/components/field-admin-modal.tsx @@ -0,0 +1,174 @@ +import { useEffect, useState } from "react"; +import { + Modal, + Stack, + TextInput, + Select, + NumberInput, + Textarea, + Button, + Group, + Alert, +} from "@mantine/core"; +import { IconAlertCircle } from "@tabler/icons-react"; +import { + type FieldType, + createField, + updateField, +} from "../services/admin-client"; +import type { BridgeField } from "../types/database-view.types"; + +const FIELD_TYPE_OPTIONS: Array<{ value: FieldType; label: string }> = [ + { value: "text", label: "Texte court" }, + { value: "long_text", label: "Texte long" }, + { value: "number", label: "Nombre" }, + { value: "rating", label: "Note (étoiles)" }, + { value: "boolean", label: "Case à cocher" }, + { value: "date", label: "Date" }, + { value: "single_select", label: "Choix unique" }, + { value: "multiple_select", label: "Choix multiple" }, + { value: "url", label: "URL" }, + { value: "email", label: "Email" }, + { value: "phone_number", label: "Téléphone" }, + { value: "duration", label: "Durée" }, + { value: "formula", label: "Formule (calcul)" }, +]; + +interface FieldAdminModalProps { + opened: boolean; + onClose: () => void; + /** Provided in create mode. */ + tableId?: number; + /** Provided in edit mode. */ + field?: BridgeField | null; + bridgeUrl?: string | null; + onSuccess?: () => void; +} + +export function FieldAdminModal({ + opened, + onClose, + tableId, + field, + bridgeUrl, + onSuccess, +}: FieldAdminModalProps) { + const isEdit = Boolean(field); + const [name, setName] = useState(""); + const [type, setType] = useState("text"); + const [formula, setFormula] = useState(""); + const [decimals, setDecimals] = useState(0); + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + if (opened) { + setError(null); + setSubmitting(false); + if (field) { + setName(field.name ?? ""); + setType((field.type as FieldType) ?? "text"); + setFormula(((field as unknown) as { formula?: string }).formula ?? ""); + setDecimals(0); + } else { + setName(""); + setType("text"); + setFormula(""); + setDecimals(0); + } + } + }, [opened, field]); + + async function handleSubmit() { + setError(null); + setSubmitting(true); + try { + const payload: Record = { name: name.trim(), type }; + if (type === "formula") payload.formula = formula.trim(); + if (type === "number") payload.number_decimal_places = decimals; + + if (isEdit && field) { + await updateField(Number(field.id), payload as never, bridgeUrl); + } else if (tableId) { + await createField(tableId, payload as never, bridgeUrl); + } else { + throw new Error("tableId manquant en mode create"); + } + onSuccess?.(); + onClose(); + } catch (e) { + setError((e as Error).message); + setSubmitting(false); + } + } + + const canSubmit = + name.trim().length > 0 && + (type !== "formula" || formula.trim().length > 0); + + return ( + + + {error && ( + } color="red"> + {error} + + )} + setName(e.currentTarget.value)} + data-autofocus + required + /> +