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 1710b41f
authored
Apr 22, 2025
by
Nabiilah Putri Safa
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
edit log
1 parent
17be0b75
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
181 additions
and
129 deletions
components/beranda/UserLog.vue
views/dstipro/beranda/keamanan/index.vue
components/beranda/UserLog.vue
View file @
1710b41
<
script
setup
lang=
"ts"
>
import
{
onMounted
,
ref
,
watchEffect
}
from
'vue'
import
{
useKeycloakStore
}
from
'@/@core/stores/keycloakStore'
// Store Keycloak
const
keycloakStore
=
useKeycloakStore
()
// Data dan state
const
items
=
ref
<
any
[]
>
([])
const
loading
=
ref
(
false
)
const
searchQuery
=
ref
(
''
)
// Fungsi ambil tanggal dari shift_start atau shift_end
const
getTanggal
=
(
waktu
:
string
)
=>
(
waktu
?
waktu
.
split
(
' '
)[
0
]
:
'-'
)
// Fungsi ambil jam dari start_time atau end_time, kasih "-" kalau kosong
const
getJam
=
(
waktu
:
string
)
=>
{
if
(
!
waktu
)
return
'-'
return
waktu
.
length
>=
5
?
waktu
.
slice
(
0
,
5
)
:
waktu
}
// Fungsi ambil nama hari dari tanggal
const
getNamaHari
=
(
tanggal
:
string
)
=>
{
if
(
!
tanggal
||
tanggal
===
'-'
)
return
'-'
const
date
=
new
Date
(
tanggal
)
if
(
isNaN
(
date
.
getTime
()))
return
'-'
return
new
Intl
.
DateTimeFormat
(
'id-ID'
,
{
weekday
:
'long'
}).
format
(
date
)
}
const
getJadwalShift
=
(
start
:
string
,
end
:
string
)
=>
{
const
jamStart
=
start
&&
start
.
length
>=
5
?
start
.
slice
(
0
,
5
)
:
'-'
const
jamEnd
=
end
&&
end
.
length
>=
5
?
end
.
slice
(
0
,
5
)
:
'-'
return
`
${
jamStart
}
-
${
jamEnd
}
`
}
const
getStatus
=
(
start
:
string
|
undefined
,
end
:
string
|
undefined
)
=>
{
if
(
!
start
&&
!
end
)
return
'Tidak Ada'
if
(
!
start
||
!
end
)
return
'Belum Hitung'
return
'On Time'
<
script
lang=
"ts"
setup
>
import
{
useKeycloakStore
}
from
"@core/stores/keycloakStore"
;
const
keycloakStore
=
useKeycloakStore
();
const
isAuthenticated
=
computed
(()
=>
keycloakStore
.
authenticated
);
interface
ShiftData
{
shift_date
:
string
;
shift_start
:
string
;
shift_end
:
string
;
shift
:
string
;
start_time
?:
string
;
end_time
?:
string
;
}
// Header tabel
const
logHeaders
=
[
{
title
:
'TANGGAL'
,
key
:
'tanggal'
},
{
title
:
'NAMA HARI'
,
key
:
'namaHari'
},
{
title
:
'SHIFT'
,
key
:
'shift'
},
{
title
:
'JADWAL SHIFT'
,
key
:
'jadwalShift'
},
{
title
:
'MULAI AKTUAL'
,
key
:
'start_time'
},
{
title
:
'SELESAI AKTUAL'
,
key
:
'end_time'
},
{
title
:
'STATUS'
,
key
:
'status'
},
]
// Fungsi ambil data dari API
async
function
getData
()
{
loading
.
value
=
true
items
.
value
=
[]
const
shifts
=
ref
<
ShiftData
[]
>
([]);
const
loading
=
ref
(
false
);
const
error
=
ref
(
""
);
const
searchQuery
=
ref
(
""
);
const
headersShift
=
[
{
title
:
"Tanggal"
,
key
:
"shift_date"
,
sortable
:
true
},
{
title
:
"Nama Hari"
,
key
:
"day_name"
,
sortable
:
false
},
{
title
:
"Shift"
,
key
:
"shift"
,
sortable
:
false
},
{
title
:
"Jadwal Shift"
,
key
:
"shift_schedule"
,
sortable
:
false
},
{
title
:
"Mulai Aktual"
,
key
:
"start_time"
,
sortable
:
false
},
{
title
:
"Selesai Aktual"
,
key
:
"end_time"
,
sortable
:
false
},
{
title
:
"Status"
,
key
:
"status"
,
sortable
:
false
},
];
async
function
fetchShiftData
()
{
loading
.
value
=
true
;
error
.
value
=
""
;
try
{
const
apiEndpoint
=
'https://api.ui.ac.id/my/hr/attendance'
const
apiEndpoint
=
`https://api.ui.ac.id/my/hr/attendance`
;
const
response
=
await
fetch
(
apiEndpoint
,
{
headers
:
{
Authorization
:
`Bearer
${
keycloakStore
.
accessToken
}
`
,
},
})
})
;
if
(
!
response
.
ok
)
throw
new
Error
(
'Gagal mengambil data'
)
const
dataku
=
await
response
.
json
()
if
(
!
response
.
ok
)
{
throw
new
Error
(
"Gagal fetch data"
);
}
// Tambahkan properti tanggal, namaHari, mulai aktual, dan selesai aktual ke setiap item
items
.
value
=
dataku
.
map
((
item
:
any
)
=>
{
const
tanggal
=
getTanggal
(
item
.
shift_start
)
||
getTanggal
(
item
.
shift_end
)
const
data
=
await
response
.
json
();
return
{
shifts
.
value
=
data
.
map
((
item
:
any
)
=>
({
...
item
,
tanggal
,
namaHari
:
getNamaHari
(
tanggal
),
jadwalShift
:
getJadwalShift
(
item
.
shift_start
,
item
.
shift_end
),
start_time
:
getJam
(
item
.
start_time
),
end_time
:
getJam
(
item
.
end_time
),
status
:
getStatus
(
item
.
start_time
,
item
.
end_time
),
}
})
}
catch
(
err
)
{
console
.
error
(
'Gagal mengambil data:'
,
err
)
}
finally
{
loading
.
value
=
false
shift_start
:
`
${
item
.
shift_date
}
${
item
.
shift_start
||
"00:00"
}
`
,
shift_end
:
`
${
item
.
shift_date
}
${
item
.
shift_end
||
"00:00"
}
`
,
}))
.
sort
((
a
,
b
)
=>
new
Date
(
b
.
shift_date
).
getTime
()
-
new
Date
(
a
.
shift_date
).
getTime
());
}
catch
(
err
:
any
)
{
error
.
value
=
err
.
message
||
"Terjadi kesalahan saat mengambil data"
;
}
finally
{
loading
.
value
=
false
;
}
}
// Fetch data saat mounted
function
getDayName
(
date
:
string
)
{
const
days
=
[
"Minggu"
,
"Senin"
,
"Selasa"
,
"Rabu"
,
"Kamis"
,
"Jumat"
,
"Sabtu"
];
const
dayIndex
=
new
Date
(
date
).
getDay
();
return
days
[
dayIndex
];
}
const
resolveDayColor
=
(
day
:
string
)
=>
{
if
(
day
===
"Sabtu"
||
day
===
"Minggu"
)
return
"error"
;
};
function
getHour
(
time
:
string
|
undefined
)
{
if
(
!
time
)
return
"-"
;
const
parts
=
time
.
split
(
" "
);
return
parts
.
length
===
2
?
parts
[
1
]
:
time
;
}
function
getStatus
(
start
:
string
|
undefined
,
end
:
string
|
undefined
)
{
if
(
!
start
&&
!
end
)
return
"Tidak Ada"
;
if
(
!
start
||
!
end
)
return
"Belum Hitung"
;
return
"On Time"
;
}
const
resolveStatusColor
=
(
status
:
string
)
=>
{
if
(
status
===
"On Time"
)
return
"success"
;
if
(
status
===
"Belum Hitung"
)
return
"primary"
;
if
(
status
===
"Tidak Ada"
)
return
"error"
;
};
const
filteredShifts
=
computed
(()
=>
{
if
(
!
searchQuery
.
value
)
return
shifts
.
value
;
return
shifts
.
value
.
filter
((
shift
)
=>
{
const
dayName
=
getDayName
(
shift
.
shift_date
);
const
status
=
getStatus
(
shift
.
start_time
,
shift
.
end_time
);
return
(
shift
.
shift_date
.
toLowerCase
().
includes
(
searchQuery
.
value
.
toLowerCase
())
||
dayName
.
toLowerCase
().
includes
(
searchQuery
.
value
.
toLowerCase
())
||
shift
.
shift
.
toLowerCase
().
includes
(
searchQuery
.
value
.
toLowerCase
())
||
shift
.
start_time
?.
toLowerCase
().
includes
(
searchQuery
.
value
.
toLowerCase
())
||
shift
.
end_time
?.
toLowerCase
().
includes
(
searchQuery
.
value
.
toLowerCase
())
||
status
.
toLowerCase
().
includes
(
searchQuery
.
value
.
toLowerCase
())
);
});
});
onMounted
(()
=>
{
keycloakStore
.
refresh
()
getData
()
})
// Auto refresh data saat token berubah
watchEffect
(
async
()
=>
{
if
(
!
keycloakStore
.
accessToken
)
return
await
getData
()
})
const
filteredItems
=
computed
(()
=>
{
if
(
!
searchQuery
.
value
)
return
items
.
value
const
query
=
searchQuery
.
value
.
toLowerCase
()
return
items
.
value
.
filter
(
item
=>
logHeaders
.
some
(
header
=>
item
[
header
.
key
]
&&
String
(
item
[
header
.
key
]).
toLowerCase
().
includes
(
query
),
),
)
})
fetchShiftData
();
});
</
script
>
<
template
>
<VCard
title=
"Log Absen"
class=
"recentnamaHariCard"
<AppCardActions
:title=
"`Log Absen`"
class=
"jadwalShift"
action-collapsed
action-remove
>
<!-- Search Input -->
<div
class=
"search-container mb-4 pl-2 pr-2"
>
<VTextField
v-model=
"searchQuery"
...
...
@@ -145,36 +132,101 @@ const filteredItems = computed(() => {
outlined
/>
</div>
<VDataTable
:headers=
"logHeaders"
:items=
"filteredItems"
hide-default-footer
fixed-header
item-value=
"tanggal"
:sort-by=
"['tanggal']"
:sort-asc=
"[true]"
:headers=
"headersShift"
:items=
"filteredShifts"
:loading=
"loading"
loading-text=
"Memuat data..."
>
<template
#
item
.
namaHari=
"
{ item }">
<!-- Tanggal -->
<template
#
item
.
shift_date=
"
{ item }">
{{
item
.
shift_date
}}
</
template
>
<!-- Nama Hari -->
<
template
#
item
.
day_name=
"{ item }"
>
<VChip
:color=
"
item.namaHari === 'Sabtu' || item.namaHari === 'Minggu' ? 'error' : 'default'
"
:class=
"`text-$
{
item.namaHari === 'Sabtu' || item.namaHari === 'Minggu' ? 'error' : 'default'
}`"
:color=
"
resolveDayColor(getDayName(item.shift_date))
"
:class=
"`text-$
{
resolveDayColor(getDayName(item.shift_date))
}`"
size="small"
class="font-weight-medium"
>
{{
item
.
namaHari
}}
{{
getDayName
(
item
.
shift_date
)
}}
</VChip>
</
template
>
<!-- Jadwal Shift -->
<
template
#
item
.
shift_schedule=
"{ item }"
>
{{
`${getHour(item.shift_start)
}
- ${getHour(item.shift_end)
}
`
}}
<
/template
>
<!--
Mulai
Aktual
-->
<
template
#
item
.
start_time
=
"
{
item
}"
>
{{
getHour
(
item
.
start_time
)
}}
<
/template
>
<!--
Selesai
Aktual
-->
<
template
#
item
.
end_time
=
"
{
item
}"
>
{{
getHour
(
item
.
end_time
)
}}
<
/template
>
<!--
Status
-->
<
template
#
item
.
status
=
"
{
item
}
">
<VChip
:color=
"
item.status === 'Tidak Ada' ? 'error' : 'primary'
"
:class=
"`text-$
{
item.status === 'Tidak Ada' ? 'error' : 'primary'
}`"
:color="
resolveStatusColor
(
getStatus
(
item
.
start_time
,
item
.
end_time
))
"
:class="`text-${
resolveStatusColor(getStatus(item.start_time, item.end_time))
}
`"
size="
small
"
class="
font
-
weight
-
medium
"
>
{{
item
.
status
}}
{{
getStatus
(
item
.
start_time
,
item
.
end_time
)
}}
<
/VChip
>
<
/template
>
<
/VDataTable
>
</
VCard
>
<
/
AppCardActions
>
<
/template
>
<
style
lang
=
"scss"
>
.
jadwalShift
{
.
v
-
table
{
&--
density
-
default
{
.
v
-
table__wrapper
{
table
{
thead
{
th
{
background
-
color
:
#
f5f5f5
;
border
-
block
-
end
:
2
px
solid
#
ddd
;
color
:
#
2
c2c2c
;
font
-
weight
:
600
;
padding
-
block
:
12
px
;
padding
-
inline
:
1.5
em
;
}
}
tbody
{
tr
{
td
{
border
-
block
-
end
:
1
px
solid
#
eee
;
min
-
block
-
size
:
auto
;
padding
-
block
:
8
px
;
padding
-
inline
:
1
em
;
vertical
-
align
:
top
;
&
.
vti
-
table__td
--
Jadwal
{
color
:
#
4
a4a4a
;
font
-
weight
:
500
;
}
}
}
}
}
}
}
}
}
.
search
-
container
{
display
:
flex
;
justify
-
content
:
flex
-
end
;
}
<
/style
>
views/dstipro/beranda/keamanan/index.vue
View file @
1710b41
This diff is collapsed.
Click to expand it.
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