diff --git a/dev-docs/VUEXY_COMPONENTS.md b/dev-docs/VUEXY_COMPONENTS.md index 960e12b6..3e5cb611 100644 --- a/dev-docs/VUEXY_COMPONENTS.md +++ b/dev-docs/VUEXY_COMPONENTS.md @@ -1,777 +1,33 @@ -# Vuexy Component Registry +# Vuexy Components — DEPRECATED -Reference document for all frontend work. Consult BEFORE writing any new component. -Generated from Vuexy template v10.11.1 (`resources/vuexy-admin-v10.11.1/vue-version/`) and Crewli codebase scan. +> This document is deprecated. The Crewli SPA is migrating from Vuetify +> to PrimeVue per [RFC-WS-FRONTEND-PRIMEVUE](./RFC-WS-FRONTEND-PRIMEVUE.md). ---- +## During F4 component migration -## 1. @core Components +The SPA contains both Vuetify and PrimeVue components simultaneously. +Reference the active framework per surface: -Source: `resources/vuexy-admin-v10.11.1/vue-version/typescript-version/full-version/src/@core/components/` -Copied into: `apps/app/src/@core/components/` +- **For PrimeVue conventions** (target state): see [`PRIMEVUE_COMPONENTS.md`](./PRIMEVUE_COMPONENTS.md) +- **For Vuetify conventions on un-migrated surfaces**: see git history + at commit `1c449ff6204cae6371da08c34ea8934d6b2ffcb8` (HEAD of `main` + immediately before F2): -### Form Element Wrappers (`@core/components/app-form-elements/`) + ```bash + git show 1c449ff6204cae6371da08c34ea8934d6b2ffcb8:dev-docs/VUEXY_COMPONENTS.md + ``` -These wrap Vuetify form components with a **separate label row** for consistent styling. Use these instead of raw Vuetify form components in all forms. +## End of life -| Component | Purpose | Key Props | When to Use | -|-----------|---------|-----------|-------------| -| **AppTextField** | VTextField wrapper with separate label | All VTextField props via `$attrs` + `label` | Text input fields | -| **AppTextarea** | VTextarea wrapper with separate label | All VTextarea props via `$attrs` + `label` | Multi-line text input | -| **AppSelect** | VSelect wrapper with separate label | All VSelect props via `$attrs` + `label` | Dropdown select fields | -| **AppAutocomplete** | VAutocomplete wrapper with separate label | All VAutocomplete props via `$attrs` + `label` | Searchable dropdowns | -| **AppCombobox** | VCombobox wrapper with separate label | All VCombobox props via `$attrs` + `label` | Free-text + dropdown combo | -| **AppDateTimePicker** | Flatpickr-based date/time picker with Vuetify styling | `modelValue`, `placeholder`, `autofocus`, `counter`, `prefix`, `suffix`, all VInput/VField props | Date and time selection | +This file is deleted entirely in F6 cleanup once F4a–F4d have migrated +all surfaces to PrimeVue and F5 has validated the migration. -All wrappers forward **all slots and events** from the underlying Vuetify component. +## Why a stub instead of immediate deletion -### Custom Form Controls (`@core/components/app-form-elements/`) - -Styled card-based selection controls with title, description, and optional media. - -| Component | Purpose | Key Props | Emits | -|-----------|---------|-----------|-------| -| **CustomCheckboxes** | Styled checkboxes with title + description | `selectedCheckbox: string[]`, `checkboxContent: CustomInputContent[]`, `gridColumn?` | `update:selectedCheckbox` | -| **CustomCheckboxesWithIcon** | Checkboxes with icon display | Same as above (content includes `icon`) | `update:selectedCheckbox` | -| **CustomCheckboxesWithImage** | Checkboxes with image backgrounds | `selectedCheckbox: string[]`, `checkboxContent: { bgImage, value, label? }[]` | `update:selectedCheckbox` | -| **CustomRadios** | Styled radio buttons with title + description | `selectedRadio: string`, `radioContent: CustomInputContent[]`, `gridColumn?` | `update:selectedRadio` | -| **CustomRadiosWithIcon** | Radio buttons with icon display | Same as above (content includes `icon`) | `update:selectedRadio` | -| **CustomRadiosWithImage** | Radio buttons with image backgrounds | `selectedRadio: string`, `radioContent: { bgImage, value, label? }[]` | `update:selectedRadio` | - -### Card Components (`@core/components/cards/`) - -| Component | Purpose | Key Props | When to Use | -|-----------|---------|-----------|-------------| -| **CardStatisticsHorizontal** | Stat card — icon right, text left | `title`, `icon`, `stats`, `color?` (default: primary) | Dashboard KPI cards (horizontal layout) | -| **CardStatisticsVertical** | Stat card with embedded ApexChart | `title`, `icon`, `stats`, `height`, `series`, `chartOptions`, `color?` | Stat cards with trend charts | -| **CardStatisticsVerticalSimple** | Simple stat card — icon + value | `title`, `icon`, `stats`, `color?` | Simple stat display without charts | -| **AppCardActions** | Card with collapse/refresh/remove action buttons | `collapsed?`, `noActions?`, `actionCollapsed?`, `actionRefresh?`, `actionRemove?`, `loading?`, `title?` | Cards needing user-togglable actions | -| **AppCardCode** | Code display with syntax highlighting + TS/JS toggle | `title`, `code: Record<'ts'\|'js', string>`, `codeLanguage?`, `noPadding?` | Documentation / code examples | - -### Utility Components (`@core/components/`) - -| Component | Purpose | Key Props / Events | When to Use | -|-----------|---------|-------------------|-------------| -| **AppStepper** | Multi-step wizard (horizontal/vertical) | `items: Item[]`, `currentStep?`, `direction?`, `iconSize?`, `isActiveStepValid?`, `align?`; emits `update:currentStep` | Multi-step forms, wizards, processes | -| **AppDrawerHeaderSection** | Drawer header with title + close button | `title`; emits `cancel`; slot: `beforeClose` | Header section in side drawers | -| **AppBarSearch** | Full-screen search dialog (Ctrl+K / Cmd+K) | `isDialogVisible`, `searchResults: T[]`, `isLoading?`; emits `search`, `update:isDialogVisible`; slots: `suggestions`, `searchResult`, `noData`, `noDataSuggestion` | Global search in app bar | -| **DialogCloseBtn** | Styled close button (X icon) for dialogs | `icon?` (default: tabler-x), `iconSize?` (default: 20) | Close button in dialog/modal headers | -| **TablePagination** | Table pagination with item count display | `page`, `itemsPerPage`, `totalItems`; emits `update:page` | Custom pagination for non-VDataTable tables | -| **MoreBtn** | Three-dot context menu button | `menuList?`, `itemProps?`, `iconSize?`, `class?` | More actions menu in cards/table rows | -| **ScrollToTop** | Fixed scroll-to-top button (appears after 200px scroll) | None | Add to layout for long pages | -| **TiptapEditor** | Rich text editor (Tiptap) with formatting toolbar | `modelValue`, `placeholder?`; emits `update:modelValue` | Rich text editing (notes, descriptions) | -| **ProductDescriptionEditor** | Rich text editor variant with toolbar | Same as TiptapEditor | Product/content description editing | -| **DropZone** | Drag-and-drop file upload with image preview cards | None (internal state) | Image file uploads | -| **TheCustomizer** | Theme customizer drawer (primary color, mode, skin, layout, width, direction) | None (reads/writes config store) | App-level theme customization panel | -| **ThemeSwitcher** | Light/Dark/System theme switcher dropdown | `themes: ThemeSwitcherTheme[]` | Theme switching in app bar | -| **Notifications** | Notification bell with dropdown panel | `notifications`, `badgeProps?`, `location?`; emits `read`, `unread`, `remove`, `click:notification` | User notifications in app bar | -| **Shortcuts** | Grid menu of quick-access shortcut links | `shortcuts: Shortcut[]`, `togglerIcon?` | Quick navigation in app bar | -| **I18n** | Language/locale switcher menu | `languages: I18nLanguage[]`, `location?` | Language switching in app bar | -| **CustomizerSection** | Section wrapper for theme customizer | `title`, `divider?` (default: true) | Organize customizer sections | - ---- - -## 2. Layouts - -### @layouts System (`@layouts/`) - -The layout system is a Vuexy plugin providing navigation infrastructure, state management, and responsive behavior. - -#### Layout Components (`@layouts/components/`) - -| Component | Purpose | Key Props / Slots | -|-----------|---------|-------------------| -| **VerticalNavLayout** | Complete vertical nav layout wrapper | Props: `navItems`, `verticalNavAttrs`; Slots: `vertical-nav-header`, `before-vertical-nav-items`, `navbar` (receives toggleVerticalOverlayNavActive), default (page content), `footer` | -| **VerticalNav** | Sidebar navigation with PerfectScrollbar | Props: `tag`, `navItems`, `isOverlayNavActive`, `toggleIsOverlayNavActive`; Slots: `nav-header`, `before-nav-items`, `nav-items`, `after-nav-items` | -| **VerticalNavLink** | Individual nav link with ACL support | Props: `item: NavLink` (title, icon, to/href, badgeContent, disable) | -| **VerticalNavGroup** | Expandable nav group with accordion behavior | Props: `item: NavGroup` (title, icon, children[]) | -| **VerticalNavSectionTitle** | Section divider/header in nav | Props: `item: NavSectionTitle` (heading) | -| **HorizontalNavLayout** | Complete horizontal nav layout wrapper | Props: `navItems`; Slots: `navbar`, default, `footer` | -| **HorizontalNav** | Horizontal navigation bar | Props: `navItems` | -| **HorizontalNavLink** | Horizontal nav link item | Props: `item: NavLink`, `isSubItem` | -| **HorizontalNavGroup** | Dropdown group for horizontal nav | Props: `item: NavGroup`, `childrenAtEnd`, `isSubItem` | -| **HorizontalNavPopper** | Floating UI dropdown for horizontal nav | Props: `popperInlineEnd`, `tag`, `contentContainerTag`, `isRtl`; Slots: default (trigger), `content` | -| **TransitionExpand** | Smooth height expand/collapse animation | Used by VerticalNavGroup | -| **VNodeRenderer** | Generic VNode rendering (TSX) | Props: `nodes: VNode \| VNode[]` | - -#### Layout Configuration (`@layouts/config.ts`) - -| Setting | Default | Options | -|---------|---------|---------| -| Content width | Boxed | `ContentWidth.Fluid`, `ContentWidth.Boxed` | -| Nav type | Vertical | `AppContentLayoutNav.Vertical`, `AppContentLayoutNav.Horizontal` | -| Overlay nav breakpoint | md (768px) | Any Vuetify breakpoint | -| Navbar | Sticky | `NavbarType.Sticky`, `NavbarType.Static`, `NavbarType.Hidden` | -| Footer | Static | `FooterType.Sticky`, `FooterType.Static`, `FooterType.Hidden` | -| Horizontal nav | Sticky | Same as NavbarType | -| Navbar blur | true | boolean | -| Vertical nav collapsed | false | boolean | - -#### Layout Store (`useLayoutConfigStore`) - -Pinia store managing all layout state. Values persisted via cookies: -- `navbarType`, `isNavbarBlurEnabled`, `isVerticalNavCollapsed` -- `appContentWidth`, `appContentLayoutNav` -- `horizontalNavType`, `footerType`, `isAppRTL` - -Computed: `_layoutClasses`, `isLessThanOverlayNavBreakpoint`, `isVerticalNavMini(isVerticalNavHovered)` - -#### ACL Integration (`@layouts/plugins/casl.ts`) - -- `can(action, subject)` — Check user ability (returns true if ACL disabled) -- `canViewNavMenuGroup(item)` — Determines if group renders based on children abilities -- `canNavigate(to)` — Route-level access control using `route.meta.action/subject` - -### App Layouts (`apps/app/src/layouts/`) - -| Layout | File | Used In | Purpose | -|--------|------|---------|---------| -| **default** | `layouts/default.vue` | All authenticated pages | Switches between vertical/horizontal nav based on config store | -| **blank** | `layouts/blank.vue` | Login, error pages | Minimal layout — no navigation | -| **OrganizerLayout** | `layouts/OrganizerLayout.vue` | All pages under `pages/{events,members,organisation,account-settings,dashboard,invitations}/**` | Organizer experience — thin wrapper around `DefaultLayoutWithVerticalNav` | -| **PortalLayout** | `layouts/PortalLayout.vue` | All pages under `pages/portal/**` | Volunteer/crew portal — custom navbar with two modes (platform / event) + mobile drawer; container max-width 1440px, navbar height 64px | -| **PublicLayout** | `layouts/PublicLayout.vue` | All pages under `pages/register/**` | Unauthenticated scaffold for public form viewer / registration flow | - -`PortalLayout` is a **custom implementation** — it does NOT use @layouts VerticalNavLayout. Modes (set via route `meta.navMode`): -- **Platform mode** (`navMode: 'platform'`): Crewli logo + optional page title + UserAvatarMenu -- **Event mode** (`navMode: 'event'`): Org name + event name + back link -- Mobile: VNavigationDrawer hamburger menu with user info + nav links + logout -- Desktop: UserAvatarMenu component in navbar - -**default** delegates to: -- `layouts/components/DefaultLayoutWithVerticalNav.vue` — Vertical sidebar (default) -- `layouts/components/DefaultLayoutWithHorizontalNav.vue` — Horizontal top nav (alternative) - -**Navbar components** (in `layouts/components/`): -- `UserProfile.vue` — Avatar menu with logout -- `NavSearchBar.vue` — Search with grouped suggestions -- `NavBarNotifications.vue` — Notification bell + panel -- `NavbarShortcuts.vue` — Quick access buttons -- `NavbarThemeSwitcher.vue` — Light/Dark/System switcher -- `Footer.vue` — Copyright footer - -### Route Meta for Layout Selection - -```typescript -definePage({ - meta: { - layout: 'blank' | 'default' | 'portal', - public: true, // skip auth guard - requiresAuth: false, // optional auth - navMode: 'event' | 'platform', // portal only - navTitle: 'Page Title', // portal platform mode only - navActiveLink: 'events', // highlight nav item in app - } -}) -``` - ---- - -## 3. Established Page Patterns - -### 3.1 List Page Pattern (Table + Filters + KPI Cards + Detail Drawer) - -**Structure:** KPI metric cards → filter row → VDataTableServer → detail drawer + form dialogs -**Crewli reference:** `apps/app/src/pages/events/[id]/persons/index.vue` -**Vuexy template reference:** `resources/.../src/pages/apps/user/list/index.vue` - -**Components used:** -- VRow/VCol (cols="12" sm="6" md="3") for 4 metric cards at top -- VCard + VAvatar (variant="tonal", rounded, size=44) + VIcon (size=28) for each metric -- AppSelect for filter dropdowns in a row -- VDataTableServer (or VDataTable) with custom column templates via `#item.[column]` -- VChip for status badges and type labels (with `:color` from status color map) -- VBtn for row actions (approve, edit, delete) -- PersonDetailPanel (VNavigationDrawer) for row-click detail view -- CreatePersonDialog / EditPersonDialog (VDialog) for CRUD -- VSnackbar for success/error notifications -- VSkeletonLoader for loading state -- VAlert (type="error") with retry button for error state - -**KPI tiles pattern:** -```typescript -const tiles = computed(() => [ - { title: 'Totaal', value: meta.value?.total ?? 0, icon: 'tabler-users', color: 'primary' }, - { title: 'Goedgekeurd', value: meta.value?.total_approved ?? 0, icon: 'tabler-circle-check', color: 'success' }, - { title: 'Wachtend', value: meta.value?.pending ?? 0, icon: 'tabler-clock', color: 'warning' }, - { title: 'Afgewezen', value: meta.value?.rejected ?? 0, icon: 'tabler-x', color: 'error' }, -]) -``` - -**Data fetching pattern:** -```typescript -const { data, isLoading, isError, refetch } = usePersonList(eventId, filters) -const { mutate: approve } = useApprovePerson(eventId) -const { mutate: deletePerson } = useDeletePerson(eventId) -``` - ---- - -### 3.2 Detail Panel Pattern (Right Drawer with Tabs) - -**Structure:** VNavigationDrawer (temporary, right) → header with avatar + status chips → VTabs → tabbed content -**Crewli reference:** `apps/app/src/components/persons/PersonDetailPanel.vue` -**Vuexy template reference:** `resources/.../src/views/apps/user/list/AddNewUserDrawer.vue` (drawer pattern) - -**Components used:** -- VNavigationDrawer (`location="end"`, `temporary`, `width=480`) -- VAvatar (size=56, variant="tonal", color="primary") with initials -- VChip for status/type badges -- VBtn icon variant (variant="text", size="small") for edit/close actions -- VTabs + VTabsWindow + VTabsWindowItem for tabbed content -- VList + VListItem with `#prepend` slot (VIcon) for data display -- VTextarea for inline-editable admin notes (save on blur) -- VSwitch for toggle fields -- VSkeletonLoader (type="article") for loading state -- VDivider between sections - -**Props pattern:** -```typescript -defineProps<{ eventId: string; orgId: string; personId: string | null }>() -const modelValue = defineModel({ required: true }) -defineEmits<{ edit: [person: Person] }>() -``` - -**Interaction flow:** Row click → `selectedPersonId` set → drawer opens → edit button emits `edit` → parent opens edit dialog. - -**Drawer scroll fix** (custom CSS required for scrollable content): -```css -.detail-drawer :deep(.v-navigation-drawer__content) { min-height: 0; } -``` - ---- - -### 3.3 Form Dialog Pattern (Create/Edit) - -**Structure:** VDialog → VCard → VCardTitle → VForm → form fields → VCardActions -**Crewli reference:** `apps/app/src/components/persons/CreatePersonDialog.vue` -**Vuexy template reference:** `resources/.../src/components/dialogs/UserInfoEditDialog.vue` - -**Components used:** -- VDialog (`max-width="500"` or responsive: `$vuetify.display.smAndDown ? 'auto' : 600`) -- VCard with VCardTitle, VCardText, VCardActions -- VForm (ref for validation) -- AppTextField / AppSelect / AppDateTimePicker / AppAutocomplete for form fields -- VBtn with `:loading="isPending"` for submit -- DialogCloseBtn in card header (Vuexy pattern) or VBtn icon for close -- `@core/utils/validators` for field validation (requiredValidator, emailValidator) - -**Error handling pattern:** -```typescript -const errors = ref>({}) -const refVForm = ref() -const { mutate, isPending } = useCreatePerson(eventIdRef) - -function onSubmit() { - refVForm.value?.validate().then(({ valid }) => { - if (!valid) return - mutate(payload, { - onSuccess: () => { modelValue.value = false }, - onError: (err) => { errors.value = err.response.data.errors }, - }) - }) -} -``` - -```vue - -``` - -**v-model pattern for dialog visibility:** -```typescript -const modelValue = defineModel({ required: true }) -``` - ---- - -### 3.4 Dashboard / Stat Cards Pattern - -**Structure:** VRow/VCol grid with color-coded metric cards → conditional content sections -**Crewli reference:** `apps/app/src/components/events/EventMetricCards.vue` -**Vuexy template reference:** `resources/.../src/pages/dashboards/analytics.vue` - -**Components used:** -- VRow + VCol (cols="12" sm="6" md="3") for responsive 4-column grid -- VCard with `card-border-shadow-{color}` custom class and `cursor-pointer h-100` -- VAvatar (`:color`, `variant="tonal"`, `size=44`, `rounded`) + VIcon (`size=28`) -- VCardText with `d-flex align-center mb-1` for icon + value row -- VSkeletonLoader (type="heading") in matching grid for loading state -- VAlert (variant="tonal", type="error") with retry for error state - -**Color logic pattern:** -```typescript -const shiftFillColor = computed(() => { - if (!stats.value || stats.value.shifts_total === 0) return 'success' - const rate = stats.value.shifts_filled / stats.value.shifts_total - if (rate >= 0.9) return 'success' - if (rate >= 0.6) return 'warning' - return 'error' -}) -``` - -**Card template:** -```vue - - - -
- - - -

