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 7067e9e7
authored
Apr 16, 2025
by
Samuel Taniel Mulyadi
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'sam' into 'master'
Sam See merge request
!17
2 parents
28ea9917
8b75d2d6
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
484 additions
and
328 deletions
@core/scss/template/pages/page-auth.scss
components/beranda/UserJadwal.vue
components/beranda/UserRiwayat.vue
pages/[tab].vue
pages/login-v5.vue
pages/login.vue
views/dstipro/beranda/riwayat/index.vue
views/pages/authentication/AuthProvider.vue
@core/scss/template/pages/page-auth.scss
View file @
7067e9e
...
@@ -75,7 +75,7 @@
...
@@ -75,7 +75,7 @@
min-block-size
:
100vh
;
/* Ensures the element covers the full viewport height */
min-block-size
:
100vh
;
/* Ensures the element covers the full viewport height */
padding-block-start
:
10px
;
padding-block-start
:
10px
;
// margin: 5% 5%;marginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmargin
// margin: 5% 5%;marginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmargin
marginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmarginmargin
}
}
.main-component-margin
{
.main-component-margin
{
...
@@ -115,9 +115,9 @@ body {
...
@@ -115,9 +115,9 @@ body {
.form-component-login
{
.form-component-login
{
position
:
relative
;
position
:
relative
;
z-index
:
1
;
z-index
:
1
;
display
:
block
;
display
:
flex
;
/* changed from block to flex */
overflow
:
hidden
;
overflow
:
hidden
;
flex-
flow
:
row
;
flex-
direction
:
column
;
/* or row depending on layout */
align-items
:
center
;
align-items
:
center
;
justify-content
:
center
;
justify-content
:
center
;
border-radius
:
1
.7rem
;
border-radius
:
1
.7rem
;
...
@@ -128,6 +128,7 @@ body {
...
@@ -128,6 +128,7 @@ body {
grid-template-columns
:
1fr
1fr
;
grid-template-columns
:
1fr
1fr
;
grid-template-rows
:
auto
;
grid-template-rows
:
auto
;
margin-inline
:
20%
0%
;
margin-inline
:
20%
0%
;
min-block-size
:
300px
;
min-inline-size
:
345px
;
min-inline-size
:
345px
;
outline
:
0
.5px
solid
#747474
;
outline
:
0
.5px
solid
#747474
;
outline-offset
:
0
;
outline-offset
:
0
;
...
@@ -169,11 +170,13 @@ body {
...
@@ -169,11 +170,13 @@ body {
font-family
:
"Open Sans"
,
sans-serif
;
font-family
:
"Open Sans"
,
sans-serif
;
font-size
:
14px
;
font-size
:
14px
;
grid-gap
:
0
.7rem
;
grid-gap
:
0
.7rem
;
inline-size
:
100%
;
margin-block
:
1%
;
margin-block
:
1%
;
margin-inline
:
20px
;
margin-inline
:
20px
;
object-fit
:
fill
;
object-fit
:
fill
;
padding-block
:
10%
;
padding-block
:
10%
;
padding-inline
:
5%
;
padding-inline
:
5%
;
padding-inline
:
35px
;
}
}
.heading-8
{
.heading-8
{
...
...
components/beranda/UserJadwal.vue
View file @
7067e9e
...
@@ -243,7 +243,7 @@ function getColorClass(index: number) {
...
@@ -243,7 +243,7 @@ function getColorClass(index: number) {
v-else
v-else
class=
"timetable"
class=
"timetable"
>
>
<div
class=
"scrollable"
>
<div
class=
"scrollable
2
"
>
<table
class=
"w-100 text-left table-schedule fixed-table"
>
<table
class=
"w-100 text-left table-schedule fixed-table"
>
<thead>
<thead>
<tr>
<tr>
...
@@ -279,16 +279,16 @@ function getColorClass(index: number) {
...
@@ -279,16 +279,16 @@ function getColorClass(index: number) {
getScheduleByDay(day)[rowIndex - 1].end
getScheduleByDay(day)[rowIndex - 1].end
}}
}}
</span>
</span>
<span
class=
"
room
text-xs"
>
{{ getScheduleByDay(day)[rowIndex - 1].room }}
</span>
<span
class=
"text-xs"
>
{{ getScheduleByDay(day)[rowIndex - 1].room }}
</span>
</div>
</div>
<div
class=
"course-title font-semibold text-sm mb-1"
>
<div
class=
"course-title
2
font-semibold text-sm mb-1"
>
<span
<span
v-if=
"getScheduleByDay(day)[rowIndex - 1].course.includes('-')"
v-if=
"getScheduleByDay(day)[rowIndex - 1].course.includes('-')"
style=
" padding-inline-end: 4px;text-decoration: underline;"
style=
" padding-inline-end: 4px;text-decoration: underline;"
>
>
{{ getScheduleByDay(day)[rowIndex - 1].course.split(' - ')[0] }}
{{ getScheduleByDay(day)[rowIndex - 1].course.split(' - ')[0] }}
</span>
</span>
<span
class=
"
text-sm
"
>
<span
class=
"
course-name
"
>
{{
{{
getScheduleByDay(day)[rowIndex - 1].course.includes('-')
getScheduleByDay(day)[rowIndex - 1].course.includes('-')
? getScheduleByDay(day)[rowIndex - 1].course.split(' - ')[1]
? getScheduleByDay(day)[rowIndex - 1].course.split(' - ')[1]
...
@@ -322,6 +322,18 @@ function getColorClass(index: number) {
...
@@ -322,6 +322,18 @@ function getColorClass(index: number) {
min-inline-size
:
500px
;
min-inline-size
:
500px
;
}
}
.scrollable2
{
overflow
:
auto
hidden
;
/* Optional: you can allow vertical scroll if needed */
box-sizing
:
border-box
;
inline-size
:
100%
;
min-inline-size
:
1000px
;
}
/* Optional: make inner content grow past 700px if needed */
.scrollable2
>
*
{
min-inline-size
:
1000px
;
}
.date-range-box
,
.date-range-box
,
.date-range-box2
{
.date-range-box2
{
border-radius
:
5px
;
border-radius
:
5px
;
...
@@ -385,21 +397,64 @@ function getColorClass(index: number) {
...
@@ -385,21 +397,64 @@ function getColorClass(index: number) {
}
}
.course-title
{
.course-title
{
display
:
flex
;
flex-wrap
:
wrap
;
/* Allow wrapping when needed */
border-block-end
:
1px
solid
;
font-size
:
12px
;
font-weight
:
bold
;
gap
:
4px
;
/* Optional: space between prefix and name */
inline-size
:
100%
;
padding-block-end
:
4px
;
}
.course-title2
{
display
:
flex
;
flex-wrap
:
wrap
;
/* Allow wrapping when needed */
border-block-end
:
1px
solid
;
border-block-end
:
1px
solid
;
font-size
:
12px
;
font-size
:
12px
;
font-weight
:
bold
;
font-weight
:
bold
;
gap
:
4px
;
/* Optional: space between prefix and name */
inline-size
:
100%
;
inline-size
:
100%
;
margin-block-end
:
5px
;
padding-block-end
:
4px
;
padding-block-end
:
5px
;
text-overflow
:
ellipsis
;
/* Show "..." */
}
}
.course-prefix
{
.course-prefix
{
color
:
color-mix
(
in
srgb
,
rgba
(
var
(
--v-global-theme-primary
))
70%
,
black
30%
);
color
:
color-mix
(
in
srgb
,
rgba
(
var
(
--v-global-theme-primary
))
70%
,
black
30%
);
padding-inline-end
:
4px
;
text-decoration
:
underline
;
text-decoration
:
underline
;
}
}
.course-name
{
display
:
-webkit-box
;
overflow
:
hidden
;
-webkit-box-orient
:
vertical
;
-webkit-line-clamp
:
2
;
text-overflow
:
ellipsis
;
}
.course-header
{
display
:
flex
;
justify-content
:
space-between
;
gap
:
8px
;
/* optional spacing */
inline-size
:
100%
;
}
.time
{
flex-shrink
:
0
;
/* Don't shrink */
font-weight
:
normal
;
text-align
:
start
;
white-space
:
nowrap
;
}
.room
{
overflow
:
hidden
;
flex-grow
:
1
;
flex-shrink
:
1
;
font-weight
:
normal
;
text-align
:
end
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
}
.grid-body
{
.grid-body
{
display
:
grid
;
display
:
grid
;
grid-template-columns
:
80px
auto
;
/* Time column + Schedule grid */
grid-template-columns
:
80px
auto
;
/* Time column + Schedule grid */
...
@@ -451,31 +506,13 @@ function getColorClass(index: number) {
...
@@ -451,31 +506,13 @@ function getColorClass(index: number) {
grid-template-rows
:
repeat
(
840
,
1px
);
/* 1px per minute */
grid-template-rows
:
repeat
(
840
,
1px
);
/* 1px per minute */
}
}
.course-header
{
.building
{
display
:
flex
;
justify-content
:
space-between
;
gap
:
8px
;
/* optional spacing */
inline-size
:
100%
;
}
.time
{
flex-shrink
:
0
;
/* Don't shrink */
font-weight
:
normal
;
text-align
:
start
;
white-space
:
nowrap
;
}
.room
{
overflow
:
hidden
;
flex-grow
:
1
;
flex-shrink
:
1
;
font-weight
:
normal
;
font-weight
:
normal
;
text-align
:
end
;
opacity
:
0.8
;
text-overflow
:
ellipsis
;
text-align
:
start
;
/* Aligns room to the right */
white-space
:
nowrap
;
}
}
.building
{
.building
2
{
font-weight
:
normal
;
font-weight
:
normal
;
opacity
:
0.8
;
opacity
:
0.8
;
text-align
:
start
;
/* Aligns room to the right */
text-align
:
start
;
/* Aligns room to the right */
...
...
components/beranda/UserRiwayat.vue
View file @
7067e9e
<
script
setup
lang=
"ts"
>
<
script
lang=
"ts"
setup
>
import
{
onMounted
}
from
'vu
e'
import
{
useKeycloakStore
}
from
'@core/stores/keycloakStor
e'
import
{
useKeycloakStore
}
from
'@/@core/stores/keycloakStor
e'
import
{
computed
,
onMounted
,
ref
}
from
'vu
e'
const
data
=
ref
<
Record
<
string
,
any
>
|
null
>
(
null
)
const
keycloakStore
=
useKeycloakStore
(
)
const
error
=
ref
<
Record
<
string
,
any
>
|
null
>
(
null
)
const
isAuthenticated
=
computed
(()
=>
keycloakStore
.
authenticated
)
const
items
=
ref
<
any
[]
>
([])
const
items
=
ref
<
any
[]
>
([])
const
expandedRows
=
ref
<
any
[]
>
([])
const
expandedRows
=
ref
<
any
[]
>
([])
const
loading
=
ref
(
false
)
const
loading
=
ref
(
false
)
const
error
=
ref
(
''
)
const
searchQuery
=
ref
(
''
)
onMounted
(()
=>
{
// Headers
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
=
[
const
riwayatHeaders
=
[
{
title
:
''
,
key
:
'data-table-expand'
,
sortable
:
false
},
{
title
:
''
,
key
:
'data-table-expand'
,
sortable
:
false
},
{
title
:
'Tahun'
,
key
:
'THN'
,
sortable
:
false
},
{
title
:
'Tahun'
,
key
:
'THN'
,
sortable
:
false
},
...
@@ -45,41 +31,48 @@ const expandedHeaders = [
...
@@ -45,41 +31,48 @@ const expandedHeaders = [
{
title
:
'Pengajar'
,
key
:
'PENGAJAR'
,
sortable
:
false
},
{
title
:
'Pengajar'
,
key
:
'PENGAJAR'
,
sortable
:
false
},
]
]
function
resolveStatusColor
(
status
:
string
)
{
switch
(
status
)
{
case
'Aktif'
:
return
'primary'
case
'Lulus'
:
return
'success'
default
:
return
'default'
}
}
async
function
getData
()
{
async
function
getData
()
{
loading
.
value
=
true
loading
.
value
=
true
error
.
value
=
''
error
.
value
=
''
// Reset items sebelum fetch data baru
items
.
value
=
[]
items
.
value
=
[]
try
{
try
{
const
apiEndpoint
=
'https://api.ui.ac.id/my/ac'
const
res
=
await
fetch
(
'https://api.ui.ac.id/my/ac'
,
{
const
response
=
await
fetch
(
apiEndpoint
,
{
headers
:
{
headers
:
{
Authorization
:
`Bearer
${
keycloakStore
.
accessToken
}
`
,
Authorization
:
`Bearer
${
keycloakStore
.
accessToken
}
`
,
},
},
})
})
if
(
!
res
ponse
.
ok
)
if
(
!
res
.
ok
)
throw
new
Error
(
'Gagal mengambil data'
)
throw
new
Error
(
'Gagal mengambil data'
)
const
dataku
=
await
response
.
json
()
const
raw
=
await
res
.
json
()
items
.
value
=
dataku
.
map
((
item
:
any
)
=>
({
items
.
value
=
raw
.
map
((
item
:
any
,
idx
:
number
)
=>
{
...
item
,
const
expandedFiltered
=
Object
.
values
(
item
.
IRS
||
{})
expanded
:
Object
.
values
(
item
.
IRS
||
{}).
map
((
mk
:
any
)
=>
({
.
filter
((
mk
:
any
)
=>
mk
.
STATUS
!==
'Kosong'
)
// Remove STATUS: Kosong
KD_MK
:
mk
.
KD_MK
,
.
map
((
mk
:
any
)
=>
({
NM_MK
:
mk
.
NM_MK
,
KD_MK
:
mk
.
KD_MK
,
NM_KLS_MK
:
mk
.
NM_KLS_MK
,
NM_MK
:
mk
.
NM_MK
,
STATUS
:
mk
.
STATUS
,
NM_KLS_MK
:
mk
.
NM_KLS_MK
,
NILAI_HURUF
:
mk
.
NILAI_HURUF
,
STATUS
:
mk
.
STATUS
,
PENGAJAR
:
mk
.
PENGAJAR
?
Object
.
values
(
mk
.
PENGAJAR
).
join
(
', '
)
:
'-'
,
NILAI_HURUF
:
mk
.
NILAI_HURUF
,
})),
PENGAJAR
:
mk
.
PENGAJAR
?
Object
.
values
(
mk
.
PENGAJAR
).
join
(
', '
)
:
'-'
,
}))
}))
// Pastikan data adalah array sebelum dimasukkan ke items
return
{
// items.value = Array.isArray(dataku) ? dataku : [];
id
:
`
${
item
.
THN
}
-
${
item
.
SEMESTER
}
-
${
item
.
TERM
}
-
${
idx
}
`
,
// items.value = dataku;
...
item
,
expanded
:
expandedFiltered
,
}
}).
filter
(
item
=>
item
.
expanded
.
length
>
0
)
// Optionally remove parent rows with no expanded items
}
}
catch
(
err
:
any
)
{
catch
(
err
:
any
)
{
error
.
value
=
err
.
message
||
'Terjadi kesalahan saat mengambil data'
error
.
value
=
err
.
message
||
'Terjadi kesalahan saat mengambil data'
...
@@ -89,74 +82,50 @@ async function getData() {
...
@@ -89,74 +82,50 @@ async function getData() {
}
}
}
}
// Fetch data from API
onMounted
(()
=>
{
onMounted
(()
=>
{
getData
()
getData
()
})
})
// Search query state
const
searchQuery
=
ref
(
''
)
// Filter aplikasi berdasarkan pencarian
const
filteredRiwayat
=
computed
(()
=>
{
const
filteredRiwayat
=
computed
(()
=>
{
if
(
!
searchQuery
.
value
)
if
(
!
searchQuery
.
value
)
return
items
.
value
return
items
.
value
const
query
=
searchQuery
.
value
.
toLowerCase
()
const
query
=
searchQuery
.
value
.
toLowerCase
()
return
items
.
value
return
items
.
value
.
map
(
item
=>
{
.
map
(
item
=>
{
// Filter mata kuliah (MK) berdasarkan NM_MK
const
filteredMK
=
item
.
expanded
.
filter
((
mk
:
any
)
=>
const
filteredMK
=
item
.
expanded
.
filter
((
mk
:
any
)
=>
mk
.
NM_MK
.
toLowerCase
().
includes
(
query
),
mk
.
NM_MK
.
toLowerCase
().
includes
(
query
),
)
)
// Jika ada MK yang cocok, tetap tampilkan item tetapi hanya dengan MK yang cocok
const
matchParent
=
[
item
.
THN
,
item
.
SEMESTER
,
item
.
STATUS
].
some
(
field
=>
if
(
filteredMK
.
length
>
0
)
String
(
field
).
toLowerCase
().
includes
(
query
),
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
if
(
filteredMK
.
length
>
0
||
matchParent
)
{
})
return
{
.
filter
(
Boolean
)
// Hapus item yang null
...
structuredClone
(
item
),
expanded
:
filteredMK
.
length
>
0
?
filteredMK
:
item
.
expanded
,
}
}
// return items.value.filter((item) =>
return
null
// [item.THN, item.SEMESTER, item.STATUS].some((field) =>
})
// String(field).toLowerCase().includes(query)
.
filter
(
Boolean
)
// )
// );
})
})
const
resolveStatusColor
=
(
status
:
string
)
=>
{
if
(
status
===
'Aktif'
)
return
'primary'
if
(
status
===
'Lulus'
)
return
'success'
}
</
script
>
</
script
>
<
template
>
<
template
>
<!--
<div
class=
"mb-10"
>
<h1>
Welcome,
{{
keycloakStore
.
name
}}
</h1>
</div>
-->
<VCard
<VCard
title=
"Riwayat Mata Kuliah"
title=
"Riwayat Mata Kuliah"
class=
"riwayatList"
class=
"riwayatList"
>
>
<!-- Search
Input
-->
<!-- Search -->
<div
class=
"search-container mb-4 pl-2 pr-2"
>
<div
class=
"search-container mb-4 pl-2 pr-2"
>
<VTextField
<VTextField
v-model=
"searchQuery"
v-model=
"searchQuery"
label=
"
Search
"
label=
"
Cari Mata Kuliah atau Semester
"
placeholder=
"
Search ...
"
placeholder=
"
Contoh: Pancasila, 2023
"
append-inner-icon=
"ri-search-line"
append-inner-icon=
"ri-search-line"
clearable
clearable
single-line
single-line
...
@@ -166,76 +135,60 @@ const resolveStatusColor = (status: string) => {
...
@@ -166,76 +135,60 @@ const resolveStatusColor = (status: string) => {
/>
/>
</div>
</div>
<!--
SECTION Datat
able -->
<!--
Data T
able -->
<VDataTable
<VDataTable
v-model:expanded=
"expandedRows"
v-model:expanded=
"expandedRows"
:headers=
"riwayatHeaders"
:headers=
"riwayatHeaders"
:items=
"filteredRiwayat"
:items=
"filteredRiwayat"
item-value=
"id"
hide-default-footer
hide-default-footer
fixed-header
fixed-header
item-value=
"SEMESTER"
show-expand
:sort-by=
"['SEMESTER']"
:sort-by=
"['SEMESTER']"
:sort-asc=
"[true]"
:sort-asc=
"[true]"
show-expand
>
>
<template
#
expanded-row=
"
{ item }">
<template
#
expanded-row=
"
{ item }">
<tr>
<tr>
<td
colspan=
"
6
"
>
<td
colspan=
"
100%
"
>
<VDataTable
<VDataTable
density=
"compact"
:headers=
"expandedHeaders"
:headers=
"expandedHeaders"
:items=
"item.expanded"
:items=
"item.expanded"
density=
"compact"
class=
"ml-4"
class=
"ml-4"
hide-default-footer
/>
/>
</td>
</td>
</tr>
</tr>
</
template
>
</
template
>
<!-- Tahun Ajar -->
<
template
#
item
.
THN=
"{ item }"
>
<
template
#
item
.
THN=
"{ item }"
>
<div
class=
"d-flex align-center gap-x-3"
>
<div>
<div>
<h6
class=
"text-h6"
>
<h6
class=
"text-h6 text-no-wrap"
>
{{
`${item.THN
}
/${Number(item.THN) + 1
}
`
}}
{{
`${Number(item.THN)
}
/${Number(item.THN) + 1
}
`
}}
<
/h6
>
<
/h6
>
<
/div
>
<
/div
>
<
/div
>
<
/template
>
<
/template
>
<!--
Status
-->
<
template
#
item
.
STATUS
=
"
{
item
}
">
<
template
#
item
.
STATUS
=
"
{
item
}
">
<VChip
<VChip
:color="
resolveStatusColor
(
item
.
STATUS
)
"
:color="
resolveStatusColor
(
item
.
STATUS
)
"
:class="`text-${resolveStatusColor(item.STATUS)
}
`"
size="
small
"
class="
font
-
weight
-
medium
"
class="
font
-
weight
-
medium
"
size="
small
"
>
>
{{
item
.
STATUS
}}
{{
item
.
STATUS
}}
<
/VChip
>
<
/VChip
>
<
/template
>
<
/template
>
<!--
TODO
Refactor
this
after
vuetify
provides
proper
solution
for
removing
default
footer
-->
<
template
#
bottom
/>
<
template
#
bottom
/>
<
/VDataTable
>
<
/VDataTable
>
<!--
!
SECTION
-->
<
/VCard
>
<
/VCard
>
<
/template
>
<
/template
>
<
style
lang
=
"scss"
>
<
style
scoped
lang
=
"scss"
>
.
riwayatList
{
.
riwayatList
{
.
v
-
table
{
.
v
-
table__wrapper
{
&--
density
-
default
{
table
tbody
tr
td
{
.
v
-
table__wrapper
{
block
-
size
:
56
px
;
table
{
tbody
{
tr
{
td
{
block
-
size
:
56
px
;
}
}
}
}
}
}
}
}
}
}
}
...
...
pages/[tab].vue
View file @
7067e9e
...
@@ -2,7 +2,6 @@
...
@@ -2,7 +2,6 @@
import
{
computed
}
from
'vue'
import
{
computed
}
from
'vue'
import
{
useRoute
,
useRouter
}
from
'vue-router'
import
{
useRoute
,
useRouter
}
from
'vue-router'
import
{
useKeycloakStore
}
from
'@/@core/stores/keycloakStore'
import
{
useKeycloakStore
}
from
'@/@core/stores/keycloakStore'
import
UserBerita
from
'@/components/beranda/UserBerita.vue'
import
UserJadwal
from
'@/components/beranda/UserJadwal.vue'
import
UserJadwal
from
'@/components/beranda/UserJadwal.vue'
import
UserLib
from
'@/components/beranda/UserLib.vue'
import
UserLib
from
'@/components/beranda/UserLib.vue'
import
UserLog
from
'@/components/beranda/UserLog.vue'
import
UserLog
from
'@/components/beranda/UserLog.vue'
...
@@ -35,6 +34,7 @@ const activeTab = computed({
...
@@ -35,6 +34,7 @@ const activeTab = computed({
const
tabs
=
[
const
tabs
=
[
{
title
:
'Profil'
,
icon
:
'ri-user-line'
,
tab
:
'profile'
},
{
title
:
'Profil'
,
icon
:
'ri-user-line'
,
tab
:
'profile'
},
{
title
:
'Keamanan'
,
icon
:
'ri-lock-line'
,
tab
:
'keamanan'
},
{
title
:
'Keamanan'
,
icon
:
'ri-lock-line'
,
tab
:
'keamanan'
},
// { title: 'Berita', icon: 'ri-news-line', tab: 'berita' },
// { title: 'Berita', icon: 'ri-news-line', tab: 'berita' },
{
title
:
'Riwayat'
,
icon
:
'ri-file-history-line'
,
tab
:
'riwayat'
},
{
title
:
'Riwayat'
,
icon
:
'ri-file-history-line'
,
tab
:
'riwayat'
},
{
title
:
'Jadwal'
,
icon
:
'ri-calendar-line'
,
tab
:
'jadwal'
},
{
title
:
'Jadwal'
,
icon
:
'ri-calendar-line'
,
tab
:
'jadwal'
},
...
@@ -46,14 +46,23 @@ const tabs = [
...
@@ -46,14 +46,23 @@ const tabs = [
]
]
// Filter tab berdasarkan civitas
// Filter tab berdasarkan civitas
const
filteredTabs
=
computed
(()
=>
const
tabsByCivitas
:
Record
<
string
,
string
[]
>
=
{
tabs
.
filter
(
tab
=>
{
staf
:
[
'profile'
,
'keamanan'
,
'log'
,
'peta'
,
'lib'
],
// Jangan sembunyikan "riwayat", hanya "log-absen" untuk mahasiswa
dosen
:
[
'profile'
,
'keamanan'
,
'jadwal'
,
'log'
,
'peta'
,
'lib'
],
if
(
tab
.
tab
===
'log'
&&
keycloakStore
.
civitas
===
'mahasiswa'
)
return
false
;
mahasiswa
:
[
'profile'
,
'keamanan'
,
'riwayat'
,
'jadwal'
,
'peta'
,
'lib'
],
return
true
;
}
})
);
// Compute allowed tabs for the current user
const
allowedTabs
=
computed
(()
=>
{
const
civitas
=
keycloakStore
.
civitas
return
tabsByCivitas
[
civitas
]
??
[]
})
// Filter tabs based on the allowlist
const
filteredTabs
=
computed
(()
=>
tabs
.
filter
(
tab
=>
allowedTabs
.
value
.
includes
(
tab
.
tab
)),
)
// Fungsi untuk navigasi tab
// Fungsi untuk navigasi tab
const
navigateTab
=
(
tab
:
string
)
=>
{
const
navigateTab
=
(
tab
:
string
)
=>
{
...
@@ -103,9 +112,12 @@ const navigateTab = (tab: string) => {
...
@@ -103,9 +112,12 @@ const navigateTab = (tab: string) => {
</VWindowItem>
</VWindowItem>
<!-- Berita -->
<!-- Berita -->
<!--
<VWindowItem
value=
"berita"
>
<!--
<VWindowItem
value=
"berita"
>
<UserBerita
/>
<UserBerita
/>
</VWindowItem>
-->
</VWindowItem>
-->
<!--
<div>
-->
<!-- Riwayat -->
<!-- Riwayat -->
<VWindowItem
value=
"riwayat"
>
<VWindowItem
value=
"riwayat"
>
...
...
pages/login-v5.vue
0 → 100644
View file @
7067e9e
<
script
setup
lang=
"ts"
>
import
{
useKeycloakStore
}
from
'@/@core/stores/keycloakStore'
import
AuthProvider
from
'@/views/pages/authentication/AuthProvider.vue'
import
authLoginBg
from
'/assets/images/naput/bg-blue-yellow.png'
import
authLoginLogo
from
'/assets/images/naput/illust-login.png'
const
authThemeImg
=
authLoginLogo
const
authThemeBg
=
authLoginBg
const
keycloakStore
=
useKeycloakStore
()
definePageMeta
({
layout
:
'blank'
,
unauthenticatedOnly
:
true
,
})
</
script
>
<
template
>
<body
:style=
"
{ backgroundImage: `url(${authThemeBg})` }"
class="body-2"
>
<section
class=
"global-main-login-component"
>
<div
class=
"card-main-login-component4"
>
<div
class=
"logo-login-component4"
>
<VImg
:src=
"authThemeImg"
max-width=
"650"
max-height=
"300"
class=
"image-10 lazyload"
/>
</div>
<div
class=
"form-login-component"
>
<VCol
cols=
"12"
>
<img
src=
"/assets/images/naput/logo-ui-hitam.png"
width=
"100"
height=
"auto"
class=
"mb-0"
>
</VCol>
<VCol
cols=
"12"
>
<AuthProvider
/>
</VCol>
</div>
</div>
</section>
</body>
<!--
</VRow>
-->
</
template
>
<
style
lang=
"scss"
>
@use
"@core/scss/template/pages/page-auth"
;
</
style
>
pages/login.vue
View file @
7067e9e
<
script
setup
lang=
"ts"
>
<
script
setup
lang=
"ts"
>
import
{
useKeycloakStore
}
from
'@/@core/stores/keycloakStore'
import
ImgDark
from
'@images/dstipro/auth-bg-dark.png'
import
ImgLight
from
'@images/dstipro/auth-bg-light.png'
import
authLoginBg
from
'/assets/images/naput/bg-blue-yellow.png'
import
authV2LoginLogoLight
from
'@images/dstipro/ui-logo-light.png'
import
authLoginLogo
from
'/assets/images/naput/illust-login.png'
import
authV2LoginBgDark
from
'@images/naput/header-bg-dark.png'
import
authV2LoginBgLight
from
'@images/naput/header-bg.png'
import
AuthProvider
from
'@/views/pages/authentication/AuthProvider.vue'
import
AuthProvider
from
'@/views/pages/authentication/AuthProvider.vue'
const
authThemeImg
=
authLoginLogo
const
authThemeImg
=
useGenerateImageVariant
(
authV2LoginLogoLight
,
authV2LoginLogoLight
,
)
const
authThemeBg
=
authLoginBg
const
authThemeColor
=
useGenerateColorVariant
(
'#FFFFFF'
,
'#303030'
,
)
const
keycloakStore
=
useKeycloakStore
()
const
authThemeBgElement
=
useGenerateImageVariant
(
ImgLight
,
ImgDark
,
)
const
authThemeBg
=
useGenerateImageVariant
(
authV2LoginBgLight
,
authV2LoginBgDark
,
)
definePageMeta
({
definePageMeta
({
layout
:
'blank'
,
layout
:
'blank'
,
...
@@ -19,40 +35,63 @@ definePageMeta({
...
@@ -19,40 +35,63 @@ definePageMeta({
</
script
>
</
script
>
<
template
>
<
template
>
<
body
<
VRow
:style=
"
{ backgroundImage: `url(${authThemeBg})` }"
no-gutters
class="
body-2
"
class=
"
auth-wrapper d-flex align-center
"
>
>
<section
class=
"global-main-login-component"
>
<body
<div
class=
"card-main-login-component4"
>
:style=
"
{ backgroundImage: `url(${authThemeBg})` }"
<div
class=
"logo-login-component4"
>
class="body-4"
<VImg
>
:src=
"authThemeImg"
<div
class=
"main-component-margin"
>
max-width=
"650"
<section
class=
"main-component-login"
>
max-height=
"300"
<div
class=
"image-10 lazyload"
:style=
"
{
/>
backgroundImage: `linear-gradient(rgba(${authThemeColorRGB}, 0.5), rgba(${authThemeColorRGB}, 0.5)), url(${authThemeBgElement})`,
</div>
backgroundSize: 'cover',
<div
class=
"form-login-component"
>
backgroundPosition: 'center',
<VCol
cols=
"12"
>
backgroundRepeat: 'no-repeat',
<img
backgroundBlendMode: 'normal, normal',
src=
"/assets/images/naput/logo-ui-hitam.png"
width: '100%',
width=
"100"
opacity: 1,
height=
"auto"
}"
class=
"mb-0"
class="form-component-login"
>
>
</VCol>
<div
class=
"combine-contact4_content"
>
<h2
class=
"heading-8"
>
<VCol
cols=
"12"
>
SSO
<AuthProvider
/>
</h2>
</VCol>
<h2
class=
"text-h5 heading-7"
>
</div>
Single Sign On
</h2>
<AuthProvider
/>
</div>
</div>
<div
class=
"logo-component-login"
:style=
"
{
backgroundImage: `url(${ImgDark})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
width: '100%',
}"
>
<div
class=
"logo-image-component-login"
>
<VImg
:src=
"authThemeImg"
max-height=
"100"
class=
"image-10 lazyload"
/>
</div>
</div>
</section>
</div>
</div>
</section>
</body>
</body>
</VRow>
<!--
</VRow>
-->
</
template
>
</
template
>
<
style
lang=
"scss"
>
<
style
lang=
"scss"
>
@use
"@core/scss/template/pages/page-auth"
;
@use
"@core/scss/template/pages/page-auth"
;
</
style
>
</
style
>
\ No newline at end of file
views/dstipro/beranda/riwayat/index.vue
View file @
7067e9e
<
script
lang=
"ts"
setup
>
<
script
lang=
"ts"
setup
>
import
{
useKeycloakStore
}
from
"@core/stores/keycloakStore"
;
import
{
useKeycloakStore
}
from
'@core/stores/keycloakStore'
import
{
computed
,
onMounted
,
ref
}
from
'vue'
const
keycloakStore
=
useKeycloakStore
();
const
keycloakStore
=
useKeycloakStore
()
// Gunakan computed agar selalu reaktif
const
isAuthenticated
=
computed
(()
=>
keycloakStore
.
authenticated
)
const
isAuthenticated
=
computed
(()
=>
keycloakStore
.
authenticated
);
// const isAuthenticated = keycloakStore.authenticated;
// State management
const
items
=
ref
<
any
[]
>
([])
const
items
=
ref
<
any
[]
>
([]);
const
expandedRows
=
ref
<
any
[]
>
([])
const
expandedRows
=
ref
<
any
[]
>
([]);
const
loading
=
ref
(
false
)
const
loading
=
ref
(
false
);
const
error
=
ref
(
''
)
const
error
=
ref
(
''
);
const
searchQuery
=
ref
(
''
)
//
Project Riwayat Header
//
Headers
const
riwayatHeaders
=
[
const
riwayatHeaders
=
[
{
title
:
""
,
key
:
"data-table-expand"
,
sortable
:
false
},
{
title
:
''
,
key
:
'data-table-expand'
,
sortable
:
false
},
{
title
:
'Tahun'
,
key
:
'THN'
,
sortable
:
false
},
{
title
:
'Tahun'
,
key
:
'THN'
,
sortable
:
false
},
{
title
:
'Term'
,
key
:
'TERM'
,
sortable
:
false
},
{
title
:
'Term'
,
key
:
'TERM'
,
sortable
:
false
},
{
title
:
'Semester'
,
key
:
'SEMESTER'
,
sortable
:
true
},
{
title
:
'Semester'
,
key
:
'SEMESTER'
,
sortable
:
true
},
...
@@ -24,169 +23,172 @@ const riwayatHeaders = [
...
@@ -24,169 +23,172 @@ const riwayatHeaders = [
]
]
const
expandedHeaders
=
[
const
expandedHeaders
=
[
{
title
:
"Kode MK"
,
key
:
"KD_MK"
,
sortable
:
true
},
{
title
:
'Kode MK'
,
key
:
'KD_MK'
,
sortable
:
true
},
{
title
:
"Nama MK"
,
key
:
"NM_MK"
,
sortable
:
true
},
{
title
:
'Nama MK'
,
key
:
'NM_MK'
,
sortable
:
true
},
{
title
:
"Kelas"
,
key
:
"NM_KLS_MK"
,
sortable
:
false
},
{
title
:
'Kelas'
,
key
:
'NM_KLS_MK'
,
sortable
:
false
},
{
title
:
"Status"
,
key
:
"STATUS"
,
sortable
:
false
},
{
title
:
'Status'
,
key
:
'STATUS'
,
sortable
:
false
},
{
title
:
"Nilai"
,
key
:
"NILAI_HURUF"
,
sortable
:
true
},
{
title
:
'Nilai'
,
key
:
'NILAI_HURUF'
,
sortable
:
true
},
{
title
:
"Pengajar"
,
key
:
"PENGAJAR"
,
sortable
:
false
},
{
title
:
'Pengajar'
,
key
:
'PENGAJAR'
,
sortable
:
false
},
]
;
]
async
function
getData
()
{
function
resolveStatusColor
(
status
:
string
)
{
loading
.
value
=
true
;
switch
(
status
)
{
error
.
value
=
''
;
case
'Aktif'
:
return
'primary'
case
'Lulus'
:
return
'success'
default
:
return
'default'
}
}
// Reset items sebelum fetch data baru
async
function
getData
()
{
items
.
value
=
[];
loading
.
value
=
true
error
.
value
=
''
items
.
value
=
[]
try
{
try
{
const
apiEndpoint
=
`https://api.ui.ac.id/my/ac`
;
const
res
=
await
fetch
(
'https://api.ui.ac.id/my/ac'
,
{
const
response
=
await
fetch
(
apiEndpoint
,
{
headers
:
{
headers
:
{
Authorization
:
`Bearer
${
keycloakStore
.
accessToken
}
`
,
Authorization
:
`Bearer
${
keycloakStore
.
accessToken
}
`
,
},
},
});
})
if
(
!
response
.
ok
)
throw
new
Error
(
'Gagal mengambil data'
);
if
(
!
res
.
ok
)
const
dataku
=
await
response
.
json
();
throw
new
Error
(
'Gagal mengambil data'
)
const
raw
=
await
res
.
json
()
items
.
value
=
dataku
.
map
((
item
:
any
)
=>
({
...
item
,
items
.
value
=
raw
.
map
((
item
:
any
,
idx
:
number
)
=>
{
expanded
:
Object
.
values
(
item
.
IRS
||
{}).
map
((
mk
:
any
)
=>
({
const
expandedFiltered
=
Object
.
values
(
item
.
IRS
||
{})
KD_MK
:
mk
.
KD_MK
,
.
filter
((
mk
:
any
)
=>
mk
.
STATUS
!==
'Kosong'
)
// Remove STATUS: Kosong
NM_MK
:
mk
.
NM_MK
,
.
map
((
mk
:
any
)
=>
({
NM_KLS_MK
:
mk
.
NM_KLS_MK
,
KD_MK
:
mk
.
KD_MK
,
STATUS
:
mk
.
STATUS
,
NM_MK
:
mk
.
NM_MK
,
NILAI_HURUF
:
mk
.
NILAI_HURUF
,
NM_KLS_MK
:
mk
.
NM_KLS_MK
,
PENGAJAR
:
mk
.
PENGAJAR
?
Object
.
values
(
mk
.
PENGAJAR
).
join
(
", "
)
:
"-"
,
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 : [];
return
{
// items.value = dataku;
id
:
`
${
item
.
THN
}
-
${
item
.
SEMESTER
}
-
${
item
.
TERM
}
-
${
idx
}
`
,
...
item
,
}
catch
(
err
:
any
)
{
expanded
:
expandedFiltered
,
error
.
value
=
err
.
message
||
'Terjadi kesalahan saat mengambil data'
;
}
}
finally
{
}).
filter
(
item
=>
item
.
expanded
.
length
>
0
)
// Optionally remove parent rows with no expanded items
loading
.
value
=
false
;
}
catch
(
err
:
any
)
{
error
.
value
=
err
.
message
||
'Terjadi kesalahan saat mengambil data'
}
finally
{
loading
.
value
=
false
}
}
}
}
// Fetch data from API
onMounted
(()
=>
{
onMounted
(()
=>
{
getData
()
;
getData
()
})
;
})
// Search query state
const
searchQuery
=
ref
(
''
);
// Filter aplikasi berdasarkan pencarian
const
filteredRiwayat
=
computed
(()
=>
{
const
filteredRiwayat
=
computed
(()
=>
{
if
(
!
searchQuery
.
value
)
return
items
.
value
;
if
(
!
searchQuery
.
value
)
const
query
=
searchQuery
.
value
.
toLowerCase
();
return
items
.
value
const
query
=
searchQuery
.
value
.
toLowerCase
()
return
items
.
value
return
items
.
value
.
map
((
item
)
=>
{
.
map
(
item
=>
{
// Filter mata kuliah (MK) berdasarkan NM_MK
const
filteredMK
=
item
.
expanded
.
filter
((
mk
:
any
)
=>
const
filteredMK
=
item
.
expanded
.
filter
((
mk
:
any
)
=>
mk
.
NM_MK
.
toLowerCase
().
includes
(
query
)
mk
.
NM_MK
.
toLowerCase
().
includes
(
query
)
,
)
;
)
// Jika ada MK yang cocok, tetap tampilkan item tetapi hanya dengan MK yang cocok
const
matchParent
=
[
item
.
THN
,
item
.
SEMESTER
,
item
.
STATUS
].
some
(
field
=>
if
(
filteredMK
.
length
>
0
)
{
String
(
field
).
toLowerCase
().
includes
(
query
),
return
{
...
item
,
expanded
:
filteredMK
};
)
}
// Jika tidak ada MK yang cocok, cek apakah tahun, semester, atau status cocok
if
(
filteredMK
.
length
>
0
||
matchParent
)
{
if
(
return
{
[
item
.
THN
,
item
.
SEMESTER
,
item
.
STATUS
].
some
((
field
)
=>
...
structuredClone
(
item
),
String
(
field
).
toLowerCase
().
includes
(
query
)
expanded
:
filteredMK
.
length
>
0
?
filteredMK
:
item
.
expanded
,
)
}
)
{
return
item
;
}
}
return
null
;
// Tidak cocok, hapus dari hasil pencarian
return
null
})
})
.
filter
(
Boolean
);
// Hapus item yang null
.
filter
(
Boolean
)
})
// 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
>
</
script
>
<
template
>
<
template
>
<VCard
title=
"Riwayat Mata Kuliah"
class=
"riwayatList"
>
<VCard
title=
"Riwayat Mata Kuliah"
<!-- Search Input -->
class=
"riwayatList"
>
<!-- Search -->
<div
class=
"search-container mb-4 pl-2 pr-2"
>
<div
class=
"search-container mb-4 pl-2 pr-2"
>
<VTextField
v-model=
"searchQuery"
label=
"Search"
placeholder=
"Search ..."
append-inner-icon=
"ri-search-line"
<VTextField
clearable
single-line
hide-details
dense
outlined
/>
v-model=
"searchQuery"
label=
"Cari Mata Kuliah atau Semester"
placeholder=
"Contoh: Pancasila, 2023"
append-inner-icon=
"ri-search-line"
clearable
single-line
hide-details
dense
outlined
/>
</div>
</div>
<!-- SECTION Datatable -->
<!-- Data Table -->
<VDataTable
:headers=
"riwayatHeaders"
:items=
"filteredRiwayat"
hide-default-footer
fixed-header
<VDataTable
item-value=
"SEMESTER"
:sort-by=
"['SEMESTER']"
:sort-asc=
"[true]"
v-model:expanded=
"expandedRows"
show-expand
>
v-model:expanded=
"expandedRows"
:headers=
"riwayatHeaders"
<template
v-slot:expanded-row=
"
{ item }">
:items=
"filteredRiwayat"
item-value=
"id"
hide-default-footer
fixed-header
show-expand
:sort-by=
"['SEMESTER']"
:sort-asc=
"[true]"
>
<template
#
expanded-row=
"
{ item }">
<tr>
<tr>
<td
colspan=
"6"
>
<td
colspan=
"100%"
>
<VDataTable
density=
"compact"
:headers=
"expandedHeaders"
:items=
"item.expanded"
class=
"ml-4"
/>
<VDataTable
:headers=
"expandedHeaders"
:items=
"item.expanded"
density=
"compact"
class=
"ml-4"
hide-default-footer
/>
</td>
</td>
</tr>
</tr>
</
template
>
</
template
>
<!-- Tahun Ajar -->
<
template
#
item
.
THN=
"{ item }"
>
<
template
#
item
.
THN=
"{ item }"
>
<div
class=
"d-flex align-center gap-x-3"
>
<div>
<div>
<h6
class=
"text-h6"
>
<h6
class=
"text-h6 text-no-wrap"
>
{{
`${item.THN
}
/${Number(item.THN) + 1
}
`
}}
{{
Number
(
item
.
THN
)
+
'/'
+
(
Number
(
item
.
THN
)
+
1
)
}}
<
/h6
>
</h6>
</div>
<
/div
>
<
/div
>
<
/template
>
<
/template
>
<!-- Status -->
<
template
#
item
.
STATUS
=
"
{
item
}
">
<
template
#
item
.
STATUS
=
"
{
item
}
">
<VChip
:color=
"resolveStatusColor(item.STATUS)"
:class=
"`text-$
{resolveStatusColor(item.STATUS)}`" size="small"
<VChip
class="font-weight-medium">
:color="
resolveStatusColor
(
item
.
STATUS
)
"
class="
font
-
weight
-
medium
"
size="
small
"
>
{{
item
.
STATUS
}}
{{
item
.
STATUS
}}
<
/VChip
>
<
/VChip
>
<
/template
>
<
/template
>
<!-- TODO Refactor this after vuetify provides proper solution for removing default footer -->
<
template
#
bottom
/>
<
template
#
bottom
/>
<
/VDataTable
>
<
/VDataTable
>
<!-- !SECTION -->
<
/VCard
>
<
/VCard
>
<
/template
>
<
/template
>
<
style
lang=
"scss"
>
<
style
scoped
lang
=
"scss"
>
.
riwayatList
{
.
riwayatList
{
.v-table
{
.
v
-
table__wrapper
{
&--density-default
{
table
tbody
tr
td
{
.v-table__wrapper
{
block
-
size
:
56
px
;
table
{
tbody
{
tr
{
td
{
block-size
:
56px
;
}
}
}
}
}
}
}
}
}
}
}
...
...
views/pages/authentication/AuthProvider.vue
View file @
7067e9e
...
@@ -11,6 +11,12 @@ function login() {
...
@@ -11,6 +11,12 @@ function login() {
})
})
}
}
function
loginGoogle
()
{
keycloakInstance
.
login
({
redirectUri
:
`
${
window
.
location
.
origin
}
/profile`
,
idpHint
:
'google'
,
})
}
// Cek apakah user sudah login saat komponen dimuat
// Cek apakah user sudah login saat komponen dimuat
onMounted
(()
=>
{
onMounted
(()
=>
{
if
(
keycloakInstance
.
authenticated
)
if
(
keycloakInstance
.
authenticated
)
...
@@ -20,12 +26,59 @@ onMounted(() => {
...
@@ -20,12 +26,59 @@ onMounted(() => {
<
template
>
<
template
>
<VBtn
<VBtn
class=
"
font-weight-bold
"
class=
"
sso-btn font-weight-medium
"
color=
"#
FFDC01
"
color=
"#
3d3d3d
"
block
block
type=
"submit"
type=
"submit"
rounded=
"xl"
@
click=
"login"
@
click=
"login"
>
>
Login SSO
Sign in SSO
</VBtn>
<VBtn
class=
"google-btn font-weight-medium"
color=
"#3d3d3d"
block
type=
"submit"
rounded=
"xl"
variant=
"outlined"
style=
"margin-block-start: 5px;"
@
click=
"loginGoogle"
>
<template
#
prepend
>
<VImg
src=
"https://www.gstatic.com/firebasejs/ui/2.0.0/images/auth/google.svg"
alt=
"Google"
max-width=
"20"
class=
"mr-2"
/>
</
template
>
Sign in with Google
</VBtn>
</VBtn>
</template>
</template>
<
style
scoped
>
/* Use :deep() correctly to apply styles to the Vuetify button */
.v-btn.sso-btn
{
background-color
:
#535666
!important
;
/* semi-transparent dark */
transition
:
background-color
0.3s
ease
,
transform
0.2s
ease
;
}
.v-btn.sso-btn
:hover
{
background-color
:
#3d3e50
!important
;
transform
:
translateY
(
-2px
);
}
/* Google button */
.v-btn.google-btn
{
border
:
2px
solid
#a3a3a3
;
background-color
:
rgba
(
255
,
255
,
255
,
80%
)
!important
;
transition
:
background-color
0.3s
ease
,
transform
0.2s
ease
;
}
.v-btn.google-btn
:hover
{
background-color
:
rgba
(
61
,
61
,
61
,
100%
);
transform
:
translateY
(
-2px
);
}
</
style
>
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