Skip to content

Last updated:

Layouts

CPR uses Nuxt layouts to provide consistent page chrome -- headers, navigation, providers, and module-specific tab bars. Layouts live in app/layouts/ and are assigned to pages via definePageMeta.

How Layouts Are Assigned

Pages declare their layout using definePageMeta in <script setup>:

vue
<!-- app/pages/login.vue -->
<script setup lang="ts">
definePageMeta({
  layout: 'auth',
});
</script>

If no layout is specified, Nuxt uses default. Some module layouts (configuration, pharmacy, queue) nest inside the default layout using <NuxtLayout name="default">, so pages using those layouts get the full app chrome plus module-specific tab navigation.

Layout Reference

default

File: app/layouts/default.vue

The primary application layout used by most pages. Provides the full app shell.

What it provides:

  • Teal header bar (bg-[#117ea7]) with the CPR logo
  • Desktop navigation tabs: Dashboard, Patient, Queue, Surgery Schedule, Pharmacy, Configuration
  • Permission-gated navigation -- tabs are only shown if the user has the required permission (e.g., patients.access)
  • Mobile hamburger menu with slide-out navigation
  • BranchSelector component for switching clinic branches
  • User profile dropdown with avatar, role display, profile link, and logout
  • <UiSnackBar /> for toast notifications
  • <UiDialogProvider /> for dynamic form modals opened via useDialog
  • <UiConfirmDialogProvider /> for confirmation dialogs opened via useConfirmDialog

When to use: All authenticated application pages. This is the default -- you don't need to specify it explicitly.

vue
<!-- This page uses the default layout automatically -->
<template>
  <div class="px-6 lg:px-8 pb-8">
    <UiPageHeader title="Dashboard" />
    <!-- page content -->
  </div>
</template>

WARNING

The default layout renders UiDialogProvider and UiConfirmDialogProvider. These are required for useDialog and useConfirmDialog to work. If you create a custom layout, include these providers.


auth

File: app/layouts/auth.vue

Split-screen layout for unauthenticated pages (login, password reset).

What it provides:

  • Left panel (42% width, hidden on mobile): Teal gradient branding with CPR logo, tagline, animated floating particles, rotating inspirational quotes, and app version number
  • Right panel: Centered white content area (max-width 480px) with the CPR logo above the slot
  • Fully responsive -- on mobile, only the right content panel is shown

When to use: Login, forgot password, and code verification pages.

vue
<!-- app/pages/login.vue -->
<script setup lang="ts">
definePageMeta({
  layout: 'auth',
});
</script>

<template>
  <div>
    <h2 class="text-2xl font-bold mb-6">Sign in to your account</h2>
    <!-- login form -->
  </div>
</template>

configuration

File: app/layouts/configuration.vue

Module layout for all configuration pages. Nests inside the default layout.

What it provides:

  • All default layout features (header, nav, providers)
  • Horizontal tab bar with permission-gated tabs:
    • Medicine (default tab at /configuration)
    • Procedures (/configuration/procedures)
    • Insurance (/configuration/insurances)
    • Billing Items (/configuration/billing-items)
    • Doctors (/configuration/doctors)
    • Surgery Locations (/configuration/surgery-locations)
  • Active tab highlighting based on the current route name

When to use: Pages under /configuration/.

vue
<!-- app/pages/configuration/insurances/index.vue -->
<script setup lang="ts">
definePageMeta({
  layout: 'configuration',
});
</script>

<template>
  <div class="px-6 lg:px-8 pb-8">
    <!-- Insurance list page content -->
  </div>
</template>

pharmacy

File: app/layouts/pharmacy.vue

Module layout for pharmacy pages. Nests inside the default layout.

What it provides:

  • All default layout features
  • Horizontal tab bar with permission-gated tabs:
    • Transactions (default tab at /pharmacy)
    • Inventory (/pharmacy/inventory)
    • Stock Movement (/pharmacy/stock-movement)
    • Items (/pharmacy/items)
    • Purchase Orders (/pharmacy/purchase-orders)
    • Deliveries (/pharmacy/deliveries)
    • Suppliers (/pharmacy/suppliers)
  • Content area with horizontal padding (px-6 lg:px-8)

When to use: Pages under /pharmacy/.

vue
<script setup lang="ts">
definePageMeta({
  layout: 'pharmacy',
});
</script>

queue

File: app/layouts/queue.vue

Module layout for queue management pages. Nests inside the default layout.

What it provides:

  • All default layout features
  • Horizontal tab bar with permission-gated tabs:
    • Todays Queue (default tab at /queue)
    • Transactions List (/queue/transactions)
  • Content area with horizontal padding

When to use: Pages under /queue/.

vue
<script setup lang="ts">
definePageMeta({
  layout: 'queue',
});
</script>

print

File: app/layouts/print.vue

Minimal layout for print-friendly pages. No header, no navigation, no providers.

What it provides:

  • Clean white background
  • 2rem padding on screen, zero padding when printing
  • Print-specific CSS: A4 page size, 1.5cm margins, exact color reproduction
  • Small base font size (11px) optimized for printed output

When to use: Report and print preview pages.

vue
<script setup lang="ts">
definePageMeta({
  layout: 'print',
});
</script>

<template>
  <ReportLayout>
    <!-- report content -->
  </ReportLayout>
</template>

WARNING

The print layout does not include UiDialogProvider, UiConfirmDialogProvider, or UiSnackBar. The useDialog, useConfirmDialog, and useSnackBar composables will not work on print pages.

Module Layout Architecture

The configuration, pharmacy, and queue layouts share a common pattern: they nest inside the default layout and add a tab bar. This creates a two-level navigation structure:

default layout
  +-- Header (logo, nav, branch selector, user dropdown)
  +-- Providers (snackbar, dialog, confirm dialog)
  +-- module layout (configuration / pharmacy / queue)
       +-- Tab bar
       +-- <slot /> (page content)

The tab bar uses NuxtLink components pointed at the module's sub-routes, with active state derived from route.name. Tabs are permission-gated using the $can() helper:

vue
<template v-for="tab in tabs">
  <NuxtLink
    v-if="$can(tab.permission)"
    :key="tab.value"
    :to="`/configuration/${tab.value}`"
    :class="activeTab === tab.value ? 'active-classes' : 'inactive-classes'"
  >
    {{ tab.label }}
  </NuxtLink>
</template>

CPR - Clinical Patient Records