Commit a2f1917e by Samuel Taniel Mulyadi

Merge branch 'sam' into 'staging'

Sam

See merge request !9
2 parents 67de097e f7b141d4
<script setup lang="tsx"> <script setup lang="tsx">
import { useStorage } from '@vueuse/core'
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
import { useTheme } from 'vuetify'
import { staticPrimaryColor, staticPrimaryDarkenColor } from '@/plugins/vuetify/theme' import { staticPrimaryColor, staticPrimaryDarkenColor } from '@/plugins/vuetify/theme'
import { Direction, Layout, Skins, Theme } from '@core/enums' import { Direction, Layout, Skins, Theme } from '@core/enums'
import { useConfigStore } from '@core/stores/config' import { useConfigStore } from '@core/stores/config'
...@@ -9,6 +6,9 @@ import horizontalLight from '@images/customizer-icons/horizontal-light.svg' ...@@ -9,6 +6,9 @@ import horizontalLight from '@images/customizer-icons/horizontal-light.svg'
import { AppContentLayoutNav, ContentWidth } from '@layouts/enums' import { AppContentLayoutNav, ContentWidth } from '@layouts/enums'
import { cookieRef, namespaceConfig } from '@layouts/stores/config' import { cookieRef, namespaceConfig } from '@layouts/stores/config'
import { themeConfig } from '@themeConfig' import { themeConfig } from '@themeConfig'
import { useStorage } from '@vueuse/core'
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
import { useTheme } from 'vuetify'
import borderSkin from '@images/customizer-icons/border-light.svg' import borderSkin from '@images/customizer-icons/border-light.svg'
import collapsed from '@images/customizer-icons/collapsed-light.svg' import collapsed from '@images/customizer-icons/collapsed-light.svg'
...@@ -488,7 +488,7 @@ const resetCustomizer = async () => { ...@@ -488,7 +488,7 @@ const resetCustomizer = async () => {
<!-- SECTION LAYOUT --> <!-- SECTION LAYOUT -->
<CustomizerSection title="Layout"> <CustomizerSection title="Layout">
<!-- 👉 Layouts --> <!-- 👉 Layouts -->
<div class="d-flex flex-column gap-3"> <!-- <div class="d-flex flex-column gap-3">
<h6 class="text-base font-weight-medium"> <h6 class="text-base font-weight-medium">
Layout Layout
</h6> </h6>
...@@ -503,7 +503,7 @@ const resetCustomizer = async () => { ...@@ -503,7 +503,7 @@ const resetCustomizer = async () => {
<span class="text-sm text-medium-emphasis">{{ item.label }}</span> <span class="text-sm text-medium-emphasis">{{ item.label }}</span>
</template> </template>
</CustomRadiosWithImage> </CustomRadiosWithImage>
</div> </div> -->
<!-- 👉 Content Width --> <!-- 👉 Content Width -->
<div class="d-flex flex-column gap-3"> <div class="d-flex flex-column gap-3">
...@@ -524,7 +524,7 @@ const resetCustomizer = async () => { ...@@ -524,7 +524,7 @@ const resetCustomizer = async () => {
</div> </div>
<!-- 👉 Direction --> <!-- 👉 Direction -->
<div class="d-flex flex-column gap-3"> <!-- <div class="d-flex flex-column gap-3">
<h6 class="text-base font-weight-medium"> <h6 class="text-base font-weight-medium">
Direction Direction
</h6> </h6>
...@@ -539,7 +539,7 @@ const resetCustomizer = async () => { ...@@ -539,7 +539,7 @@ const resetCustomizer = async () => {
<span class="text-sm text-medium-emphasis">{{ item?.label }}</span> <span class="text-sm text-medium-emphasis">{{ item?.label }}</span>
</template> </template>
</CustomRadiosWithImage> </CustomRadiosWithImage>
</div> </div> -->
</CustomizerSection> </CustomizerSection>
<!-- !SECTION --> <!-- !SECTION -->
</PerfectScrollbar> </PerfectScrollbar>
......
<script lang="ts" setup> <script lang="ts" setup>
import { HorizontalNav } from '@layouts/components' import type { HorizontalNavItems } from '@layouts/types';
import type { HorizontalNavItems } from '@layouts/types'
// ℹ️ Using import from `@layouts` causing build to hangup // ℹ️ Using import from `@layouts` causing build to hangup
// import { useLayouts } from '@layouts' // import { useLayouts } from '@layouts'
import { useLayoutConfigStore } from '@layouts/stores/config' import { useLayoutConfigStore } from '@layouts/stores/config';
defineProps<{ defineProps<{
navItems: HorizontalNavItems navItems: HorizontalNavItems
...@@ -29,11 +28,11 @@ const configStore = useLayoutConfigStore() ...@@ -29,11 +28,11 @@ const configStore = useLayoutConfigStore()
</div> </div>
</div> </div>
<!-- 👉 Navigation --> <!-- 👉 Navigation -->
<div class="layout-horizontal-nav"> <!-- <div class="layout-horizontal-nav">
<div class="horizontal-nav-content-container"> <div class="horizontal-nav-content-container">
<HorizontalNav :nav-items="navItems" /> <HorizontalNav :nav-items="navItems" />
</div> </div>
</div> </div> -->
</div> </div>
<main class="layout-page-content"> <main class="layout-page-content">
......
<script lang="ts" setup> <script lang="ts" setup>
import type { Component } from 'vue'
import { PerfectScrollbar } from 'vue3-perfect-scrollbar'
import { VNodeRenderer } from './VNodeRenderer'
import { layoutConfig } from '@layouts' import { layoutConfig } from '@layouts'
import { VerticalNavGroup, VerticalNavLink, VerticalNavSectionTitle } from '@layouts/components' import { VerticalNavGroup, VerticalNavLink, VerticalNavSectionTitle } from '@layouts/components'
import { useLayoutConfigStore } from '@layouts/stores/config' import { useLayoutConfigStore } from '@layouts/stores/config'
import { injectionKeyIsVerticalNavHovered } from '@layouts/symbols' import { injectionKeyIsVerticalNavHovered } from '@layouts/symbols'
import type { NavGroup, NavLink, NavSectionTitle, VerticalNavItems } from '@layouts/types' import type { NavGroup, NavLink, NavSectionTitle, VerticalNavItems } from '@layouts/types'
import type { Component } from 'vue'
interface Props { interface Props {
tag?: string | Component tag?: string | Component
...@@ -77,14 +75,16 @@ const hideTitleAndIcon = configStore.isVerticalNavMini(isHovered) ...@@ -77,14 +75,16 @@ const hideTitleAndIcon = configStore.isVerticalNavMini(isHovered)
to="/" to="/"
class="app-logo app-title-wrapper" class="app-logo app-title-wrapper"
> >
<VNodeRenderer :nodes="layoutConfig.app.logo" /> <img src="@/assets/images/naput/logo-UI.png" alt="Logo UI" width="35" height="auto"/>
<!-- <VNodeRenderer :nodes="layoutConfig.app.logo" /> -->
<Transition name="vertical-nav-app-title"> <Transition name="vertical-nav-app-title">
<h1 <h1
v-show="!hideTitleAndIcon" v-show="!hideTitleAndIcon"
class="app-logo-title leading-normal" class="app-logo-title leading-normal"
> >
{{ layoutConfig.app.title }} CIVITAS UI
</h1> </h1>
</Transition> </Transition>
</NuxtLink> </NuxtLink>
...@@ -123,7 +123,7 @@ const hideTitleAndIcon = configStore.isVerticalNavMini(isHovered) ...@@ -123,7 +123,7 @@ const hideTitleAndIcon = configStore.isVerticalNavMini(isHovered)
name="nav-items" name="nav-items"
:update-is-vertical-nav-scrolled="updateIsVerticalNavScrolled" :update-is-vertical-nav-scrolled="updateIsVerticalNavScrolled"
> >
<PerfectScrollbar <!-- <PerfectScrollbar
:key="configStore.isAppRTL" :key="configStore.isAppRTL"
tag="ul" tag="ul"
class="nav-items" class="nav-items"
...@@ -136,7 +136,7 @@ const hideTitleAndIcon = configStore.isVerticalNavMini(isHovered) ...@@ -136,7 +136,7 @@ const hideTitleAndIcon = configStore.isVerticalNavMini(isHovered)
:key="index" :key="index"
:item="item" :item="item"
/> />
</PerfectScrollbar> </PerfectScrollbar> -->
</slot> </slot>
<slot name="after-nav-items" /> <slot name="after-nav-items" />
......
...@@ -5,10 +5,23 @@ import { useKeycloakStore } from '@/@core/stores/keycloakStore' ...@@ -5,10 +5,23 @@ import { useKeycloakStore } from '@/@core/stores/keycloakStore'
const keycloakStore = useKeycloakStore() const keycloakStore = useKeycloakStore()
const schedule = ref<any[]>([]) const schedule = ref<any[]>([])
const loading = ref(false) const loading = ref(false)
const viewMode = ref<'vertical' | 'horizontal'>('vertical') // ← track the mode
const daysOfWeek = ['Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat'] const daysOfWeek = ['Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat']
const startHour = 7 const startHour = 7
const endHour = 21 const endHour = 21
const currentTime = ref(new Date())
// Function to check if the current time is within a schedule item range
function isCurrentScheduleItem(item: any) {
const now = currentTime.value
const startTime = parseTime(item.start)
const endTime = parseTime(item.end)
const currentMinutes = (now.getHours() - startHour) * 60 + now.getMinutes()
// Check if the current time is within the range (startTime < currentMinutes < endTime)
return currentMinutes >= startTime && currentMinutes <= endTime
}
async function getData() { async function getData() {
loading.value = true loading.value = true
...@@ -40,6 +53,8 @@ async function getData() { ...@@ -40,6 +53,8 @@ async function getData() {
lecturer: Object.values(course.PENGAJAR).join(', '), // Join multiple lecturers if exist lecturer: Object.values(course.PENGAJAR).join(', '), // Join multiple lecturers if exist
room: jadwal.NM_RUANG, room: jadwal.NM_RUANG,
building: jadwal.NM_GED, building: jadwal.NM_GED,
tgl_mulai: jadwal.TGL_MULAI,
tgl_selesai: jadwal.TGL_SELESAI,
})), })),
) )
} }
...@@ -53,6 +68,8 @@ async function getData() { ...@@ -53,6 +68,8 @@ async function getData() {
lecturer: item.NAMA_DOSEN, lecturer: item.NAMA_DOSEN,
room: item.NM_RUANG, room: item.NM_RUANG,
building: item.NM_GED, building: item.NM_GED,
tgl_mulai: item.TGL_MULAI,
tgl_selesai: item.TGL_SELESAI,
})) }))
} }
else if (keycloakStore.civitas === 'staf') { else if (keycloakStore.civitas === 'staf') {
...@@ -97,14 +114,60 @@ function calculateRowSpan(start: string, end: string) { ...@@ -97,14 +114,60 @@ function calculateRowSpan(start: string, end: string) {
return rowSpan return rowSpan
} }
// Group schedule by day
function getScheduleByDay(day: string) {
return schedule.value.filter(item => item.day === day)
}
// Get alternating color classes
function getColorClass(index: number) {
const colors = ['red', 'blue', 'green']
return `schedule-color-${colors[index % 3]}`
}
</script> </script>
<template> <template>
<VCard <VCard class="timetable-card">
title="Jadwal Kuliah" <template #title>
class="timetable-card" <div class="d-flex align-center">
> <!-- Left: Title -->
<VCard class="timetable"> <span class="me-auto">Jadwal Kuliah</span>
<!-- Right: Date Range Box -->
<div class="me-auto date-range-box">
<span v-if="schedule.length > 0">
{{ schedule[0].tgl_mulai }} / {{ schedule[0].tgl_selesai }}
</span>
</div>
<!-- Right: Icons -->
<div class="d-flex gap-2">
<VBtn
icon
variant="text"
:color="viewMode === 'vertical' ? 'primary' : 'default'"
@click="viewMode = 'vertical'"
>
<VIcon icon="ri-layout-vertical-line" />
</VBtn>
<VBtn
icon
variant="text"
:color="viewMode === 'horizontal' ? 'primary' : 'default'"
@click="viewMode = 'horizontal'"
>
<VIcon icon="ri-layout-horizontal-line" />
</VBtn>
</div>
</div>
</template>
<VCard
v-if="viewMode === 'vertical'"
class="timetable"
>
<!-- Header Row --> <!-- Header Row -->
<div class="grid-header"> <div class="grid-header">
<div class="time-label"> <div class="time-label">
...@@ -173,10 +236,86 @@ function calculateRowSpan(start: string, end: string) { ...@@ -173,10 +236,86 @@ function calculateRowSpan(start: string, end: string) {
</div> </div>
</div> </div>
</VCard> </VCard>
<VCard
v-else
class="timetable px-4 py-6"
>
<table class="w-100 text-left table-schedule fixed-table">
<thead>
<tr>
<th
v-for="day in daysOfWeek"
:key="day"
class="px-2 py-2"
>
{{ day }}
</th>
</tr>
</thead>
<tbody>
<tr
v-for="rowIndex in 10"
:key="`row-${rowIndex}`"
>
<td
v-for="(day, dayIndex) in daysOfWeek"
:key="day + rowIndex"
class="align-top px-2 py-3"
>
<div
v-if="getScheduleByDay(day)[rowIndex - 1]"
class="schedule-box"
:class="[getColorClass(rowIndex - 1)]"
>
<div class="course-header mb-1">
<span class="text-sm font-medium d-flex align-center gap-1">
<i class="ri-time-line" /> {{
getScheduleByDay(day)[rowIndex - 1].start
}} - {{
getScheduleByDay(day)[rowIndex - 1].end
}}
</span>
<span class="text-xs">{{ getScheduleByDay(day)[rowIndex - 1].room }}</span>
</div>
<div class="course-title font-semibold text-sm mb-1">
<span
v-if="getScheduleByDay(day)[rowIndex - 1].course.includes('-')"
style="text-decoration: underline;"
>
{{ getScheduleByDay(day)[rowIndex - 1].course.split(' - ')[0] }}
</span>
<span class="text-sm">
{{
getScheduleByDay(day)[rowIndex - 1].course.includes('-')
? getScheduleByDay(day)[rowIndex - 1].course.split(' - ')[1]
: getScheduleByDay(day)[rowIndex - 1].course
}}
</span>
</div>
<div class="text-xs">
{{ getScheduleByDay(day)[rowIndex - 1].building }}
</div>
</div>
</td>
</tr>
</tbody>
</table>
</VCard>
</VCard> </VCard>
</template> </template>
<style scoped> <style scoped>
.date-range-box {
display: inline-block;
border-radius: 5px;
background-color: rgba(var(--v-global-theme-primary), 0.1);
font-size: 14px;
font-weight: bold;
margin-inline-start: 10px;
padding-block: 5px;
padding-inline: 10px;
}
.timetable { .timetable {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
...@@ -219,6 +358,7 @@ function calculateRowSpan(start: string, end: string) { ...@@ -219,6 +358,7 @@ function calculateRowSpan(start: string, end: string) {
.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%);
text-decoration: underline;
} }
.grid-body { .grid-body {
...@@ -248,13 +388,14 @@ function calculateRowSpan(start: string, end: string) { ...@@ -248,13 +388,14 @@ function calculateRowSpan(start: string, end: string) {
.schedule-item { .schedule-item {
display: flex; display: flex;
overflow: hidden;
flex-direction: column; flex-direction: column;
align-items: flex-start; /* Keeps text left-aligned */ align-items: flex-start; /* Keeps text left-aligned */
justify-content: flex-start; /* Aligns content to the top */ justify-content: flex-start; /* Aligns content to the top */
padding: 4px; padding: 6px;
border-radius: 6px; border-radius: 6px;
margin: 2px; margin: 2px;
background-color: color-mix(in srgb, rgba(var(--v-global-theme-primary)) 70%, white 30%); background-color: color-mix(in srgb, rgba(var(--v-global-theme-primary)) 80%, white 20%);
color: white; color: white;
font-size: 12px; font-size: 12px;
outline: 1px solid rgba(var(--v-border-color), var(--v-border-opacity)); outline: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
...@@ -297,6 +438,46 @@ function calculateRowSpan(start: string, end: string) { ...@@ -297,6 +438,46 @@ function calculateRowSpan(start: string, end: string) {
text-align: start; /* Aligns room to the right */ text-align: start; /* Aligns room to the right */
} }
.fixed-table {
border-collapse: collapse;
inline-size: 100%;
table-layout: fixed;
}
.fixed-table thead th {
background-color: rgba(var(--v-global-theme-primary));
color: white;
font-weight: bold;
text-align: center;
}
.fixed-table th,
.fixed-table td {
padding: 0;
margin: 0;
}
.schedule-box {
padding: 6px;
border-radius: 8px;
color: white;
font-size: 0.85rem;
min-block-size: 90px;
}
/* Color themes for items */
.schedule-color-red {
background-color: #6aa1fa;
}
.schedule-color-blue {
background-color: #46c298;
}
.schedule-color-green {
background-color: #eda36b;
}
@media screen and (max-width: 1200px) { @media screen and (max-width: 1200px) {
.course-title { .course-title {
border-block-end: 0 solid; border-block-end: 0 solid;
......
<script setup lang="ts">
import { useKeycloakStore } from "@/@core/stores/keycloakStore";
import { ref } from "vue";
// Store Keycloak
const keycloakStore = useKeycloakStore();
// Data dan state
const items = ref<any[]>([]);
const loading = ref(false);
const searchQuery = ref("");
const logHeaders = [
{ title: "KOLEKSI", key: "koleksi" },
{ title: "TANGGAL PEMINJAMAN", key: "tglpinjam" },
{ title: "TANGGAL PENGEMBALIAN", key: "tglkembali" },
{ title: "DIPINJAM", key: "dipinjam" },
{ title: "PERPANJANGAN", key: "perpanjangan" },
{ title: "DENDA", key: "denda" },
{ title: "DENDA TOTAL", key: "dendatotoal" },
];
async function getData() {
loading.value = true;
items.value = [];
try {
const apiEndpoint = "https://api.ui.ac.id/my/lib";
const response = await fetch(apiEndpoint, {
headers: {
Authorization: `Bearer ${keycloakStore.accessToken}`,
},
});
if (!response.ok) throw new Error("Gagal mengambil data");
const dataku = await response.json();
// Tambahkan properti tanggal, namaHari, mulai aktual, dan selesai aktual ke setiap item
items.value = dataku.map((item: any) => {
return {
...item,
};
});
} catch (err) {
console.error("Gagal mengambil data:", err);
} finally {
loading.value = false;
}
}
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)
)
);
});
</script>
<template>
<VCard title="Status Peminjaman Buku" class="recentnamaHariCard">
<div class="search-container mb-4 pl-2 pr-2">
<VTextField
v-model="searchQuery"
label="Search"
placeholder="Search ..."
append-inner-icon="ri-search-line"
clearable
single-line
hide-details
dense
outlined
/>
</div>
<VDataTable
:headers="logHeaders"
:items="filteredItems"
hide-default-footer
fixed-header
item-value="tglpinjam"
:sort-by="['tglpinjam']"
:sort-asc="[true]"
></VDataTable>
</VCard>
</template>
...@@ -95,6 +95,17 @@ watchEffect(async () => { ...@@ -95,6 +95,17 @@ watchEffect(async () => {
await getData(); 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)
)
);
});
</script> </script>
...@@ -115,7 +126,7 @@ watchEffect(async () => { ...@@ -115,7 +126,7 @@ watchEffect(async () => {
</div> </div>
<VDataTable <VDataTable
:headers="logHeaders" :headers="logHeaders"
:items="items" :items="filteredItems"
hide-default-footer hide-default-footer
fixed-header fixed-header
item-value="tanggal" item-value="tanggal"
......
...@@ -12,7 +12,6 @@ import NavbarThemeSwitcher from '@/layouts/components/NavbarThemeSwitcher.vue' ...@@ -12,7 +12,6 @@ import NavbarThemeSwitcher from '@/layouts/components/NavbarThemeSwitcher.vue'
import UserProfile from '@/layouts/components/UserProfile.vue' import UserProfile from '@/layouts/components/UserProfile.vue'
import NavBarI18n from '@core/components/I18n.vue' import NavBarI18n from '@core/components/I18n.vue'
import { HorizontalNavLayout } from '@layouts' import { HorizontalNavLayout } from '@layouts'
import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
</script> </script>
<template> <template>
...@@ -23,11 +22,12 @@ import { VNodeRenderer } from '@layouts/components/VNodeRenderer' ...@@ -23,11 +22,12 @@ import { VNodeRenderer } from '@layouts/components/VNodeRenderer'
to="/" to="/"
class="d-flex align-start gap-x-4" class="d-flex align-start gap-x-4"
> >
<VNodeRenderer :nodes="themeConfig.app.logo" /> <!-- <VNodeRenderer :nodes="themeConfig.app.logo" /> -->
<div style="display: inline-flex; align-items: center; gap: 8px;">
<img src="@/assets/images/naput/logo-UI.png" alt="Logo UI" width="35" height="auto" />
<h1 style="font-size: 1.25rem; text-transform: uppercase; margin-left: 10px;">CIVITAS UI</h1>
</div>
<h1 class="leading-normal text-xl text-uppercase">
{{ themeConfig.app.title }}
</h1>
</NuxtLink> </NuxtLink>
<VSpacer /> <VSpacer />
......
<script setup lang="ts">
import { VForm } from 'vuetify/components/VForm'
import authV2MaskDark from '@images/pages/mask-v2-dark.png'
import authV2MaskLight from '@images/pages/mask-v2-light.png'
import { useKeycloakStore } from "@/@core/stores/keycloakStore"
import AuthProvider from '@/views/pages/authentication/AuthProvider.vue'
import authLoginLogo from "/assets/images/naput/illust-login.png"
import authLoginBg from "/assets/images/naput/rektorat-bg.png"
const authThemeImg = authLoginLogo;
const authThemeBg = authLoginBg;
const keycloakStore = useKeycloakStore();
const data = ref<Record<string, any> | null>(null);
const error = ref<Record<string, any> | null>(null);
const { signIn, data: sessionData } = useAuth()
const authThemeMask = useGenerateImageVariant(authV2MaskLight, authV2MaskDark)
definePageMeta({
layout: 'blank',
unauthenticatedOnly: true,
})
const isPasswordVisible = ref(false)
const route = useRoute()
const ability = useAbility()
const errors = ref<Record<string, string | undefined>>({
email: undefined,
password: undefined,
})
const refVForm = ref<VForm>()
const credentials = ref({
email: 'admin@demo.com',
password: 'admin',
})
const rememberMe = ref(false)
</script>
<template>
<div class="container">
<div class="left"></div>
<div class="right">
<img src="/assets/images/naput/logo-ui-hitam.png" alt="Logo UI" class="logo" />
<VCol
cols="5"
class="text-center"
>
<AuthProvider/>
</VCol>
</div>
</div>
</template>
<style scoped>
.login-button {
margin-top: 20px;
background-color: #FFD700;
padding: 12px 24px;
font-size: 18px;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: bold;
transition: background-color 0.2s;
color: white;
}
.container {
display: flex;
height: 100vh;
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: Arial, sans-serif;
}
/* Left Side */
.left {
flex: 1;
background: url(/assets/images/naput/rbg.png) no-repeat center center;
background-size: cover;
}
/* Right Side */
.right {
flex: 1;
background-color: #FDF8E7;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding-top: 240px;
gap: 20px; /* Menambah jarak antar elemen */
}
/* Logo */
.logo {
width: 120px;
margin-bottom: 20px;
}
/* Title */
h1 {
font-size: 24px;
color: #000;
text-align: center;
}
.login-button:hover {
background-color: #E6C200;
}
@media (max-width: 768px) {
.container {
justify-content: center;
align-items: center;
background: url(/assets/images/naput/rektorat-ipad-bg.png) no-repeat center center;
background-size: cover;
}
.left {
display: none;
}
.right {
background-color: rgba(253, 248, 231, 0.9);
padding: 40px;
border-radius: 20px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
max-width: 350px;
width: 90%;
height: auto;
display: flex;
justify-content: center;
align-items: center;
}
}
@media (max-width: 480px) {
.container {
background: url(/assets/images/naput/rektorat-iphone-bg.png) no-repeat center center;
background-size: cover;
}
.right {
padding: 30px;
max-width: 320px;
width: 90%;
}
.logo {
width: 90px;
}
}
</style>
...@@ -3,7 +3,6 @@ import { useKeycloakStore } from '@/@core/stores/keycloakStore' ...@@ -3,7 +3,6 @@ import { useKeycloakStore } from '@/@core/stores/keycloakStore'
import authLoginBg from '/assets/images/naput/bg-blue-yellow.png' import authLoginBg from '/assets/images/naput/bg-blue-yellow.png'
import authLoginLogo from '/assets/images/naput/illust-login.png' import authLoginLogo from '/assets/images/naput/illust-login.png'
import AuthProvider from '@/views/pages/authentication/AuthProvider.vue' import AuthProvider from '@/views/pages/authentication/AuthProvider.vue'
const authThemeImg = authLoginLogo const authThemeImg = authLoginLogo
...@@ -42,9 +41,6 @@ definePageMeta({ ...@@ -42,9 +41,6 @@ definePageMeta({
height="auto" height="auto"
class="mb-0" class="mb-0"
> >
<h4 class="text-h4 mb-1">
Welcome 👋🏻 {{ keycloakStore.name }}
</h4>
</VCol> </VCol>
<VCol cols="12"> <VCol cols="12">
...@@ -59,4 +55,4 @@ definePageMeta({ ...@@ -59,4 +55,4 @@ definePageMeta({
<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
...@@ -3,6 +3,7 @@ import { computed } from 'vue' ...@@ -3,6 +3,7 @@ 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 UserBerita from '@/components/beranda/UserBerita.vue'
import UserLib from '@/components/beranda/UserLib.vue'
import UserLog from '@/components/beranda/UserLog.vue' import UserLog from '@/components/beranda/UserLog.vue'
import UserJadwal from '@/components/beranda/UserJadwal.vue' import UserJadwal from '@/components/beranda/UserJadwal.vue'
import UserRiwayat from '@/components/beranda/UserRiwayat.vue' import UserRiwayat from '@/components/beranda/UserRiwayat.vue'
...@@ -39,6 +40,8 @@ const tabs = [ ...@@ -39,6 +40,8 @@ const tabs = [
{ title: 'Jadwal', icon: 'ri-calendar-line', tab: 'jadwal' }, { title: 'Jadwal', icon: 'ri-calendar-line', tab: 'jadwal' },
{ title: 'Log Absen', icon: 'ri-history-line', tab: 'log' }, { title: 'Log Absen', icon: 'ri-history-line', tab: 'log' },
{ title: 'Peta', icon: 'ri-history-line', tab: 'peta' }, { title: 'Peta', icon: 'ri-history-line', tab: 'peta' },
{ title: 'Peminjaman Buku', icon: 'ri-book-line', tab: 'lib' },
] ]
...@@ -118,6 +121,10 @@ const navigateTab = (tab: string) => { ...@@ -118,6 +121,10 @@ const navigateTab = (tab: string) => {
<VWindowItem value="peta"> <VWindowItem value="peta">
<UserPeta /> <UserPeta />
</VWindowItem> </VWindowItem>
<VWindowItem value="lib">
<UserLib />
</VWindowItem>
</vwindow> </vwindow>
</div> </div>
</template> </template>
...@@ -183,6 +183,12 @@ const ulangTahunBerikutnya = computed(() => { ...@@ -183,6 +183,12 @@ const ulangTahunBerikutnya = computed(() => {
const goToParking = () => { const goToParking = () => {
window.open('https://parkir.ui.ac.id/apps/site/index', '_blank') window.open('https://parkir.ui.ac.id/apps/site/index', '_blank')
} }
const dialog = ref(false)
function openDialog() {
dialog.value = true
}
</script> </script>
<template> <template>
...@@ -249,7 +255,7 @@ const goToParking = () => { ...@@ -249,7 +255,7 @@ const goToParking = () => {
<VBtn <VBtn
variant="tonal" variant="tonal"
:color="daysLeft > 0 ? 'primary' : 'error'" :color="daysLeft > 0 ? 'primary' : 'error'"
@click="goToParking" @click="openDialog"
> >
{{ parkirStatus }} {{ parkirStatus }}
<VTooltip <VTooltip
...@@ -287,8 +293,65 @@ const goToParking = () => { ...@@ -287,8 +293,65 @@ const goToParking = () => {
</div> </div>
</VTooltip> </VTooltip>
</VBtn> </VBtn>
</div>
<!-- Dialog popup -->
<VDialog
v-model="dialog"
width="400"
>
<template #default>
<VCard>
<VCardTitle>
Informasi Parkir
</VCardTitle>
<VCardText>
<div class="d-flex align-center gap-x-2 mb-2">
<VIcon
size="20"
icon="ri-calendar-line"
/>
<span>Mulai: {{ parkirItems.date_start_service }}</span>
</div>
<div class="d-flex align-center gap-x-2 mb-2">
<VIcon
size="20"
icon="ri-calendar-check-line"
/>
<span>Berakhir: {{ parkirItems.date_end_service }}</span>
</div>
<div class="d-flex align-center gap-x-2 mb-4">
<VIcon
size="20"
icon="ri-time-line"
/>
<span>{{ daysLeft }} hari tersisa</span>
</div>
<VProgressLinear
:model-value="progress"
color="primary"
height="6"
rounded
/>
</VCardText>
<VCardActions>
<VSpacer />
<VBtn
color="primary"
@click="goToParking"
>
Lihat Detail
</VBtn>
<VBtn
text
@click="dialog = false"
>
Tutup
</VBtn>
</VCardActions>
</VCard>
</template>
</VDialog>
</div>
<div <div
v-if="!isBirthday" v-if="!isBirthday"
class="d-flex align-center gap-x-2" class="d-flex align-center gap-x-2"
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!