index.vue 5.61 KB
<script lang="ts" setup>
import { useKeycloakStore } from "@core/stores/keycloakStore";

const keycloakStore = useKeycloakStore();
// Gunakan computed agar selalu reaktif
const isAuthenticated = computed(() => keycloakStore.authenticated);
// const isAuthenticated = keycloakStore.authenticated;

// State management
const items = ref<any[]>([]);
const expandedRows = ref<any[]>([]);
const loading = ref(false);
const error = ref('');

// Project Riwayat Header
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>
  <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 :headers="riwayatHeaders" :items="filteredRiwayat" hide-default-footer fixed-header
      item-value="SEMESTER" :sort-by="['SEMESTER']" :sort-asc="[true]" v-model:expanded="expandedRows" show-expand>

      <template v-slot: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>