UNPKG

@daviduo/vue-searchable-sortable-table

Version:

Un componente tabella Vue 3 versatile e configurabile con Tailwind CSS, ricerca e ordinamento.

293 lines (238 loc) 17.9 kB
# Vue Sorted Searchable Table (`@your-npm-scope/vue-sorted-searchable-table`) Un componente tabella Vue 3 versatile e altamente configurabile, costruito con Tailwind CSS. Include funzionalità di ordinamento (client-side o server-side), ricerca per campo specifico o globale (con debounce), header della tabella "sticky" per lo scrolling verticale interno, e un sistema di slot completo per una facile personalizzazione dell'UI e del rendering delle celle. ## Caratteristiche Principali * **Ordinamento Flessibile**: Ordinamento per colonna attivabile, con supporto sia per la logica interna al componente sia per l'ordinamento esterno gestito dal server (tramite eventi). * **Ricerca Avanzata**: * Ricerca testuale per campo specifico selezionabile da un dropdown. * Opzione "Cerca ovunque" per una ricerca globale (se abilitata). * Debounce configurabile per ottimizzare le performance. * **Header Sticky e Scrolling Verticale**: * Possibilità di definire un'altezza massima per la tabella. * Se il contenuto eccede l'altezza massima, il corpo della tabella diventa scrollabile mentre l'header (`<thead>`) rimane fisso in cima. * **Personalizzazione Completa**: * Slot per azioni globali nell'header. * Slot per azioni per riga e per l'header della colonna azioni. * Slot dinamici per il rendering personalizzato di ogni cella dati. * **Stilizzato con Tailwind CSS**: Richiede che Tailwind CSS sia configurato nel progetto che utilizza il componente. * **Design Responsivo**: Si adatta a diverse dimensioni di schermo. * **Messaggi Configurabili**: Per lo stato di caricamento e per quando non ci sono dati. * **Internazionalizzazione Semplice**: Etichette e messaggi principali passabili come props. ## Installazione ```bash npm install @your-npm-scope/vue-sorted-searchable-table # o yarn add @your-npm-scope/vue-sorted-searchable-table ``` ## Dipendenze dell'Utente Questo componente si affida a `peerDependencies` che devono essere già presenti e configurate nel tuo progetto: * **`vue`**: `^3.2.0` o superiore * **`@heroicons/vue`**: `^2.0.0` o superiore (utilizzato per le icone di ordinamento e del dropdown di ricerca). Assicurati di installare la versione corretta (es. `@heroicons/vue/20/solid`). ### IMPORTANTE: Integrazione con Tailwind CSS Questo componente è interamente stilizzato usando classi di utilità Tailwind CSS. Affinché lo stile funzioni correttamente, il tuo progetto **deve avere Tailwind CSS configurato**. Inoltre, dovrai assicurarti che le classi Tailwind utilizzate dal componente siano incluse nel processo di "purging" o "tree-shaking" di Tailwind del tuo progetto. Il modo più semplice è aggiungere il percorso ai file del componente (compilati o sorgenti) all'array `content` nel tuo file `tailwind.config.js`: ```javascript // tailwind.config.js (esempio) module.exports = { content: [ "./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}", // Aggiungi il percorso al componente installato: "./node_modules/@your-npm-scope/vue-sorted-searchable-table/dist/**/*.js", // Se il pacchetto espone file JS compilati // Oppure, se il pacchetto espone i file .vue sorgente (meglio per il tree-shaking di Tailwind): // "./node_modules/@your-npm-scope/vue-sorted-searchable-table/src/**/*.vue", ], // ... il resto della tua configurazione Tailwind } ``` Consulta la documentazione del pacchetto specifico una volta pubblicato per il percorso corretto da includere. ## Utilizzo Puoi importare il componente e usarlo nei tuoi file `.vue`. **1. Registrazione Globale (tramite plugin - opzionale):** Se il pacchetto esporta un plugin (controlla la sua documentazione), puoi registrarlo globalmente: ```javascript // main.js (o il tuo file di entry point Vue) import { createApp } from 'vue'; import App from './App.vue'; import VueSortedSearchableTablePlugin from '@your-npm-scope/vue-sorted-searchable-table'; const app = createApp(App); app.use(VueSortedSearchableTablePlugin /*, { componentName: 'MyCustomTable' } */); // Puoi passare opzioni se il plugin lo supporta app.mount('#app'); ``` **2. Importazione Diretta nel Componente:** Questo è l'approccio più comune. ```html <template> <div class="container mx-auto p-4"> <vue-sorted-searchable-table :items="tableItems" :columns="tableColumns" item-key-field="id" title="Gestione Utenti" description="Elenco degli utenti registrati nel sistema." :is-loading="isLoading" :searchable="true" :search-all="true" search-debounce-time="500" table-max-height="70vh" :external-sort="true" :initial-sort-key="currentSort.key" :initial-sort-direction="currentSort.direction" @sort-change="handleSortChange" @search="handleSearchQuery" @add-item="handleAddNewItem" empty-state-message="Nessun utente trovato per i criteri specificati." add-label="Nuovo Utente" > <template #header-actions> <button @click="exportData" class="ml-4 px-3 py-2 text-sm font-semibold text-white bg-green-600 rounded-md shadow-sm hover:bg-green-500"> Esporta Dati </button> </template> <template #row-actions-cell="{ item }"> <a href="#" @click.prevent="viewItemDetails(item)" class="text-indigo-600 hover:text-indigo-900"> Dettagli </a> <a href="#" @click.prevent="deleteItem(item)" class="ml-4 text-red-600 hover:text-red-900"> Elimina </a> </template> <template #cell-status="{ value }"> <span :class="getStatusClass(value)" class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full"> {{ value }} </span> </template> <template #cell-registratoIl="{ value }"> <span>{{ formatDate(value) }}</span> </template> </vue-sorted-searchable-table> </div> </template> <script setup> import { ref, computed } from 'vue'; // Assumendo che il componente sia esportato come 'VueSortedSearchableTable' o un nome simile import { VueSortedSearchableTable } from '@your-npm-scope/vue-sorted-searchable-table'; const isLoading = ref(false); const tableItems = ref([ { id: 'u001', nome: 'Mario Rossi', email: 'mario.rossi@example.com', status: 'Attivo', registratoIl: '2023-01-15T10:00:00Z' }, { id: 'u002', nome: 'Laura Bianchi', email: 'laura.bianchi@example.com', status: 'Inattivo', registratoIl: '2023-02-20T14:30:00Z' }, { id: 'u003', nome: 'Luca Verdi', email: 'luca.verdi@example.com', status: 'Sospeso', registratoIl: '2022-12-05T08:15:00Z' }, // ...altri dati ]); const tableColumns = ref([ { key: 'nome', label: 'Nome Completo', sortable: true, isSearchableField: true }, { key: 'email', label: 'Indirizzo Email', sortable: true, isSearchableField: true }, { key: 'status', label: 'Stato', sortable: true, isSearchableField: true, cellClass: 'text-center' }, { key: 'registratoIl', label: 'Data Registrazione', sortable: true, headerClass: 'text-left' }, ]); const currentSort = ref({ key: 'nome', direction: 'asc' }); const currentSearchTerm = ref(''); // Potresti voler memorizzare qui il termine di ricerca per logica complessa function handleSortChange(sortParams) { console.log('Sort requested:', sortParams); currentSort.value = sortParams; // Qui implementeresti la logica per ricaricare i dati dal backend con i nuovi parametri di ordinamento // Esempio: fetchData({ sort: sortParams.key, direction: sortParams.direction, search: currentSearchTerm.value }); alert(`Ordinamento cambiato: ${sortParams.key} - ${sortParams.direction}`); } function handleSearchQuery(searchPayload) { console.log('Search payload received:', searchPayload); // searchPayload è una stringa: '' (reset), 'search=query', o 'fieldKey=query' // Qui implementeresti la logica per ricaricare i dati dal backend con i parametri di ricerca // Esempio: fetchData({ search: searchPayload, sort: currentSort.value.key, ... }); currentSearchTerm.value = searchPayload; // Aggiorna il termine di ricerca corrente alert(`Ricerca per: ${searchPayload}`); } function handleAddNewItem() { console.log('Add new item action triggered'); alert('Azione Aggiungi Nuovo Elemento'); } function exportData() { console.log('Export data action triggered'); alert('Esporta Dati'); } function viewItemDetails(item) { console.log('View details for:', item); alert(`Dettagli per: ${item.nome}`); } function deleteItem(item) { console.log('Delete item:', item); if (confirm(`Sei sicuro di voler eliminare ${item.nome}?`)) { alert(`${item.nome} eliminato (simulazione).`); // tableItems.value = tableItems.value.filter(i => i.id !== item.id); // Esempio per rimozione client-side } } function getStatusClass(status) { if (status === 'Attivo') return 'bg-green-100 text-green-800'; if (status === 'Inattivo') return 'bg-yellow-100 text-yellow-800'; if (status === 'Sospeso') return 'bg-red-100 text-red-800'; return 'bg-gray-100 text-gray-800'; } function formatDate(dateString) { if (!dateString) return 'N/A'; return new Date(dateString).toLocaleDateString('it-IT', { year: 'numeric', month: 'short', day: 'numeric' }); } </script> ``` ## API del Componente ### Props | Prop | Tipo | Default | Descrizione | | :----------------------- | :------ | :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `title` | String | `undefined` | Titolo opzionale visualizzato sopra la tabella. | | `description` | String | `undefined` | Descrizione opzionale visualizzata sotto il titolo. | | `items` | Array | `[]` ( **richiesto** ) | Array di oggetti che rappresentano le righe della tabella. | | `columns` | Array | `[]` ( **richiesto** ) | Array di oggetti che definiscono le colonne. Vedi "Struttura Oggetto Colonna" sotto. | | `itemKeyField` | String | `undefined` (**richiesto**)| Nome della proprietà univoca in ogni oggetto `item` (usato internamente per `:key` nel `v-for`). | | `showAddButton` | Boolean | `true` | Mostra o nasconde il pulsante di default "Aggiungi elemento" (viene ignorato se lo slot `header-actions` è utilizzato). | | `addLabel` | String | `'Aggiungi elemento'` | Etichetta per il pulsante di default "Aggiungi elemento". | | `isLoading` | Boolean | `false` | Se `true`, mostra un messaggio di caricamento nel corpo della tabella e disabilita alcune interazioni (come la ricerca o l'ordinamento). | | `emptyStateMessage` | String | `'Nessun elemento trovato.'`| Messaggio visualizzato quando l'array `items` è vuoto e `isLoading` è `false`. | | `initialSortKey` | String | `null` | Chiave della colonna per l'ordinamento iniziale. Se `externalSort` è `true`, questa prop dovrebbe riflettere lo stato di ordinamento corrente gestito dal componente padre. | | `initialSortDirection` | String | `'asc'` | Direzione dell'ordinamento iniziale (`'asc'` o `'desc'`). Come `initialSortKey` se `externalSort` è `true`. | | `externalSort` | Boolean | `false` | Se `true`, l'ordinamento non viene eseguito internamente; viene invece emesso l'evento `sort-change` per la gestione da parte del componente padre. | | `showDefaultActionsHeader`| Boolean | `false` | Se `true`, mostra un'intestazione per la colonna delle azioni anche se lo slot `row-actions-header` non è fornito. | | `tableMaxHeight` | String | `null` | Altezza massima CSS per la tabella (es. `'400px'`, `'60vh'`). Se impostata, abilita lo scrolling verticale interno e l'header (`<thead>`) diventa sticky. | | `searchable` | Boolean | `true` | Abilita o disabilita la funzionalità di ricerca integrata (input e dropdown). | | `searchAll` | Boolean | `false` | Se `true` e `searchable` è `true`, aggiunge l'opzione "Cerca ovunque" al dropdown dei campi di ricerca. | | `searchDebounceTime` | Number | `1000` | Tempo di attesa (in millisecondi) dopo che l'utente smette di digitare prima che l'evento `search` venga emesso. | #### Struttura Oggetto Colonna (per la prop `columns`) Ogni oggetto nell'array `columns` definisce una colonna della tabella e può avere le seguenti proprietà: | Proprietà | Tipo | Richiesto | Default | Descrizione | | :------------------ | :------ | :-------- | :------ | :---------------------------------------------------------------------------------------------------------- | | `key` | String | Sì | | La chiave univoca per accedere al valore corrispondente nell'oggetto `item` (es. `item[key]`). | | `label` | String | Sì | | L'etichetta testuale visualizzata nell'header (`<th>`) della colonna. | | `sortable` | Boolean | No | `false` | Se `true`, la colonna sarà cliccabile per l'ordinamento. | | `isSearchableField` | Boolean | No | `true` | Se `true` (default), questa colonna apparirà come opzione nel dropdown dei campi per la ricerca testuale. | | `headerClass` | String | No | `''` | Stringa di classi CSS personalizzate da applicare all'elemento `<th>` di questa colonna. | | `cellClass` | String | No | `''` | Stringa di classi CSS personalizzate da applicare a tutti gli elementi `<td>` di questa colonna. | ### Eventi Emessi * **`add-item`**: * Payload: `undefined` * Emeso quando il pulsante di default "Aggiungi elemento" (visibile se `showAddButton` è `true` e lo slot `header-actions` non è utilizzato) viene cliccato. * **`sort-change`**: * Payload: `Object` - `{ key: String, direction: String }` (es. `{ key: 'nome', direction: 'asc' }`) * Emeso **solo se** `externalSort` è `true` e l'utente clicca sull'intestazione di una colonna `sortable`. Il componente padre è responsabile di aggiornare i dati e le props `initialSortKey`/`initialSortDirection`. * **`search`**: * Payload: `String` * Emeso dopo il `searchDebounceTime` quando l'utente modifica il testo nell'input di ricerca o cambia il campo selezionato nel dropdown di ricerca. * Il formato del payload è: * `''` (stringa vuota): Se il campo di testo della query è vuoto (usato per indicare di resettare o non applicare filtri di ricerca). * `'search=[query]'`: Se l'opzione "Cerca ovunque" è selezionata (richiede `searchAll: true`) e `[query]` è il testo inserito. * `'[fieldKey]=[query]'`: Se un campo specifico è selezionato dal dropdown (es. `'nome=Mario Rossi'`). ### Slot Disponibili * **`header-actions`**: * Scopo: Nessuno. * Utilizza questo slot per inserire pulsanti o altri controlli personalizzati nell'area dell'header della tabella, a destra del titolo e della descrizione. Sostituisce completamente il pulsante di default "Aggiungi elemento". * **`row-actions-header`**: * Scopo: Nessuno. * Permette di definire il contenuto dell'intestazione (`<th>`) per la colonna delle azioni per riga. Utile se `showDefaultActionsHeader` è `true` o se si usa lo slot `row-actions-cell`. * **`row-actions-cell`**: * Scopo: `{ item: Object }` (l'oggetto dati completo per la riga corrente). * Utilizza questo slot per inserire controlli (es. pulsanti "Modifica", "Elimina") nella cella finale di ogni riga. * **`cell-{column.key}`** (Slot Dinamici): * Scopo: `{ item: Object, value: any }` (`item` è l'oggetto dati completo per la riga corrente, `value` è il valore specifico della cella, cioè `item[column.key]`). * Permette di personalizzare completamente il rendering del contenuto di una cella specifica. Sostituisci `{column.key}` con la `key` effettiva della colonna che vuoi personalizzare. * Esempio: Per una colonna definita con `{ key: 'prezzo', ... }`, puoi usare `<template #cell-prezzo="{ value }">...</template>`. ## Licenza MIT (o la licenza specificata nel `package.json` del tuo pacchetto) ---