Data

WeekScheduler

Griglia settimanale risorsa x giorno (es. autisti x giorni della settimana), con giorni divisi in turni opzionali e slot personalizzabili per intestazioni, celle ed eventi. Pensato per planner, scheduler, gestione turni e assegnazioni.

Import

import { WeekScheduler } from '@pzeta/vue-components'

Esempio Base

Props

PropTipoDefaultDescrizione
resourcesSchedulerResource[](required)Elenco delle risorse mostrate sulle righe (es. autisti, mezzi, sale)
eventsSchedulerEvent[](required)Elenco degli eventi/giri/turni da posizionare nella griglia
startDateDate | stringinizio settimana correnteData di riferimento; viene normalizzata all'inizio della settimana secondo weekStartsOn
weekStartsOn0 | 1 | 2 | 3 | 4 | 5 | 61Giorno di inizio settimana (0 = domenica, 1 = lunedi)
resourceKeystring | (r) => string | number'id'Nome del campo (o accessor) usato come chiave della risorsa
eventResourceKeystring | (e) => string | number | null'resourceId'Nome del campo (o accessor) che lega l'evento alla risorsa; null/undefined => riga non assegnati
eventDateKeystring | (e) => string'date'Nome del campo (o accessor) che fornisce la data ISO YYYY-MM-DD dell'evento
showUnassignedRowbooleanfalseMostra una riga aggiuntiva in alto per gli eventi senza resourceId
unassignedLabelstring'Non assegnati'Etichetta della riga non assegnati
todayHighlightbooleantrueEvidenzia con sfondo blu la colonna del giorno corrente
localestring'it-IT'Locale Intl.DateTimeFormat per i label dei giorni
minCellHeightstring'40px'Altezza minima della cella (CSS)
resourceColumnWidthstring'9rem'Larghezza della colonna risorse (CSS)
dayLabelFormat'short' | 'long' | 'numeric''short'Formato del nome del giorno nell'header colonna
range'week' | 'month' | 'quarter''week'Arco delle colonne: la settimana di startDate, il suo mese, o tre mesi a partire dal suo mese
minDayWidthstring'6rem'Larghezza minima di una colonna giorno: sugli archi lunghi la griglia scorre in orizzontale
shiftsSchedulerShift[][]Turni in cui si divide ogni giorno, in ordine. Vuoto = nessuna suddivisione
eventShiftKeystring | (e) => string | number | null'shift'Campo (o accessor) con il turno iniziale dell'evento, confrontato con SchedulerShift.key
eventShiftEndKeystring | (e) => string | number | null'shiftEnd'Campo (o accessor) con il turno finale, compreso, nello stesso giorno
shiftLayout'columns' | 'rows''columns'columns: una sotto-colonna per turno; rows: una riga per turno
minShiftWidthstringminDayWidthSolo columns: larghezza minima di ogni sotto-colonna turno
shiftLabelWidthstring'6rem'Solo rows: larghezza della colonna con le etichette dei turni
draggableEventsbooleanfalseGli eventi si trascinano su altre celle, turni o sulla riga non assegnati ed emettono eventDrop
eventDraggable(e) => boolean-Con draggableEvents, false blocca il singolo evento (es. turni gia' confermati)
droppablebooleanfalseLe celle accettano le righe di una DataTable con draggable-rows, o di qualunque elemento che chiama startRowsDrag, ed emettono cellDrop
canDrop(ctx: SchedulerCanDropContext) => boolean-false rifiuta la destinazione: evidenziata in rosso, nessun emit. ctx ha to e event (trascinamento interno) o data (rilascio esterno)

SchedulerResource

Proprieta'TipoDescrizione
idstring | numberIdentificatore univoco (override possibile via resourceKey)
[key]unknownQualsiasi altro campo: viene esposto nello slot #resource per il rendering custom

SchedulerEvent

Proprieta'TipoDescrizione
idstring | numberIdentificatore univoco dell'evento
resourceIdstring | number | nullChiave della risorsa di appartenenza; null per gli eventi non assegnati
datestringData ISO YYYY-MM-DD
shiftstring | numberTurno iniziale, solo con shifts (campo letto da eventShiftKey)
shiftEndstring | numberTurno finale, compreso, solo con shifts (campo letto da eventShiftEndKey)
[key]unknownQualsiasi altro campo: viene esposto nello slot #event

SchedulerShift

Proprieta'TipoDescrizione
keystring | numberIdentificatore del turno, confrontato con il turno dell'evento (1 e '1' coincidono)
labelstringEtichetta mostrata nell'intestazione (columns) o a inizio riga (rows)
[key]unknownCampi liberi (orari, colore, ...) letti dagli slot, es. shiftHeader

SchedulerDay

Proprieta'TipoDescrizione
dateDateOggetto data del giorno
dateStrstringData ISO YYYY-MM-DD
labelstringEtichetta formattata secondo locale e dayLabelFormat
isTodaybooleantrue se corrisponde al giorno corrente
dayIndexnumberIndice del giorno nella settimana (0-6)

Slots

SlotPropsDescrizione
resourceHeader-Contenuto dell'intestazione della colonna risorse (in alto a sinistra)
dayHeader{ day: SchedulerDay }Override del label di una colonna giorno
shiftHeader{ day: SchedulerDay, shift: SchedulerShift } ({ shift } in rows)Override dell'etichetta di un turno: sotto il giorno in columns, a inizio riga in rows
resource{ resource: SchedulerResource }Rendering della cella risorsa (colonna fissa di sinistra)
event{ event, resource, day, shift, endShift, span, isUnassigned }Rendering di un singolo evento. Con i turni e' il contenuto della barra: shift/endShift sono il turno iniziale e finale, span il numero di turni occupati; senza turni valgono null, null, 1
cell{ resource, day, shift, events, isUnassigned, dragAttrs }Senza turni: override completo della cella. Con i turni: sfondo della sotto-cella, sotto le barre; events contiene gli eventi che coprono shift. Senza turni e con draggableEvents, v-bind="dragAttrs(event)" rende trascinabile l'elemento che disegna l'evento
unassignedLabel-Override del label della riga non assegnati
footerTotalsLabel-Etichetta della riga dei totali (prima colonna)
footerTotals{ day, date, count, shift }Una cella per giorno, o per giorno x turno in columns; count conta gli eventi del giorno, o quelli che coprono shift. La riga compare solo se lo slot e' fornito
Senza turni lo slot #cell ha precedenza sullo slot #event: se viene fornito, il rendering degli eventi all'interno della cella e' interamente delegato al consumer. Con shifts invece le barre le posiziona sempre il componente, perche' una barra che attraversa piu' turni non puo' essere disegnata da uno slot che vede una sola sotto-cella: gli eventi si disegnano con #event, e #cell diventa lo sfondo della sotto-cella (es. un pulsante "+"), mostrato sotto le barre.

Emits

EventoPayloadDescrizione
update:startDateDateEmesso quando la data di partenza cambia (per v-model:start-date)
cellClick{ resource: SchedulerResource | null, date: string, events: SchedulerEvent[], shift: SchedulerShift | null, originalEvent: MouseEvent }Emesso al click su una cella vuota o sul background di una cella. Con i turni riporta la sotto-cella cliccata, ed events contiene gli eventi che coprono quel turno
eventClick{ event: SchedulerEvent, resource: SchedulerResource | null, date: string, shift: SchedulerShift | null, endShift: SchedulerShift | null, originalEvent: MouseEvent }Emesso al click su un singolo evento; originalEvent.stopPropagation() viene chiamato automaticamente. shift/endShift sono il turno iniziale e finale (null senza turni)
eventDrop{ event, from: SchedulerSpanRef, to: SchedulerSpanRef, copy: boolean, originalEvent: DragEvent }Con draggableEvents: evento rilasciato su un'altra posizione. from/to hanno resource, date, shift, endShift; copy e' true con Ctrl o Alt. Il componente non sposta niente: aggiorna events il consumer
cellDrop{ data: unknown[], to: SchedulerCellRef, originalEvent: DragEvent }Con droppable: elementi esterni rilasciati su una cella (to ha resource, date, shift)

Esempi

Slot custom con stato colorato

Pattern tipico per planner di giri/turni: ogni evento ha uno stato (PIANIFICATO, IN_CORSO, ...) mappato a una variante di colore.

Riga "Non assegnati" + handler click

Abilita una riga in alto per gli eventi con resourceId: null e gestisce sia il click sulla cella sia il click sul singolo evento.

v-model:start-date permette di pilotare la settimana visualizzata da un controllo esterno (toolbar, date picker, scorciatoie).

Turni: Mattino e Pomeriggio

Con shifts ogni giorno si divide in una sotto-colonna per turno. Un evento con shift e shiftEnd diversi e' una sola barra sui turni che occupa; gli eventi che si sovrappongono nella stessa cella vanno su corsie diverse.

Turni in righe su un mese

Con molti giorni le sotto-colonne diventano troppe (un trimestre con 3 turni sono fino a 276 colonne): shift-layout="rows" da' a ogni risorsa una riga per turno e lascia una colonna per giorno. Un evento su piu' turni e' una barra verticale; le corsie si affiancano dentro la colonna del giorno.

Drag & drop tra celle

Con draggable-events gli eventi si trascinano su un'altra risorsa, un altro giorno o un altro turno, compresa la riga "Non assegnati". Lo scheduler emette eventDrop e non sposta niente: e' la pagina ad aggiornare events (e a salvare). Una barra su piu' turni conserva lunghezza e punto di presa; se non entra nella giornata la destinazione non e' valida. Con Ctrl (Alt su macOS) copy e' true e il consumer duplica invece di spostare.

WCAG 2.2, criterio 2.5.7 Dragging Movements: ogni spostamento fatto trascinando deve avere un'alternativa con un solo puntatore. Lo scheduler non la fornisce da se': il percorso tipico e' @event-click che apre un dialog con risorsa, giorno e turni. La demo DragDrop.vue della libreria lo mostra.

Rilascio da sorgenti esterne

Con droppable le celle accettano le righe di una DataTable con draggable-rows (tutta la selezione, se la riga trascinata ne fa parte) e ricevono cellDrop. Lo stesso meccanismo vale per Tree e TreeTable. Qualunque altro elemento diventa una sorgente chiamando startRowsDrag nel suo dragstart e endRowsDrag nel dragend. Un elemento esterno occupa una sola sotto-cella: se il nuovo evento deve coprire piu' turni, lo decide il consumer.

Accessor function per chiavi non standard

Se il modello dati usa chiavi diverse (es. idautista, idgiro), passa una funzione invece di una stringa.

<WeekScheduler
  :resources="autisti"
  :events="giri"
  :resource-key="(r) => r.idautista"
  :event-resource-key="(e) => e.idautista"
  :event-date-key="(e) => e.data"
  :event-shift-key="(e) => e.codturnoinizio"
  :event-shift-end-key="(e) => e.codturnofine"
>
  <template #resource="{ resource }">
    {{ resource.cognome }} {{ resource.nome }}
  </template>
  <template #event="{ event }">
    Giro #{{ event.idgiro }}
  </template>
</WeekScheduler>

Note Comportamento

  • La data di partenza viene normalizzata all'inizio della settimana corrente in base a weekStartsOn: passare 2026-05-14 con weekStartsOn: 1 produce comunque una griglia che parte da lunedi
  • Gli eventi vengono indicizzati per (resourceId, date) in un Map ricalcolato in modo reattivo su events/resources: le mutazioni alle reference sono efficienti
  • Il click su un evento ferma la propagazione (stopPropagation) per non far scattare anche cellClick
  • La colonna delle risorse e' sticky (position: sticky; left: 0). La griglia ha una larghezza minima calcolata dalle colonne (resourceColumnWidth + giorni x minDayWidth, o giorni x turni x minShiftWidth in columns): sotto quella larghezza scorre in orizzontale
  • Eventi con resourceId non presente in resources vengono ignorati nel rendering (non si crea una riga fantasma)
  • Con i turni, un evento il cui turno iniziale non corrisponde a nessuno shift.key (o e' null) non viene mostrato, come gli eventi senza risorsa quando showUnassignedRow e' spento. Un turno finale assente, sconosciuto o precedente all'iniziale riduce l'evento al solo turno iniziale. Gli eventi a cavallo di due giorni non sono supportati
  • Con i turni le corsie sono il minimo indispensabile: gli eventi si scorrono per turno iniziale e ognuno va nella prima corsia libera, quindi una barra Mattino e una Pomeriggio stanno sulla stessa corsia
  • Con i turni la tabella usa table-layout: fixed: le colonne hanno larghezze uguali e non si adattano al contenuto, che va troncato (es. truncate) se non ci sta
  • Drag & drop: e' HTML5 nativo come in DataTable, Tree e TreeTable, quindi su touch vale il supporto del browser. Durante il trascinamento la destinazione e' evidenziata su tutte le sotto-celle che l'evento occuperebbe (blu se valida, rossa se rifiutata), e vicino ai bordi la griglia scorre in orizzontale. Un rilascio nella posizione di partenza non emette nulla. Senza draggableEvents e droppable non c'e' nessun attributo draggable ne' listener di trascinamento

Accessibilita'

  • Le celle e gli eventi sono <div> con handler click; per planner accessibili da tastiera valutare l'override via slot #cell con elementi <button> nativi
  • Il giorno corrente riceve una variazione di colore (bg-blue-50 text-blue-700) quando todayHighlight e' attivo
  • Con i turni ogni barra contiene un testo visibile solo agli screen reader con giorno e intervallo di turni (es. "lun 06/10, Mattino–Pomeriggio"), letto prima del contenuto dello slot #event

TypeScript

import type {
  WeekSchedulerProps,
  WeekSchedulerEmits,
  SchedulerResource,
  SchedulerEvent,
  SchedulerCellClickPayload,
  SchedulerEventClickPayload,
  SchedulerShift,
  SchedulerEventDropPayload,
  SchedulerCellDropPayload,
} from '@pzeta/vue-components'

interface Autista extends SchedulerResource {
  idautista: number
  cognome: string
  nome: string
}

interface Giro extends SchedulerEvent {
  idgiro: number
  descrizione: string
  codstato: 'PIANIFICATO' | 'ASSEGNATO' | 'IN_CORSO' | 'COMPLETATO' | 'ANNULLATO'
}

const onEventClick = (payload: SchedulerEventClickPayload) => {
  const giro = payload.event as Giro
  console.log('giro', giro.idgiro, giro.codstato)
}

Quando usarlo

  • Planner giornaliero/settimanale per risorse (autisti, mezzi, dipendenti, sale)
  • Gestione turni e assegnazioni
  • Visualizzazione a griglia di prenotazioni o appuntamenti

Per casi diversi:

  • Vista mese con sidebar eventi del giorno → EventCalendar
  • Cronologia eventi singola (non gridata per risorsa) → Timeline
  • Selezione di una singola data → DatePicker (categoria forms)