Skip to content

Last updated:

Naming Conventions

Consistent naming makes the CPR codebase navigable and predictable. Follow these conventions for all new code.

Files and Directories

Components -- PascalCase.vue

Component files use PascalCase. Domain-specific components are grouped in directories matching their domain.

app/components/
  ui/
    UiButton.vue
    UiCard.vue
    UiModal.vue
    UiPagination.vue
    UiSearchInput.vue
    UiTable.vue
    UiSnackBar.vue
  configuration/
    doctor/
      DoctorTable.vue
      DoctorTableRow.vue
      DoctorFilters.vue
    forms/
      DoctorForms/
        DoctorForm.vue
        UpdateDoctorForm.vue
  patient/
    AddPatientModal.vue
    PatientMedicalRecord/
      PatientIntro.vue
      PatientVisits.vue
  queue/
    QueueList.vue
    QueueDetailsPanel.vue

Composables -- camelCase.ts

Composable files are named use[Feature].ts. Domain composables live in a subdirectory.

app/composables/
  useApiFetch.ts
  useFormValidation.ts
  useValidationRules.ts
  useListFilters.ts
  useDialog.ts
  useSnackBar.ts
  useConfirmDialog.ts
  useCanAccess.ts
  doctor/
    useDoctor.ts
    useCreateDoctor.ts
    useUpdateDoctor.ts
    useDeleteDoctor.ts

Services -- [domain].service.ts

Service files use the domain name in camelCase followed by .service.ts. They live in app/sevices/.

app/sevices/
  basicCrud.service.ts
  doctor.service.ts
  patient.service.ts
  insurance.service.ts
  transaction.service.ts

Stores -- [domain].store.ts

Store files use the domain name in camelCase followed by .store.ts.

app/stores/
  auth.store.ts
  doctor.store.ts
  patient.store.ts
  insurance.store.ts
  enums.store.ts

Types -- [domain].type.ts

Type definition files use the domain name in camelCase followed by .type.ts.

app/types/
  doctor.type.ts
  patient.type.ts
  apiReponse.type.ts
  permission.type.ts
  user.type.ts

Pages -- kebab-case Directories

Page directories use kebab-case. The index file in each directory is the list/landing page.

app/pages/
  login.vue
  dashboard/
    index.vue
  configuration/
    doctors/
      index.vue
    billing-items/
      index.vue
    surgery-locations/
      index.vue
  pharmacy/
    stock-movement/
      index.vue
    purchase-orders/
      index.vue
  reset-password/
    index.vue
    enter-code.vue
    new-password.vue
    success-state.vue

Component Names

Domain-Prefixed Components

Components are named with their domain as a prefix for clarity in templates and file trees.

DoctorTable.vue         # not Table.vue
DoctorFilters.vue       # not Filters.vue
DoctorTableRow.vue      # not TableRow.vue
PatientIntro.vue        # not Intro.vue
QueueDetailsPanel.vue   # not DetailsPanel.vue

UI Components -- Ui Prefix

All reusable, domain-agnostic UI components use the Ui prefix. They live in app/components/ui/.

UiButton.vue
UiCard.vue
UiModal.vue
UiTable.vue
UiInput.vue
UiDropdown.vue
UiBadge.vue
UiAlert.vue
UiAvatar.vue
UiPagination.vue
UiSearchInput.vue
UiSearchFilters.vue
UiStatusTabs.vue
UiStatCard.vue
UiSkeletonLoader.vue
UiIconButton.vue
UiFilterButton.vue
UiConfirmDialog.vue
UiSnackBar.vue

Utility Components

Cross-cutting components that are not strictly UI primitives live in app/components/utils/.

BranchSelector.vue
PhilippineAddressForm.vue

TypeScript Naming

Interfaces and Types

Use PascalCase. Name interfaces after the domain entity they represent.

ts
export interface Doctor {
  id: number;
  first_name: string;
  last_name: string;
  full_name: string;
  doctor_role_id: number;
  doctor_role?: DoctorRole;
  status: string;
}

export interface DoctorFilters {
  search?: string;
  status?: string;
  doctor_role_id?: number;
  page?: number;
  perPage?: number;
}

Props Interface

Always name the props interface Props within a component. It is local to the file.

vue
<script setup lang="ts">
interface Props {
  variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  disabled?: boolean;
  loading?: boolean;
}

const props = withDefaults(defineProps<Props>(), {
  variant: 'primary',
  size: 'md',
});
</script>

Pinia Store Functions

Store definitions use use[Domain]Store as the exported function name.

ts
export const useDoctorStore = defineStore('doctor', () => { ... });
export const useAuthStore = defineStore('auth', () => { ... });
export const useEnumsStore = defineStore('enums', () => { ... });

Service Exports

Service classes are PascalCase. Export a singleton instance in camelCase and as the default export.

ts
class DoctorService {
  // ...
}

export const doctorService = new DoctorService();
export default doctorService;

Events

Emit Names -- kebab-case

Use kebab-case for custom events. Follow Vue conventions for v-model events.

ts
defineEmits<{
  (e: 'edit', doctor: Doctor): void;
  (e: 'delete', doctor: Doctor): void;
  (e: 'page-change', page: number): void;
  (e: 'row-click', row: Record<string, unknown>): void;
  (e: 'update:modelValue', value: string): void;
  (e: 'update:searchQuery', value: string): void;
}>();

In templates, emit using kebab-case names:

vue
<DoctorTableRow
  @edit="$emit('edit', $event)"
  @delete="$emit('delete', $event)"
/>

Reactive Variables

Boolean Naming

Prefix boolean refs and props with is, has, or show to make their purpose clear.

ts
const isLoading = ref(false);
const isAuthenticated = computed(() => !!state.token);
const showModal = ref(false);
const hasPermission = can('doctors.create');

Exception: when the boolean comes from a composable return value, loading, success, and visible are acceptable without prefix for brevity.

ts
// Composable return -- no prefix needed
return { loading, error, success, createDoctor };

CSS

Tailwind Utility Classes

Use Tailwind utility classes directly in templates. Avoid custom CSS unless creating reusable component styles.

vue
<template>
  <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
    <UiInput v-model="form.first_name" label="First Name" />
  </div>
</template>

Reusable Styles with @apply

When a combination of utilities is repeated across many components, extract it using @apply inside @layer components in your global CSS.

css
@layer components {
  .card-header {
    @apply px-6 py-4 border-b border-gray-100 font-semibold text-gray-900;
  }
}

Constants

UPPER_SNAKE_CASE

True constants (values known at build time, never changing at runtime) use UPPER_SNAKE_CASE.

ts
const MAX_FILE_SIZE = 5 * 1024 * 1024;
const DEFAULT_PER_PAGE = 15;
const API_TIMEOUT_MS = 30000;

Configuration objects that are constant use a descriptive camelCase name:

ts
const variantClasses = {
  primary: 'text-white bg-[#117ea7] hover:bg-[#0e6b8f] ...',
  secondary: 'text-gray-700 bg-white border border-gray-300 ...',
  danger: 'text-white bg-red-600 hover:bg-red-700 ...',
};

Icons

@heroicons/vue

Import icons from @heroicons/vue/24/outline or @heroicons/vue/24/solid. Use them as components.

vue
<script setup lang="ts">
import { PlusIcon, ArchiveBoxIcon } from '@heroicons/vue/24/outline';
</script>

<template>
  <UiButton :left-icon="PlusIcon">Add Doctor</UiButton>
</template>

CPR - Clinical Patient Records