Skip to content

Adding Views ​

Create new frontend views.

Step 1: Create View Component ​

Create sis_frontend/src/views/{domain}/EntityListView.vue:

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

const router = useRouter()
const toast = useToast()
const { t } = useI18n()

const entities = ref<Entity[]>([])
const loading = ref(false)
const totalRecords = ref(0)

async function loadEntities() {
  loading.value = true
  try {
    const response = await entityApi.getAll()
    entities.value = response.data.results
    totalRecords.value = response.data.count
  } catch (error) {
    toast.add({
      severity: 'error',
      summary: t('common.error'),
      detail: t('common.errors.loadFailed')
    })
  } finally {
    loading.value = false
  }
}

function viewEntity(entity: Entity) {
  router.push(`/entities/${entity.entity_id}`)
}

onMounted(() => loadEntities())
</script>

<template>
  <div class="entity-list">
    <div class="flex justify-between items-center mb-4">
      <h1>{{ t('entities.title') }}</h1>
      <Button
        :label="t('entities.addNew')"
        icon="pi pi-plus"
        @click="router.push('/entities/new')"
      />
    </div>

    <DataTable
      :value="entities"
      :loading="loading"
      paginator
      :rows="20"
    >
      <Column field="name" :header="t('common.name')" sortable />
      <Column field="status_display" :header="t('common.status')" />
      <Column :header="t('common.actions')">
        <template #body="{ data }">
          <Button
            icon="pi pi-eye"
            text
            @click="viewEntity(data)"
          />
        </template>
      </Column>
    </DataTable>
  </div>
</template>

Step 2: Create Detail View ​

Create sis_frontend/src/views/{domain}/EntityDetailView.vue:

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

const route = useRoute()
const router = useRouter()
const toast = useToast()
const { t } = useI18n()

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

const isNew = computed(() => route.params.id === 'new')

async function loadEntity() {
  if (isNew.value) return

  loading.value = true
  try {
    const id = Number(route.params.id)
    const response = await entityApi.getById(id)
    entity.value = response.data
  } catch (error) {
    toast.add({
      severity: 'error',
      summary: t('common.error'),
      detail: t('common.errors.loadFailed')
    })
  } finally {
    loading.value = false
  }
}

async function save() {
  if (!entity.value) return

  saving.value = true
  try {
    if (isNew.value) {
      await entityApi.create(entity.value)
      toast.add({
        severity: 'success',
        summary: t('common.success'),
        detail: t('entities.created')
      })
    } else {
      await entityApi.update(entity.value.entity_id, entity.value)
      toast.add({
        severity: 'success',
        summary: t('common.success'),
        detail: t('entities.updated')
      })
    }
    router.push('/entities')
  } catch (error) {
    toast.add({
      severity: 'error',
      summary: t('common.error'),
      detail: t('common.errors.saveFailed')
    })
  } finally {
    saving.value = false
  }
}

onMounted(() => {
  if (isNew.value) {
    entity.value = { name: '', status_id: 1 } as Entity
  } else {
    loadEntity()
  }
})
</script>

<template>
  <div class="entity-detail">
    <h1>{{ isNew ? t('entities.new') : t('entities.edit') }}</h1>

    <div v-if="loading">Loading...</div>

    <form v-else-if="entity" @submit.prevent="save">
      <div class="field">
        <label for="name">{{ t('common.name') }}</label>
        <InputText id="name" v-model="entity.name" required />
      </div>

      <div class="flex gap-2 mt-4">
        <Button
          :label="t('common.cancel')"
          severity="secondary"
          @click="router.push('/entities')"
        />
        <Button
          :label="t('common.save')"
          type="submit"
          :loading="saving"
        />
      </div>
    </form>
  </div>
</template>

Step 3: Add Routes ​

Add to sis_frontend/src/router/index.ts:

typescript
{
  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 }
}

Step 4: Add to Navigation ​

Menu items are configured through the authorization system's dashboard tiles and the menuLayout store. For development, you can add a route directly in the AppLayout.vue sidebar:

vue
<MenuItem :label="t('entities.title')" icon="pi pi-list" to="/entities" />

Step 5: Add TypeScript Types ​

Add to sis_frontend/src/api/types/index.ts:

typescript
export interface Entity {
  entity_id: number
  name: string
  description: string | null
  status_id: number
  status_code: string
  status_display: string
  department_id: number | null
  department_name: string | null
  is_active: boolean
  created_at: string
  updated_at: string
}

export interface EntityCreate {
  name: string
  description?: string
  status_id: number
  department_id?: number
}

Step 6: Add API Service ​

Create sis_frontend/src/api/services/domain.api.ts:

typescript
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.put<Entity>(`/domain/entities/${id}`, data),

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

Step 7: Add Translation Keys ​

Add to sis_frontend/src/locales/en.json:

json
{
  "entities": {
    "title": "Entities",
    "new": "New Entity",
    "edit": "Edit Entity",
    "addNew": "Add Entity",
    "created": "Entity created successfully",
    "updated": "Entity updated successfully"
  }
}

Using Lookup Values ​

For fields that reference lookup tables, use the lookups store:

vue
<script setup lang="ts">
import { onMounted } from 'vue'
import { useLookupsStore } from '@/stores/lookups'

const lookupsStore = useLookupsStore()

onMounted(() => {
  lookupsStore.fetchLookup('ENTITY_STATUS')
})
</script>

<template>
  <div class="field">
    <label>Status</label>
    <Select
      v-model="entity.status_id"
      :options="lookupsStore.getByType('ENTITY_STATUS')"
      optionLabel="display_value"
      optionValue="id"
      placeholder="Select status..."
    />
  </div>
</template>

Student Information System Documentation