change branch to Akbar-antren
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
<!-- components/AppBar.vue -->
|
||||
<template>
|
||||
<v-app-bar app color="#ff9248" dark>
|
||||
<v-app-bar-nav-icon @click="emit('toggle-rail')"></v-app-bar-nav-icon>
|
||||
@@ -2,31 +2,62 @@
|
||||
<v-menu
|
||||
v-model="menu"
|
||||
:close-on-content-click="false"
|
||||
location="bottom right"
|
||||
origin="top right"
|
||||
location="top end"
|
||||
origin="bottom right"
|
||||
transition="slide-y-transition"
|
||||
open-on-hover
|
||||
>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn icon v-bind="props">
|
||||
<template v-slot:activator="{ props: menuProps }">
|
||||
<v-list-item
|
||||
v-if="!rail"
|
||||
v-bind="menuProps"
|
||||
:title="user?.name || user?.preferred_username || 'User'"
|
||||
:subtitle="user?.email || 'No email'"
|
||||
class="pa-2 ma-1 rounded-lg bg-blue-grey-lighten-5 text-blue-grey-darken-4"
|
||||
color="blue-grey-darken-4"
|
||||
link
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-avatar size="40">
|
||||
<v-img
|
||||
:src="user?.picture || 'https://i.pravatar.cc/300?img=68'"
|
||||
:alt="`${user?.name || 'User'} Profile`"
|
||||
></v-img>
|
||||
</v-avatar>
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<v-icon size="20">mdi-chevron-up</v-icon>
|
||||
</template>
|
||||
</v-list-item>
|
||||
|
||||
<v-btn
|
||||
v-else
|
||||
icon
|
||||
v-bind="menuProps"
|
||||
size="large"
|
||||
variant="text"
|
||||
color="blue-grey-darken-4"
|
||||
:title="user?.name || 'Profile'"
|
||||
>
|
||||
<v-avatar size="40">
|
||||
<v-img
|
||||
:src="user?.picture || 'https://i.pravatar.cc/300?img=68'"
|
||||
<v-img
|
||||
:src="user?.picture || 'https://i.pravatar.cc/300?img=68'"
|
||||
:alt="`${user?.name || 'User'} Profile`"
|
||||
></v-img>
|
||||
</v-avatar>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<v-card class="rounded-lg elevation-4 pa-4" width="300">
|
||||
<v-card class="rounded-lg elevation-4 pa-4" width="300" color="blue-grey-lighten-5">
|
||||
<div class="d-flex align-center pb-2">
|
||||
<v-avatar size="48">
|
||||
<v-img
|
||||
:src="user?.picture || 'https://i.pravatar.cc/300?img=68'"
|
||||
<v-img
|
||||
:src="user?.picture || 'https://i.pravatar.cc/300?img=68'"
|
||||
:alt="`${user?.name || 'User'} Profile`"
|
||||
></v-img>
|
||||
</v-avatar>
|
||||
<div class="ml-4">
|
||||
<div class="text-subtitle-1 font-weight-bold">
|
||||
<div class="text-subtitle-1 font-weight-bold text-blue-grey-darken-4">
|
||||
{{ user?.name || user?.preferred_username || 'User' }}
|
||||
</div>
|
||||
<div class="text-caption text-grey-darken-1">
|
||||
@@ -40,16 +71,16 @@
|
||||
<v-divider class="my-2"></v-divider>
|
||||
|
||||
<v-list dense>
|
||||
<v-list-item link class="rounded-lg" @click="handleAction('account')">
|
||||
<v-list-item link class="rounded-lg text-blue-grey-darken-4" @click="handleAction('account')">
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-cog</v-icon>
|
||||
<v-icon color="blue-grey-darken-4">mdi-cog</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>Pengaturan Akun</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item link class="rounded-lg" @click="handleAction('darkMode')">
|
||||
<v-list-item link class="rounded-lg text-blue-grey-darken-4" @click="handleAction('darkMode')">
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-weather-night</v-icon>
|
||||
<v-icon color="blue-grey-darken-4">mdi-weather-night</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>Mode Gelap</v-list-item-title>
|
||||
<template v-slot:append>
|
||||
@@ -57,23 +88,23 @@
|
||||
v-model="darkMode"
|
||||
hide-details
|
||||
density="compact"
|
||||
color="primary"
|
||||
color="orange-darken-2"
|
||||
></v-switch>
|
||||
</template>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item link class="rounded-lg" @click="handleAction('profile')">
|
||||
<v-list-item link class="rounded-lg text-blue-grey-darken-4" @click="handleAction('profile')">
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-account</v-icon>
|
||||
<v-icon color="blue-grey-darken-4">mdi-account</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>Profil Saya</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider class="my-2"></v-divider>
|
||||
|
||||
<v-list-item
|
||||
link
|
||||
class="rounded-lg text-red"
|
||||
<v-list-item
|
||||
link
|
||||
class="rounded-lg text-red"
|
||||
@click="signOut"
|
||||
:disabled="isLoggingOut"
|
||||
>
|
||||
@@ -91,12 +122,16 @@
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { navigateTo } from '#app';
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
user: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
rail: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
@@ -105,43 +140,29 @@ const darkMode = ref(false);
|
||||
const isLoggingOut = ref(false);
|
||||
const emit = defineEmits(['logout']);
|
||||
|
||||
/**
|
||||
* Handles the logout action - delegates to parent
|
||||
*/
|
||||
const signOut = async () => {
|
||||
if (isLoggingOut.value) return;
|
||||
|
||||
isLoggingOut.value = true;
|
||||
menu.value = false;
|
||||
|
||||
|
||||
try {
|
||||
console.log('🚪 ProfilePopup signOut called...')
|
||||
emit('logout');
|
||||
// 🚨 Emits event up to SideBar to trigger useAuth().logout()
|
||||
emit('logout');
|
||||
} finally {
|
||||
isLoggingOut.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleAction = (action) => {
|
||||
console.log('Action triggered:', action);
|
||||
|
||||
switch(action) {
|
||||
case 'account':
|
||||
// Navigate to account settings
|
||||
navigateTo('/settings/account')
|
||||
break;
|
||||
case 'profile':
|
||||
// Navigate to profile page
|
||||
navigateTo('/profile')
|
||||
break;
|
||||
case 'darkMode':
|
||||
// Dark mode toggle is handled by v-model
|
||||
break;
|
||||
default:
|
||||
console.log('Unknown action:', action);
|
||||
}
|
||||
|
||||
// Close menu for navigation actions
|
||||
|
||||
if (action !== 'darkMode') {
|
||||
menu.value = false;
|
||||
}
|
||||
|
||||
@@ -1,49 +1,57 @@
|
||||
<!-- components/sideBar.vue -->
|
||||
<template>
|
||||
<v-navigation-drawer
|
||||
:model-value="drawer"
|
||||
:rail="rail"
|
||||
permanent
|
||||
app
|
||||
class="bg-white"
|
||||
@update:model-value="emit('update:drawer', $event)"
|
||||
|
||||
@mouseenter="handleMouseEnter"
|
||||
@mouseleave="handleMouseLeave"
|
||||
>
|
||||
<v-list density="compact" nav>
|
||||
<template v-for="item in items" :key="item.name">
|
||||
<v-menu
|
||||
<v-list-item
|
||||
class="py-4 px-2"
|
||||
:class="{'text-center': rail}"
|
||||
>
|
||||
<div class="d-flex align-center" :class="{'justify-center': rail}">
|
||||
<v-icon color="orange-darken-2" size="36">mdi-hospital-box-outline</v-icon>
|
||||
<v-list-item-title v-if="!rail" class="ml-2 text-h6 font-weight-bold text-blue-grey-darken-4">
|
||||
<span class="text-orange-darken-2">Antrian</span> RSSA
|
||||
</v-list-item-title>
|
||||
</div>
|
||||
</v-list-item>
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-list density="compact" nav class="mt-2" v-model:opened="openedGroups"> <template v-for="item in items" :key="item.name">
|
||||
|
||||
<v-list-group
|
||||
v-if="item.children"
|
||||
open-on-hover
|
||||
:location="rail ? 'end' : undefined"
|
||||
:offset="10"
|
||||
:value="item.name"
|
||||
:disabled="rail"
|
||||
>
|
||||
<template v-slot:activator="{ props }">
|
||||
<template v-slot:activator="{ props: groupProps }">
|
||||
<v-list-item
|
||||
v-bind="props"
|
||||
v-bind="groupProps"
|
||||
:prepend-icon="item.icon"
|
||||
:title="item.name"
|
||||
:value="item.name"
|
||||
:to="!rail ? item.path : undefined"
|
||||
:link="!rail"
|
||||
color="orange-darken-2"
|
||||
active-class="bg-orange-lighten-5 text-orange-darken-2 font-weight-bold"
|
||||
></v-list-item>
|
||||
</template>
|
||||
|
||||
<v-card class="py-2" min-width="200">
|
||||
<v-card-title class="text-subtitle-1 font-weight-bold px-4 py-2">
|
||||
{{ item.name }}
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
<v-list density="compact" nav>
|
||||
<v-list-item
|
||||
v-for="child in item.children"
|
||||
:key="child.name"
|
||||
:to="child.path"
|
||||
:title="child.name"
|
||||
:prepend-icon="child.icon"
|
||||
link
|
||||
class="px-4"
|
||||
></v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
<v-list-item
|
||||
v-for="child in item.children"
|
||||
:key="child.name"
|
||||
:to="child.path"
|
||||
:title="child.name"
|
||||
:prepend-icon="child.icon"
|
||||
link
|
||||
class="pl-8"
|
||||
color="orange-darken-2"
|
||||
active-class="bg-orange-lighten-5 text-orange-darken-2 font-weight-bold"
|
||||
></v-list-item>
|
||||
</v-list-group>
|
||||
|
||||
<v-tooltip
|
||||
v-else
|
||||
@@ -52,23 +60,51 @@
|
||||
location="end"
|
||||
:text="item.name"
|
||||
>
|
||||
<template #activator="{ props }">
|
||||
<template #activator="{ props: tooltipProps }">
|
||||
<v-list-item
|
||||
v-bind="props"
|
||||
v-bind="tooltipProps"
|
||||
:prepend-icon="item.icon"
|
||||
:title="item.name"
|
||||
:to="item.path"
|
||||
link
|
||||
color="orange-darken-2"
|
||||
active-class="bg-orange-lighten-5 text-orange-darken-2 font-weight-bold"
|
||||
></v-list-item>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</template>
|
||||
</v-list>
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-list v-if="user" nav density="compact" class="pa-2">
|
||||
<ProfilePopup
|
||||
:user="user"
|
||||
@logout="handleLogout"
|
||||
:rail="rail"
|
||||
/>
|
||||
</v-list>
|
||||
<v-list v-else nav density="compact">
|
||||
<v-list-item
|
||||
@click="redirectToLogin"
|
||||
prepend-icon="mdi-login"
|
||||
title="Login"
|
||||
link
|
||||
color="blue-grey-darken-4"
|
||||
></v-list-item>
|
||||
</v-list>
|
||||
|
||||
</v-navigation-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
import { defineProps, defineEmits, onMounted, ref, watch } from 'vue'; // IMPORT WATCH
|
||||
import { navigateTo } from '#app';
|
||||
import ProfilePopup from './ProfilePopup.vue';
|
||||
import { useAuth } from '~/composables/useAuth';
|
||||
|
||||
const { user, logout, checkAuth } = useAuth();
|
||||
|
||||
interface NavItem {
|
||||
id: number;
|
||||
@@ -93,11 +129,61 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:drawer']);
|
||||
const emit = defineEmits(['update:drawer', 'toggle-rail']);
|
||||
|
||||
// --- NEW STATE FOR DROPDOWN GROUPS ---
|
||||
// This holds the names of the currently open list groups.
|
||||
const openedGroups = ref<string[]>([]);
|
||||
|
||||
// --- NEW WATCHER TO CLOSE DROPDOWNS ---
|
||||
watch(() => props.rail, (newRailState) => {
|
||||
if (newRailState === true) {
|
||||
// If the sidebar is collapsing (rail is true), close all groups
|
||||
openedGroups.value = [];
|
||||
}
|
||||
});
|
||||
// ------------------------------------
|
||||
|
||||
|
||||
// Local state for hover-to-expand rail feature
|
||||
const isHovering = ref(false);
|
||||
|
||||
// --- HOVER LOGIC for Rail Expansion ---
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
if (props.rail) {
|
||||
isHovering.value = true;
|
||||
emit('toggle-rail');
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
if (isHovering.value) {
|
||||
isHovering.value = false;
|
||||
emit('toggle-rail');
|
||||
}
|
||||
};
|
||||
|
||||
// --- AUTH LOGIC (Kept the same) ---
|
||||
|
||||
const handleLogout = async () => {
|
||||
console.log('🚪 SideBar logout initiated...');
|
||||
try {
|
||||
await logout();
|
||||
} catch (error) {
|
||||
console.error('❌ SideBar logout error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const redirectToLogin = () => {
|
||||
navigateTo('/LoginPage');
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await checkAuth();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-navigation-drawer__content {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
/* No specific overrides needed */
|
||||
</style>
|
||||
Reference in New Issue
Block a user