Commit f46013e1 by Samuel Taniel Mulyadi

Merge branch 'sam' into 'staging'

Sam

See merge request !22
2 parents e5f2a991 753145f4
<script setup lang="ts"> <script setup lang="ts">
import { useKeycloakStore } from "@/@core/stores/keycloakStore"; import { useKeycloakStore } from "@/@core/stores/keycloakStore";
import { ref } from "vue"; import { ref, computed, onMounted } from "vue";
// Store Keycloak // Store Keycloak
const keycloakStore = useKeycloakStore(); const keycloakStore = useKeycloakStore();
...@@ -10,6 +11,7 @@ const items = ref<any[]>([]); ...@@ -10,6 +11,7 @@ const items = ref<any[]>([]);
const loading = ref(false); const loading = ref(false);
const searchQuery = ref(""); const searchQuery = ref("");
// Header tabel
const logHeaders = [ const logHeaders = [
{ title: "KOLEKSI", key: "koleksi" }, { title: "KOLEKSI", key: "koleksi" },
{ title: "TANGGAL PEMINJAMAN", key: "tglpinjam" }, { title: "TANGGAL PEMINJAMAN", key: "tglpinjam" },
...@@ -17,13 +19,12 @@ const logHeaders = [ ...@@ -17,13 +19,12 @@ const logHeaders = [
{ title: "DIPINJAM", key: "dipinjam" }, { title: "DIPINJAM", key: "dipinjam" },
{ title: "PERPANJANGAN", key: "perpanjangan" }, { title: "PERPANJANGAN", key: "perpanjangan" },
{ title: "DENDA", key: "denda" }, { title: "DENDA", key: "denda" },
{ title: "DENDA TOTAL", key: "dendatotoal" }, { title: "DENDA TOTAL", key: "dendatotal" },
]; ];
// Fungsi ambil data
async function getData() { async function getData() {
loading.value = true; loading.value = true;
items.value = [];
try { try {
const apiEndpoint = "https://api.ui.ac.id/my/lib"; const apiEndpoint = "https://api.ui.ac.id/my/lib";
const response = await fetch(apiEndpoint, { const response = await fetch(apiEndpoint, {
...@@ -31,16 +32,24 @@ async function getData() { ...@@ -31,16 +32,24 @@ async function getData() {
Authorization: `Bearer ${keycloakStore.accessToken}`, Authorization: `Bearer ${keycloakStore.accessToken}`,
}, },
}); });
if (!response.ok) throw new Error("Gagal mengambil data"); if (!response.ok) throw new Error("Gagal mengambil data");
const dataku = await response.json(); const dataku = await response.json();
// Tambahkan properti tanggal, namaHari, mulai aktual, dan selesai aktual ke setiap item // dataku adalah array, tiap elemen punya properti root-level
items.value = dataku.map((item: any) => { const requiredKeys = [
return { "denda",
...item, "dendatotal",
}; "dipinjam",
}); "koleksi",
"perpanjangan",
"tglkembali",
"tglpinjam",
"username",
];
// ambil hanya objek yang memiliki semua key di root
items.value = Array.isArray(dataku)
? dataku.filter((r: any) => requiredKeys.every(k => k in r))
: [];
} catch (err) { } catch (err) {
console.error("Gagal mengambil data:", err); console.error("Gagal mengambil data:", err);
} finally { } finally {
...@@ -48,22 +57,32 @@ async function getData() { ...@@ -48,22 +57,32 @@ async function getData() {
} }
} }
// Panggil saat komponen mount
onMounted(() => {
getData();
});
// Filter hasil pencarian
const filteredItems = computed(() => { const filteredItems = computed(() => {
if (!searchQuery.value) return items.value; if (!searchQuery.value) return items.value;
const q = searchQuery.value.toLowerCase();
const query = searchQuery.value.toLowerCase(); return items.value.filter(item =>
return items.value.filter((item) =>
logHeaders.some( logHeaders.some(
(header) => item[header.key] && String(item[header.key]).toLowerCase().includes(query) h => item[h.key] && String(item[h.key]).toLowerCase().includes(q)
) )
); );
}); });
</script> </script>
<template> <template>
<VCard title="Status Peminjaman Buku" class="recentnamaHariCard"> <AppCardActions
<div class="search-container mb-4 pl-2 pr-2"> :title="`Status Peminjaman Buku`"
class="jadwalShift"
action-collapsed
action-remove
>
<!-- Search Input -->
<div class="search-container mb-4 pl-2 pr-2">
<VTextField <VTextField
v-model="searchQuery" v-model="searchQuery"
label="Search" label="Search"
...@@ -76,6 +95,7 @@ const filteredItems = computed(() => { ...@@ -76,6 +95,7 @@ const filteredItems = computed(() => {
outlined outlined
/> />
</div> </div>
<VDataTable <VDataTable
:headers="logHeaders" :headers="logHeaders"
:items="filteredItems" :items="filteredItems"
...@@ -85,5 +105,52 @@ const filteredItems = computed(() => { ...@@ -85,5 +105,52 @@ const filteredItems = computed(() => {
:sort-by="['tglpinjam']" :sort-by="['tglpinjam']"
:sort-asc="[true]" :sort-asc="[true]"
></VDataTable> ></VDataTable>
</VCard>
</AppCardActions>
</template> </template>
<style lang="scss">
.jadwalShift {
.v-table {
&--density-default {
.v-table__wrapper {
table {
thead {
th {
background-color: #f5f5f5;
border-block-end: 2px solid #ddd;
color: #2c2c2c;
font-weight: 600;
padding-block: 12px;
padding-inline: 1.5em;
}
}
tbody {
tr {
td {
border-block-end: 1px solid #eee;
min-block-size: auto;
padding-block: 8px;
padding-inline: 1em;
vertical-align: top;
&.vti-table__td--Jadwal {
color: #4a4a4a;
font-weight: 500;
}
}
}
}
}
}
}
}
}
.search-container {
display: flex;
justify-content: flex-end;
}
</style>
\ No newline at end of file
<script lang="ts" setup> <script lang="ts" setup>
import { HorizontalNavLayout } from '@layouts'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useKeycloakStore } from '@/@core/stores/keycloakStore'
import keycloakInstance from '@/keycloak'
import Footer from '@/layouts/components/Footer.vue' import Footer from '@/layouts/components/Footer.vue'
import NavbarThemeSwitcher from '@/layouts/components/NavbarThemeSwitcher.vue' import NavbarThemeSwitcher from '@/layouts/components/NavbarThemeSwitcher.vue'
import NavbarTokenExpiredTime from '@/layouts/components/NavbarTokenExpiredTime.vue' import NavbarTokenExpiredTime from '@/layouts/components/NavbarTokenExpiredTime.vue'
import UserProfile from '@/layouts/components/UserProfile.vue' import UserProfile from '@/layouts/components/UserProfile.vue'
import navItems from '@/navigation/horizontal' import navItems from '@/navigation/horizontal'
import { HorizontalNavLayout } from '@layouts'
// Keycloak & state // Keycloak & state
const keycloakStore = useKeycloakStore()
const authenticated = computed(() => keycloakStore.authenticated)
const now = ref(Math.floor(Date.now() / 1000))
const tokenLifetime = ref(0)
let timer: ReturnType<typeof setInterval>
onMounted(() => {
// Set tokenLifetime only once on mount
if (keycloakInstance.tokenParsed?.exp && keycloakInstance.tokenParsed?.iat)
tokenLifetime.value = keycloakInstance.tokenParsed.exp - keycloakInstance.tokenParsed.iat
// Start timer
timer = setInterval(() => {
now.value = Math.floor(Date.now() / 1000)
}, 1000)
})
onUnmounted(() => {
clearInterval(timer)
})
// Computed expiration values
const computedExpIn = computed(() => {
return authenticated.value && keycloakInstance.tokenParsed?.exp
? Math.max(keycloakInstance.tokenParsed?.exp - now.value, 0)
: 0
})
</script> </script>
<template> <template>
...@@ -77,23 +44,7 @@ const computedExpIn = computed(() => { ...@@ -77,23 +44,7 @@ const computedExpIn = computed(() => {
<NavbarThemeSwitcher /> <NavbarThemeSwitcher />
<!-- <NavbarShortcuts /> --> <!-- <NavbarShortcuts /> -->
<!-- <NavBarNotifications class="me-2" /> --> <!-- <NavBarNotifications class="me-2" /> -->
<!--
<div class="pa-4">
<VAlert
v-if="authenticated"
type="info"
border="start"
variant="outlined"
class="mb-4"
>
<h3 class="text-h6 font-weight-bold">
Access token expires in {{ computedExpIn }} sec,
Refresh token in {{ computedRefExpIn }} sec,
Session: {{ computedSessionTimer }}
</h3>
</VAlert>
</div>
-->
<UserProfile /> <UserProfile />
</template> </template>
......
import { defineNuxtRouteMiddleware, navigateTo } from "nuxt/app" import { defineNuxtRouteMiddleware, navigateTo } from 'nuxt/app'
import { useKeycloakStore } from "~/@core/stores/keycloakStore" import { useKeycloakStore } from '~/@core/stores/keycloakStore'
export default defineNuxtRouteMiddleware((to) => { export default defineNuxtRouteMiddleware(to => {
const keycloakStore = useKeycloakStore() const keycloakStore = useKeycloakStore()
if (process.client) { if (process.client) {
const isLoginPage = to.path === '/login' const isLoginPage = to.path === '/login'
// Jika belum login dan bukan sedang menuju halaman login, redirect ke login // Jika belum login dan bukan sedang menuju halaman login, redirect ke login
if (!keycloakStore.authenticated && !isLoginPage) { if (!keycloakStore.authenticated && !isLoginPage)
return navigateTo('/login') return navigateTo('/login')
}
// Kalau sudah login dan mencoba ke /login, bisa arahkan ke dashboard atau home // Kalau sudah login dan mencoba ke /login, bisa arahkan ke dashboard atau home
if (keycloakStore.authenticated && isLoginPage) { if (keycloakStore.authenticated && isLoginPage)
return navigateTo('/') // atau ke '/dashboard' misalnya return navigateTo('/profile') // atau ke '/dashboard' misalnya
}
} }
}) })
\ No newline at end of file
...@@ -4,9 +4,18 @@ import keycloakInstance from '@/keycloak' ...@@ -4,9 +4,18 @@ import keycloakInstance from '@/keycloak'
export default defineNuxtPlugin(async nuxtApp => { export default defineNuxtPlugin(async nuxtApp => {
const keycloakStore = useKeycloakStore() const keycloakStore = useKeycloakStore()
/* `const { } = nuxtApp` is a destructuring assignment in JavaScript/TypeScript. In this
case, it is extracting the `` property from the `nuxtApp` object and assigning it to a new
constant named ``. This allows you to access the `` object directly without having
to use `nuxtApp.` every time. It's a shorthand way of accessing nested properties in
objects. */
// const { $router } = nuxtApp
try { try {
const authenticated = await keycloakInstance.init({ const authenticated = await keycloakInstance.init({
onLoad: 'check-sso', onLoad: 'check-sso',
checkLoginIframe: true,
checkLoginIframeInterval: 5, // in seconds, default is 5
}) })
keycloakStore.authenticated = authenticated keycloakStore.authenticated = authenticated
...@@ -14,7 +23,9 @@ export default defineNuxtPlugin(async nuxtApp => { ...@@ -14,7 +23,9 @@ export default defineNuxtPlugin(async nuxtApp => {
if (authenticated) { if (authenticated) {
keycloakStore.refresh() keycloakStore.refresh()
console.log('User is authenticated') console.log('User is authenticated')
navigateTo('/profile')
// $router.push('/profile')
// console.log('Suppose to navigate To')
setInterval(() => { setInterval(() => {
const now = Math.floor(Date.now() / 1000) const now = Math.floor(Date.now() / 1000)
...@@ -25,13 +36,14 @@ export default defineNuxtPlugin(async nuxtApp => { ...@@ -25,13 +36,14 @@ export default defineNuxtPlugin(async nuxtApp => {
keycloakInstance.logout({ redirectUri: `${window.location.origin}/login` }) keycloakInstance.logout({ redirectUri: `${window.location.origin}/login` })
} }
else { else {
console.log('Token expires in:', tokenExp - now, 'seconds') // console.log('Token expires in:', tokenExp - now, 'seconds')
} }
}, 10_000) }, 10_000)
} }
else { else {
console.log('User is not authenticated') console.log('User is not authenticated')
navigateTo('/login')
// $router.push('/login')
} }
} }
catch (error) { catch (error) {
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!