Composables

useCrudView

Gestione UI CRUD (dialog + editing + delete con conferma) sopra una lista reattiva. Include useCrudViewFromResource per il bridge automatico.

Signature

function useCrudView<T extends Identifiable, CreateRequest, UpdateRequest = Partial<CreateRequest>>(
  options: CrudViewOptions<T, CreateRequest, UpdateRequest>,
): CrudViewResult<T, CreateRequest, UpdateRequest>

function useCrudViewFromResource<T extends Identifiable, CreateRequest, UpdateRequest = Partial<CreateRequest>>(
  resource: CrudResourceResult<T, CreateRequest, UpdateRequest>,
  options: CrudViewFromResourceOptions<T>,
): CrudViewResult<T, CreateRequest, UpdateRequest>

Astrae il pattern dialogVisible + editingItemId + handleCreate/handleEdit/handleSave/handleDelete ricorrente in viste lista CRUD.

Headless by design: toast e confirm non sono importati dalla libreria — sono callback iniettate dal MFE (es. useToast()/useConfirm() di PrimeVue). Così la libreria resta agnostica rispetto alla UI library.

Type parameters

ParameterVincoloDescrizione
Textends Identifiable ({ id: string })Entità della lista
CreateRequestPayload create
UpdateRequestdefault Partial<CreateRequest>Payload update

Opzioni CrudViewOptions

interface CrudViewOptions<T extends Identifiable, CreateRequest, UpdateRequest = Partial<CreateRequest>> {
  items: Ref<T[]>
  loadItems?: () => Promise<void>
  createItem: (payload: CreateRequest) => Promise<T>
  updateItem?: (id: string, payload: UpdateRequest) => Promise<T>
  deleteItem?: (id: string) => Promise<void>
  resourceName: string
  singularName?: string
  getDisplayName?: (item: T) => string
  showErrorToast?: (message: string) => void
  showDeleteConfirm?: (message: string, header: string, onAccept: () => void) => void
}
OpzioneTipoDefaultDescrizione
itemsRef<T[]>Lista reattiva (tipicamente da useCrudResource)
loadItems() => Promise<void>Reload dopo save/delete (opzionale)
createItem(payload) => Promise<T>Obbligatorio
updateItem(id, payload) => Promise<T>Se omesso, handleSave userà sempre createItem
deleteItem(id) => Promise<void>Se omesso, handleDelete lancia errore
resourceNamestringNome plurale (es. 'clienti')
singularNamestringresourceNameNome singolare per messaggi ("Conferma Eliminazione cliente")
getDisplayName(item) => stringitem => item.idEstrae nome visualizzato (es. (c) => c.ragioneSociale)
showErrorToast(message) => voidCallback toast in caso di errore save/delete
showDeleteConfirm(message, header, onAccept) => voidCallback confirm dialog. Senza, elimina direttamente senza conferma

Risultato CrudViewResult

interface CrudViewResult<T, CreateRequest, UpdateRequest> {
  dialogVisible: Ref<boolean>
  editingItemId: Ref<string | null>
  formKey: Ref<number>
  editingItem: ComputedRef<T | undefined>
  handleCreate: () => void
  handleEdit: (id: string) => void
  handleSave: (payload: CreateRequest | UpdateRequest) => Promise<void>
  handleDelete: (id: string) => void
}
CampoDescrizione
dialogVisiblev-model per <Dialog>
editingItemIdnull = creazione, string = modifica
formKeyIncrementato a ogni handleCreate/handleEdit → forza re-mount del form (reset stato interno)
editingItemItem corrente da items.value.find(i => i.id === editingItemId.value)
handleCreate()Apre dialog in modalità creazione (editingItemId = null)
handleEdit(id)Apre dialog in modalità modifica
handleSave(payload)Se editingItemId !== null && updateItem: update; altrimenti create. Chiude dialog, ricarica, gestisce errori con toast
handleDelete(id)Mostra confirm con messaggio "Eliminare definitivamente {singularName} '{displayName}'? L'operazione non è reversibile." e poi elimina

Esempio manuale

import { ref } from 'vue'
import { useCrudView } from '@pzeta/mfe-contracts/composables'
import { useToast, useConfirm } from 'primevue'

interface Cliente { id: string; ragioneSociale: string; piva: string }
interface CreateClienteRequest { ragioneSociale: string; piva: string }

const items = ref<Cliente[]>([])
const toast = useToast()
const confirm = useConfirm()

const view = useCrudView<Cliente, CreateClienteRequest>({
  items,
  resourceName: 'clienti',
  singularName: 'cliente',
  getDisplayName: (c) => c.ragioneSociale,
  loadItems: async () => { items.value = await fetchClienti() },
  createItem: async (p) => await apiPostCliente(p),
  updateItem: async (id, p) => await apiPutCliente(id, p),
  deleteItem: async (id) => { await apiDeleteCliente(id) },
  showErrorToast: (msg) =>
    toast.add({ severity: 'error', detail: msg, life: 5000 }),
  showDeleteConfirm: (message, header, onAccept) =>
    confirm.require({ message, header, accept: onAccept }),
})
<template>
  <button @click="view.handleCreate">Nuovo</button>

  <table>
    <tr v-for="c in items" :key="c.id">
      <td>{{ c.ragioneSociale }}</td>
      <td>
        <button @click="view.handleEdit(c.id)">Modifica</button>
        <button @click="view.handleDelete(c.id)">Elimina</button>
      </td>
    </tr>
  </table>

  <Dialog v-model:visible="view.dialogVisible.value">
    <ClienteForm
      :key="view.formKey.value"
      :cliente="view.editingItem.value"
      @save="view.handleSave"
    />
  </Dialog>
</template>

Bridge: useCrudViewFromResource

Quando hai già un useCrudResource, useCrudViewFromResource collega automaticamente i due composables ed elimina il wiring manuale:

import {
  useCrudResource,
  useCrudViewFromResource,
} from '@pzeta/mfe-contracts/composables'

const resource = useCrudResource<Cliente, CreateClienteRequest>({
  resourceName: 'clienti',
  loadItems: /* ... */,
  createItem: /* ... */,
  updateItem: /* ... */,
  deleteItem: /* ... */,
})

const view = useCrudViewFromResource(resource, {
  singularName: 'cliente',
  getDisplayName: (c) => c.ragioneSociale,
  showErrorToast: /* ... */,
  showDeleteConfirm: /* ... */,
})

Internamente collega resource.itemsview.items, resource.loadview.loadItems, e così via.

Opzioni CrudViewFromResourceOptions

interface CrudViewFromResourceOptions<T extends Identifiable> {
  singularName?: string
  getDisplayName?: (item: T) => string
  showErrorToast?: (message: string) => void
  showDeleteConfirm?: (message: string, header: string, onAccept: () => void) => void
}

Solo aspetti di presentazione: tutto il resto è ereditato dal resource.

Comportamento handleDelete

1. Trova l'item: items.find(i => i.id === id)
2. Costruisce displayName via getDisplayName(item)
3. Costruisce message: "Eliminare definitivamente {singularName} '{displayName}'? L'operazione non è reversibile."
4. Costruisce header: "Conferma Eliminazione {singularName}"
5. Se showDeleteConfirm fornita:
     → mostra confirm con onAccept = doDelete()
   altrimenti:
     → esegue doDelete() direttamente

Tipi correlati