{{ value }}

-
-

{{ title }}

-

- {{ secondary }} - {{ subtitle }} -

-
-
-
-``` - ---- - -### 3.5 Tab Navigation Pattern (Event Tabs Wrapper) - -**Structure:** Header (back + title + status chips + actions) → VTabs → slot for child page content -**Crewli reference:** `apps/app/src/components/events/EventTabsNav.vue` -**Vuexy template reference:** `resources/.../src/pages/pages/account-settings/[tab].vue` - -**Components used:** -- VBtn (icon="tabler-arrow-left", variant="text") for back navigation -- VChip for status/type badges with color maps -- VTabs with VTab using `:to` prop for route-based tab switching (no VWindow needed) -- Slot for tab content — child page renders inside -- VSkeletonLoader (type="card") for loading -- VAlert (type="error") with retry for errors - -**Tab definition:** -```typescript -const tabs = [ - { label: 'Overzicht', icon: 'tabler-layout-dashboard', route: 'events-id' }, - { label: 'Personen', icon: 'tabler-users', route: 'events-id-persons' }, - { label: 'Secties & Shifts', icon: 'tabler-layout-grid', route: 'events-id-sections' }, - { label: 'Instellingen', icon: 'tabler-settings', route: 'events-id-settings' }, -] -const activeTab = computed(() => - tabs.find(t => route.name === t.route || route.name?.startsWith(`${t.route}-`))?.route -) -``` - ---- - -### 3.6 Settings Page Pattern (Nested Tabs) - -**Structure:** VTabs (route/query-param based) → VWindow + VWindowItem → delegated tab components -**Crewli reference:** `apps/app/src/pages/organisation/settings.vue` -**Vuexy template reference:** `resources/.../src/pages/pages/account-settings/[tab].vue` - -Two approaches from Vuexy: -- **Horizontal tabs:** `VTabs` (class="v-tabs-pill") with VTab `:to` route binding + VWindow -- **Vertical tabs:** VRow → VCol md="4" (VTabs direction="vertical") + VCol md="8" (VWindow) - -**Key patterns:** -- `v-tabs-pill` class for pill-style tabs (Vuexy custom class) -- `disable-tab-transition` on VWindow to prevent animation delays -- Route params or query params for tab state persistence - ---- - -### 3.7 Sections & Shifts Pattern (Draggable + Table + Drawer) - -**Structure:** Draggable section list (sidebar) → shift table (main area) → shift detail drawer -**Crewli reference:** `apps/app/src/components/sections/SectionsShiftsPanel.vue` - -**Components used:** -- `vuedraggable` library for reorderable section list -- VCard per section with click-to-activate -- VDataTable for shifts within active section -- ShiftDetailPanel (VNavigationDrawer) for shift details -- Custom CSS for drag ghost states (necessary — no Vuetify equivalent) - ---- - -### 3.8 Confirm Dialog Pattern - -**Inline confirm** (preferred for new code): -```vue - - - Status wijzigen - - Weet je zeker dat je de status wilt wijzigen naar {{ target }}? - - - - Annuleren - Bevestigen - - - -``` - -**Reusable ConfirmDialog** component also available at `components/dialogs/ConfirmDialog.vue` with success/cancel result dialogs. - ---- - -### 3.9 Portal Page Patterns - -#### Event List (Card Grid) -**Reference:** `apps/app/src/pages/portal/evenementen/index.vue` -- VRow + VCol (cols="12" sm="6" md="4") responsive grid -- Custom EventCard component per event (gradient banner, status chip, hover animation) -- VSkeletonLoader for loading, VAlert for empty state - -#### Event Detail (Hash-based Tabs) -**Reference:** `apps/app/src/pages/portal/evenementen/[eventId].vue` -- VTabs with hash-based routing (#overzicht, #rooster, #claimen, #informatie) -- VWindow + VWindowItem per tab with dedicated tab components -- StatusCard shows different UI per approval status (pending/approved/rejected) -- Conditional tab visibility based on approval status - -#### Registration (Multi-step Public Form) -**Reference:** `apps/app/src/pages/register/[public_token].vue` -- VForm + `useFormDraft` composable for state, autosave, idempotency-key drafts -- Per-field validators from `@core/utils/validators` (`emailValidator`, `requiredValidator`) -- Zod schemas in `apps/app/src/schemas/registrationSchema.ts` validate the - outgoing payload at submit time -- Conditional form fields based on event configuration -- Real-time email duplicate checking -- Password creation for new users - -#### Auth Pages (Centered Card) -**Reference:** `apps/app/src/pages/login.vue` -- VCard centered on blank layout -- VTextField with password visibility toggle -- VAlert for error messages, VBtn with loading state - ---- - -## 4. Vuetify Component Quick Reference - -Preferred Vuetify components for common needs. Use these, not custom solutions. - -| Need | Use | NOT | -|------|-----|-----| -| Data tables (server pagination) | `VDataTableServer` | VDataTable or custom table | -| Data tables (client-side) | `VDataTable` | Custom table markup | -| Form text input | `AppTextField` (@core wrapper) | Raw VTextField or custom input | -| Form textarea | `AppTextarea` (@core wrapper) | Raw VTextarea | -| Select / dropdown | `AppSelect` or `AppAutocomplete` (@core) | Raw VSelect or custom dropdown | -| Date/time picker | `AppDateTimePicker` (@core, Flatpickr) | `input[type=date]` or custom picker | -| Confirmation dialog | VDialog + VCard (inline pattern, §3.8) | `window.confirm()` | -| Side panel / drawer | VNavigationDrawer (`location="end"`, `temporary`) | Custom sidebar | -| Tabs (route-based) | VTabs + VTab (with `:to` for routing) | Custom tab logic | -| Tabs (local state) | VTabs + VWindow + VWindowItem | Manual `v-if` switching | -| Loading: page/card | VSkeletonLoader (`type="card"`, `"article"`, `"heading"`) | Custom spinner | -| Loading: button | VBtn `:loading="isPending"` | Disabled + spinner | -| Loading: global bar | VProgressLinear (indeterminate) | Custom loading bar | -| Error state | VAlert (`type="error"`) with retry VBtn in `#append` slot | Custom error div | -| Empty state | VCard (`variant="outlined"`) + VIcon + message + VBtn | Blank area | -| Status / type badge | VChip (`:color` from color map, `size="small"`) | Custom styled span | -| Icon buttons | VBtn (`icon` variant, `variant="text"`, `size="small"`) | Custom icon wrapper | -| Tooltips | VTooltip (default location: top via defaults) | `title` attribute | -| Toast / notification | VSnackbar | Custom notification | -| User avatar | VAvatar (`variant="tonal"`) with initials or VImg | Custom circle div | -| Stat metric cards | VCard + VAvatar (tonal, rounded) + VIcon layout | CardStatisticsHorizontal (unless you need chart) | -| Progress indicator | VProgressLinear | Custom progress bar | -| Responsive grid | VRow + VCol with `cols`/`sm`/`md`/`lg` props | CSS Grid or fixed widths | -| List display | VList + VListItem with `#prepend`/`#append` slots | Custom list markup | -| Dividers | VDivider | Custom hr or border | -| Spacer in card actions | VSpacer | Custom margin/flex | -| Menu / dropdown actions | VMenu + VList + VListItem | Custom popover | -| Drag-and-drop lists | `vuedraggable` (external library) | Custom drag logic | -| Rich text editing | TiptapEditor (@core) | Custom editor | -| Form validation | VForm ref + `@core/utils/validators` + Zod schema for payload + API 422 error map | VeeValidate (removed; was never actually adopted) | -| Multi-step wizard | AppStepper (@core) | Custom step logic | -| Drawer header | AppDrawerHeaderSection (@core) | Custom header | -| Code display | AppCardCode (@core) | Custom code block | - ---- - -## 5. Plugins & Configuration - -### Vuetify Defaults (`plugins/vuetify/defaults.ts`) - -Pre-configured component defaults — no need to repeat these props in your code: - -| Component | Default Props | -|-----------|---------------| -| VBtn | `color: 'primary'` | -| VChip | `label: true` | -| VList | `density: 'compact'`, `color: 'primary'` | -| VTabs | `color: 'primary'`, `density: 'comfortable'` | -| VSelect, VTextField, VAutocomplete | `variant: 'outlined'`, `color: 'primary'`, `density: 'comfortable'`, `hideDetails: 'auto'` | -| VCheckbox, VRadio, VSwitch | `color: 'primary'`, `density: 'comfortable'` | -| VTooltip | `location: 'top'` | -| VMenu | `offset: '2px'` | -| VExpansionPanel | `expandIcon: 'tabler-chevron-right'`, `collapseIcon: 'tabler-chevron-right'` | -| VDataTable | Pagination icons: `tabler-chevrons-left/right`, `tabler-chevron-left/right` | -| VPagination | First/last: `tabler-chevrons-left/right`, prev/next: `tabler-chevron-left/right` | - -### Theme Colors (`plugins/vuetify/theme.ts`) - -| Color | Hex | Usage | -|-------|-----|-------| -| Primary | `#7367F0` | Buttons, links, active states | -| Secondary | `#808390` | Muted text, secondary actions | -| Success | `#28C76F` | Approved, positive states | -| Info | `#00BAD1` | Informational states | -| Warning | `#FF9F43` | Pending, attention-needed states | -| Error | `#FF4C51` | Rejected, destructive actions | - -Light theme: background `#F8F7FA`, surface `#FFFFFF` -Dark theme: background `#25293C`, surface `#2F3349` - -Full grey scale: grey-50 through grey-900. Semantic on-colors: on-primary, on-secondary, etc. - -### Icon System (`plugins/vuetify/icons.ts`) - -- **Tabler icons** via Iconify (prefix: `tabler-`) -- Custom SVG overrides for checkbox and radio form controls -- 40+ Vuetify icon aliases configured (expand, collapse, sort, check, clear, edit, delete, calendar, etc.) - -### Plugin Registration System (`@core/utils/plugins.ts`) - -Auto-discovers and registers plugins from `plugins/*.ts` and `plugins/*/index.ts` using `import.meta.glob`. Numbered prefixes control load order: -- `1.router/` — loaded first -- `2.pinia.ts` — loaded second -- Everything else — alphabetical - -### @core Utilities - -| Utility | Path | Key Exports | -|---------|------|-------------| -| **validators** | `@core/utils/validators.ts` | `requiredValidator`, `emailValidator`, `passwordValidator`, `confirmedValidator`, `betweenValidator`, `integerValidator`, `regexValidator`, `alphaValidator`, `urlValidator`, `lengthValidator`, `alphaDashValidator` | -| **formatters** | `@core/utils/formatters.ts` | `avatarText(name)` → initials, `kFormatter(num)` → "1.5k", `formatDate(value, options?)`, `formatDateToMonthShort(value)`, `prefixWithPlus(value)` | -| **helpers** | `@core/utils/helpers.ts` | `isEmpty(value)`, `isNullOrUndefined(value)`, `isEmptyArray(arr)`, `isObject(obj)`, `isToday(date)` | -| **colorConverter** | `@core/utils/colorConverter.ts` | `hexToRgb(hex)`, `rgbaToHex(rgba, forceRemoveAlpha?)` | -| **vuetify** | `@core/utils/vuetify.ts` | `resolveVuetifyTheme(defaultTheme)` — resolves from cookie/system preference | - -### @core Composables - -| Composable | Path | Purpose | -|------------|------|---------| -| **useCookie** | `@core/composable/useCookie.ts` | Reactive cookie management — returns `Ref` synced to `document.cookie`, JSON serialization, 30-day default expiry | -| **useResponsiveSidebar** | `@core/composable/useResponsiveSidebar.ts` | Returns `isLeftSidebarOpen` ref, auto-hides on mobile (mdAndDown) | -| **useSkins** | `@core/composable/useSkins.ts` | `injectSkinClasses()` — adds `skin--{value}` to body; `layoutAttrs` — computed for semi-dark vertical nav | -| **useGenerateImageVariant** | `@core/composable/useGenerateImageVariant.ts` | Returns correct image for current theme/skin (light, dark, light-bordered, dark-bordered) | -| **createUrl** | `@core/composable/createUrl.ts` | Reactive URL builder with query params from `MaybeRefOrGetter` sources | - -### @core Enums & Types (`@core/enums.ts`, `@core/types.ts`) - -```typescript -// Enums -Skins: 'default' | 'bordered' -Theme: 'light' | 'dark' | 'system' -Layout: 'vertical' | 'horizontal' | 'collapsed' -Direction: 'ltr' | 'rtl' - -// Key types -UserThemeConfig // extends LayoutConfig with i18n, theme, skin, isVerticalNavSemiDark -CustomInputContent // { title, desc, value, subtitle?, icon? } for Custom Checkboxes/Radios -GridColumn // Vuetify grid column props -SortItem // { key, order } for data table sorting -Options // { page, itemsPerPage, sortBy, groupBy, search } for data table -``` - -### @core Chart Libraries - -| Library | Path | Components | -|---------|------|------------| -| **ApexCharts** | `@core/libs/apex-chart/apexCharConfig.ts` | Configuration utilities for ApexCharts | -| **Chart.js** | `@core/libs/chartjs/` | 7 wrappers: BarChart, BubbleChart, DoughnutChart, LineChart, PolarAreaChart, RadarChart, ScatterChart | - ---- - -## 6. Crewli Custom Components - -### Event Components (`apps/app/src/components/events/`) - -| Component | Purpose | Used By | -|-----------|---------|---------| -| **EventTabsNav** | Event header + horizontal tab navigation wrapper | All event sub-pages | -| **EventMetricCards** | 4-column KPI stat card grid with color logic | Event overview page | -| **CreateEventDialog** | Create event form dialog | Events list page | -| **EditEventDialog** | Edit event form dialog | EventTabsNav | -| **CreateSubEventDialog** | Create sub-event for festivals | Programmaonderdelen page | -| **DeleteSubEventDialog** | Delete sub-event confirmation | Programmaonderdelen page | -| **RegistrationLinkCard** | Copyable registration link display | EventTabsNav | - -### Person Components (`apps/app/src/components/persons/`) - -| Component | Purpose | Used By | -|-----------|---------|---------| -| **PersonDetailPanel** | Right drawer — avatar + status + tabs (Info/Shifts/Accreditatie) | Persons list page | -| **CreatePersonDialog** | Create person form with crowd type + company selection | Persons list page | -| **EditPersonDialog** | Edit person form | Persons list page, PersonDetailPanel | - -### Section & Shift Components (`apps/app/src/components/sections/`, `shifts/`) - -| Component | Purpose | Used By | -|-----------|---------|---------| -| **SectionsShiftsPanel** | Draggable section list + shift table + shift detail drawer | Sections page | -| **CreateSectionDialog** | Create section form | SectionsShiftsPanel | -| **EditSectionDialog** | Edit section form | SectionsShiftsPanel | -| **CreateShiftDialog** | Create shift form | SectionsShiftsPanel | -| **CreateTimeSlotDialog** | Create time slot form | Time slots page | -| **ShiftDetailPanel** | Right drawer — shift details + assignments | SectionsShiftsPanel | -| **AssignShiftDialog** | Assign person to shift | SectionsShiftsPanel | -| **AssignPersonDialog** | Assign person to shift (from person context) | ShiftDetailPanel | - -### Crowd List Components (`apps/app/src/components/crowd-lists/`) - -| Component | Purpose | Used By | -|-----------|---------|---------| -| **CrowdListFormDialog** | Create/edit crowd list | Crowd lists page | -| **CrowdListDetailPanel** | Right drawer — crowd list details + members | Crowd lists page | -| **AddPersonToCrowdListDialog** | Add person to crowd list | CrowdListDetailPanel | - -### Organisation Components (`apps/app/src/components/organisation/`, `organisations/`) - -| Component | Purpose | Used By | -|-----------|---------|---------| -| **EditOrganisationDialog** | Edit organisation details | Organisation page | -| **CompanyDialog** | Create/edit company | Companies page | -| **CrowdTypesManager** | Manage crowd types (table + CRUD) | Organisation settings | -| **PersonTagsTab** | Manage person tags | Organisation settings | -| **RegistrationFieldTemplatesTab** | Manage field templates | Organisation settings | -| **EmailBrandingTab** | Email branding settings (colors, logo) | Organisation settings | - -### Member Components (`apps/app/src/components/members/`) - -| Component | Purpose | Used By | -|-----------|---------|---------| -| **InviteMemberDialog** | Invite member form | Members page | -| **EditMemberRoleDialog** | Change member role | Members page | - -### Registration Field Components (`apps/app/src/components/event/`) - -| Component | Purpose | Used By | -|-----------|---------|---------| -| **RegistrationFieldCard** | Draggable field card with edit/delete | Registration fields page | -| **RegistrationFieldFormDialog** | Create/edit registration field | Registration fields page | -| **TemplatePickerDialog** | Select from predefined field templates | Registration fields page | -| **ImportFromEventDialog** | Import fields from another event | Registration fields page | - -### Layout Components (`apps/app/src/components/layout/`) - -| Component | Purpose | Used By | -|-----------|---------|---------| -| **OrganisationSwitcher** | Switch between organisations | Navigation sidebar | - -### Portal Components (`apps/app/src/components/portal/`) - -| Component | Purpose | Used By | -|-----------|---------|---------| -| **portal/EventCard** | Event card for grid (gradient banner, status chip, hover) | Events list | -| **portal/StatusCard** | Approval status display with quick action cards (pending/approved/rejected variants) | Event overview tab | -| **portal/UserAvatarMenu** | User menu in portal navbar | Portal layout | -| **portal/AppLoadingIndicator** | Global loading bar (Suspense fallback, VProgressLinear) | Portal layouts | -| **event/OverzichtTab** | Event overview tab (StatusCard + shift summary) | Event detail | -| **event/RoosterTab** | My shifts schedule tab (cancel dialog) | Event detail | -| **event/ClaimenTab** | Claim available shifts tab (day-grouped, confirm dialog) | Event detail | -| **event/InformatieTab** | Event info display tab (VList read-only) | Event detail | - ---- - -## 7. Three-State Pattern (Loading / Error / Empty) - -Every data-driven view MUST handle three states. Follow this pattern: - -```vue - - - - - - Kon data niet laden. - - - - - - -

