@mostajs/crud-ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog — @mostajs/crud-ui
2
+
3
+ Format [Keep a Changelog](https://keepachangelog.com/fr/1.0.0/) · [SemVer](https://semver.org/lang/fr/).
4
+ Auteur : Dr Hamid MADANI <drmdh@msn.com>
5
+
6
+ ## [0.1.0] — 2026-06-20
7
+
8
+ ### Added
9
+ - **Panneau CRUD générique** (rendu HTML-string, zéro dépendance) — lot **UI-2** du plan d'extraction. Extrait de `panel()`/`createForm()`/registre `CRUD` de `incubator/app/server.mjs`.
10
+ - `panel({ title, sub, back, note, rows, cols, hidden, routeBase, detail, actions, create, extra, empty })` → corps de panneau, **composant `@mostajs/app-shell-ui`** (table/form/pageTitle/back).
11
+ - Helpers `detailLink`, `actionForm`, `actionsCell` (boutons filtrés par `show(row)`).
12
+ - Conventions de route (surchargeables) : détail `GET routeBase/<id>`, action `POST routeBase/act/<id>`, création `POST routeBase/create`. Lecture seule sans `create`/`actions`.
13
+ - Vue pure/déterministe, échappement délégué au socle. 5 tests `@mostajs/mjs-unit` (§11.1.bis) + exemple `examples/run.mjs`. Proposition `docs/00-PROPOSITION-PLAN-CRUD-UI-20062026.md`.
package/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # @mostajs/crud-ui
2
+
3
+ > Panneau **CRUD générique** (rendu **HTML-string**, zéro dépendance) — spec déclaratif + lignes → formulaire + table + actions.
4
+
5
+ **Auteur** : Dr Hamid MADANI <drmdh@msn.com> · **Licence** : AGPL-3.0-or-later · Membre de `mosta-ui-html-stack`.
6
+
7
+ Vue **pure** composant `@mostajs/app-shell-ui` : l'app charge les données et fournit un spec ; `crud-ui` assemble le HTML (formulaire de création + table avec lien détail + boutons d'action POST). Aucun accès données, aucun effet de bord. Extrait du `panel()` de l'app Hadhinat (`incubator/app/server.mjs`).
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @mostajs/crud-ui @mostajs/app-shell-ui
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```js
18
+ import { page } from '@mostajs/app-shell-ui';
19
+ import { panel } from '@mostajs/crud-ui';
20
+
21
+ const rows = await app.incubator.companies.list(); // données chargées par l'app
22
+
23
+ const body = panel({
24
+ title: 'Entreprises', back: { href: '/', label: '← Architecture' },
25
+ rows, routeBase: '/m/companies', detail: true,
26
+ actions: [{ id: 'advance', label: 'Avancer ▸', show: (r) => r._status !== 'graduated' }],
27
+ create: { fields: [{ name: 'legalName', label: 'Raison sociale', required: 1 }] },
28
+ });
29
+
30
+ const html = page({ title: 'Entreprises', brand: '🏢 Hadhinat', body });
31
+ ```
32
+
33
+ Génère : formulaire `POST /m/companies/create`, table avec lien détail `GET /m/companies/<id>`, boutons `POST /m/companies/act/<id>` (filtrés par `show(row)`).
34
+
35
+ ## API
36
+
37
+ | Fonction | Rôle |
38
+ |---|---|
39
+ | `panel({title, sub, back, note, rows, cols, hidden, routeBase, detail, actions, create, extra, empty})` | Corps du panneau (à enrober via `app-shell-ui.page()`) |
40
+ | `detailLink(href, label?)` · `actionForm(routeBase, action, row)` · `actionsCell(routeBase, actions, row)` | Helpers réutilisables |
41
+
42
+ - `detail` : `true` → lien `routeBase/<id>` ; ou `(row)=>href`. `actions[].show(row)` filtre les boutons. Sans `create`/`actions` → table en lecture seule.
43
+ - `create.fields` au format `app-shell-ui` `form` (l'app résout les options dynamiques en amont).
44
+
45
+ > **Sécurité** : valeurs/route/labels échappés (via `app-shell-ui`). `note`/`extra`/`create.extra` = HTML brut (responsabilité de l'appelant).
46
+
47
+ ## Tests & exemple
48
+
49
+ ```bash
50
+ npm test # 5 tests @mostajs/mjs-unit
51
+ npm run example # panneau complet → examples/out.html
52
+ ```
53
+
54
+ Voir `docs/00-PROPOSITION-PLAN-CRUD-UI-20062026.md` (lot UI-2).
package/llms.txt ADDED
@@ -0,0 +1,38 @@
1
+ # @mostajs/crud-ui — llms.txt
2
+
3
+ RÔLE
4
+ Panneau CRUD GÉNÉRIQUE (rendu HTML-string, zéro dépendance) : à partir d'un spec déclaratif + de lignes DÉJÀ
5
+ chargées, produit formulaire de création + table (lien détail + boutons d'action POST). COMPOSE @mostajs/app-shell-ui
6
+ (table/form/pageTitle/back/esc) — aucune primitive bas-niveau, aucun accès données, aucun effet de bord. Extrait du
7
+ panel()/createForm()/registre CRUD de incubator/app/server.mjs. Membre de mosta-ui-html-stack (simple répertoire).
8
+
9
+ PRINCIPE
10
+ Vue PURE : l'APP charge les données (list/run) et appelle panel(...) ; crud-ui n'ASSEMBLE que le HTML. Conventions de
11
+ route surchargeables : détail GET routeBase/<id> · action POST routeBase/act/<id> · création POST routeBase/create.
12
+
13
+ COMPOSE
14
+ @mostajs/app-shell-ui OBLIGATOIRE (dep). Fournit table/form/pageTitle/back/esc + thème.
15
+
16
+ EXPORTS (".", "./html")
17
+ panel({ title?, sub?, back?={href,label}, note?, rows, cols?, hidden?=['id','_status'], routeBase='',
18
+ detail?=false|(row)=>href, actions?=[{id,label,show?(row)}], create?={fields,title?,submit?,enctype?,extra?},
19
+ extra?, empty? }) -> htmlString // CORPS du panneau (à enrober via app-shell-ui.page())
20
+ detailLink(href, label?='📋 Fiche') ; actionForm(routeBase, action, row) ; actionsCell(routeBase, actions, row)
21
+
22
+ DÉTAILS
23
+ - create.fields = format app-shell-ui `form` ({name,label,type?,value?,required?,options?,list?}) ; l'app résout les
24
+ options dynamiques en amont. enctype:'multipart/form-data' pour un upload.
25
+ - detail=true → lien `${routeBase}/${row.id}` (row.id requis) ; detail=(row)=>href pour un href arbitraire.
26
+ - actions[].show(row) filtre les boutons (gardes de cycle de vie) ; '—' si aucun applicable.
27
+ - lecture seule = sans create ni actions → simple table (cf. requests/messages).
28
+
29
+ PIÈGES
30
+ - panel() retourne le CORPS, pas un document : l'app l'enrobe avec @mostajs/app-shell-ui `page()`.
31
+ - row.id est requis dès que detail/actions sont utilisés (lien + hidden id).
32
+ - note/extra/create.extra = HTML BRUT (non échappé) ; routeBase/label/valeurs/id le sont (via app-shell-ui).
33
+
34
+ EXEMPLE / TEST
35
+ examples/run.mjs panneau « entreprises » (create+détail+actions) → page complète → out.html
36
+ test-scripts/unit/crud-ui.test.mjs 5 tests @mostajs/mjs-unit (helpers, panel complet, lecture seule, detail(fn)/extra/vide, échappement)
37
+
38
+ LICENCE AGPL-3.0-or-later · Auteur Dr Hamid MADANI <drmdh@msn.com>
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@mostajs/crud-ui",
3
+ "version": "0.1.0",
4
+ "description": "Panneau CRUD générique (rendu HTML-string, zéro dépendance) : spec déclaratif + lignes → formulaire de création + table (lien détail + actions POST). Compose @mostajs/app-shell-ui. Membre de mosta-ui-html-stack.",
5
+ "license": "AGPL-3.0-or-later",
6
+ "author": "Dr Hamid MADANI <drmdh@msn.com>",
7
+ "type": "module",
8
+ "main": "src/html/index.js",
9
+ "files": [
10
+ "src",
11
+ "llms.txt",
12
+ "README.md",
13
+ "CHANGELOG.md"
14
+ ],
15
+ "exports": {
16
+ ".": "./src/html/index.js",
17
+ "./html": "./src/html/index.js"
18
+ },
19
+ "keywords": [
20
+ "mostajs",
21
+ "ui",
22
+ "html",
23
+ "crud",
24
+ "panel",
25
+ "ssr",
26
+ "zero-dependency",
27
+ "ui-html-stack"
28
+ ],
29
+ "dependencies": {
30
+ "@mostajs/app-shell-ui": "^0.1.0"
31
+ },
32
+ "devDependencies": {
33
+ "@mostajs/mjs-unit": "^0.3.0"
34
+ },
35
+ "scripts": {
36
+ "test": "bash test-scripts/run-tests.sh",
37
+ "example": "node examples/run.mjs",
38
+ "clean": "rm -f examples/out.html test-report.html"
39
+ }
40
+ }
@@ -0,0 +1,2 @@
1
+ // @mostajs/crud-ui — barrel ./html. Author: Dr Hamid MADANI <drmdh@msn.com>
2
+ export { panel, detailLink, actionForm, actionsCell } from './panel.js';
@@ -0,0 +1,43 @@
1
+ // @mostajs/crud-ui — panneau CRUD générique (extrait de panel()/createForm() de server.mjs). Author: Dr Hamid MADANI <drmdh@msn.com>
2
+ // Vue PURE : assemble un panneau (formulaire + table + actions) à partir d'un spec + de lignes DÉJÀ chargées par l'app.
3
+ // COMPOSE @mostajs/app-shell-ui (table/form/pageTitle/back/esc) — aucune primitive bas-niveau ni accès données ici.
4
+ import { esc, table, form, pageTitle, back as backLink } from '@mostajs/app-shell-ui';
5
+
6
+ /** Lien de détail (cellule de tête de ligne). */
7
+ export function detailLink(href, label = '📋 Fiche') {
8
+ return `<a href="${esc(href)}" style="color:var(--blue);font-weight:600;text-decoration:none">${esc(label)}</a>`;
9
+ }
10
+
11
+ /** Mini-formulaire POST pour une action de ligne → `${routeBase}/act/${action.id}`. */
12
+ export function actionForm(routeBase, action, row) {
13
+ return `<form method="post" action="${esc(routeBase)}/act/${esc(action.id)}"><input type="hidden" name="id" value="${esc(row.id)}"><button class="act">${esc(action.label)}</button></form>`;
14
+ }
15
+
16
+ /** Cellule d'actions : boutons filtrés par show(row) ; '—' si aucun applicable. */
17
+ export function actionsCell(routeBase, actions, row) {
18
+ return actions.filter((a) => !a.show || a.show(row)).map((a) => actionForm(routeBase, a, row)).join(' ') || '—';
19
+ }
20
+
21
+ /**
22
+ * Panneau CRUD. Retourne le CORPS (à enrober via app-shell-ui.page()).
23
+ * Voir docs/00-PROPOSITION-PLAN-CRUD-UI-20062026.md pour le spec complet.
24
+ */
25
+ export function panel({
26
+ title, sub = '', back, note = '', rows = [], cols, hidden = ['id', '_status'],
27
+ routeBase = '', detail = false, actions, create, extra = '', empty,
28
+ } = {}) {
29
+ const parts = [];
30
+ if (back) parts.push(backLink(back.href, back.label));
31
+ if (title) parts.push(pageTitle(title, sub));
32
+ if (note) parts.push(note);
33
+ if (create) parts.push(form(create.fields, {
34
+ action: `${routeBase}/create`, method: 'post',
35
+ title: create.title || '➕ Ajouter', submit: create.submit || 'Créer',
36
+ enctype: create.enctype, extra: create.extra,
37
+ }));
38
+ const lead = detail ? (r) => detailLink(typeof detail === 'function' ? detail(r) : `${routeBase}/${r.id}`) : undefined;
39
+ const trail = actions ? { header: 'Actions', render: (r) => actionsCell(routeBase, actions, r) } : undefined;
40
+ parts.push(table(rows, { cols, hidden, lead, trail, empty }));
41
+ if (extra) parts.push(extra);
42
+ return parts.join('\n');
43
+ }