@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 +13 -0
- package/README.md +54 -0
- package/llms.txt +38 -0
- package/package.json +40 -0
- package/src/html/index.js +2 -0
- package/src/html/panel.js +43 -0
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,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
|
+
}
|