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

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

interface Props {
  searchQuery: string
}

const props = defineProps<Props>()

// List NIP Staf DSTI
const listStaf = [
  { nama: 'Agus Mulyana', nip: '141113024' },
  { nama: 'Akmal Gafar Putra', nip: '1408150023' },
  { nama: 'Andri Setyawan', nip: '100140310251109891' },
  { nama: 'Anggi Saputra', nip: '141125004' },
  { nama: 'Ardiansyah', nip: '100140310282903891' },
  { nama: 'Dina Mardia', nip: '140825006' },
  { nama: 'Muh. Bahrian Shalat', nip: '198309102022073001' },
  { nama: 'M. Budi Utama', nip: '198502232009121002' },
  { nama: 'Renia Fathiayusa', nip: '141113007' },
];

const loading = ref(false);
const detailedStaff = ref<any[]>([]);
const selectedStaff = ref<any>(null); // Untuk staff yang dipilih untuk View/Delete
const isViewDialogOpen = ref(false);
const isDeleteDialogOpen = ref(false);

// Fungsi untuk mendapatkan data lengkap staf
async function fetchDetailedStaff() {
  loading.value = true;
  detailedStaff.value = [];

  for (const staf of listStaf) {
    try {
      const hrisEndpoint = `https://api.ui.ac.id/staf/${staf.nip}`;
      const response = await fetch(hrisEndpoint, {
        headers: {
          Authorization: `Bearer ${keycloakStore.accessToken}`,
        },
      });

      if (!response.ok) {
        throw new Error(`Gagal mengambil data untuk NIP: ${staf.nip}`);
      }

      const data = await response.json();
      detailedStaff.value.push({
        ...staf,
        kategori: data.employee_category || '-',
        foto: data.employee_photo || '-',
        jabatan: data.jabatan_pekerjaan || '-',
        unit: data.organization_unit || '-',
        posisi: data.posisi || '-',
      });
    } catch (error) {
      console.error(error);
      detailedStaff.value.push({
        ...staf,
        kategori: 'Error fetching data',
        foto: '-',
        jabatan: '-',
        unit: '-',
        posisi: '-',
      });
    }
  }

  loading.value = false;
}

onMounted(() => {
  fetchDetailedStaff();
});

// Daftar header tabel
const headers = [
  { title: 'Nama', align: 'start', key: 'nama', sortable: true },
  { title: 'NIP', key: 'nip', sortable: true },
  { title: 'Foto', key: 'foto', sortable: false },
  { title: 'Unit Organisasi', key: 'unit', sortable: true },
  { title: 'Posisi', key: 'posisi', sortable: true },
  { title: 'Aksi', key: 'actions', align: 'center', sortable: false },
];

// Filter data berdasarkan query pencarian dari props
const filteredStaff = computed(() => {
  if (!props.searchQuery) return detailedStaff.value;
  const query = props.searchQuery.toLowerCase();
  return detailedStaff.value.filter(
    (staff) =>
      staff.nama.toLowerCase().includes(query) ||
      staff.nip.includes(query) ||
      staff.unit.toLowerCase().includes(query) ||
      staff.posisi.toLowerCase().includes(query)
  );
});

// Fungsi untuk membuka dialog View
function openViewDialog(staff: any) {
  selectedStaff.value = staff;
  isViewDialogOpen.value = true;
}

// Fungsi untuk membuka dialog Delete
function openDeleteDialog(staff: any) {
  selectedStaff.value = staff;
  isDeleteDialogOpen.value = true;
}

// Fungsi untuk menghapus staf
function deleteStaff() {
  if (selectedStaff.value) {
    detailedStaff.value = detailedStaff.value.filter((staff) => staff.nip !== selectedStaff.value.nip);
  }
  isDeleteDialogOpen.value = false;
}
</script>

<template>
  <div v-if="isAuthenticated">
    <VCard class="mb-6">
      <VCardText>
        <!-- Header -->
        <div class="d-flex justify-space-between align-center flex-wrap gap-4 mb-6">
          <div>
            <h5 class="text-h5">Our Staff</h5>
            <div class="text-body-1">From All Direktorat</div>
          </div>
        </div>

        <!-- Tabel Staf -->
        <VDataTable :headers="headers" :items="filteredStaff" :loading="loading" item-value="nip" class="elevation-1"
          :items-per-page="10">
          <!-- Kolom Foto -->
          <template #item.foto="{ item }">
            <img :src="item.foto" alt="Foto Staf" v-if="item.foto !== '-'"
              style="border-radius: 50%; block-size: 50px; inline-size: 50px;" />
            <span v-else>Tidak Ada Foto</span>
          </template>

          <!-- Kolom Aksi -->
          <template #item.actions="{ item }">
            <div class="d-flex gap-2 justify-center">
              <VBtn icon size="small" variant="outlined" color="primary" @click="() => openViewDialog(item)">
                <VIcon icon="ri-eye-line" />
              </VBtn>
              <VBtn icon size="small" variant="outlined" color="error" @click="() => openDeleteDialog(item)">
                <VIcon icon="ri-delete-bin-line" />
              </VBtn>
            </div>
          </template>
        </VDataTable>
      </VCardText>
    </VCard>

    <!-- Dialog View -->
    <VDialog v-model="isViewDialogOpen" persistent :max-width="$vuetify.display.smAndDown ? 'auto' : 500">
      <!-- 👉 dialog close btn -->
      <DialogCloseBtn variant="text" size="default" @click="isViewDialogOpen = false" />

      <VCard class="pa-sm-5 pa-3">
        <VCardTitle>
          <h5 class="text-h5 mb-1">
            <VIcon size="18" class="mb-1 bg-primary rounded-0">
              ri-user-follow-fill
            </VIcon> {{ selectedStaff?.nama || 'Staff Details' }}
          </h5>
        </VCardTitle>
        <VCardText>
          <div class="pa-2">
            <VImg :src="selectedStaff?.foto" alt="Foto" class="cursor-pointer"
              style="inline-size: 100%; max-block-size: 200px;" />
          </div>
          <div class="mb-2">
            <VAlert color="primary" variant="tonal">
              <p class="mb-0">
                <strong>NIP:</strong> {{ selectedStaff?.nip }}
              </p>
              <p class="mb-0">
                <strong>Unit Organisasi:</strong> {{ selectedStaff?.unit }}
              </p>
              <p class="mb-0">
                <strong>Posisi:</strong> {{ selectedStaff?.posisi }}
              </p>
            </VAlert>
          </div>
        </VCardText>
        <VCardActions>
          <VBtn variant="tonal" class="bg-primary" color="white" @click="isViewDialogOpen = false">Close</VBtn>
        </VCardActions>
      </VCard>
    </VDialog>

    <!-- Dialog Delete -->
    <VDialog v-model="isDeleteDialogOpen" persistent :max-width="$vuetify.display.smAndDown ? 'auto' : 400">
      <!-- 👉 dialog close btn -->
      <DialogCloseBtn variant="text" size="default" @click="isDeleteDialogOpen = false" />

      <VCard class="pa-sm-5 pa-3">
        <VCardTitle class="text-center pb-5 text-h4">Delete Staff</VCardTitle>
        <VCardText class="text-justify">Are you sure you want to delete <strong>{{ selectedStaff?.nama }}</strong>?
        </VCardText>
        <VCardActions>
          <VBtn variant="tonal" class="bg-error" color="white" @click="deleteStaff">Delete</VBtn>
          <VBtn variant="outlined" @click="isDeleteDialogOpen = false">Cancel</VBtn>
        </VCardActions>
      </VCard>
    </VDialog>
  </div>
</template>