Skip to content

Frontend Architecture ​

Vue 3 + TypeScript frontend design.

Project Structure ​

sis_frontend/
├── src/
│   ├── api/                 # API layer
│   │   ├── index.ts         # Axios instance
│   │   ├── services/        # Domain API services
│   │   │   └── {domain}.api.ts
│   │   └── types/           # TypeScript interfaces
│   │       └── index.ts
│   ├── components/          # Reusable components
│   │   └── {Feature}.vue
│   ├── composables/         # Vue composables
│   │   └── use{Feature}.ts
│   ├── router/              # Vue Router
│   │   └── index.ts
│   ├── stores/              # Pinia stores
│   │   └── {domain}.ts
│   ├── views/               # Page components
│   │   └── {domain}/
│   │       └── {Feature}View.vue
│   ├── App.vue
│   └── main.ts
├── index.html
├── package.json
├── tsconfig.json
└── vite.config.ts

Component Pattern ​

Vue 3 Composition API with <script setup>:

vue
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useToast } from 'primevue/usetoast'
import type { Entity } from '@/api/types'
import { entityApi } from '@/api/services/domain.api'

// Props and emits
const props = defineProps<{
  entityId?: number
}>()

const emit = defineEmits<{
  saved: [entity: Entity]
}>()

// State
const entity = ref<Entity | null>(null)
const loading = ref(false)

// Computed
const isNew = computed(() => !props.entityId)

// Methods
async function loadEntity() {
  if (!props.entityId) return
  loading.value = true
  try {
    const response = await entityApi.getById(props.entityId)
    entity.value = response.data
  } finally {
    loading.value = false
  }
}

async function save() {
  // Save logic
  emit('saved', entity.value!)
}

// Lifecycle
onMounted(() => loadEntity())
</script>

<template>
  <div class="entity-form">
    <!-- Template -->
  </div>
</template>

<style scoped>
/* Component styles */
</style>

API Service Pattern ​

typescript
// api/services/domain.api.ts
import api from '../index'
import type { Entity, EntityCreate, PaginatedResponse } from '../types'

export const entityApi = {
  getAll: (params?: Record<string, any>) =>
    api.get<PaginatedResponse<Entity>>('/domain/entities/', { params }),

  getById: (id: number) =>
    api.get<Entity>(`/domain/entities/${id}/`),

  create: (data: EntityCreate) =>
    api.post<Entity>('/domain/entities/', data),

  update: (id: number, data: Partial<EntityCreate>) =>
    api.patch<Entity>(`/domain/entities/${id}/`, data),

  delete: (id: number) =>
    api.delete(`/domain/entities/${id}/`),

  // Custom action
  customAction: (id: number) =>
    api.post(`/domain/entities/${id}/custom_action/`)
}

TypeScript Types ​

typescript
// api/types/index.ts
export interface Entity {
  entity_id: number
  name: string
  status_code: string
  status_display: string
  created_at: string
}

export interface EntityCreate {
  name: string
  status_id: number
}

export interface PaginatedResponse<T> {
  count: number
  next: string | null
  previous: string | null
  results: T[]
}

Pinia Store Pattern ​

typescript
// stores/entities.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { Entity } from '@/api/types'
import { entityApi } from '@/api/services/domain.api'

export const useEntityStore = defineStore('entities', () => {
  // State
  const entities = ref<Entity[]>([])
  const loading = ref(false)

  // Getters
  const activeEntities = computed(() =>
    entities.value.filter(e => e.status_code === 'ACTIVE')
  )

  // Actions
  async function fetchEntities() {
    loading.value = true
    try {
      const response = await entityApi.getAll()
      entities.value = response.data.results
    } finally {
      loading.value = false
    }
  }

  return {
    entities,
    loading,
    activeEntities,
    fetchEntities
  }
})

Router Configuration ​

typescript
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/entities',
    name: 'entities',
    component: () => import('@/views/domain/EntityListView.vue'),
    meta: { requiresAuth: true }
  },
  {
    path: '/entities/:id',
    name: 'entity-detail',
    component: () => import('@/views/domain/EntityDetailView.vue'),
    meta: { requiresAuth: true }
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

PrimeVue Components ​

Use PrimeVue for UI components:

vue
<template>
  <DataTable :value="entities" :loading="loading">
    <Column field="name" header="Name" sortable />
    <Column field="status_display" header="Status" />
  </DataTable>
</template>

Student Information System Documentation