ItemAdapter (NormalizedItem)
Perché esiste
Senza un adapter, il componente ElementCard dovrebbe sapere di:
- naming italiano (
titolo,priorita,etichette,assegnatario) - naming inglese (
title,priority,tags,assignee) - naming flat-RPC (
priorita_nome,priorita_colore,nome_assegnatario)
L'ItemAdapter traduce ogni item raw nel modello canonico NormalizedItem, eliminando la logica di alias dal componente. Il consumer fornisce un adapter; il default ne gestisce automaticamente le tre forme storiche.
Modello canonico
interface NormalizedItem {
/** Identificatore univoco della scheda. */
id: ItemId
/** Identificatore della colonna/stato a cui appartiene. */
columnId: KanbanColumnId
/** Titolo principale visualizzato nella card. */
title: string
/** Descrizione/dettaglio. Vuoto o null = non visualizzato. */
description?: string | null
/** Priorità mappata. Undefined = nessuna priorità. */
priority?: NormalizedPriority
/** Tag/etichette. Sempre array (vuoto se assenti). */
tags: NormalizedTag[]
/** Persona/team assegnata. Undefined = nessun assegnatario. */
assignee?: NormalizedAssignee
/** Data di scadenza (string ISO, Date, o null). */
dueDate?: string | Date | null
/** Numero/codice della scheda (ticket #, codice CRM, ecc.). */
number?: string | number
/** Numero di commenti. 0 se nessuno. */
commentCount: number
/** Attività collegate. Sempre array. */
activities: Array<Record<string, unknown>>
/** Allegati. Sempre array. */
attachments: Array<Record<string, unknown>>
/** Item raw originale, utile per consumer che vogliono campi extra. */
raw: Record<string, unknown>
}
interface NormalizedPriority { name: string; color?: string }
interface NormalizedTag { id: string | number; name: string; color?: string }
interface NormalizedAssignee { id?: string | number; name: string }
raw è sempre presente: l'item originale del backend resta accessibile nel NormalizedItem. I template card custom possono leggere campi extra direttamente da item.raw.miocampo senza estendere l'interfaccia.Tipo ItemAdapter
type ItemAdapter<TItem = Record<string, unknown>> = (item: TItem) => NormalizedItem
Il generic TItem permette di tipizzare il backend (ItemAdapter<MyApiCard>); il default Record<string, unknown> accetta qualsiasi forma.
Adapter di default
defaultPostgRESTItemAdapter gestisce automaticamente le 3 forme storiche di @pzeta/vue-kanban:
| Forma | Esempio chiavi |
|---|---|
| Italiano (PostgREST diretto) | titolo, descrizione, priorita, etichette, assegnatario, datascadenza, numero |
| Inglese (REST esterni) | title, description, priority, tags, assignee, dueDate, number |
| Flat-RPC (vista denormalizzata) | priorita_nome, priorita_colore, nome_assegnatario, id_assegnatario |
Esposto come default su EnhancedKanbanBoard e KanbanFasiBoard. Non serve passarlo esplicitamente per backend PZeta standard.
Adapter custom
Per backend con shape diversa (es. CRM esterno con UUID e campi custom):
import type { ItemAdapter, NormalizedItem } from '@pzeta/vue-kanban'
interface MyApiCard {
uuid: string
status: string
headline: string
summary: string
priority?: { label: string; hex: string }
labels: Array<{ id: number; name: string; color: string }>
owner?: { id: number; fullName: string }
dueAt: string | null
code: string
commentsCount: number
activities: unknown[]
files: unknown[]
}
const myAdapter: ItemAdapter<MyApiCard> = (card) => ({
id: card.uuid,
columnId: card.status,
title: card.headline,
description: card.summary,
priority: card.priority
? { name: card.priority.label, color: card.priority.hex }
: undefined,
tags: card.labels.map(l => ({ id: l.id, name: l.name, color: l.color })),
assignee: card.owner ? { id: card.owner.id, name: card.owner.fullName } : undefined,
dueDate: card.dueAt,
number: card.code,
commentCount: card.commentsCount,
activities: card.activities,
attachments: card.files,
raw: card,
})
Iniezione nei componenti
Via prop
<EnhancedKanbanBoard
board-id-or-code="X"
:item-adapter="myAdapter"
/>
Via provide/inject (avanzato)
L'adapter è fornito ai discendenti tramite ITEM_ADAPTER_KEY. Componenti card custom posizionati nel sotto-albero possono recuperarlo:
<script setup lang="ts">
import { inject } from 'vue'
import { ITEM_ADAPTER_KEY, defaultPostgRESTItemAdapter } from '@pzeta/vue-kanban'
const adapter = inject(ITEM_ADAPTER_KEY, defaultPostgRESTItemAdapter)
const props = defineProps<{ rawItem: Record<string, unknown> }>()
const normalized = computed(() => adapter(props.rawItem))
</script>
<template>
<div class="custom-card">
<h4>{{ normalized.title }}</h4>
<span v-if="normalized.priority" :style="{ color: normalized.priority.color }">
{{ normalized.priority.name }}
</span>
</div>
</template>
Pattern: esporre campi extra dal backend
I campi non presenti nel modello canonico restano accessibili tramite raw:
<script setup lang="ts">
import type { NormalizedItem } from '@pzeta/vue-kanban'
const props = defineProps<{ item: NormalizedItem }>()
const valoreContratto = computed(() => props.item.raw.valore_contratto as number | undefined)
const cliente = computed(() => props.item.raw.cliente as { nome: string } | undefined)
</script>
<template>
<div>
<h4>{{ item.title }}</h4>
<p v-if="cliente">Cliente: {{ cliente.nome }}</p>
<p v-if="valoreContratto">€ {{ valoreContratto.toLocaleString('it-IT') }}</p>
</div>
</template>
TypeScript
import {
type ItemAdapter,
type NormalizedItem,
type NormalizedPriority,
type NormalizedTag,
type NormalizedAssignee,
defaultPostgRESTItemAdapter,
ITEM_ADAPTER_KEY,
} from '@pzeta/vue-kanban'
ConfigService (deprecato)
ConfigService è stato rimosso in v2. Usare initializePostgRESTClient() per configurare il client e PostgRESTDataSourceOptions per il resto.
KanbanDataSource (Strategy)
Interfaccia Strategy per disaccoppiare la libreria dal backend. PostgRESTDataSource di default; implementabile per REST custom, GraphQL, mock.