Appearance
Shared UI Components
All shared UI components live in frontend/src/shared/ui/. They are generic, reusable, and have no knowledge of domain models (vendors, invoices, etc.).
Usage
Import directly from the shared layer:
typescript
import AppButton from '@/shared/ui/AppButton.vue'
import AppModal from '@/shared/ui/AppModal.vue'AppButton
A styled button with variant, size, and loading state support.
vue
<AppButton variant="primary" :loading="isSaving" @click="save">
Save Vendor
</AppButton>
<AppButton variant="danger" size="sm" @click="confirmDelete">
Delete
</AppButton>| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'primary' | 'secondary' | 'danger' | 'ghost' | 'primary' | Visual style |
size | 'sm' | 'md' | 'lg' | 'md' | Button size |
loading | boolean | false | Shows spinner, disables click |
disabled | boolean | false | Disables the button |
AppTextInput
Standard text input with label and validation error display.
vue
<AppTextInput
v-model="form.name"
label="Vendor Name"
placeholder="Enter vendor name"
:error="errors.name"
required
/>AppSelectInput
Dropdown select with typed options.
vue
<AppSelectInput
v-model="form.vendorType"
label="Vendor Type"
:options="vendorTypeOptions"
:error="errors.vendorType"
/>AppCombobox
Searchable dropdown / autocomplete — useful for large option lists like vendors.
vue
<AppCombobox
v-model="selectedVendorId"
label="Vendor"
:options="vendorOptions"
placeholder="Search vendors..."
/>AppDataTable
A full-featured data table with sortable columns, pagination controls, and empty/loading states.
vue
<AppDataTable
:columns="columns"
:rows="vendors"
:loading="isLoading"
:total-count="totalCount"
:page="page"
:page-size="pageSize"
@sort="onSort"
@page-change="onPageChange"
/>AppModal
Accessible dialog with slot-based header, body, and footer.
vue
<AppModal :open="showModal" title="Create Vendor" @close="showModal = false">
<template #body>
<VendorForm @saved="onSaved" />
</template>
</AppModal>AppDeleteModal
Specialised modal for soft-delete confirmation with an optional reason field.
vue
<AppDeleteModal
:open="showDeleteModal"
entity-name="Vendor"
:entity-label="vendor.name"
@confirm="deleteVendor"
@cancel="showDeleteModal = false"
/>AppPermissionGate
Conditionally renders its default slot based on the user's JWT permissions. Renders nothing (not a 403 error) if the permission is absent.
vue
<AppPermissionGate collection="Vendor" action="Create">
<AppButton @click="openCreate">New Vendor</AppButton>
</AppPermissionGate>| Prop | Type | Description |
|---|---|---|
collection | string | Permission collection (e.g. 'Vendor') |
action | string | Permission action (e.g. 'Create') |
AppStatusBadge
Colour-coded pill badge for enum status values like InvoiceStatus.
vue
<AppStatusBadge :status="invoice.status" />
<!-- Renders: FullyPaid (green), Overdue (red), PartiallyPaid (amber), Canceled (grey) -->AppKpiCard
Dashboard KPI card with a title, value, and optional icon.
vue
<AppKpiCard title="Total Due" :value="summary.totalDue" icon="💰" format="currency" />AppToast
Global toast notification system. Controlled via the useToast() composable:
typescript
const toast = useToast()
toast.success('Vendor saved successfully')
toast.error('Failed to delete invoice')AppWizard
Multi-step wizard with step indicators — used by the Onboarding flow.
vue
<AppWizard :steps="steps" :current-step="currentStep">
<template #step-1><PharmacySetupStep /></template>
<template #step-2><AdminUserStep /></template>
</AppWizard>AppTabList
Horizontal tab navigation for switching between views within a page.
vue
<AppTabList :tabs="['Overview', 'Invoices', 'Transactions']" v-model="activeTab" />AppPasswordInput
Password field with show/hide toggle.
vue
<AppPasswordInput v-model="form.password" label="Password" :error="errors.password" />AppSplash
Full-screen loading/splash screen — shown while the app initialises or checks onboarding status.