Geen resultaten gevonden.

- Toevoegen -
- - - -``` - -**Loading skeleton for grids** (match the data layout): -```vue - - - - - -``` - ---- - -## 8. SCSS & Styling Reference - -### Available SCSS Imports - -From the Vuexy template (`@core/scss/`): - -| Category | Key Files | Purpose | -|----------|-----------|---------| -| **Base** | `_components.scss`, `_dark.scss`, `_misc.scss` | Core component overrides | -| **Layout** | `_default-layout.scss`, `_default-layout-w-vertical-nav.scss` | Layout structure styles | -| **Navigation** | `_vertical-nav.scss`, `_horizontal-nav.scss` | Nav-specific styles | -| **Utilities** | `_utilities.scss`, `_mixins.scss`, `_variables.scss` | Helper classes and mixins | -| **Skins** | `skins/` directory | Bordered skin variant styles | -| **Template pages** | `template/pages/` | Page-specific styles (auth, misc, etc.) | -| **Component overrides** | `template/components/` | Per-component SCSS (alert, avatar, button, card, chip, dialog, table, tabs, tooltip, etc.) | -| **Library styles** | `template/libs/` | ApexChart, FullCalendar, Shepherd, Swiper overrides | - -### Page-Level SCSS Usage - -```vue - - - - - -``` - -### Custom CSS Classes from Vuexy - -| Class | Purpose | -|-------|---------| -| `card-border-shadow-{color}` | Colored left border shadow on cards (primary, success, warning, error, info) | -| `v-tabs-pill` | Pill-style tabs | -| `skin--default`, `skin--bordered` | Skin system classes (injected on body) | -| `match-height` | Equal height cards in a row | -| `disable-tab-transition` | Prevent VWindow tab animation | - ---- - -## 9. Cleanup Opportunities - -Most custom CSS in the codebase is justified. Identified areas: - -| File | CSS | Status | -|------|-----|--------| -| `SectionsShiftsPanel.vue` | `.section-item { cursor: grab }`, `.section-ghost` | **Justified** — vuedraggable states, no Vuetify equivalent | -| `registration-fields.vue` | `.field-ghost` | **Justified** — vuedraggable styling | -| `RegistrationFieldCard.vue` | `.cursor-grab`, `.min-width-0` | **Justified** — no Vuetify utility for cursor/min-width | -| `CrowdListDetailPanel.vue` | `:deep(.v-navigation-drawer__content) { min-height: 0 }` | **Justified** — drawer scroll fix | -| `ShiftDetailPanel.vue` | Same drawer override | **Justified** — same fix | -| `EmailBrandingTab.vue` | `.color-swatch` sizing | **Justified** — custom color preview element | -| `AddEditPermissionDialog.vue` | `.permission-table td` styling | **Minor** — template code, custom padding intentional | -| `AppSearchHeader.vue` | `.search-header` padding/background | **Template code** — not actively used in Crewli | - -**Overall:** Excellent Vuetify adoption. Custom CSS is minimal and justified for cases without Vuetify equivalents (drag states, drawer scroll fixes, specialized visual elements). +Per project decision 2026-05-10: during F4a–F4c, some F4 developers +will need to look up Vuetify conventions for components on un-migrated +surfaces. Git-history archaeology +(`git show 1c449ff6204cae6371da08c34ea8934d6b2ffcb8:dev-docs/VUEXY_COMPONENTS.md`) +is workable but inconvenient. This stub provides an explicit forwarding +marker, with the actual content recoverable via git history per the +explicit SHA above.