Skip to content
Toggle navigation
Projects
Groups
Snippets
Help
dtd
/
civitas.ui
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit f46013e1
authored
Apr 28, 2025
by
Samuel Taniel Mulyadi
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'sam' into 'staging'
Sam See merge request
!22
2 parents
e5f2a991
753145f4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
111 additions
and
83 deletions
components/beranda/UserLib.vue
layouts/components/DefaultLayoutWithHorizontalNav.vue
middleware/acl.global.ts
plugins/keycloak.ts
components/beranda/UserLib.vue
View file @
f46013e
<
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
:
"dendatot
o
al"
},
{
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
layouts/components/DefaultLayoutWithHorizontalNav.vue
View file @
f46013e
<
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
>
...
...
middleware/acl.global.ts
View file @
f46013e
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
plugins/keycloak.ts
View file @
f46013e
...
@@ -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
)
{
...
...
Write
Preview
Markdown
is supported
Attach a file
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to post a comment