@vevedh/nfz-qgrid
v0.1.0-rc.5
Published
Nuxt 4 layer providing a reusable Quasar QTable-based data grid for Vue 3, Quasar 2, UnoCSS and NFZ applications.
Maintainers
Readme
NFZ QGrid Layer
@vevedh/nfz-qgrid est un layer Nuxt 4 qui expose un composant de grille autonome basé sur Quasar QTable.
Objectif : fournir une grille métier réutilisable, typée, responsive et compatible avec les applications Nuxt 4 + Quasar 2 + UnoCSS + NFZ/Feathers, sans coupler le composant à un backend particulier.
Fonctionnalités
- recherche globale ;
- filtres par colonne ;
- filtres avancés avec règles
and/or; - vues enregistrées en
localStorage; - redimensionnement et réordonnancement des colonnes ;
- sélection simple, multiple ou désactivée ;
- édition inline via
QPopupEditavecQDate,QTime,QSelect,QInput,QToggle; - inférence stricte des dates pour éviter qu’un texte avec chiffres ouvre
QDate; - rendu badge automatique en lecture pour les colonnes
list/select; - éditeur JSON pratique avec validation, formatage, compactage et reset ;
- slots de cellules, actions de ligne et état vide ;
- exports JSON, CSV, Excel HTML et PDF navigateur ;
- import JSON/XLSX par événement ;
- garde-fous d’import : taille fichier, nombre de lignes, taille cellule, clés dangereuses filtrées ;
- responsive avec scroll horizontal confiné.
- compatibilité TypeScript strict renforcée pour la normalisation des dates françaises
DD/MM/YYYY.
État de la version 0.1.0-rc.5
Cette version ne modifie pas le runtime du composant. Elle enrichit la documentation développeur avec un guide complet de la toolbar, ajoute des captures d’écran dans la documentation VitePress et poursuit la mémoire de patch introduite en 0.1.0-rc.4. Le package conserve les validations de release candidate : fixture Nuxt 4 consommatrice, imports publics, tests unitaires, documentation FR/EN et inférence stricte des éditeurs.
Documentation toolbar et captures d’écran
La documentation développeur contient maintenant un guide dédié aux boutons de la toolbar :
- recherche globale ;
- mode dense ;
- affichage/masquage des filtres colonnes ;
- configuration des colonnes ;
- libellés personnalisés ;
- filtres CRUD ;
- vues ;
- import JSON/XLSX ;
- export JSON/CSV/Excel/PDF ;
- plein écran.
Pages ajoutées :
docs/guide/toolbar-actions.md
docs/en/guide/toolbar-actions.mdLes captures d’écran sont stockées dans :
docs/public/screenshots/Elles sont utilisables directement dans VitePress avec des chemins publics comme :
Mémoire de patch mainteneur
Depuis 0.1.0-rc.4, chaque archive contient :
patch-memory/README.md
patch-memory/000-index.md
patch-memory/TEMPLATE.md
patch-memory/entries/Avant chaque nouveau patch, consulter patch-memory/000-index.md, puis la dernière entrée dans patch-memory/entries/, avant de modifier le code. Après chaque patch, ajouter une nouvelle entrée avec l'analyse, la résolution, les fichiers touchés, les impacts API/UI/DX et les commandes de validation.
Cette mémoire complète PROMPT_CONTEXT.md, PATCHLOG.md, JOURNAL.md et CHANGELOG.md sans remplacer la documentation publique.
Helpers publics testables
Depuis 0.1.0-rc.1, les helpers critiques sont extraits du composant Vue pour être testés et réutilisés.
import {
inferNfzQGridColumnTypeFromValues,
resolveNfzQGridColumnEditor,
coerceNfzQGridEditedValue
} from '@vevedh/nfz-qgrid/utils/editors'
import {
sanitizeNfzQGridImportedRows,
escapeNfzQGridHtml,
safeNfzQGridExportFileName
} from '@vevedh/nfz-qgrid/utils/sanitize'Ces helpers couvrent l’inférence stricte des éditeurs, la normalisation date/heure, la coercition des valeurs éditées, la limitation des textes importés, l’échappement HTML des exports et la protection contre les clés dangereuses.
Tests et fixture consommatrice
La validation locale complète inclut maintenant une application Nuxt 4 propre qui consomme le layer depuis tests/fixtures/nuxt-consumer.
bun run test
bun run consumer:prepare
bun run consumer:typecheck
bun run checkbun run check exécute le typecheck du playground, les tests unitaires, le typecheck de la fixture consommatrice et le build VitePress.
Publication RC
Pour publier cette version comme release candidate (0.1.0-rc.5) :
bun run publish:rcLa publication stable devra ensuite utiliser 0.1.0 et le tag npm latest, uniquement après validation terrain dans une application consommatrice réelle.
Installation
bun add @vevedh/nfz-qgrid nuxt-quasar-ui quasar @unocss/nuxt unocssL’import XLSX utilise la dépendance optionnelle xlsx :
bun add xlsxConfiguration Nuxt 4
// nuxt.config.ts
export default defineNuxtConfig({
extends: ['@vevedh/nfz-qgrid'],
modules: [
'nuxt-quasar-ui',
'@unocss/nuxt'
],
compatibilityDate: '2026-05-17',
quasar: {
lang: 'fr',
iconSet: 'material-icons',
plugins: ['Dialog', 'Notify', 'Loading'],
extras: {
fontIcons: ['material-icons']
},
components: {
autoImport: true
}
},
typescript: {
strict: true
}
})Utilisation minimale
<script setup lang="ts">
import { ref } from 'vue'
import type { NfzQGridColumn } from '@vevedh/nfz-qgrid/types'
type UserRow = {
id: string
name: string
email: string
group: 'DSI' | 'DRH' | 'OTHER'
}
const rows = ref<UserRow[]>([
{ id: 'u-001', name: 'UTILISATEUR DEMO 01', email: '[email protected]', group: 'DSI' }
])
const columns: NfzQGridColumn<UserRow>[] = [
{ name: 'name', label: 'Nom', field: 'name', sortable: true, align: 'left', type: 'string', editor: 'text' },
{ name: 'email', label: 'Email', field: 'email', sortable: true, align: 'left', type: 'string', editor: 'text' },
{ name: 'group', label: 'Groupe', field: 'group', sortable: true, align: 'left' }
]
const selectedKeys = ref<string[]>([])
</script>
<template>
<NfzQGridStandalone
v-model:selected-keys="selectedKeys"
:rows="rows"
:columns="columns"
row-key="id"
storage-key="users-grid"
/>
</template>Note DX sur l’inférence automatique
Pour les colonnes métier, privilégiez une déclaration explicite :
{ name: 'agent', field: 'agent', type: 'string', editor: 'text' }
{ name: 'createdAt', field: 'createdAt', type: 'date', editor: 'date' }Depuis 0.1.0-beta.7, l’inférence date/heure est stricte. Une valeur texte comme AGENT DEMO 01 reste une chaîne et ouvre un QInput, pas QDate.
Exemple Nuxt 4 + NFZ/Feathers
Le composant ne dépend pas directement du client NFZ. Dans une application Nuxt 4 + NFZ, chargez les données dans un store ou un composable, puis transmettez-les à la grille.
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import type { NfzQGridColumn } from '@vevedh/nfz-qgrid/types'
type UserRow = {
_id: string
email: string
groups?: string[]
}
const api = useAdminFeathers()
const rows = ref<UserRow[]>([])
const loading = ref(false)
const columns: NfzQGridColumn<UserRow>[] = [
{ name: 'email', label: 'Email', field: 'email', sortable: true },
{
name: 'groups',
label: 'Groupes',
field: row => Array.isArray(row.groups) ? row.groups.join(', ') : '',
sortable: true
}
]
async function refreshUsers(): Promise<void> {
loading.value = true
try {
const result = await api.service('users').find({ query: { $limit: 50 } })
rows.value = Array.isArray(result) ? result : result.data
}
finally {
loading.value = false
}
}
onMounted(refreshUsers)
</script>
<template>
<NfzQGridStandalone
:rows="rows"
:columns="columns"
:loading="loading"
row-key="_id"
storage-key="admin-users-grid"
:import-enabled="false"
/>
</template>La sécurité métier doit rester côté backend Feathers/NFZ : hooks, policies, RBAC, validation Zod, limitation des queries et audit.
Édition inline date / heure / liste
Pour les colonnes de type date, heure ou liste contrôlée, déclarez explicitement type, editor et, pour les listes, options. Le composant utilise alors QDate, QTime ou QSelect dans la popup d'édition.
<script setup lang="ts">
import { ref } from 'vue'
import type { NfzQGridCellEditPayload, NfzQGridColumn } from '@vevedh/nfz-qgrid/types'
type Row = {
id: string
label: string
createdAt: string
reminderTime: string
status: 'draft' | 'pending' | 'done' | 'rejected'
}
const rows = ref<Row[]>([
{ id: 'row-001', label: 'Ligne 01', createdAt: '2026-01-10', reminderTime: '08:30', status: 'pending' }
])
const statusOptions = [
{ label: 'Brouillon', value: 'draft', color: 'grey', icon: 'edit_note' },
{ label: 'En attente', value: 'pending', color: 'warning', icon: 'hourglass_empty' },
{ label: 'Terminé', value: 'done', color: 'positive', icon: 'check_circle' },
{ label: 'Rejeté', value: 'rejected', color: 'negative', icon: 'cancel' }
]
const columns: NfzQGridColumn<Row>[] = [
{ name: 'label', label: 'Libellé', field: 'label', sortable: true, editor: 'text' },
{ name: 'createdAt', label: 'Date', field: 'createdAt', sortable: true, type: 'date', editor: 'date' },
{ name: 'reminderTime', label: 'Heure', field: 'reminderTime', sortable: true, type: 'time', editor: 'time' },
{ name: 'status', label: 'Statut', field: 'status', sortable: true, type: 'list', editor: 'select', options: statusOptions }
]
function onCellEdit(payload: NfzQGridCellEditPayload<Row>): void {
const field = payload.column.name as keyof Row
rows.value = rows.value.map(row => row.id === payload.row.id ? { ...row, [field]: payload.value } as Row : row)
}
</script>
<template>
<NfzQGridStandalone
:rows="rows"
:columns="columns"
row-key="id"
editable
@cell-edit-start="payload => console.info('start', payload)"
@cell-edit-change="payload => console.info('change', payload)"
@cell-edit-save="payload => console.info('save', payload)"
@cell-edit-cancel="payload => console.info('cancel', payload)"
@cell-edit="onCellEdit"
/>
</template>Valeurs normalisées : YYYY-MM-DD pour les dates, HH:mm pour les heures, YYYY-MM-DDTHH:mm pour les dates/heures, et la valeur contrôlée de l'option pour QSelect.
Tous les events d'édition exposent le même payload typé NfzQGridCellEditPayload, y compris cell-edit-start. Le champ previousValue est donc toujours disponible pour l'audit, les confirmations ou un rollback applicatif.
Imports sécurisés
<NfzQGridStandalone
:rows="rows"
:columns="columns"
:import-enabled="true"
:max-import-file-bytes="2 * 1024 * 1024"
:max-import-rows="5000"
:max-cell-length="10000"
@import-data="payload => openImportPreview(payload)"
/>Bon pattern de production : importer → prévisualiser → valider côté backend → confirmer → écrire en base.
Structure du projet
app/components/nfz-qgrid/NfzQGridStandalone.vue
app/assets/css/nfz-qgrid.css
app/types/nfz-qgrid.ts
.playground/app/pages/index.vue
docs/
examples/basic.vue
tests/package.test.ts
PROMPT_CONTEXT.md
PATCHLOG.md
JOURNAL.mdDéveloppement
bun install
bun run dev
bun run typecheck
bun run test
bun run docs:dev
bun run docs:build
bun run checkPublication
npm pack --dry-run
npm publish --access public --tag betaDocumentation
La documentation VitePress est bilingue :
/: guide français ;/en/: guide anglais.
À chaque patch, maintenir au minimum :
README.md;PROMPT_CONTEXT.md;PATCHLOG.md;JOURNAL.md;docs/FR/EN.
Licence
MIT
Édition liste avec badge
Pour une colonne de statut, le grid utilise QSelect en édition et conserve un badge en lecture :
import type { NfzQGridColumn, NfzQGridSelectOption } from '@vevedh/nfz-qgrid/types'
type Row = {
id: string
status: 'draft' | 'pending' | 'done' | 'rejected'
}
const statusOptions: NfzQGridSelectOption[] = [
{ label: 'Brouillon', value: 'draft', color: 'grey', icon: 'edit_note' },
{ label: 'En attente', value: 'pending', color: 'warning', icon: 'hourglass_empty' },
{ label: 'Terminé', value: 'done', color: 'positive', icon: 'check_circle' },
{ label: 'Rejeté', value: 'rejected', color: 'negative', icon: 'cancel' }
]
const columns: NfzQGridColumn<Row>[] = [
{
name: 'status',
label: 'Statut',
field: 'status',
type: 'list',
editor: 'select',
options: statusOptions
}
]Pour désactiver uniquement le badge de lecture :
{ name: 'status', field: 'status', type: 'list', editor: 'select', badge: false }Éditeur JSON
Une colonne type: 'json' utilise maintenant NfzQGridJsonCellEditor : validation immédiate, compteur de taille, actions de formatage/compactage et reset.
const columns: NfzQGridColumn<Row>[] = [
{
name: 'metadata',
label: 'JSON',
field: 'metadata',
type: 'json',
editor: 'json',
sortable: false
}
]Le composant est aussi réutilisable hors grid :
<script setup lang="ts">
import { ref } from 'vue'
import NfzQGridJsonCellEditor from '@vevedh/nfz-qgrid/components/NfzQGridJsonCellEditor.vue'
const jsonText = ref('{
"source": "demo"
}')
</script>
<template>
<NfzQGridJsonCellEditor v-model="jsonText" :max-length="10000" />
</template>