Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 69 additions & 19 deletions client/src/components/CshrToolbar.vue
Original file line number Diff line number Diff line change
@@ -1,22 +1,46 @@
<template>
<VToolbar color="primary">
<VBtn v-if="isMobile" icon="mdi-menu" @click="$emit('toggle-drawer')" color="white" />
<VSpacer />
<VMenu v-model="menu">
<template #activator="{ props }">
<VBtn icon="mdi-bell-badge-outline" size="large" v-bind="props" color="white" v-if="hasReadNotifications" />
<VBtn
icon="mdi-bell-badge-outline"
size="large"
v-bind="props"
color="white"
v-if="hasReadNotifications"
/>
<VBtn icon="mdi-bell-outline" size="large" v-bind="props" color="white" v-else />
</template>

<VList max-width="600" min-width="100%" :height="notifications.length ? 700 : 'auto'" class="mt-1">
<VList
max-width="600"
min-width="100%"
:height="notifications.length ? 700 : 'auto'"
class="mt-1"
>
<v-toolbar color="#262b2e" class="mb-2">
<v-btn :disabled="!notifications.length || !hasReadNotifications || isLoadingReadDeleteAll" type="info"
color="success" variant="tonal" class="mr-2" :loading="isLoadingReadDeleteAll"
@click="handleReadAllNotifications">
<v-btn
:disabled="!notifications.length || !hasReadNotifications || isLoadingReadDeleteAll"
type="info"
color="success"
variant="tonal"
class="mr-2"
:loading="isLoadingReadDeleteAll"
@click="handleReadAllNotifications"
>
<v-icon>mdi-read</v-icon>
Mark all as read
</v-btn>
<v-btn :disabled="!notifications.length || isLoadingReadDeleteAll" type="info" color="error"
variant="outlined" :loading="isLoadingReadDeleteAll" @click="handleDeleteAllNotifications">
<v-btn
:disabled="!notifications.length || isLoadingReadDeleteAll"
type="info"
color="error"
variant="outlined"
:loading="isLoadingReadDeleteAll"
@click="handleDeleteAllNotifications"
>
<v-icon>mdi-trash-can</v-icon>
Delete all notifications
</v-btn>
Expand All @@ -32,8 +56,11 @@
</VListItem>
<template v-else>
<template v-for="(notification, index) in notifications" :key="notification.id">
<VListItem class="pa-4 mt-1 mb-1 mr-2 ml-2" @click="setNotification(notification)"
:style="{ background: !notification.is_read ? 'rgb(37 58 86 / 43%)' : 'transparent' }">
<VListItem
class="pa-4 mt-1 mb-1 mr-2 ml-2"
@click="setNotification(notification)"
:style="{ background: !notification.is_read ? 'rgb(37 58 86 / 43%)' : 'transparent' }"
>
<VListItemTitle class="text-wrap">
<span class="is-not-read" v-if="!notification.is_read"></span>
{{ notification.title }}
Expand All @@ -54,18 +81,36 @@
<template #activator="{ props }">
<div class="d-flex justify-center align-center mx-2">
<VProgressCircular indeterminate v-if="isUserLoading" />
<profileImage width="55px" v-else-if="user" :with-link="false" :user="user" v-bind="props" />
<profileImage
width="55px"
v-else-if="user"
:with-link="false"
:user="user"
v-bind="props"
/>
</div>
</template>

<VList class="mt-1">
<VListItem prepend-icon="mdi-account" title="Your Profile" @click="$router.push('/profile')" />
<VListItem prepend-icon="mdi-logout" title="Logout" class="text-error" @click="$emit('logout')" />
<VListItem
prepend-icon="mdi-account"
title="Your Profile"
@click="$router.push('/profile')"
/>
<VListItem
prepend-icon="mdi-logout"
title="Logout"
class="text-error"
@click="$emit('logout')"
/>
</VList>
</VMenu>

<NotificationDetailsDialog route-query="toolbar-notification" v-model="selectedNotification"
@set:notification="setNotification" />
<NotificationDetailsDialog
route-query="toolbar-notification"
v-model="selectedNotification"
@set:notification="setNotification"
/>
</VToolbar>
</template>

