# Vuexy Component Registry 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. --- ## 1. @core Components Source: `resources/vuexy-admin-v10.11.1/vue-version/typescript-version/full-version/src/@core/components/` Copied into: `apps/app/src/@core/components/` ### Form Element Wrappers (`@core/components/app-form-elements/`) These wrap Vuetify form components with a **separate label row** for consistent styling. Use these instead of raw Vuetify form components in all forms. | 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 | All wrappers forward **all slots and events** from the underlying Vuetify component. ### 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 Form with VeeValidate + Zod) **Reference:** `apps/app/src/pages/register/[public_token].vue` - VForm with VeeValidate field binding + Zod schemas from `@/schemas/` - 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` + API error mapping | (VeeValidate + Zod only in portal registration) | | 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).