Vue Kanban
Cos'è @pzeta/vue-kanban
@pzeta/vue-kanban è una libreria di componenti Vue 3 per la gestione di board Kanban data-source agnostic, con implementazione PostgREST di default ma backend pluggable via Strategy pattern.
Componenti UI, composables e dialog wrappers per gestire pipeline di lavorazione (CRM, ticketing, fasi di progetto), con drag-and-drop nativo, metriche di stallo/avanzamento e CRUD ottimistico.
KanbanDataSource, item adapter per normalizzare naming italiano/inglese/RPC, dialog default su sub-export @pzeta/vue-kanban/dialogs. Bundle base 278 KB (-42% vs v1). Vedi Migration v1 → v2.Architettura
EnhancedKanbanBoard / KanbanFasiBoard ← Componenti UI (smart)
↓
useBoardService(boardId, { dataSource? }) ← Composables Vue
↓
KanbanDataSource (interface) ← Strategy pattern
↓
PostgRESTDataSource (default) ← Implementazione di default
↓
PostgRESTClient ← Backend (qualsiasi REST/GraphQL/mock via custom)
Le card sono disaccoppiate dal backend tramite il NormalizedItem view-model: ogni ItemAdapter traduce un item raw nel formato canonico, eliminando dal componente la logica di alias (priorita vs priority vs priorita_nome).
Funzionalità principali
- Data-source agnostic:
PostgRESTDataSourcedi default, sostituibile implementandoKanbanDataSource - Item adapter: normalizzazione automatica fra naming italiano/inglese/RPC flat
- Slot-driven dialog: 5 dialog opzionali (form, tag, comment, attachment, activity) via
@pzeta/vue-kanban/dialogs - Drag & Drop nativo (
vue-draggable-next) con CRUD ottimistico e rollback automatico - Metriche fasi: avanzamento, schede in stallo, WIP limit (con
KanbanFasiBoard) - Type-safe: TypeScript strict, generics su tipi backend
- Tree-shakable: bundle base 278 KB; dialog default su sub-entry separato
Modello canonico
interface NormalizedItem {
id: string | number
columnId: string | number
title: string
description?: string | null
priority?: { name: string; color?: string }
tags: Array<{ id: string | number; name: string; color?: string }>
assignee?: { id?: string | number; name: string }
dueDate?: string | Date | null
number?: string | number
commentCount: number
activities: Array<Record<string, unknown>>
attachments: Array<Record<string, unknown>>
raw: Record<string, unknown> // item originale del backend
}
Componenti principali
| Componente | Da | Descrizione |
|---|---|---|
KanbanBoard | @pzeta/vue-kanban | Board base data-agnostic, drag-and-drop generico |
EnhancedKanbanBoard | @pzeta/vue-kanban | Smart component con useBoardService + slot dialog |
KanbanFasiBoard | @pzeta/vue-kanban | Variante con metriche fasi avanzate (WIP, stallo, avanzamento) |
ElementCard | @pzeta/vue-kanban | Card di default basata su NormalizedItem |
Default*Dialog | @pzeta/vue-kanban/dialogs | 5 wrapper allineati ai slot props (form, tag, comment, attachment, activity) |
Quando usare cosa
| Scenario | Soluzione |
|---|---|
| Board generica (Ticket, Project, CRM) su PostgREST | EnhancedKanbanBoard |
| Pipeline con WIP limit, metriche, team per fase | KanbanFasiBoard |
| Layout completamente custom | KanbanBoard + composable manuale |
| Solo logica, nessun componente | useBoardService headless |
| Backend non-PostgREST (REST/GraphQL/mock) | useBoardService(id, { dataSource: myCustomDS }) |
| Backend con shape diversa (camelCase, snake_case custom) | EnhancedKanbanBoard :item-adapter="myAdapter" |
Sotto-pagine
Componenti Board
Componenti per la visualizzazione della board Kanban.
Dialog default (sub-export)
5 wrapper allineati ai slot props di EnhancedKanbanBoard / KanbanFasiBoard. Importati da @pzeta/vue-kanban/dialogs per mantenere optional le peer dep.
Quick start
// main.ts
import { createApp } from 'vue'
import { initializePostgRESTClient } from '@pzeta/vue-kanban'
import App from './App.vue'
const app = createApp(App)
await initializePostgRESTClient()
app.mount('#app')
<script setup lang="ts">
import { EnhancedKanbanBoard, getIdUtenteFromJwt } from '@pzeta/vue-kanban'
import {
DefaultFormDialog,
DefaultTagDialog,
DefaultCommentDialog,
DefaultAttachmentDialog,
DefaultActivityDialog,
} from '@pzeta/vue-kanban/dialogs'
const getToken = () => sessionStorage.getItem('jwt')
const getCurrentUserId = () => getIdUtenteFromJwt()
</script>
<template>
<EnhancedKanbanBoard
board-id-or-code="TICKET"
:get-token="getToken"
:get-current-user-id="getCurrentUserId"
>
<template #dialog-form="p"><DefaultFormDialog v-bind="p" /></template>
<template #dialog-attachment="p"><DefaultAttachmentDialog v-bind="p" /></template>
<template #dialog-tag="p"><DefaultTagDialog v-bind="p" /></template>
<template #dialog-activity="p"><DefaultActivityDialog v-bind="p" :get-token="getToken" /></template>
<template #dialog-comment="p"><DefaultCommentDialog v-bind="p" /></template>
</EnhancedKanbanBoard>
</template>
Migration v1 → v2
| Cosa | v1.x | v2.x |
|---|---|---|
| Configurazione client | configService.setConfig({ apiBaseUrl }) | await initializePostgRESTClient() |
| Dialog default | Inline in KanbanFasiBoard | Sub-export @pzeta/vue-kanban/dialogs (slot-driven) |
@pzeta/attivita | dependencies | peerDependenciesMeta.optional |
@pzeta/vue-attachment | dependencies | peerDependenciesMeta.optional |
attivitaGetToken prop | Specifico | Rinominato → getToken (provider unico) |
attivitaBaseUrl prop | Su board | Rimosso, configurato a livello app |
| Bundle format | ES + UMD | ES only |
| Bundle base | 482 KB | 278 KB (-42%) |
| Backend coupling | Hard-coded PostgREST | Strategy via KanbanDataSource |