Expand All @@ -83,6 +128,12 @@ import { ApiClientBase } from '@/clients/api/base'
export default {
name: 'CshrToolbar',
components: { NotificationDetailsDialog, profileImage },
props: {
isMobile: {
type: Boolean,
default: false
}
},
setup(_, { emit }) {
const $api = useApi()
const user = useAsyncState(async () => await $api.myprofile.getUser(), null)
Expand All @@ -94,22 +145,21 @@ export default {
const notifications = computed(() => notificationStore.notifications)

onMounted(async () => {
await user.execute();
await user.execute()
if (user.state.value) {
await loadNotifications()
}
if (!user.state.value) {
emit('logout')
}
});
})

const loadNotifications = async () => {
const notificationData = await $api.notifications.list()
notificationStore.setNotifications(notificationData!)
isLoading.value = false
}


function formatedDate(date: string) {
const _date = new Date(date)
const _formatDate = formatDate(date)
Expand All @@ -119,7 +169,6 @@ export default {

const selectedNotification = ref<notificationType>() as Ref<notificationType>


const setNotification = async (notification: notificationType) => {
selectedNotification.value = notification
if (!selectedNotification.value.is_read) {
Expand Down Expand Up @@ -184,7 +233,8 @@ export default {
handleDeleteAllNotifications,
menu
}
}
},
emits: ['logout', 'toggle-drawer']
}
</script>

Expand Down
53 changes: 46 additions & 7 deletions client/src/components/SideDrawer.vue
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
<template>
<v-card class="layout-container">
<v-navigation-drawer v-if="$route.path != '/login'" theme="dark" permanent temporary class="fixed-sidebar">
<v-navigation-drawer
v-if="$route.path != '/login'"
v-model="drawer"
theme="dark"
:permanent="!isMobile"
:temporary="isMobile"
class="fixed-sidebar">
<router-link to="/">
<v-img :src="logo" max-width="110" class="ml-3 mt-5 mb-4"></v-img>
</router-link>
<v-divider></v-divider>
<v-list color="transparent">
<template v-for="item in filteredItems" :key="item.title">
<v-list-item :to="item.path" :prepend-icon="item.icon" color="white"
:class="{ 'router-link-active': $route.path === item.path }">
<v-list-item
:to="item.path"
:prepend-icon="item.icon"
color="white"
:class="{ 'router-link-active': $route.path === item.path }"
@click="isMobile && (drawer = false)">
{{ item.title }}
</v-list-item>
</template>
</v-list>
</v-navigation-drawer>

<div :class="$route.path != '/login' ? 'main-container' : ''">
<CshrToolbar v-if="$route.path != '/login'" @logout="logout" />
<CshrToolbar v-if="$route.path != '/login'" @logout="logout" @toggle-drawer="drawer = !drawer" :is-mobile="isMobile" />
<div class="scrollable-content">
<template v-if="isReadyRouter.state.value">
<router-view />
Expand All @@ -33,7 +43,7 @@
import { useRoute, useRouter } from 'vue-router'
import { useAsyncState } from '@vueuse/core'
import logo from '@/assets/cshr_logo.png'
import { computed } from 'vue'
import { computed, ref, onMounted, onUnmounted } from 'vue'
import CshrToolbar from './CshrToolbar.vue'
import { $api } from '@/clients'
import { ApiClientBase } from '@/clients/api/base'
Expand All @@ -47,6 +57,26 @@ export default {
const $route = useRoute()
const $router = useRouter()
const user = ApiClientBase.user
const drawer = ref(true)
const isMobile = ref(false)

const checkMobile = () => {
isMobile.value = window.innerWidth < 960
if (!isMobile.value) {
drawer.value = true
} else {
drawer.value = false
}
}

onMounted(() => {
checkMobile()
window.addEventListener('resize', checkMobile)
})

onUnmounted(() => {
window.removeEventListener('resize', checkMobile)
})

const isReadyRouter = useAsyncState(async () => {
await $router.isReady()
Expand Down Expand Up @@ -125,7 +155,9 @@ export default {
$route,
navItems,
filteredItems,
logout
logout,
drawer,
isMobile
}
}
}
Expand All @@ -146,7 +178,7 @@ export default {
left: 0;
bottom: 0;
width: 250px;
z-index: 10;
z-index: 1000;
}

/* Main container styles */
Expand All @@ -157,6 +189,13 @@ export default {
margin-left: 250px;
}

/* Mobile responsive styles */
@media (max-width: 960px) {
.main-container {
margin-left: 0;
}
}

/* Navbar styles */
.fixed-navbar {
position: fixed;
Expand Down
94 changes: 67 additions & 27 deletions client/src/views/LoginView.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<v-container fluid class="pa-0">
<v-row align="center" justify="center">
<v-col cols="12" md="7">
<v-container fluid class="pa-0 login-container">
<v-row align="center" justify="center" class="ma-0">
<v-col cols="12" md="7" class="pa-0 d-none d-md-block">
<v-card height="100vh" style="background-color: black">
<v-img :src="background" height="65%" cover />
<div class="pa-2 pa-md-10 mx-auto">
Expand All @@ -19,33 +19,35 @@
</div>
</v-card>
</v-col>
<v-col cols="12" md="5">
<v-form v-model="valid" @submit.prevent>
<v-img :src="logo" max-width="140" class="mx-auto justify-center"></v-img>
<h2 class="text-center my-5">Sign In</h2>
<v-row class="justify-center align-center">
<v-col cols="7">
<v-text-field v-model="email" label="Email" :rules="emailRules"></v-text-field>
</v-col>
<v-col cols="12" md="5" class="login-form-col">
<div class="login-form-wrapper">
<v-form v-model="valid" @submit.prevent>
<v-img :src="logo" max-width="140" class="mx-auto justify-center my-6"></v-img>
<h2 class="text-center my-5">Sign In</h2>
<v-row class="justify-center align-center">
<v-col cols="10" sm="8" md="7">
<v-text-field v-model="email" label="Email" :rules="emailRules"></v-text-field>
</v-col>

<v-col cols="7">
<v-text-field v-model="password" :append-inner-icon="show ? 'mdi-eye' : 'mdi-eye-off'"
:rules="passwordRules" :type="show ? 'text' : 'password'" label="Password"
@click:append-inner="show = !show"></v-text-field>
</v-col>
<v-col cols="10" sm="8" md="7">
<v-text-field v-model="password" :append-inner-icon="show ? 'mdi-eye' : 'mdi-eye-off'"
:rules="passwordRules" :type="show ? 'text' : 'password'" label="Password"
@click:append-inner="show = !show"></v-text-field>
</v-col>

<v-col cols="7" class="d-flex justify-content-between align-center pa-0">
<v-checkbox v-model="rememberMe" label="Remember me"></v-checkbox>
</v-col>
<v-col cols="10" sm="8" md="7" class="d-flex justify-content-between align-center pa-0">
<v-checkbox v-model="rememberMe" label="Remember me"></v-checkbox>
</v-col>

<v-col cols="7">
<v-btn color="primary" type="submit" :disabled="!valid" width="100%" :loading="isLoading"
@click="execute">
Login
</v-btn>
</v-col>
</v-row>
</v-form>
<v-col cols="10" sm="8" md="7">
<v-btn color="primary" type="submit" :disabled="!valid" width="100%" :loading="isLoading"
@click="execute">
Login
</v-btn>
</v-col>
</v-row>
</v-form>
</div>
</v-col>
</v-row>
</v-container>
Expand Down Expand Up @@ -101,6 +103,44 @@ export default defineComponent({
})
</script>
<style scoped>
.login-container {
min-height: 100vh;
height: 100vh;
overflow-y: auto;
overflow-x: hidden;
}

.login-form-col {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
overflow-y: auto;
overflow-x: hidden;
}

.login-form-wrapper {
width: 100%;
max-width: 100%;
padding: 20px 0;
overflow-y: auto;
overflow-x: hidden;
}

/* Mobile specific styles */
@media (max-width: 960px) {
.login-form-col {
min-height: 100vh;
padding: 20px 0;
}

.login-form-wrapper {
display: flex;
flex-direction: column;
justify-content: center;
}
}

:deep(.v-card-title) {
white-space: unset;
}
Expand Down