Vue Kanban

Libreria Vue 3 per board Kanban data-source agnostic. PostgREST di default, sostituibile via KanbanDataSource. Slot-driven dialogs e item adapter.

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.

v2.0 — architettura riscritta: backend pluggable via 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: PostgRESTDataSource di default, sostituibile implementando KanbanDataSource
  • 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

ComponenteDaDescrizione
KanbanBoard@pzeta/vue-kanbanBoard base data-agnostic, drag-and-drop generico
EnhancedKanbanBoard@pzeta/vue-kanbanSmart component con useBoardService + slot dialog
KanbanFasiBoard@pzeta/vue-kanbanVariante con metriche fasi avanzate (WIP, stallo, avanzamento)
ElementCard@pzeta/vue-kanbanCard di default basata su NormalizedItem
Default*Dialog@pzeta/vue-kanban/dialogs5 wrapper allineati ai slot props (form, tag, comment, attachment, activity)

Quando usare cosa

ScenarioSoluzione
Board generica (Ticket, Project, CRM) su PostgRESTEnhancedKanbanBoard
Pipeline con WIP limit, metriche, team per faseKanbanFasiBoard
Layout completamente customKanbanBoard + composable manuale
Solo logica, nessun componenteuseBoardService 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

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

Cosav1.xv2.x
Configurazione clientconfigService.setConfig({ apiBaseUrl })await initializePostgRESTClient()
Dialog defaultInline in KanbanFasiBoardSub-export @pzeta/vue-kanban/dialogs (slot-driven)
@pzeta/attivitadependenciespeerDependenciesMeta.optional
@pzeta/vue-attachmentdependenciespeerDependenciesMeta.optional
attivitaGetToken propSpecificoRinominato → getToken (provider unico)
attivitaBaseUrl propSu boardRimosso, configurato a livello app
Bundle formatES + UMDES only
Bundle base482 KB278 KB (-42%)
Backend couplingHard-coded PostgRESTStrategy via KanbanDataSource