@cityway/bo-ui 1.1.0-beta016 → 1.1.0-beta018
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/cityway-bo-ui-1.1.0-beta018.tgz +0 -0
- package/docs/components/drag-n-drop-nested-list.md +47 -1
- package/docs/components/filter-bar.md +1 -1
- package/docs/components/page-header.md +238 -238
- package/fesm2022/cityway-bo-ui.mjs +71 -21
- package/fesm2022/cityway-bo-ui.mjs.map +1 -1
- package/index.d.ts +46 -3
- package/package.json +2 -2
- package/cityway-bo-ui-1.1.0-beta016.tgz +0 -0
|
Binary file
|
|
@@ -45,6 +45,7 @@ Liste hiérarchique réordonnable par glisser-déposer (CDK drag-drop), pilotée
|
|
|
45
45
|
| `delete` | `DragItem` | Clic sur l'action « supprimer » (corbeille) — sur une carte **non** `pendingDelete`. |
|
|
46
46
|
| `restore` | `DragItem` | Clic sur « Restaurer » — sur une carte `pendingDelete` (la corbeille bascule en restauration). |
|
|
47
47
|
| `add` | `DragAddEvent` | Clic sur un lien « Ajouter » (racine ou pied de carte). Payload : `{ parent?: DragItem; depth: number }` (`parent` = `undefined` à la racine). |
|
|
48
|
+
| `expandedChange` | `{ item: DragItem; expanded: boolean }` | L'utilisateur a plié / déplié une carte (chevron). À refléter dans `DragItem.expanded` pour que l'état survive au prochain `items`. **Jamais émis** pour un état posé par l'hôte (`expanded`, `expandSelected`). |
|
|
48
49
|
|
|
49
50
|
### Contenu projeté (corps de carte custom)
|
|
50
51
|
|
|
@@ -72,7 +73,9 @@ interface DragItem {
|
|
|
72
73
|
label: string; // libellé (fallback de title dans l'en-tête)
|
|
73
74
|
code?: string; // affiché seul dans le corps quand il n'y a pas d'itemTemplate
|
|
74
75
|
order?: number; // position 1..n (renumérotée si reorderOnDrop)
|
|
75
|
-
disabled?: boolean; // verrouille le drag de CETTE carte (poignée inactive)
|
|
76
|
+
disabled?: boolean; // verrouille le drag de CETTE carte (poignée affichée, inactive)
|
|
77
|
+
sortable?: boolean; // false : ordre non manipulable, AUCUNE poignée (défaut column.sortable → true)
|
|
78
|
+
expanded?: boolean; // état déplié voulu par l'hôte, réappliqué à chaque nouvel `items` (undefined : replié au départ)
|
|
76
79
|
children?: DragItem[]; // sous-arbre
|
|
77
80
|
|
|
78
81
|
title?: string; // titre de la carte (fallback sur label)
|
|
@@ -108,6 +111,8 @@ interface DragListColumn {
|
|
|
108
111
|
cssClass?: string; // classe sur chaque <ul> de niveau
|
|
109
112
|
|
|
110
113
|
collapsible?: boolean; // cartes pliables (défaut true) ; false désactive globalement
|
|
114
|
+
sortable?: boolean; // défaut de DragItem.sortable ; false = liste à ordre fixe (aucune poignée)
|
|
115
|
+
expandSelected?: boolean; // déplie les ancêtres de la carte selectedId (prime sur DragItem.expanded)
|
|
111
116
|
showView?: boolean; // visibilité par défaut des actions (surchargée par DragItem.actions)
|
|
112
117
|
showEdit?: boolean;
|
|
113
118
|
showDelete?: boolean;
|
|
@@ -132,6 +137,8 @@ interface DragAddEvent {
|
|
|
132
137
|
|
|
133
138
|
- **Ajout** (`canAdd`) : `item.actions.add` → `column.showAdd` → `depth < maxDepth` (si `maxDepth` défini) → `true`. Indépendant de la présence d'enfants (une carte vide reste addable).
|
|
134
139
|
- **Actions view/edit/delete** (`showAction`) : `item.actions[action]` → `column.show{View|Edit|Delete}` → `true`.
|
|
140
|
+
- **Triable** (`isSortable`) : `item.sortable` → `column.sortable` → `true`. Non triable : poignée **non rendue** et drag désactivé. Distinct de `disabled` (poignée affichée mais inerte) ; `disabled` et `pendingDelete` n'ont pas d'effet supplémentaire sur une carte non triable.
|
|
141
|
+
- **Déplié** (`isCollapsedCard`) : dernier état connu de la carte, mis à jour par le chevron (utilisateur), par `item.expanded` à chaque nouvelle valeur de `items`, puis par `column.expandSelected` (ancêtres de `selectedId` dépliés, à chaque changement de sélection ou d'`items`) ; à défaut, une carte pliable démarre repliée. L'état est indexé par `id` : une carte dont l'`id` change (id définitif après enregistrement) repart de zéro, d'où l'intérêt de lui passer `expanded`.
|
|
135
142
|
- **Pliable** (`isCollapsible`) : faux si `pendingDelete` ou `column.collapsible === false` ; sinon vrai si la carte a des enfants OU peut en recevoir.
|
|
136
143
|
- **Niveau plein** (`isLevelFull`) : vrai si `maxItemsByDepth[depth]` défini et atteint (`excludePendingDeleteFromMax` exclut les marqués du décompte).
|
|
137
144
|
|
|
@@ -200,6 +207,43 @@ items: DragItem[] = [
|
|
|
200
207
|
];
|
|
201
208
|
```
|
|
202
209
|
|
|
210
|
+
### Liste à ordre fixe (`sortable: false`)
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
// L'ordre n'est pas manuel (ex. trié côté API) : aucune poignée rendue, aucun drag.
|
|
214
|
+
// Le reste fonctionne (sélection, édition, suppression différée, ajout, sous-niveaux).
|
|
215
|
+
column: DragListColumn = { title: 'Seuils de zoom', sortable: false };
|
|
216
|
+
|
|
217
|
+
// Ou carte par carte (l'item prime sur la colonne) :
|
|
218
|
+
items: DragItem[] = [
|
|
219
|
+
{ id: 1, label: 'Racine', sortable: false, children: [
|
|
220
|
+
{ id: 11, label: 'Enfant réordonnable' }, // hérite de la colonne → triable
|
|
221
|
+
] },
|
|
222
|
+
];
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
> `disabled` ≠ `sortable: false` : `disabled` = « déplaçable en principe, verrouillé pour l'instant » (poignée affichée, inerte) ; `sortable: false` = « cet ordre ne se manipule pas » (pas de poignée).
|
|
226
|
+
|
|
227
|
+
### Dépliage piloté par l'hôte (`expanded`, `expandSelected`)
|
|
228
|
+
|
|
229
|
+
```html
|
|
230
|
+
<cw-drag-n-drop-nested-list [items]="items" [column]="column" [selectedId]="selectedId"
|
|
231
|
+
(expandedChange)="onExpandedChange($event)" />
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
// La sélection (ex. un enfant désigné par une erreur d'enregistrement) est toujours visible.
|
|
236
|
+
column: DragListColumn = { title: 'Seuils', expandSelected: true };
|
|
237
|
+
|
|
238
|
+
// Tout déplier : nouvel `items` avec `expanded` (réappliqué à chaque nouvelle valeur).
|
|
239
|
+
expandAll() { this.items = this.items.map(i => ({ ...i, expanded: true })); }
|
|
240
|
+
|
|
241
|
+
// Refléter les choix de l'utilisateur, pour qu'ils survivent au prochain rechargement.
|
|
242
|
+
onExpandedChange({ item, expanded }: { item: DragItem; expanded: boolean }) {
|
|
243
|
+
this.items = this.items.map(i => i.id === item.id ? { ...i, expanded } : i);
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
203
247
|
### Nombre max par niveau (`maxItemsByDepth`)
|
|
204
248
|
|
|
205
249
|
```ts
|
|
@@ -296,6 +340,8 @@ onReorder(items: DragItem[]) { this.items = items; }
|
|
|
296
340
|
- « Limite les rubriques à 3 par thème (`maxItemsByDepth: { 1: 3 }`) avec un message d'alerte i18n quand le max est atteint. »
|
|
297
341
|
- « Ajoute un soft-delete : *Supprimer* marque la carte (`pendingDelete`), *Restaurer* l'annule ; sélection exclusive de la carte en cours d'édition via `selectedId`. »
|
|
298
342
|
- « Affiche un corps de carte custom (titre + niveau) via `itemTemplate`, en gardant l'en-tête data-driven. »
|
|
343
|
+
- « Garde mes sections dépliées après enregistrement et ouvre automatiquement le parent de la carte en erreur (`expanded` + `expandSelected`). »
|
|
344
|
+
- « Ma liste de seuils est triée par l'API : retire les poignées de drag (`column.sortable: false`) en gardant édition et suppression. »
|
|
299
345
|
- « Rends la *Section A* non déplaçable (`disabled`) et masque l'action *supprimer* sur les feuilles via `DragItem.actions`. »
|
|
300
346
|
|
|
301
347
|
**Rappels pour Claude :**
|
|
@@ -151,7 +151,7 @@ filters: FilterFieldConfig[] = [
|
|
|
151
151
|
|
|
152
152
|
## 5. Notes d'intégration
|
|
153
153
|
|
|
154
|
-
- **i18n** : `label`, `placeholder`, `applyLabel`, `resetLabel` passent par `translate` → fournir des **clés**, pas du texte en dur.
|
|
154
|
+
- **i18n** : `label`, `placeholder`, `applyLabel`, `resetLabel` passent par `translate` → fournir des **clés**, pas du texte en dur. La traduction est faite **une seule fois**, par `cw-form-field` et `cw-button` : la filter-bar leur transmet les clés telles quelles (jusqu'à la beta016 elle les traduisait aussi, d'où une double traduction, fausse quand un libellé traduit coïncidait avec une clé).
|
|
155
155
|
- **`key`** sert à la fois de nom de `FormControl`, de clé dans `FilterValues` et de nom de query param. Pour une plage, l'URL utilise `key.start` / `key.end`.
|
|
156
156
|
- **Mode auto vs manuel** : en auto, `filtersApplied` est émis à chaque frappe (anti-rebond 300 ms). En manuel, seuls *Appliquer* (et le *reset*) déclenchent l'émission.
|
|
157
157
|
- **`syncUrl`** : nécessite que l'app fournisse le `Router` (sinon dégradation silencieuse) ; écriture en `replaceUrl: true` (l'historique n'est pas pollué) et `queryParamsHandling: 'merge'` (préserve les autres params, ex. pagination). Les valeurs vides sont retirées de l'URL.
|
|
@@ -1,238 +1,238 @@
|
|
|
1
|
-
# `cw-page-header` — En-tête de page
|
|
2
|
-
|
|
3
|
-
> Lib : `@cityway/bo-ui` · Sélecteur : `cw-page-header` · Standalone : oui
|
|
4
|
-
> Import : `import { PageHeaderComponent } from '@cityway/bo-ui';`
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## 1. Rôle & quand l'utiliser
|
|
9
|
-
|
|
10
|
-
En-tête de page transverse du back-office. Couvre :
|
|
11
|
-
|
|
12
|
-
- **titre** de page (`<h1>`) + **description** optionnelle ;
|
|
13
|
-
- **bouton retour** (navigue via `Location.back()`) ;
|
|
14
|
-
- une barre d'**actions normalisées** (Ajouter, Enregistrer, Réinitialiser) déclarées par configuration ;
|
|
15
|
-
- deux **slots dédiés** pour l'import et l'export, aux positions attendues (§2 bis) ;
|
|
16
|
-
- états **loading** et **disabled** par action ;
|
|
17
|
-
- un emplacement de **contenu projeté** pour des actions personnalisées.
|
|
18
|
-
|
|
19
|
-
À ne PAS utiliser pour : un titre de section interne (utiliser un simple `<h2>`), une barre d'outils de tableau (filtres, pagination — composants dédiés).
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## 2. API
|
|
24
|
-
|
|
25
|
-
### Inputs
|
|
26
|
-
|
|
27
|
-
| Input | Type | Défaut | Description |
|
|
28
|
-
|-------|------|--------|-------------|
|
|
29
|
-
| `title` | `string` | — (**requis**) | Titre de la page. Passé au pipe `translate` (fournir une **clé i18n**). |
|
|
30
|
-
| `description` | `string` | — | Description sous le titre. Passée au pipe `translate`, rendue en `[innerHTML]` (HTML autorisé). |
|
|
31
|
-
| `showBack` | `boolean` | `true` | Affiche le bouton retour (libellé `_COMMON._FORM.BACK`, icône `left1`). |
|
|
32
|
-
| `actions` | `PageHeaderActionEnum[]` | `[]` | Liste des actions à afficher. Voir enum §3. L'ordre d'affichage est imposé par le template (cf. §5). |
|
|
33
|
-
| `isLoading` | `PageHeaderActionState` | `{}` | Map action → `boolean` ; passe l'action en état chargement. |
|
|
34
|
-
| `isDisabled` | `PageHeaderActionState` | `{}` | Map action → `boolean` ; désactive l'action. |
|
|
35
|
-
| `columns` | `TableColumn[]` | `[]` | Colonnes du tableau associé. **Fourni → un menu déroulant « Colonnes » apparaît** (cases à cocher afficher/masquer). Voir §6 bis. |
|
|
36
|
-
| `storageKey` | `string` | — | Identifiant **stable** du tableau pour persister l'état en localStorage (ex. `'users/list'`). Sans clé : le menu fonctionne mais l'état n'est pas mémorisé. |
|
|
37
|
-
| `columnsLabel` | `string` | `_COMMON._TABLE.COLUMNS` | Clé i18n du libellé du bouton « Colonnes ». |
|
|
38
|
-
|
|
39
|
-
### Outputs
|
|
40
|
-
|
|
41
|
-
| Output | Payload | Description |
|
|
42
|
-
|--------|---------|-------------|
|
|
43
|
-
| `action` | `PageHeaderActionEnum` | Émis au clic sur une action de la barre. Le bouton retour n'émet pas (géré en interne). |
|
|
44
|
-
| `columnsChange` | `TableColumn[]` | Émis quand la visibilité des colonnes change (colonnes avec `hidden` à jour). À renvoyer dans le `signal`/state lié à la `cw-table`. |
|
|
45
|
-
|
|
46
|
-
---
|
|
47
|
-
|
|
48
|
-
## 2 bis. Slots de projection
|
|
49
|
-
|
|
50
|
-
Trois emplacements, dans l'ordre du template :
|
|
51
|
-
|
|
52
|
-
| Slot | Sélecteur | Position dans la barre | Usage |
|
|
53
|
-
|------|-----------|------------------------|-------|
|
|
54
|
-
| Import | `cwPageHeaderImport` | après *Colonnes*, avant
|
|
55
|
-
| Export | `cwPageHeaderExport` | après
|
|
56
|
-
| Libre | *(aucun)* | après l'export, avant *
|
|
57
|
-
|
|
58
|
-
```html
|
|
59
|
-
<cw-page-header [title]="'DOMAIN.USERS.TITLE'" [actions]="[actionEnum.add]" (action)="onAction($event)">
|
|
60
|
-
<cw-import-button cwPageHeaderImport [config]="importConfig" [modalTitle]="importTitle"
|
|
61
|
-
(closed)="reload()" (completed)="onImported($event)" />
|
|
62
|
-
<cw-export-button cwPageHeaderExport [options]="exportOptions" />
|
|
63
|
-
<!-- Slot libre : action personnalisée, ou bouton d'export entièrement piloté par l'hôte. -->
|
|
64
|
-
<cw-button [type]="boButtonType.secondary" [label]="'DOMAIN.USERS.INVITE'" (btnAction)="invite()" />
|
|
65
|
-
</cw-page-header>
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
**Pourquoi des slots et pas des inputs de configuration ?** Rendre l'import depuis
|
|
69
|
-
`cw-page-header` l'obligerait à référencer `cw-import-wizard`, donc à embarquer l'assistant
|
|
70
|
-
complet (avec `file-drop`, `accordion`, `card`, `progress-bar`) dans **toute** page affichant
|
|
71
|
-
un titre. Un `@defer` n'y change rien : ng-packagr aplatit la lib en un **FESM unique**, la
|
|
72
|
-
dépendance différée y est réécrite en `Promise.resolve()` sur un objet du même bundle — il
|
|
73
|
-
n'y a aucun `import()` sur lequel un bundler pourrait découper. Par projection, une page qui
|
|
74
|
-
n'importe ni n'exporte ne charge **rien** des deux composants, et une page qui exporte
|
|
75
|
-
seulement ne charge rien de l'assistant d'import.
|
|
76
|
-
|
|
77
|
-
**Le slot libre est l'échappatoire** : un hôte qui veut piloter lui-même son bouton
|
|
78
|
-
d'export — sans passer par le contrat `ExportPayload` — y pose son propre `cw-button`.
|
|
79
|
-
|
|
80
|
-
---
|
|
81
|
-
|
|
82
|
-
## 3. Enums & Types
|
|
83
|
-
|
|
84
|
-
```ts
|
|
85
|
-
enum PageHeaderActionEnum {
|
|
86
|
-
add = 'add',
|
|
87
|
-
save = 'save',
|
|
88
|
-
reset = 'reset'
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Map partielle action → booléen, pour piloter loading / disabled par action
|
|
92
|
-
type PageHeaderActionState = Partial<Record<PageHeaderActionEnum, boolean>>;
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
> ⚠️ **Migration** — `import`, `exportCurrent` et `exportAll` **ont été retirés** de l'enum.
|
|
96
|
-
> Ils ne rendaient qu'un bouton émettant un événement, tout le travail restant à l'hôte.
|
|
97
|
-
> Remplacez-les par les composants dédiés, projetés dans les slots (§2 bis) :
|
|
98
|
-
>
|
|
99
|
-
> | Avant | Après |
|
|
100
|
-
> |-------|-------|
|
|
101
|
-
> | `actions: [actionEnum.import]` + `case import:` | `<cw-import-button cwPageHeaderImport …>` |
|
|
102
|
-
> | `actions: [actionEnum.exportCurrent, actionEnum.exportAll]` + `case export…:` | `<cw-export-button cwPageHeaderExport [options]="…">` |
|
|
103
|
-
> | un bouton d'export entièrement piloté par l'hôte | votre `cw-button` dans le slot libre |
|
|
104
|
-
>
|
|
105
|
-
> Il n'y a **pas** de règle de précédence : les deux chemins n'ont jamais coexisté, le retrait
|
|
106
|
-
> est net et le compilateur signale chaque usage restant.
|
|
107
|
-
|
|
108
|
-
---
|
|
109
|
-
|
|
110
|
-
## 4. Exemples
|
|
111
|
-
|
|
112
|
-
### Page liste (Ajouter + Import + Export)
|
|
113
|
-
|
|
114
|
-
```html
|
|
115
|
-
<cw-page-header
|
|
116
|
-
[title]="'DOMAIN.USERS.TITLE'"
|
|
117
|
-
[description]="'DOMAIN.USERS.SUBTITLE'"
|
|
118
|
-
[actions]="[actionEnum.add]"
|
|
119
|
-
(action)="onAction($event)">
|
|
120
|
-
<cw-import-button cwPageHeaderImport [config]="importConfig" [modalTitle]="importTitle"
|
|
121
|
-
(closed)="reload()" (completed)="onImported($event)" />
|
|
122
|
-
<cw-export-button cwPageHeaderExport [options]="exportOptions" />
|
|
123
|
-
</cw-page-header>
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
```ts
|
|
127
|
-
import {
|
|
128
|
-
ExportButtonComponent, ImportButtonComponent, PageHeaderActionEnum, PageHeaderComponent
|
|
129
|
-
} from '@cityway/bo-ui';
|
|
130
|
-
|
|
131
|
-
// dans le composant hôte
|
|
132
|
-
actionEnum = PageHeaderActionEnum;
|
|
133
|
-
|
|
134
|
-
onAction(action: PageHeaderActionEnum): void {
|
|
135
|
-
switch (action) {
|
|
136
|
-
case PageHeaderActionEnum.add: this.create(); break;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
Configuration de `importConfig` / `exportOptions` : voir `import-button.md` et
|
|
142
|
-
`export-button.md`.
|
|
143
|
-
|
|
144
|
-
### Page édition (Enregistrer + Réinitialiser) avec états
|
|
145
|
-
|
|
146
|
-
```html
|
|
147
|
-
<cw-page-header
|
|
148
|
-
[title]="'DOMAIN.USERS.EDIT_TITLE'"
|
|
149
|
-
[description]="'DOMAIN.USERS.EDIT_SUBTITLE'"
|
|
150
|
-
[actions]="[actionEnum.reset, actionEnum.save]"
|
|
151
|
-
[isLoading]="{ save: saving() }"
|
|
152
|
-
[isDisabled]="{ reset: form.pristine }"
|
|
153
|
-
(action)="onAction($event)">
|
|
154
|
-
</cw-page-header>
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
### Titre seul (sans retour, sans action)
|
|
158
|
-
|
|
159
|
-
```html
|
|
160
|
-
<cw-page-header [title]="'DOMAIN.DASHBOARD.TITLE'" [showBack]="false" />
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
### Action personnalisée projetée
|
|
164
|
-
|
|
165
|
-
```html
|
|
166
|
-
<cw-page-header [title]="'DOMAIN.REPORTS.TITLE'" [actions]="[]">
|
|
167
|
-
<cw-button [type]="boButtonType.secondary" [label]="'DOMAIN.REPORTS.SCHEDULE'" (btnAction)="schedule()" />
|
|
168
|
-
</cw-page-header>
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
---
|
|
172
|
-
|
|
173
|
-
## 4 bis. Menu « Colonnes » (visibilité + persistance)
|
|
174
|
-
|
|
175
|
-
Quand on passe `columns` au page-header, un bouton **Colonnes** apparaît : un dropdown de cases à cocher pour afficher/masquer chaque colonne du tableau associé. L'état (colonnes masquées) est **persisté en localStorage** par tableau.
|
|
176
|
-
|
|
177
|
-
```ts
|
|
178
|
-
import { PageHeaderComponent, TableComponent, TableColumn } from '@cityway/bo-ui';
|
|
179
|
-
|
|
180
|
-
@Component({
|
|
181
|
-
standalone: true,
|
|
182
|
-
imports: [PageHeaderComponent, TableComponent],
|
|
183
|
-
template: `
|
|
184
|
-
<cw-page-header
|
|
185
|
-
[title]="'DOMAIN.USERS.TITLE'"
|
|
186
|
-
[columns]="columns()"
|
|
187
|
-
storageKey="users/list"
|
|
188
|
-
(columnsChange)="columns.set($event)">
|
|
189
|
-
</cw-page-header>
|
|
190
|
-
|
|
191
|
-
<cw-table [columns]="columns()" [data]="rows()"></cw-table>
|
|
192
|
-
`,
|
|
193
|
-
})
|
|
194
|
-
export class UsersPage {
|
|
195
|
-
// Source de vérité unique, liée au header (contrôle) ET à la table (rendu).
|
|
196
|
-
columns = signal<TableColumn[]>([
|
|
197
|
-
{ key: 'name', label: 'DOMAIN.USERS.NAME' },
|
|
198
|
-
{ key: 'email', label: 'DOMAIN.USERS.EMAIL' },
|
|
199
|
-
{ key: 'createdAt', label: 'DOMAIN.USERS.CREATED', hidden: true }, // masquée par défaut
|
|
200
|
-
{ key: 'actions', label: '', type: 'actions' }, // jamais masquable
|
|
201
|
-
]);
|
|
202
|
-
}
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
Règles :
|
|
206
|
-
- **Activation** : le menu n'apparaît que si `columns` contient au moins une colonne masquable. Sans `columns`, pas de menu.
|
|
207
|
-
- **Colonnes exclues** : `type='actions'` (jamais masquable) et toute colonne `hideable: false`.
|
|
208
|
-
- **Garde-fou** : impossible de masquer la **dernière** colonne masquable visible (sa case est désactivée).
|
|
209
|
-
- **Persistance** : une seule clé localStorage `cw-table-columns` → `{ [storageKey]: string[] }`, **on ne stocke que les clés des colonnes masquées**. Au chargement, l'état mémorisé prime sur les `hidden` du config ; **sans `storageKey`**, le menu fonctionne mais rien n'est mémorisé.
|
|
210
|
-
- **Helpers** : `loadHiddenColumns(storageKey)` / `saveHiddenColumns(storageKey, keys)` exportés par `@cityway/bo-ui` (le page-header les utilise en interne).
|
|
211
|
-
|
|
212
|
-
---
|
|
213
|
-
|
|
214
|
-
## 5. Notes d'intégration
|
|
215
|
-
|
|
216
|
-
- **i18n** : `title` et `description` passent par `translate` → fournir des **clés**, pas du texte en dur. `description` est rendue en `[innerHTML]` (peut contenir du HTML/markup traduit).
|
|
217
|
-
- **Libellés des actions** : non paramétrables. Ils sont câblés sur les clés `_COMMON._ACTION.*` (`ADD`, `SAVE`, `RESET`) et `_COMMON._FORM.BACK` pour le retour. Ces clés doivent exister dans les bundles i18n de l'app.
|
|
218
|
-
- **Ordre d'affichage** : imposé par le template, indépendant de l'ordre du tableau `actions` et de l'ordre d'écriture du contenu projeté → Colonnes, *slot import*,
|
|
219
|
-
- **Bouton retour** : navigue via `Location.back()` (historique du navigateur). Aucune sortie ni cible configurable ; masquer avec `[showBack]="false"` si non pertinent.
|
|
220
|
-
- **États par action** : `isLoading` / `isDisabled` sont des maps partielles ; une action absente de la map vaut `false`.
|
|
221
|
-
- **Dépendance** : le composant projette des `cw-button` (`@cityway/basic-ui`) en interne — `basic-ui` doit être disponible.
|
|
222
|
-
|
|
223
|
-
---
|
|
224
|
-
|
|
225
|
-
## 6. Prompts-types (génération assistée)
|
|
226
|
-
|
|
227
|
-
> Exemples de formulation pour demander à Claude de générer du code consommant ce composant.
|
|
228
|
-
|
|
229
|
-
- « Ajoute un `cw-page-header` titre *Utilisateurs* (clé i18n) avec bouton retour et action Ajouter, qui route vers `onAction()`. »
|
|
230
|
-
- « Sur ma page d'édition, mets un en-tête avec Enregistrer + Réinitialiser, où Enregistrer affiche un spinner pendant la sauvegarde et Réinitialiser est désactivé tant que le formulaire est vierge. »
|
|
231
|
-
- « Ajoute un menu Export (tout + vue filtrée) dans l'en-tête de la page liste. »
|
|
232
|
-
|
|
233
|
-
**Rappels pour Claude :**
|
|
234
|
-
- Importer `PageHeaderComponent` (standalone) dans les `imports` du composant hôte.
|
|
235
|
-
- Exposer `PageHeaderActionEnum` comme propriété du composant pour la binder dans le template (`actionEnum = PageHeaderActionEnum`).
|
|
236
|
-
- Utiliser des clés i18n pour `title` et `description` ; les libellés des actions sont fixes (clés `_COMMON._ACTION.*`).
|
|
237
|
-
- Piloter `isLoading` / `isDisabled` via des objets `{ [action]: boolean }` plutôt que des flags séparés.
|
|
238
|
-
- **L'import et l'export ne sont pas des `actions`** : projeter `<cw-import-button cwPageHeaderImport>` et `<cw-export-button cwPageHeaderExport>` (cf. `import-button.md`, `export-button.md`). Ne jamais réintroduire ces valeurs dans `PageHeaderActionEnum`.
|
|
1
|
+
# `cw-page-header` — En-tête de page
|
|
2
|
+
|
|
3
|
+
> Lib : `@cityway/bo-ui` · Sélecteur : `cw-page-header` · Standalone : oui
|
|
4
|
+
> Import : `import { PageHeaderComponent } from '@cityway/bo-ui';`
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. Rôle & quand l'utiliser
|
|
9
|
+
|
|
10
|
+
En-tête de page transverse du back-office. Couvre :
|
|
11
|
+
|
|
12
|
+
- **titre** de page (`<h1>`) + **description** optionnelle ;
|
|
13
|
+
- **bouton retour** (navigue via `Location.back()`) ;
|
|
14
|
+
- une barre d'**actions normalisées** (Ajouter, Enregistrer, Réinitialiser) déclarées par configuration ;
|
|
15
|
+
- deux **slots dédiés** pour l'import et l'export, aux positions attendues (§2 bis) ;
|
|
16
|
+
- états **loading** et **disabled** par action ;
|
|
17
|
+
- un emplacement de **contenu projeté** pour des actions personnalisées.
|
|
18
|
+
|
|
19
|
+
À ne PAS utiliser pour : un titre de section interne (utiliser un simple `<h2>`), une barre d'outils de tableau (filtres, pagination — composants dédiés).
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 2. API
|
|
24
|
+
|
|
25
|
+
### Inputs
|
|
26
|
+
|
|
27
|
+
| Input | Type | Défaut | Description |
|
|
28
|
+
|-------|------|--------|-------------|
|
|
29
|
+
| `title` | `string` | — (**requis**) | Titre de la page. Passé au pipe `translate` (fournir une **clé i18n**). |
|
|
30
|
+
| `description` | `string` | — | Description sous le titre. Passée au pipe `translate`, rendue en `[innerHTML]` (HTML autorisé). |
|
|
31
|
+
| `showBack` | `boolean` | `true` | Affiche le bouton retour (libellé `_COMMON._FORM.BACK`, icône `left1`). |
|
|
32
|
+
| `actions` | `PageHeaderActionEnum[]` | `[]` | Liste des actions à afficher. Voir enum §3. L'ordre d'affichage est imposé par le template (cf. §5). |
|
|
33
|
+
| `isLoading` | `PageHeaderActionState` | `{}` | Map action → `boolean` ; passe l'action en état chargement. |
|
|
34
|
+
| `isDisabled` | `PageHeaderActionState` | `{}` | Map action → `boolean` ; désactive l'action. |
|
|
35
|
+
| `columns` | `TableColumn[]` | `[]` | Colonnes du tableau associé. **Fourni → un menu déroulant « Colonnes » apparaît** (cases à cocher afficher/masquer). Voir §6 bis. |
|
|
36
|
+
| `storageKey` | `string` | — | Identifiant **stable** du tableau pour persister l'état en localStorage (ex. `'users/list'`). Sans clé : le menu fonctionne mais l'état n'est pas mémorisé. |
|
|
37
|
+
| `columnsLabel` | `string` | `_COMMON._TABLE.COLUMNS` | Clé i18n du libellé du bouton « Colonnes ». |
|
|
38
|
+
|
|
39
|
+
### Outputs
|
|
40
|
+
|
|
41
|
+
| Output | Payload | Description |
|
|
42
|
+
|--------|---------|-------------|
|
|
43
|
+
| `action` | `PageHeaderActionEnum` | Émis au clic sur une action de la barre. Le bouton retour n'émet pas (géré en interne). |
|
|
44
|
+
| `columnsChange` | `TableColumn[]` | Émis quand la visibilité des colonnes change (colonnes avec `hidden` à jour). À renvoyer dans le `signal`/state lié à la `cw-table`. |
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 2 bis. Slots de projection
|
|
49
|
+
|
|
50
|
+
Trois emplacements, dans l'ordre du template :
|
|
51
|
+
|
|
52
|
+
| Slot | Sélecteur | Position dans la barre | Usage |
|
|
53
|
+
|------|-----------|------------------------|-------|
|
|
54
|
+
| Import | `cwPageHeaderImport` | après *Colonnes*, avant l'export | `<cw-import-button>` |
|
|
55
|
+
| Export | `cwPageHeaderExport` | après l'import, avant le slot libre | `<cw-export-button>` |
|
|
56
|
+
| Libre | *(aucun)* | après l'export, avant *Réinitialiser* | tout le reste |
|
|
57
|
+
|
|
58
|
+
```html
|
|
59
|
+
<cw-page-header [title]="'DOMAIN.USERS.TITLE'" [actions]="[actionEnum.add]" (action)="onAction($event)">
|
|
60
|
+
<cw-import-button cwPageHeaderImport [config]="importConfig" [modalTitle]="importTitle"
|
|
61
|
+
(closed)="reload()" (completed)="onImported($event)" />
|
|
62
|
+
<cw-export-button cwPageHeaderExport [options]="exportOptions" />
|
|
63
|
+
<!-- Slot libre : action personnalisée, ou bouton d'export entièrement piloté par l'hôte. -->
|
|
64
|
+
<cw-button [type]="boButtonType.secondary" [label]="'DOMAIN.USERS.INVITE'" (btnAction)="invite()" />
|
|
65
|
+
</cw-page-header>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Pourquoi des slots et pas des inputs de configuration ?** Rendre l'import depuis
|
|
69
|
+
`cw-page-header` l'obligerait à référencer `cw-import-wizard`, donc à embarquer l'assistant
|
|
70
|
+
complet (avec `file-drop`, `accordion`, `card`, `progress-bar`) dans **toute** page affichant
|
|
71
|
+
un titre. Un `@defer` n'y change rien : ng-packagr aplatit la lib en un **FESM unique**, la
|
|
72
|
+
dépendance différée y est réécrite en `Promise.resolve()` sur un objet du même bundle — il
|
|
73
|
+
n'y a aucun `import()` sur lequel un bundler pourrait découper. Par projection, une page qui
|
|
74
|
+
n'importe ni n'exporte ne charge **rien** des deux composants, et une page qui exporte
|
|
75
|
+
seulement ne charge rien de l'assistant d'import.
|
|
76
|
+
|
|
77
|
+
**Le slot libre est l'échappatoire** : un hôte qui veut piloter lui-même son bouton
|
|
78
|
+
d'export — sans passer par le contrat `ExportPayload` — y pose son propre `cw-button`.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 3. Enums & Types
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
enum PageHeaderActionEnum {
|
|
86
|
+
add = 'add',
|
|
87
|
+
save = 'save',
|
|
88
|
+
reset = 'reset'
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Map partielle action → booléen, pour piloter loading / disabled par action
|
|
92
|
+
type PageHeaderActionState = Partial<Record<PageHeaderActionEnum, boolean>>;
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
> ⚠️ **Migration** — `import`, `exportCurrent` et `exportAll` **ont été retirés** de l'enum.
|
|
96
|
+
> Ils ne rendaient qu'un bouton émettant un événement, tout le travail restant à l'hôte.
|
|
97
|
+
> Remplacez-les par les composants dédiés, projetés dans les slots (§2 bis) :
|
|
98
|
+
>
|
|
99
|
+
> | Avant | Après |
|
|
100
|
+
> |-------|-------|
|
|
101
|
+
> | `actions: [actionEnum.import]` + `case import:` | `<cw-import-button cwPageHeaderImport …>` |
|
|
102
|
+
> | `actions: [actionEnum.exportCurrent, actionEnum.exportAll]` + `case export…:` | `<cw-export-button cwPageHeaderExport [options]="…">` |
|
|
103
|
+
> | un bouton d'export entièrement piloté par l'hôte | votre `cw-button` dans le slot libre |
|
|
104
|
+
>
|
|
105
|
+
> Il n'y a **pas** de règle de précédence : les deux chemins n'ont jamais coexisté, le retrait
|
|
106
|
+
> est net et le compilateur signale chaque usage restant.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 4. Exemples
|
|
111
|
+
|
|
112
|
+
### Page liste (Ajouter + Import + Export)
|
|
113
|
+
|
|
114
|
+
```html
|
|
115
|
+
<cw-page-header
|
|
116
|
+
[title]="'DOMAIN.USERS.TITLE'"
|
|
117
|
+
[description]="'DOMAIN.USERS.SUBTITLE'"
|
|
118
|
+
[actions]="[actionEnum.add]"
|
|
119
|
+
(action)="onAction($event)">
|
|
120
|
+
<cw-import-button cwPageHeaderImport [config]="importConfig" [modalTitle]="importTitle"
|
|
121
|
+
(closed)="reload()" (completed)="onImported($event)" />
|
|
122
|
+
<cw-export-button cwPageHeaderExport [options]="exportOptions" />
|
|
123
|
+
</cw-page-header>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import {
|
|
128
|
+
ExportButtonComponent, ImportButtonComponent, PageHeaderActionEnum, PageHeaderComponent
|
|
129
|
+
} from '@cityway/bo-ui';
|
|
130
|
+
|
|
131
|
+
// dans le composant hôte
|
|
132
|
+
actionEnum = PageHeaderActionEnum;
|
|
133
|
+
|
|
134
|
+
onAction(action: PageHeaderActionEnum): void {
|
|
135
|
+
switch (action) {
|
|
136
|
+
case PageHeaderActionEnum.add: this.create(); break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Configuration de `importConfig` / `exportOptions` : voir `import-button.md` et
|
|
142
|
+
`export-button.md`.
|
|
143
|
+
|
|
144
|
+
### Page édition (Enregistrer + Réinitialiser) avec états
|
|
145
|
+
|
|
146
|
+
```html
|
|
147
|
+
<cw-page-header
|
|
148
|
+
[title]="'DOMAIN.USERS.EDIT_TITLE'"
|
|
149
|
+
[description]="'DOMAIN.USERS.EDIT_SUBTITLE'"
|
|
150
|
+
[actions]="[actionEnum.reset, actionEnum.save]"
|
|
151
|
+
[isLoading]="{ save: saving() }"
|
|
152
|
+
[isDisabled]="{ reset: form.pristine }"
|
|
153
|
+
(action)="onAction($event)">
|
|
154
|
+
</cw-page-header>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Titre seul (sans retour, sans action)
|
|
158
|
+
|
|
159
|
+
```html
|
|
160
|
+
<cw-page-header [title]="'DOMAIN.DASHBOARD.TITLE'" [showBack]="false" />
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Action personnalisée projetée
|
|
164
|
+
|
|
165
|
+
```html
|
|
166
|
+
<cw-page-header [title]="'DOMAIN.REPORTS.TITLE'" [actions]="[]">
|
|
167
|
+
<cw-button [type]="boButtonType.secondary" [label]="'DOMAIN.REPORTS.SCHEDULE'" (btnAction)="schedule()" />
|
|
168
|
+
</cw-page-header>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## 4 bis. Menu « Colonnes » (visibilité + persistance)
|
|
174
|
+
|
|
175
|
+
Quand on passe `columns` au page-header, un bouton **Colonnes** apparaît : un dropdown de cases à cocher pour afficher/masquer chaque colonne du tableau associé. L'état (colonnes masquées) est **persisté en localStorage** par tableau.
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
import { PageHeaderComponent, TableComponent, TableColumn } from '@cityway/bo-ui';
|
|
179
|
+
|
|
180
|
+
@Component({
|
|
181
|
+
standalone: true,
|
|
182
|
+
imports: [PageHeaderComponent, TableComponent],
|
|
183
|
+
template: `
|
|
184
|
+
<cw-page-header
|
|
185
|
+
[title]="'DOMAIN.USERS.TITLE'"
|
|
186
|
+
[columns]="columns()"
|
|
187
|
+
storageKey="users/list"
|
|
188
|
+
(columnsChange)="columns.set($event)">
|
|
189
|
+
</cw-page-header>
|
|
190
|
+
|
|
191
|
+
<cw-table [columns]="columns()" [data]="rows()"></cw-table>
|
|
192
|
+
`,
|
|
193
|
+
})
|
|
194
|
+
export class UsersPage {
|
|
195
|
+
// Source de vérité unique, liée au header (contrôle) ET à la table (rendu).
|
|
196
|
+
columns = signal<TableColumn[]>([
|
|
197
|
+
{ key: 'name', label: 'DOMAIN.USERS.NAME' },
|
|
198
|
+
{ key: 'email', label: 'DOMAIN.USERS.EMAIL' },
|
|
199
|
+
{ key: 'createdAt', label: 'DOMAIN.USERS.CREATED', hidden: true }, // masquée par défaut
|
|
200
|
+
{ key: 'actions', label: '', type: 'actions' }, // jamais masquable
|
|
201
|
+
]);
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Règles :
|
|
206
|
+
- **Activation** : le menu n'apparaît que si `columns` contient au moins une colonne masquable. Sans `columns`, pas de menu.
|
|
207
|
+
- **Colonnes exclues** : `type='actions'` (jamais masquable) et toute colonne `hideable: false`.
|
|
208
|
+
- **Garde-fou** : impossible de masquer la **dernière** colonne masquable visible (sa case est désactivée).
|
|
209
|
+
- **Persistance** : une seule clé localStorage `cw-table-columns` → `{ [storageKey]: string[] }`, **on ne stocke que les clés des colonnes masquées**. Au chargement, l'état mémorisé prime sur les `hidden` du config ; **sans `storageKey`**, le menu fonctionne mais rien n'est mémorisé.
|
|
210
|
+
- **Helpers** : `loadHiddenColumns(storageKey)` / `saveHiddenColumns(storageKey, keys)` exportés par `@cityway/bo-ui` (le page-header les utilise en interne).
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 5. Notes d'intégration
|
|
215
|
+
|
|
216
|
+
- **i18n** : `title` et `description` passent par `translate` → fournir des **clés**, pas du texte en dur. `description` est rendue en `[innerHTML]` (peut contenir du HTML/markup traduit).
|
|
217
|
+
- **Libellés des actions** : non paramétrables. Ils sont câblés sur les clés `_COMMON._ACTION.*` (`ADD`, `SAVE`, `RESET`) et `_COMMON._FORM.BACK` pour le retour. Ces clés doivent exister dans les bundles i18n de l'app.
|
|
218
|
+
- **Ordre d'affichage** : imposé par le template, indépendant de l'ordre du tableau `actions` et de l'ordre d'écriture du contenu projeté → Colonnes, *slot import*, *slot export*, *slot libre*, Réinitialiser, Ajouter, Enregistrer. Lu de droite à gauche : Enregistrer, Réinitialiser, zone custom, Exporter, Importer — les actions sur le formulaire au plus près de l'action principale, les échanges de données à l'extérieur.
|
|
219
|
+
- **Bouton retour** : navigue via `Location.back()` (historique du navigateur). Aucune sortie ni cible configurable ; masquer avec `[showBack]="false"` si non pertinent.
|
|
220
|
+
- **États par action** : `isLoading` / `isDisabled` sont des maps partielles ; une action absente de la map vaut `false`.
|
|
221
|
+
- **Dépendance** : le composant projette des `cw-button` (`@cityway/basic-ui`) en interne — `basic-ui` doit être disponible.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## 6. Prompts-types (génération assistée)
|
|
226
|
+
|
|
227
|
+
> Exemples de formulation pour demander à Claude de générer du code consommant ce composant.
|
|
228
|
+
|
|
229
|
+
- « Ajoute un `cw-page-header` titre *Utilisateurs* (clé i18n) avec bouton retour et action Ajouter, qui route vers `onAction()`. »
|
|
230
|
+
- « Sur ma page d'édition, mets un en-tête avec Enregistrer + Réinitialiser, où Enregistrer affiche un spinner pendant la sauvegarde et Réinitialiser est désactivé tant que le formulaire est vierge. »
|
|
231
|
+
- « Ajoute un menu Export (tout + vue filtrée) dans l'en-tête de la page liste. »
|
|
232
|
+
|
|
233
|
+
**Rappels pour Claude :**
|
|
234
|
+
- Importer `PageHeaderComponent` (standalone) dans les `imports` du composant hôte.
|
|
235
|
+
- Exposer `PageHeaderActionEnum` comme propriété du composant pour la binder dans le template (`actionEnum = PageHeaderActionEnum`).
|
|
236
|
+
- Utiliser des clés i18n pour `title` et `description` ; les libellés des actions sont fixes (clés `_COMMON._ACTION.*`).
|
|
237
|
+
- Piloter `isLoading` / `isDisabled` via des objets `{ [action]: boolean }` plutôt que des flags séparés.
|
|
238
|
+
- **L'import et l'export ne sont pas des `actions`** : projeter `<cw-import-button cwPageHeaderImport>` et `<cw-export-button cwPageHeaderExport>` (cf. `import-button.md`, `export-button.md`). Ne jamais réintroduire ces valeurs dans `PageHeaderActionEnum`.
|