UserRiwayat.vue 6.18 KB
<script setup lang="ts">
import { onMounted } from 'vue'
import { useKeycloakStore } from '@/@core/stores/keycloakStore'

const data = ref<Record<string, any> | null>(null)
const error = ref<Record<string, any> | null>(null)

const items = ref<any[]>([])
const expandedRows = ref<any[]>([])
const loading = ref(false)

onMounted(() => {
  keycloakStore.refresh()
})

watchEffect(async () => {
  if (!keycloakStore.accessToken)
    return// Hindari fetch jika token masih kosong
  console.log('Fetching data dengan token baru...')

  const { data: newData, error: newError } = await useAuthFetch('https://api.ui.ac.id/my/ac/st')

  data.value = newData.value || null
  error.value = newError.value || null
})

const keycloakStore = useKeycloakStore()

const riwayatHeaders = [
  { title: '', key: 'data-table-expand', sortable: false },
  { title: 'Tahun', key: 'THN', sortable: false },
  { title: 'Term', key: 'TERM', sortable: false },
  { title: 'Semester', key: 'SEMESTER', sortable: true },
  { title: 'Status', key: 'STATUS', sortable: false },
  { title: 'IPS', key: 'IPS', sortable: true },
  { title: 'IPK', key: 'IPK', sortable: true },
]

const expandedHeaders = [
  { title: 'Kode MK', key: 'KD_MK', sortable: true },
  { title: 'Nama MK', key: 'NM_MK', sortable: true },
  { title: 'Kelas', key: 'NM_KLS_MK', sortable: false },
  { title: 'Status', key: 'STATUS', sortable: false },
  { title: 'Nilai', key: 'NILAI_HURUF', sortable: true },
  { title: 'Pengajar', key: 'PENGAJAR', sortable: false },
]

async function getData() {
  loading.value = true
  error.value = ''

  // Reset items sebelum fetch data baru
  items.value = []

  try {
    const apiEndpoint = 'https://api.ui.ac.id/my/ac'

    const response = await fetch(apiEndpoint, {
      headers: {
        Authorization: `Bearer ${keycloakStore.accessToken}`,
      },
    })

    if (!response.ok)
      throw new Error('Gagal mengambil data')
    const dataku = await response.json()

    items.value = dataku.map((item: any) => ({
      ...item,
      expanded: Object.values(item.IRS || {}).map((mk: any) => ({
        KD_MK: mk.KD_MK,
        NM_MK: mk.NM_MK,
        NM_KLS_MK: mk.NM_KLS_MK,
        STATUS: mk.STATUS,
        NILAI_HURUF: mk.NILAI_HURUF,
        PENGAJAR: mk.PENGAJAR ? Object.values(mk.PENGAJAR).join(', ') : '-',
      })),
    }))

    // Pastikan data adalah array sebelum dimasukkan ke items
    // items.value = Array.isArray(dataku) ? dataku : [];
    // items.value = dataku;
  }
  catch (err: any) {
    error.value = err.message || 'Terjadi kesalahan saat mengambil data'
  }
  finally {
    loading.value = false
  }
}

// Fetch data from API
onMounted(() => {
  getData()
})

// Search query state
const searchQuery = ref('')

// Filter aplikasi berdasarkan pencarian
const filteredRiwayat = computed(() => {
  if (!searchQuery.value)
    return items.value
  const query = searchQuery.value.toLowerCase()

  return items.value
    .map(item => {
      // Filter mata kuliah (MK) berdasarkan NM_MK
      const filteredMK = item.expanded.filter((mk: any) =>
        mk.NM_MK.toLowerCase().includes(query),
      )

      // Jika ada MK yang cocok, tetap tampilkan item tetapi hanya dengan MK yang cocok
      if (filteredMK.length > 0)
        return { ...item, expanded: filteredMK }

      // Jika tidak ada MK yang cocok, cek apakah tahun, semester, atau status cocok
      if (
        [item.THN, item.SEMESTER, item.STATUS].some(field =>
          String(field).toLowerCase().includes(query),
        )
      )
        return item

      return null // Tidak cocok, hapus dari hasil pencarian
    })
    .filter(Boolean) // Hapus item yang null

  // return items.value.filter((item) =>
  //   [item.THN, item.SEMESTER, item.STATUS].some((field) =>
  //     String(field).toLowerCase().includes(query)
  //   )
  // );
})

const resolveStatusColor = (status: string) => {
  if (status === 'Aktif')
    return 'primary'
  if (status === 'Lulus')
    return 'success'
}
</script>

<template>
  <!--
    <div class="mb-10">
    <h1>Welcome, {{ keycloakStore.name }}</h1>
    </div>
  -->
  <VCard
    title="Riwayat Mata Kuliah"
    class="riwayatList"
  >
    <!-- Search Input -->
    <div class="search-container mb-4 pl-2 pr-2">
      <VTextField
        v-model="searchQuery"
        label="Search"
        placeholder="Search ..."
        append-inner-icon="ri-search-line"
        clearable
        single-line
        hide-details
        dense
        outlined
      />
    </div>

    <!-- SECTION Datatable -->
    <VDataTable
      v-model:expanded="expandedRows"
      :headers="riwayatHeaders"
      :items="filteredRiwayat"
      hide-default-footer
      fixed-header
      item-value="SEMESTER"
      :sort-by="['SEMESTER']"
      :sort-asc="[true]"
      show-expand
    >
      <template #expanded-row="{ item }">
        <tr>
          <td colspan="6">
            <VDataTable
              density="compact"
              :headers="expandedHeaders"
              :items="item.expanded"
              class="ml-4"
            />
          </td>
        </tr>
      </template>

      <!-- Tahun Ajar -->
      <template #item.THN="{ item }">
        <div class="d-flex align-center gap-x-3">
          <div>
            <h6 class="text-h6 text-no-wrap">
              {{ `${Number(item.THN)}/${Number(item.THN) + 1}` }}
            </h6>
          </div>
        </div>
      </template>

      <!-- Status -->
      <template #item.STATUS="{ item }">
        <VChip
          :color="resolveStatusColor(item.STATUS)"
          :class="`text-${resolveStatusColor(item.STATUS)}`"
          size="small"
          class="font-weight-medium"
        >
          {{ item.STATUS }}
        </VChip>
      </template>

      <!-- TODO Refactor this after vuetify provides proper solution for removing default footer -->
      <template #bottom />
    </VDataTable>
    <!-- !SECTION -->
  </VCard>
</template>

<style lang="scss">
.riwayatList {
  .v-table {
    &--density-default {
      .v-table__wrapper {
        table {
          tbody {
            tr {
              td {
                block-size: 56px;
              }
            }
          }
        }
      }
    }
  }
}

.search-container {
  display: flex;
  justify-content: flex-end;
}

.ml-4 {
  margin-inline-start: 16px;
}
</style>