This commit is contained in:
Eduard Gzibovskis 2025-04-25 13:27:32 +01:00
parent f6df79d83f
commit 3aeefee5a4

View file

@ -1,247 +1,248 @@
<template> <template>
<q-layout view="hHh Lpr lFf"> <q-layout view="hHh Lpr lFf">
<q-drawer <q-drawer
:mini="!leftDrawerOpen" :mini="!leftDrawerOpen"
bordered bordered
persistent persistent
:model-value="true" :model-value="true"
> >
<q-list> <q-list>
<q-item <q-item
clickable clickable
v-ripple v-ripple
@click="toggleLeftDrawer" @click="toggleLeftDrawer"
> >
<q-item-section avatar> <q-item-section avatar>
<q-icon name="menu" /> <q-icon name="menu" />
</q-item-section> </q-item-section>
<q-item-section> <q-item-section>
<q-item-label class="text-h6"> <q-item-label class="text-h6">
StylePoint StylePoint
</q-item-label> </q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
<!-- Dynamic Navigation Items --> <!-- Dynamic Navigation Items -->
<q-item <q-item
v-for="item in navItems" v-for="item in navItems"
:key="item.name" :key="item.name"
clickable clickable
v-ripple v-ripple
:to="{ name: item.name }" :to="{ name: item.name }"
exact exact
> >
<q-tooltip <q-tooltip
anchor="center right" anchor="center right"
self="center left" self="center left"
> >
<span>{{ item.meta.title }}</span> <span>{{ item.meta.title }}</span>
</q-tooltip> </q-tooltip>
<q-item-section avatar> <q-item-section avatar>
<q-icon :name="item.meta.icon" /> <q-icon :name="item.meta.icon" />
</q-item-section> </q-item-section>
<q-item-section> <q-item-section>
<q-item-label>{{ item.meta.title }}</q-item-label> <q-item-label>{{ item.meta.title }}</q-item-label>
<q-item-label caption> <q-item-label caption>
{{ item.meta.caption }} {{ item.meta.caption }}
</q-item-label> </q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
<!-- Logout Button (Conditional) --> <!-- Logout Button (Conditional) -->
<q-item <q-item
v-if="authStore.isAuthenticated" v-if="authStore.isAuthenticated"
clickable clickable
v-ripple v-ripple
@click="logout" @click="logout"
> >
<q-tooltip <q-tooltip
anchor="center right" anchor="center right"
self="center left" self="center left"
> >
<span>Logout</span> <span>Logout</span>
</q-tooltip> </q-tooltip>
<q-item-section avatar> <q-item-section avatar>
<q-icon name="logout" /> <q-icon name="logout" />
</q-item-section> </q-item-section>
<q-item-section> <q-item-section>
<q-item-label>Logout</q-item-label> <q-item-label>Logout</q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
</q-list> </q-list>
</q-drawer> </q-drawer>
<q-page-container> <q-page-container>
<router-view /> <router-view />
</q-page-container> </q-page-container>
<!-- Chat FAB --> <!-- Chat FAB -->
<q-page-sticky <q-page-sticky
v-if="isAuthenticated" v-if="isAuthenticated"
position="bottom-right" position="bottom-right"
:offset="[18, 18]" :offset="[18, 18]"
> >
<q-fab <q-fab
v-model="fabOpen" v-model="fabOpen"
icon="chat" icon="chat"
color="accent" color="accent"
direction="up" direction="up"
padding="sm" padding="sm"
@click="toggleChat" @click="toggleChat"
/> />
</q-page-sticky> </q-page-sticky>
<!-- Chat Window Dialog --> <!-- Chat Window Dialog -->
<q-dialog <q-dialog
v-model="isChatVisible" v-model="isChatVisible"
:maximized="$q.screen.lt.sm" :maximized="$q.screen.lt.sm"
fixed fixed
persistent persistent
style="width: max(400px, 25%);" style="width: max(400px, 25%);"
> >
<q-card style="width: max(400px, 25%); height: 600px; max-height: 80vh;"> <q-card style="width: max(400px, 25%); height: 600px; max-height: 80vh;">
<q-bar class="bg-primary text-white"> <q-bar class="bg-primary text-white">
<div>Chat</div> <div>Chat</div>
<q-space /> <q-space />
<q-btn <q-btn
dense dense
flat flat
icon="close" icon="close"
@click="toggleChat" @click="toggleChat"
/> />
</q-bar> </q-bar>
<q-card-section <q-card-section
class="q-pa-none" class="q-pa-none"
style="height: calc(100% - 50px);" style="height: calc(100% - 50px);"
> >
<ChatInterface <ChatInterface
:messages="chatMessages" :messages="chatMessages"
@send-message="handleSendMessage" @send-message="handleSendMessage"
/> />
</q-card-section> </q-card-section>
<q-inner-loading :showing="isLoading"> <q-inner-loading :showing="isLoading">
<q-spinner-gears <q-spinner-gears
size="50px" size="50px"
color="primary" color="primary"
/> />
</q-inner-loading> </q-inner-loading>
<q-banner <q-banner
v-if="chatError" v-if="chatError"
inline-actions inline-actions
class="text-white bg-red" class="text-white bg-red"
> >
{{ chatError }} {{ chatError }}
<template #action> <template #action>
<q-btn <q-btn
flat flat
color="white" color="white"
label="Dismiss" label="Dismiss"
@click="clearError" @click="clearError"
/> />
</template> </template>
</q-banner> </q-banner>
</q-card> </q-card>
</q-dialog> </q-dialog>
</q-layout> </q-layout>
</template> </template>
<script setup> <script setup>
import axios from 'boot/axios'; import axios from 'boot/axios';
import { ref, computed } from 'vue'; // Import computed import { ref, computed } from 'vue'; // Import computed
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { useAuthStore } from 'stores/auth'; // Import the auth store import { useAuthStore } from 'stores/auth'; // Import the auth store
import { useChatStore } from 'stores/chat'; // Adjust path as needed import { useChatStore } from 'stores/chat'; // Adjust path as needed
import ChatInterface from 'components/ChatInterface.vue'; // Adjust path as needed import ChatInterface from 'components/ChatInterface.vue'; // Adjust path as needed
import routes from '../router/routes'; // Import routes import routes from '../router/routes'; // Import routes
const $q = useQuasar(); const $q = useQuasar();
const leftDrawerOpen = ref(false); const leftDrawerOpen = ref(false);
const router = useRouter(); const router = useRouter();
const authStore = useAuthStore(); // Use the auth store const authStore = useAuthStore(); // Use the auth store
const chatStore = useChatStore(); const chatStore = useChatStore();
const fabOpen = ref(false); // Local state for FAB animation, not chat visibility const fabOpen = ref(false); // Local state for FAB animation, not chat visibility
// Computed properties to get state from the store // Computed properties to get state from the store
const isChatVisible = computed(() => chatStore.isChatVisible); const isChatVisible = computed(() => chatStore.isChatVisible);
const chatMessages = computed(() => chatStore.chatMessages); const chatMessages = computed(() => chatStore.chatMessages);
const isLoading = computed(() => chatStore.isLoading); const isLoading = computed(() => chatStore.isLoading);
const chatError = computed(() => chatStore.error); const chatError = computed(() => chatStore.error);
const isAuthenticated = computed(() => authStore.isAuthenticated); // Get auth state const isAuthenticated = computed(() => authStore.isAuthenticated); // Get auth state
// Get the child routes of the main layout // Get the child routes of the main layout
const mainLayoutRoutes = routes.find(r => r.path === '/')?.children || []; const mainLayoutRoutes = routes.find(r => r.path === '/')?.children || [];
// Compute navigation items based on auth state and route meta // Compute navigation items based on auth state and route meta
const navItems = computed(() => const navItems = computed(() =>
{ {
return mainLayoutRoutes.filter(route => return mainLayoutRoutes.filter(route =>
{ {
const navGroup = route.meta?.navGroup; const navGroup = route.meta?.navGroup;
if (!navGroup) return false; // Only include routes with navGroup defined if (!navGroup) return false; // Only include routes with navGroup defined
if (navGroup === 'always') return true; if (navGroup === 'always') return true;
if (navGroup === 'auth' && isAuthenticated.value) return true; if (navGroup === 'auth' && isAuthenticated.value) return true;
if (navGroup === 'noAuth' && !isAuthenticated.value) return true; if (navGroup === 'noAuth' && !isAuthenticated.value) return true;
return false; // Exclude otherwise return false; // Exclude otherwise
}); });
}); });
// Method to toggle chat visibility via the store action // Method to toggle chat visibility via the store action
const toggleChat = () => const toggleChat = () =>
{ {
// Optional: Add an extra check here if needed, though hiding the button is primary // Optional: Add an extra check here if needed, though hiding the button is primary
if (isAuthenticated.value) if (isAuthenticated.value)
{ {
chatStore.toggleChat(); chatStore.toggleChat();
} fabOpen.value = chatStore.isChatVisible;
}; }
};
// Method to send a message via the store action
const handleSendMessage = (messageContent) => // Method to send a message via the store action
{ const handleSendMessage = (messageContent) =>
chatStore.sendMessage(messageContent); {
}; chatStore.sendMessage(messageContent);
};
// Method to clear errors in the store (optional)
const clearError = () => // Method to clear errors in the store (optional)
{ const clearError = () =>
chatStore.error = null; // Directly setting ref or add an action in store {
}; chatStore.error = null; // Directly setting ref or add an action in store
function toggleLeftDrawer() };
{ function toggleLeftDrawer()
leftDrawerOpen.value = !leftDrawerOpen.value; {
} leftDrawerOpen.value = !leftDrawerOpen.value;
}
async function logout()
{ async function logout()
try {
{ try
await axios.post('/api/auth/logout'); {
authStore.logout(); // Use the store action to update state await axios.post('/api/auth/logout');
// No need to manually push, router guard should redirect authStore.logout(); // Use the store action to update state
// router.push({ name: 'login' }); // No need to manually push, router guard should redirect
} // router.push({ name: 'login' });
catch (error) }
{ catch (error)
console.error('Logout failed:', error); {
console.error('Logout failed:', error);
$q.notify({
color: 'negative', $q.notify({
message: 'Logout failed. Please try again.', color: 'negative',
icon: 'report_problem' message: 'Logout failed. Please try again.',
}); icon: 'report_problem'
} });
} }
</script> }
</script>
<style scoped>
/* Add any specific styles for the layout or chat window here */ <style scoped>
.q-dialog .q-card { /* Add any specific styles for the layout or chat window here */
overflow: hidden; /* Prevent scrollbars on the card itself */ .q-dialog .q-card {
} overflow: hidden; /* Prevent scrollbars on the card itself */
</style> }
</style>