fix(timesheet): more refactors and fixes to timesheet, mostly error handling, mobile UI/UX adjustments
This commit is contained in:
parent
4231b51c11
commit
b28f8768d2
|
|
@ -2,6 +2,14 @@
|
||||||
@each $size in (1, 2, 3, 4, 5, 10, 15, 20, 25, 50, 75, 100, 200) {
|
@each $size in (1, 2, 3, 4, 5, 10, 15, 20, 25, 50, 75, 100, 200) {
|
||||||
.rounded-#{$size} {
|
.rounded-#{$size} {
|
||||||
border-radius: #{$size}px !important;
|
border-radius: #{$size}px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rounded-#{$size} > div:first-child {
|
||||||
|
border-radius: #{$size}px #{$size}px 0 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rounded-#{$size} > div:last-child {
|
||||||
|
border-radius: 0 0 #{$size}px #{$size}px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,12 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
error :{
|
||||||
|
not_found_header: "page not found",
|
||||||
|
not_found_description: "You may have entered the wrong URL, or you may not have access to this page",
|
||||||
|
go_back: "go back",
|
||||||
|
},
|
||||||
|
|
||||||
login: {
|
login: {
|
||||||
page_header: "account login",
|
page_header: "account login",
|
||||||
email: "e-mail",
|
email: "e-mail",
|
||||||
|
|
@ -244,7 +250,7 @@ export default {
|
||||||
SHIFT_OVERLAP: "An overlaps occured between 2 or more shifts",
|
SHIFT_OVERLAP: "An overlaps occured between 2 or more shifts",
|
||||||
SHIFT_OVERLAP_SHORT: "Overlap",
|
SHIFT_OVERLAP_SHORT: "Overlap",
|
||||||
INVALID_SHIFT: "A shift contains missing or corrupted data",
|
INVALID_SHIFT: "A shift contains missing or corrupted data",
|
||||||
SHIFT_TIME_REQUIRED: "Time required",
|
SHIFT_TIME_REQUIRED: "Time missing",
|
||||||
SHIFT_TYPE_REQUIRED: "Shift type required",
|
SHIFT_TYPE_REQUIRED: "Shift type required",
|
||||||
SHIFT_NOT_FOUND: "Shift missing or deleted",
|
SHIFT_NOT_FOUND: "Shift missing or deleted",
|
||||||
PAY_PERIOD_NOT_FOUND: "No pay period matching given dates",
|
PAY_PERIOD_NOT_FOUND: "No pay period matching given dates",
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,12 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
error :{
|
||||||
|
not_found_header: "page introuvable",
|
||||||
|
not_found_description: "Vous avez possiblement entré une mauvaise addresse URL, ou vous n'avez pas accès à cette section du site",
|
||||||
|
go_back: "retour en arrière",
|
||||||
|
},
|
||||||
|
|
||||||
login: {
|
login: {
|
||||||
page_header: "connexion au compte",
|
page_header: "connexion au compte",
|
||||||
email: "courriel",
|
email: "courriel",
|
||||||
|
|
@ -245,7 +251,7 @@ export default {
|
||||||
SHIFT_OVERLAP: "Il y a un chevauchement entre deux ou plusieurs quarts",
|
SHIFT_OVERLAP: "Il y a un chevauchement entre deux ou plusieurs quarts",
|
||||||
SHIFT_OVERLAP_SHORT: "Chevauchement",
|
SHIFT_OVERLAP_SHORT: "Chevauchement",
|
||||||
INVALID_SHIFT: "Un quart de travail contient des données manquantes ou corrompues",
|
INVALID_SHIFT: "Un quart de travail contient des données manquantes ou corrompues",
|
||||||
SHIFT_TIME_REQUIRED: "Heure requise",
|
SHIFT_TIME_REQUIRED: "Heures manquantes",
|
||||||
SHIFT_TYPE_REQUIRED: "Type requis",
|
SHIFT_TYPE_REQUIRED: "Type requis",
|
||||||
SHIFT_NOT_FOUND: "Quart de travail manquant ou supprimé",
|
SHIFT_NOT_FOUND: "Quart de travail manquant ou supprimé",
|
||||||
PAY_PERIOD_NOT_FOUND: "Aucune période de paie ne correspond aux dates fournies",
|
PAY_PERIOD_NOT_FOUND: "Aucune période de paie ne correspond aux dates fournies",
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
import { useUiStore } from 'src/stores/ui-store';
|
import { useUiStore } from 'src/stores/ui-store';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { RouteNames } from 'src/router/router-constants';
|
import { RouteNames } from 'src/router/router-constants';
|
||||||
import { CAN_APPROVE_PAY_PERIODS } from 'src/modules/shared/models/user.models';
|
import { ModuleNames } from 'src/modules/shared/models/user.models';
|
||||||
|
|
||||||
const auth_store = useAuthStore();
|
const auth_store = useAuthStore();
|
||||||
const ui_store = useUiStore();
|
const ui_store = useUiStore();
|
||||||
|
|
@ -32,155 +32,109 @@
|
||||||
<template>
|
<template>
|
||||||
<q-drawer
|
<q-drawer
|
||||||
v-model="ui_store.is_left_drawer_open"
|
v-model="ui_store.is_left_drawer_open"
|
||||||
:persistent="!ui_store.is_mobile_mode"
|
:persistent="!$q.platform.is.mobile"
|
||||||
mini-to-overlay
|
mini-to-overlay
|
||||||
elevated
|
elevated
|
||||||
side="left"
|
side="left"
|
||||||
:mini="is_mini"
|
:mini="is_mini"
|
||||||
@mouseenter="is_mini = false"
|
@mouseenter="is_mini = false"
|
||||||
@mouseleave="is_mini = true"
|
@mouseleave="is_mini = true"
|
||||||
class="bg-dark z-max"
|
class="bg-dark z-max column no-wrap"
|
||||||
|
:class="!$q.platform.is.mobile && is_mini ? 'items-center' : 'items-start'"
|
||||||
>
|
>
|
||||||
<q-scroll-area class="fit">
|
<!-- Home -->
|
||||||
<q-list>
|
<q-btn
|
||||||
<!-- Home -->
|
flat
|
||||||
<q-item
|
dense
|
||||||
v-ripple
|
no-wrap
|
||||||
clickable
|
size="lg"
|
||||||
side
|
icon="home"
|
||||||
@click="goToPageName(RouteNames.DASHBOARD)"
|
:label="!$q.platform.is.mobile && is_mini ? '' : $t('nav_bar.home')"
|
||||||
>
|
class="col-auto text-uppercase text-weight-bold q-my-xs"
|
||||||
<q-item-section avatar>
|
:class="!$q.platform.is.mobile && is_mini ? '': 'q-px-sm'"
|
||||||
<q-icon
|
@click="goToPageName(RouteNames.DASHBOARD)"
|
||||||
name="home"
|
/>
|
||||||
color="accent"
|
|
||||||
size="lg"
|
|
||||||
/>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label class="text-uppercase text-weight-bold">{{ $t('nav_bar.home') }}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
|
|
||||||
<!-- Timesheet Validation -- Supervisor and Accounting only -->
|
<!-- Timesheet Validation -->
|
||||||
<q-item
|
<q-btn
|
||||||
v-ripple
|
v-if="auth_store.user?.user_module_access.includes(ModuleNames.TIMESHEETS_APPROVAL)"
|
||||||
clickable
|
flat
|
||||||
side
|
dense
|
||||||
@click="goToPageName(RouteNames.TIMESHEET_APPROVALS)"
|
no-wrap
|
||||||
v-if="CAN_APPROVE_PAY_PERIODS.includes(auth_store.user?.role ?? 'GUEST')"
|
size="lg"
|
||||||
>
|
icon="event_available"
|
||||||
<q-item-section avatar>
|
:label="!$q.platform.is.mobile && is_mini ? '' : $t('nav_bar.timesheet_approvals')"
|
||||||
<q-icon
|
class="col-auto text-uppercase text-weight-bold q-my-xs"
|
||||||
name="event_available"
|
:class="!$q.platform.is.mobile && is_mini ? '': 'q-px-sm'"
|
||||||
color="accent"
|
@click="goToPageName(RouteNames.TIMESHEET_APPROVALS)"
|
||||||
size="lg"
|
/>
|
||||||
/>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label class="text-uppercase text-weight-bold">{{ $t('nav_bar.timesheet_approvals')
|
|
||||||
}}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
|
|
||||||
<!-- Employee List -- Supervisor, Accounting and HR only -->
|
<!-- Employee List -->
|
||||||
<q-item
|
<q-btn
|
||||||
v-ripple
|
v-if="auth_store.user?.user_module_access.includes(ModuleNames.EMPLOYEE_LIST)"
|
||||||
clickable
|
flat
|
||||||
side
|
dense
|
||||||
@click="goToPageName(RouteNames.EMPLOYEE_LIST)"
|
no-wrap
|
||||||
v-if="CAN_APPROVE_PAY_PERIODS.includes(auth_store.user?.role ?? 'GUEST')"
|
size="lg"
|
||||||
>
|
icon="groups"
|
||||||
<q-item-section avatar>
|
:label="!$q.platform.is.mobile && is_mini ? '' : $t('nav_bar.employee_list')"
|
||||||
<q-icon
|
class="col-auto text-uppercase text-weight-bold q-my-xs"
|
||||||
name="groups"
|
:class="!$q.platform.is.mobile && is_mini ? '': 'q-px-sm'"
|
||||||
color="accent"
|
@click="goToPageName(RouteNames.EMPLOYEE_LIST)"
|
||||||
size="lg"
|
/>
|
||||||
/>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label class="text-uppercase text-weight-bold">{{ $t('nav_bar.employee_list')
|
|
||||||
}}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
|
|
||||||
<!-- Employee Timesheet temp -- Employee, Supervisor, Accounting only -->
|
<!-- Employee Timesheet -->
|
||||||
<q-item
|
<q-btn
|
||||||
v-ripple
|
v-if="auth_store.user?.user_module_access.includes(ModuleNames.TIMESHEETS)"
|
||||||
clickable
|
flat
|
||||||
side
|
dense
|
||||||
@click="goToPageName(RouteNames.TIMESHEET)"
|
no-wrap
|
||||||
v-if="CAN_APPROVE_PAY_PERIODS.includes(auth_store.user?.role ?? 'GUEST')"
|
size="lg"
|
||||||
>
|
icon="punch_clock"
|
||||||
<q-item-section avatar>
|
:label="!$q.platform.is.mobile && is_mini ? '' : $t('nav_bar.timesheet')"
|
||||||
<q-icon
|
class="col-auto text-uppercase text-weight-bold q-my-xs"
|
||||||
name="punch_clock"
|
:class="!$q.platform.is.mobile && is_mini ? '': 'q-px-sm'"
|
||||||
color="accent"
|
@click="goToPageName(RouteNames.TIMESHEET)"
|
||||||
size="lg"
|
/>
|
||||||
/>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label class="text-uppercase text-weight-bold">{{ $t('nav_bar.timesheet')
|
|
||||||
}}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
|
|
||||||
<!-- Profile -->
|
<!-- Profile -->
|
||||||
<q-item
|
<q-btn
|
||||||
v-ripple
|
v-if="auth_store.user?.user_module_access.includes(ModuleNames.PERSONAL_PROFILE)"
|
||||||
clickable
|
flat
|
||||||
side
|
dense
|
||||||
@click="goToPageName(RouteNames.PROFILE)"
|
no-wrap
|
||||||
>
|
size="lg"
|
||||||
<q-item-section avatar>
|
icon="account_box"
|
||||||
<q-icon
|
:label="!$q.platform.is.mobile && is_mini ? '' : $t('nav_bar.profile')"
|
||||||
name="account_box"
|
class="col-auto text-uppercase text-weight-bold q-my-xs"
|
||||||
color="accent"
|
:class="!$q.platform.is.mobile && is_mini ? '': 'q-px-sm'"
|
||||||
size="lg"
|
@click="goToPageName(RouteNames.PROFILE)"
|
||||||
/>
|
/>
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label class="text-uppercase text-weight-bold">{{ $t('nav_bar.profile') }}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
|
|
||||||
<!-- Help -->
|
<!-- Help -->
|
||||||
<q-item
|
<q-btn
|
||||||
v-ripple
|
flat
|
||||||
clickable
|
dense
|
||||||
@click="goToPageName('help')"
|
no-wrap
|
||||||
>
|
size="lg"
|
||||||
<q-item-section avatar>
|
icon="contact_support"
|
||||||
<q-icon
|
:label="!$q.platform.is.mobile && is_mini ? '' : $t('nav_bar.help')"
|
||||||
name="contact_support"
|
class="col-auto text-uppercase text-weight-bold q-my-xs"
|
||||||
color="accent"
|
:class="!$q.platform.is.mobile && is_mini ? '': 'q-px-sm'"
|
||||||
size="lg"
|
@click="goToPageName(RouteNames.HELP)"
|
||||||
/>
|
/>
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label class="text-uppercase text-weight-bold">{{ $t('nav_bar.help') }}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
|
|
||||||
<!-- Logout -->
|
<!-- Logout -->
|
||||||
<q-item
|
<q-btn
|
||||||
v-ripple
|
flat
|
||||||
clickable
|
dense
|
||||||
@click="handleLogout"
|
no-wrap
|
||||||
class="absolute-bottom"
|
size="lg"
|
||||||
>
|
icon="exit_to_app"
|
||||||
<q-item-section avatar>
|
:label="!$q.platform.is.mobile && is_mini ? '' : $t('nav_bar.logout')"
|
||||||
<q-icon
|
class="col-auto text-uppercase text-weight-bold q-my-xs"
|
||||||
name="exit_to_app"
|
:class="!$q.platform.is.mobile && is_mini ? 'absolute-bottom': 'absolute-bottom-left'"
|
||||||
color="accent"
|
@click="handleLogout"
|
||||||
size="lg"
|
/>
|
||||||
/>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label class="text-uppercase text-weight-bold">{{ $t('nav_bar.logout') }}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-scroll-area>
|
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -5,12 +5,13 @@
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
|
import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
|
||||||
import { useEmployeeStore } from 'src/stores/employee-store';
|
import { useEmployeeStore } from 'src/stores/employee-store';
|
||||||
import { employee_access_options, type ModuleAccessPreset, type ModuleAccessName, employee_access_presets, getEmployeeAccessOptionIcon } from 'src/modules/employee-list/models/employee-profile.models';
|
import { employee_access_options, type ModuleAccessPreset, employee_access_presets, getEmployeeAccessOptionIcon } from 'src/modules/employee-list/models/employee-profile.models';
|
||||||
|
import type { UserModuleAccess } from 'src/modules/shared/models/user.models';
|
||||||
|
|
||||||
const employee_store = useEmployeeStore();
|
const employee_store = useEmployeeStore();
|
||||||
const preset_preview = ref<ModuleAccessPreset>();
|
const preset_preview = ref<ModuleAccessPreset>();
|
||||||
|
|
||||||
const toggleInSelected = (value: ModuleAccessName) => {
|
const toggleInSelected = (value: UserModuleAccess) => {
|
||||||
const i = employee_store.employee.user_module_access.indexOf(value);
|
const i = employee_store.employee.user_module_access.indexOf(value);
|
||||||
if (i === -1) employee_store.employee.user_module_access.push(value);
|
if (i === -1) employee_store.employee.user_module_access.push(value);
|
||||||
else employee_store.employee.user_module_access.splice(i, 1);
|
else employee_store.employee.user_module_access.splice(i, 1);
|
||||||
|
|
@ -21,7 +22,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const getPreviewBackgroundColor = (name: ModuleAccessName) => {
|
const getPreviewBackgroundColor = (name: UserModuleAccess) => {
|
||||||
if (employee_access_presets[preset_preview.value!].includes(name)) {
|
if (employee_access_presets[preset_preview.value!].includes(name)) {
|
||||||
if (!employee_store.employee.user_module_access.includes(name)) return 'bg-info text-white';
|
if (!employee_store.employee.user_module_access.includes(name)) return 'bg-info text-white';
|
||||||
|
|
||||||
|
|
@ -33,7 +34,7 @@
|
||||||
return 'bg-dark';
|
return 'bg-dark';
|
||||||
};
|
};
|
||||||
|
|
||||||
const getBackgroundColor = (name: ModuleAccessName) => {
|
const getBackgroundColor = (name: UserModuleAccess) => {
|
||||||
if (employee_store.employee.user_module_access.includes(name)) return 'bg-accent text-white';
|
if (employee_store.employee.user_module_access.includes(name)) return 'bg-accent text-white';
|
||||||
|
|
||||||
return 'bg-dark';
|
return 'bg-dark';
|
||||||
|
|
@ -142,8 +143,12 @@
|
||||||
class="row full-width cursor-pointer flex-center q-pa-sm rounded-5 no-wrap shadow-5"
|
class="row full-width cursor-pointer flex-center q-pa-sm rounded-5 no-wrap shadow-5"
|
||||||
:class="preset_preview !== undefined ? getPreviewBackgroundColor(option.value) : getBackgroundColor(option.value)"
|
:class="preset_preview !== undefined ? getPreviewBackgroundColor(option.value) : getBackgroundColor(option.value)"
|
||||||
@click="toggleInSelected(option.value)"
|
@click="toggleInSelected(option.value)"
|
||||||
>
|
>
|
||||||
<q-icon :name="getEmployeeAccessOptionIcon(option.value)" size="sm" class="q-mr-sm"/>
|
<q-icon
|
||||||
|
:name="getEmployeeAccessOptionIcon(option.value)"
|
||||||
|
size="sm"
|
||||||
|
class="q-mr-sm"
|
||||||
|
/>
|
||||||
<span class="text-uppercase text-weight-bold non-selectable">
|
<span class="text-uppercase text-weight-bold non-selectable">
|
||||||
{{ $t('employee_management.module_access.' + option.value) }}
|
{{ $t('employee_management.module_access.' + option.value) }}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import type { QSelectOption, QTableColumn } from "quasar";
|
import type { QSelectOption, QTableColumn } from "quasar";
|
||||||
|
import type { UserModuleAccess } from "src/modules/shared/models/user.models";
|
||||||
|
|
||||||
export type ModuleAccessName = 'dashboard' | 'employee_list' | 'employee_management' | 'personal_profile' | 'timesheets' | 'timesheets_approval';
|
|
||||||
export type ModuleAccessPreset = 'admin' | 'supervisor' | 'employee' | 'none';
|
export type ModuleAccessPreset = 'admin' | 'supervisor' | 'employee' | 'none';
|
||||||
export type CompanyNames = 'Targo' | 'Solucom';
|
export type CompanyNames = 'Targo' | 'Solucom';
|
||||||
|
|
||||||
|
|
@ -18,7 +18,7 @@ export class EmployeeProfile {
|
||||||
residence: string;
|
residence: string;
|
||||||
birth_date: string;
|
birth_date: string;
|
||||||
is_supervisor: boolean;
|
is_supervisor: boolean;
|
||||||
user_module_access: ModuleAccessName[];
|
user_module_access: UserModuleAccess[];
|
||||||
preset_id?: number | null;
|
preset_id?: number | null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
@ -100,7 +100,7 @@ export const employee_list_columns: QTableColumn<EmployeeProfile>[] = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const employee_access_options: QSelectOption<ModuleAccessName>[] = [
|
export const employee_access_options: QSelectOption<UserModuleAccess>[] = [
|
||||||
{ label: 'dashboard', value: 'dashboard' },
|
{ label: 'dashboard', value: 'dashboard' },
|
||||||
{ label: 'employee_list', value: 'employee_list' },
|
{ label: 'employee_list', value: 'employee_list' },
|
||||||
{ label: 'personal_profile', value: 'personal_profile' },
|
{ label: 'personal_profile', value: 'personal_profile' },
|
||||||
|
|
@ -109,14 +109,14 @@ export const employee_access_options: QSelectOption<ModuleAccessName>[] = [
|
||||||
{ label: 'timesheets_approval', value: 'timesheets_approval' },
|
{ label: 'timesheets_approval', value: 'timesheets_approval' },
|
||||||
]
|
]
|
||||||
|
|
||||||
export const employee_access_presets: Record<ModuleAccessPreset, ModuleAccessName[]> = {
|
export const employee_access_presets: Record<ModuleAccessPreset, UserModuleAccess[]> = {
|
||||||
'admin' : ['dashboard', 'employee_list', 'employee_management', 'personal_profile', 'timesheets', 'timesheets_approval'],
|
'admin' : ['dashboard', 'employee_list', 'employee_management', 'personal_profile', 'timesheets', 'timesheets_approval'],
|
||||||
'supervisor' : ['dashboard', 'employee_list', 'personal_profile', 'timesheets', 'timesheets_approval'],
|
'supervisor' : ['dashboard', 'employee_list', 'personal_profile', 'timesheets', 'timesheets_approval'],
|
||||||
'employee' : ['dashboard', 'timesheets', 'personal_profile', 'employee_list'],
|
'employee' : ['dashboard', 'timesheets', 'personal_profile', 'employee_list'],
|
||||||
'none' : [],
|
'none' : [],
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getEmployeeAccessOptionIcon = (module: ModuleAccessName): string => {
|
export const getEmployeeAccessOptionIcon = (module: UserModuleAccess): string => {
|
||||||
switch (module) {
|
switch (module) {
|
||||||
case 'dashboard': return 'home';
|
case 'dashboard': return 'home';
|
||||||
case 'employee_list' : return 'groups';
|
case 'employee_list' : return 'groups';
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,18 @@ export interface User {
|
||||||
last_name: string;
|
last_name: string;
|
||||||
email: string;
|
email: string;
|
||||||
role: UserRole;
|
role: UserRole;
|
||||||
|
user_module_access: UserModuleAccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserRole = 'ADMIN' |'SUPERVISOR' | 'HR' | 'ACCOUNTING' | 'EMPLOYEE' | 'DEALER' | 'CUSTOMER' | 'GUEST';
|
export type UserRole = 'ADMIN' | 'SUPERVISOR' | 'HR' | 'ACCOUNTING' | 'EMPLOYEE' | 'DEALER' | 'CUSTOMER' | 'GUEST';
|
||||||
|
|
||||||
export const CAN_APPROVE_PAY_PERIODS: UserRole[] = [
|
export const ModuleNames = {
|
||||||
'ADMIN',
|
DASHBOARD: 'dashboard',
|
||||||
'SUPERVISOR',
|
EMPLOYEE_LIST: 'employee_list',
|
||||||
'HR',
|
EMPLOYEE_MANAGEMENT: 'employee_management',
|
||||||
'ACCOUNTING',
|
PERSONAL_PROFILE: 'personal_profile',
|
||||||
]
|
TIMESHEETS: 'timesheets',
|
||||||
|
TIMESHEETS_APPROVAL: 'timesheets_approval',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type UserModuleAccess = typeof ModuleNames[keyof typeof ModuleNames];
|
||||||
|
|
@ -101,6 +101,7 @@
|
||||||
v-model="is_navigator_open"
|
v-model="is_navigator_open"
|
||||||
transition-show="jump-right"
|
transition-show="jump-right"
|
||||||
transition-hide="jump-right"
|
transition-hide="jump-right"
|
||||||
|
class="z-top"
|
||||||
>
|
>
|
||||||
<q-date
|
<q-date
|
||||||
v-model="expenses_store.current_expense.date"
|
v-model="expenses_store.current_expense.date"
|
||||||
|
|
|
||||||
|
|
@ -56,15 +56,16 @@
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="row items-center">
|
<div class="row items-center">
|
||||||
<span class="col-auto text-uppercase text-weight-bold text-h6 q-ml-lg q-mr-sm">
|
|
||||||
{{ $t('timesheet.expense.add_expense') }}
|
|
||||||
</span>
|
|
||||||
<q-icon
|
<q-icon
|
||||||
v-if="expense_store.mode !== 'create'"
|
name="add_circle_outline"
|
||||||
name="las la-plus-square"
|
|
||||||
size="md"
|
size="md"
|
||||||
class="col-auto"
|
class="col-auto"
|
||||||
|
:class="expense_store.is_showing_create_form ? 'invisible' : ''"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<span class="col-auto text-uppercase text-weight-bold text-h6 q-ml-xs q-mr-sm">
|
||||||
|
{{ $t('timesheet.expense.add_expense') }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,7 @@
|
||||||
v-model="is_navigator_open"
|
v-model="is_navigator_open"
|
||||||
transition-show="jump-right"
|
transition-show="jump-right"
|
||||||
transition-hide="jump-right"
|
transition-hide="jump-right"
|
||||||
|
class="z-top"
|
||||||
>
|
>
|
||||||
<q-date
|
<q-date
|
||||||
v-model="expenses_store.current_expense.date"
|
v-model="expenses_store.current_expense.date"
|
||||||
|
|
@ -217,12 +218,12 @@
|
||||||
class="col-auto"
|
class="col-auto"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<q-dialog v-model="is_showing_comment_dialog_mobile">
|
<q-dialog v-model="is_showing_comment_dialog_mobile" class="z-top">
|
||||||
<q-card class="full-width bg-secondary rounded-10">
|
<q-card class="full-width bg-primary rounded-10">
|
||||||
<q-card-section class="q-pa-none">
|
<q-card-section class="q-pa-none">
|
||||||
<span
|
<span
|
||||||
class="text-weight-bold text-accent text-uppercase text-caption"
|
class="text-weight-bold text-accent text-uppercase text-caption q-mx-md"
|
||||||
style="font-size: 1.5em;"
|
style="font-size: 1.2em;"
|
||||||
>
|
>
|
||||||
{{ $t('timesheet.expense.employee_comment') }}
|
{{ $t('timesheet.expense.employee_comment') }}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -231,14 +232,13 @@
|
||||||
<q-card-section class="q-pa-none bg-primary rounded-10">
|
<q-card-section class="q-pa-none bg-primary rounded-10">
|
||||||
<q-input
|
<q-input
|
||||||
v-model="expenses_store.current_expense.comment"
|
v-model="expenses_store.current_expense.comment"
|
||||||
standout="bg-blue-grey-9"
|
filled
|
||||||
dense
|
dense
|
||||||
hide-bottom-space
|
hide-bottom-space
|
||||||
color="primary"
|
color="accent"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
:maxlength="COMMENT_MAX_LENGTH"
|
:maxlength="COMMENT_MAX_LENGTH"
|
||||||
lazy-rules="ondemand"
|
class="bg-white no-border"
|
||||||
:rules="[rules.commentRequired]"
|
|
||||||
>
|
>
|
||||||
</q-input>
|
</q-input>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,13 @@
|
||||||
const select_ref = ref<QSelect | null>(null);
|
const select_ref = ref<QSelect | null>(null);
|
||||||
const is_showing_comment_popup = ref(false);
|
const is_showing_comment_popup = ref(false);
|
||||||
const comment_length = computed(() => shift.value.comment?.length ?? 0);
|
const comment_length = computed(() => shift.value.comment?.length ?? 0);
|
||||||
|
const error_message = ref('');
|
||||||
|
|
||||||
const { dense = false, hasShiftAfter = false, isTimesheetApproved = false } = defineProps<{
|
const { errorMessage = undefined, dense = false, hasShiftAfter = false, isTimesheetApproved = false } = defineProps<{
|
||||||
dense?: boolean;
|
dense?: boolean;
|
||||||
hasShiftAfter?: boolean;
|
hasShiftAfter?: boolean;
|
||||||
isTimesheetApproved?: boolean;
|
isTimesheetApproved?: boolean;
|
||||||
|
errorMessage?: string | undefined;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|
@ -37,6 +39,17 @@
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onTimeFieldBlur = (time_string: string) => {
|
||||||
|
if (time_string.length < 1 || !time_string) {
|
||||||
|
shift.value.has_error = true;
|
||||||
|
error_message.value = 'timesheet.errors.SHIFT_TIME_REQUIRED';
|
||||||
|
} else {
|
||||||
|
shift.value.has_error = false;
|
||||||
|
error_message.value = '';
|
||||||
|
emit('onTimeFieldBlur');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const getCommentCounterColor = (comment_length: number) => {
|
const getCommentCounterColor = (comment_length: number) => {
|
||||||
if (comment_length < 200) return 'primary';
|
if (comment_length < 200) return 'primary';
|
||||||
if (comment_length < 250) return 'warning';
|
if (comment_length < 250) return 'warning';
|
||||||
|
|
@ -50,6 +63,9 @@
|
||||||
shift_type_selected.value = undefined;
|
shift_type_selected.value = undefined;
|
||||||
ui_store.focus_next_component = false;
|
ui_store.focus_next_component = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (errorMessage)
|
||||||
|
error_message.value = errorMessage;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -162,6 +178,7 @@
|
||||||
anchor="top middle"
|
anchor="top middle"
|
||||||
self="bottom middle"
|
self="bottom middle"
|
||||||
:offset="[0, 10]"
|
:offset="[0, 10]"
|
||||||
|
:hide-delay="1000"
|
||||||
class="text-uppercase text-weight-bold text-white bg-primary"
|
class="text-uppercase text-weight-bold text-white bg-primary"
|
||||||
>
|
>
|
||||||
{{ shift.is_remote ? $t('timesheet.shift.types.REMOTE') :
|
{{ shift.is_remote ? $t('timesheet.shift.types.REMOTE') :
|
||||||
|
|
@ -184,6 +201,7 @@
|
||||||
anchor="top middle"
|
anchor="top middle"
|
||||||
self="bottom middle"
|
self="bottom middle"
|
||||||
:offset="[0, 10]"
|
:offset="[0, 10]"
|
||||||
|
:hide-delay="1000"
|
||||||
class="text-uppercase text-weight-medium text-white bg-accent"
|
class="text-uppercase text-weight-medium text-white bg-accent"
|
||||||
>
|
>
|
||||||
{{ shift.is_remote ? $t('timesheet.shift.types.REMOTE') :
|
{{ shift.is_remote ? $t('timesheet.shift.types.REMOTE') :
|
||||||
|
|
@ -208,13 +226,15 @@
|
||||||
lazy-rules
|
lazy-rules
|
||||||
no-error-icon
|
no-error-icon
|
||||||
hide-bottom-space
|
hide-bottom-space
|
||||||
|
:error="shift.has_error"
|
||||||
|
:error-message="errorMessage || error_message !== '' ? $t(errorMessage ?? error_message) : ''"
|
||||||
:label-color="!shift.is_approved ? 'accent' : 'white'"
|
:label-color="!shift.is_approved ? 'accent' : 'white'"
|
||||||
class="rounded-5 bg-dark"
|
class="rounded-5 bg-dark"
|
||||||
:class="(shift.id === -2 ? 'bg-negative ' : ' ') + (!shift.is_approved && !isTimesheetApproved ? '' : 'cursor-not-allowed inset-shadow')"
|
:class="(shift.id === -2 ? 'bg-negative ' : ' ') + (!shift.is_approved && !isTimesheetApproved ? '' : 'cursor-not-allowed inset-shadow')"
|
||||||
:input-class="'text-weight-medium ' + (shift.id === -2 ? 'text-white ' : ' ') + (shift.is_approved ? 'text-white cursor-not-allowed q-px-sm' : '')"
|
:input-class="'text-weight-medium ' + (shift.id === -2 ? 'text-white ' : ' ') + (shift.is_approved ? 'text-white cursor-not-allowed q-px-sm' : '')"
|
||||||
input-style="font-size: 1.2em;"
|
input-style="font-size: 1.2em;"
|
||||||
:style="shift.is_approved ? 'background-color: #0a7d32 !important;' : ''"
|
:style="shift.is_approved ? 'background-color: #0a7d32 !important;' : ''"
|
||||||
@blur="emit('onTimeFieldBlur')"
|
@blur="onTimeFieldBlur(shift.start_time)"
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
<span
|
<span
|
||||||
|
|
@ -239,13 +259,15 @@
|
||||||
label-slot
|
label-slot
|
||||||
no-error-icon
|
no-error-icon
|
||||||
hide-bottom-space
|
hide-bottom-space
|
||||||
|
:error="shift.has_error"
|
||||||
|
:error-message="errorMessage || error_message !== '' ? $t(errorMessage ?? error_message) : ''"
|
||||||
:label-color="!shift.is_approved ? 'accent' : 'white'"
|
:label-color="!shift.is_approved ? 'accent' : 'white'"
|
||||||
:input-class="'text-weight-medium ' + (shift.id === -2 ? 'text-white ' : ' ') + (shift.is_approved ? 'text-white cursor-not-allowed q-px-sm' : '')"
|
:input-class="'text-weight-medium ' + (shift.id === -2 ? 'text-white ' : ' ') + (shift.is_approved ? 'text-white cursor-not-allowed q-px-sm' : '')"
|
||||||
input-style="font-size: 1.2em;"
|
input-style="font-size: 1.2em;"
|
||||||
class="rounded-5 bg-dark"
|
class="rounded-5 bg-dark"
|
||||||
:class="(shift.id === -2 ? 'bg-negative ' : ' ') + (shift.is_approved ? 'cursor-not-allowed q-px-xs transparent inset-shadow' : (isTimesheetApproved ? 'inset-shadow' : ''))"
|
:class="(shift.id === -2 ? 'bg-negative ' : ' ') + (shift.is_approved ? 'cursor-not-allowed q-px-xs transparent inset-shadow' : (isTimesheetApproved ? 'inset-shadow' : ''))"
|
||||||
:style="shift.is_approved ? 'background-color: #0a7d32 !important;' : ''"
|
:style="shift.is_approved ? 'background-color: #0a7d32 !important;' : ''"
|
||||||
@blur="emit('onTimeFieldBlur')"
|
@blur="onTimeFieldBlur(shift.end_time)"
|
||||||
>
|
>
|
||||||
<template #label>
|
<template #label>
|
||||||
<span
|
<span
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
<script
|
||||||
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
|
import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api';
|
||||||
|
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||||
|
|
||||||
|
const timesheet_store = useTimesheetStore();
|
||||||
|
const timesheet_api = useTimesheetApi();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="$q.platform.is.mobile && $q.screen.width < $q.screen.height"
|
||||||
|
class="col-auto row items-start q-px-sm q-pt-sm full-width"
|
||||||
|
>
|
||||||
|
<!-- per timesheet -->
|
||||||
|
<div
|
||||||
|
v-for="timesheet, timesheet_index in timesheet_store.timesheets"
|
||||||
|
:key="timesheet_index"
|
||||||
|
class="col column flex-center q-pa-sm"
|
||||||
|
>
|
||||||
|
<!-- container -->
|
||||||
|
<div
|
||||||
|
class="rounded-5 relative-position q-px-sm q-pt-sm q-pb-xs full-width shadow-4"
|
||||||
|
style="border: 1px solid var(--q-accent);"
|
||||||
|
>
|
||||||
|
<!-- label for week number -->
|
||||||
|
<div
|
||||||
|
class="self-start text-uppercase text-weight-bolder text-accent bg-secondary absolute-top-left q-px-xs"
|
||||||
|
style="font-size: 0.8em; top: -7px; left: 10px; line-height: 1em;"
|
||||||
|
>{{ $t('timesheet.week') + ` ${timesheet_index + 1}` }}</div>
|
||||||
|
|
||||||
|
<!-- hours worked in the week -->
|
||||||
|
<div class="col-auto row">
|
||||||
|
<span class="text-weight-bolder text-uppercase text-accent text-caption q-mr-sm">{{
|
||||||
|
$t('timesheet.total_hours') }}</span>
|
||||||
|
<span>{{
|
||||||
|
(timesheet.weekly_hours.regular +
|
||||||
|
timesheet.weekly_hours.evening +
|
||||||
|
timesheet.weekly_hours.emergency +
|
||||||
|
timesheet.weekly_hours.overtime).toFixed(2)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- label for current shifts preview -->
|
||||||
|
<div
|
||||||
|
class="col-auto full-width text-center text-weight-medium text-caption text-uppercase q-mt-xs"
|
||||||
|
style="font-size: 0.65em; line-height: 1.2em;"
|
||||||
|
> {{ $t('timesheet.current_shifts') }}</div>
|
||||||
|
|
||||||
|
<!-- preview of current number of shifts -->
|
||||||
|
<div
|
||||||
|
class="col row flex-center"
|
||||||
|
style="height: 20px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="day, day_index in timesheet.days"
|
||||||
|
:key="day_index"
|
||||||
|
class="col row flex-center"
|
||||||
|
>
|
||||||
|
<q-badge
|
||||||
|
:color="day.shifts.length > 0 ? (day.shifts.every(shift => shift.is_approved) ? 'accent shadow-2' : 'dark shadow-2') : 'blue-grey-5'"
|
||||||
|
:class="day.shifts.length > 0 ? (day.shifts.every(shift => shift.is_approved) ? 'q-px-xs' : 'q-pa-sm') : ''"
|
||||||
|
:style="day.shifts.length > 0 ? '' : 'opacity: 0.5'"
|
||||||
|
>
|
||||||
|
<q-icon
|
||||||
|
v-if="day.shifts.every(shift => shift.is_approved) && day.shifts.length > 0"
|
||||||
|
name="check"
|
||||||
|
class="q-pa-none"
|
||||||
|
/>
|
||||||
|
</q-badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- button to apply weekly schedule preset -->
|
||||||
|
<div class="col-auto flex-center row q-pt-xs full-width">
|
||||||
|
<q-btn
|
||||||
|
v-if="timesheet.days.every(day => day.shifts.length < 1)"
|
||||||
|
push
|
||||||
|
dense
|
||||||
|
color="accent"
|
||||||
|
:label="$t('timesheet.apply_preset')"
|
||||||
|
class="full-width"
|
||||||
|
@click="timesheet_api.applyPreset(timesheet.timesheet_id)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -2,52 +2,42 @@
|
||||||
setup
|
setup
|
||||||
lang="ts"
|
lang="ts"
|
||||||
>
|
>
|
||||||
/* eslint-disable*/
|
import { onMounted, ref } from 'vue';
|
||||||
import { onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import { QSelect, QInput } from 'quasar';
|
import { QSelect, QInput } from 'quasar';
|
||||||
import { Shift } from 'src/modules/timesheets/models/shift.models';
|
|
||||||
import { useUiStore } from 'src/stores/ui-store';
|
import { useUiStore } from 'src/stores/ui-store';
|
||||||
import { SHIFT_OPTIONS } from 'src/modules/timesheets/utils/shift.util';
|
import { SHIFT_OPTIONS } from 'src/modules/timesheets/utils/shift.util';
|
||||||
|
import type { Shift } from 'src/modules/timesheets/models/shift.models';
|
||||||
|
|
||||||
let timer: NodeJS.Timeout;
|
|
||||||
const { t } = useI18n();
|
|
||||||
const ui_store = useUiStore();
|
const ui_store = useUiStore();
|
||||||
|
|
||||||
const COMMENT_LENGTH_MAX = 280;
|
const COMMENT_LENGTH_MAX = 280;
|
||||||
|
|
||||||
const shift = defineModel<Shift>('shift', { required: true });
|
const shift = defineModel<Shift>('shift', { required: true });
|
||||||
const shift_type_selected = ref(SHIFT_OPTIONS.find(option => option.value == shift.value.type));
|
const shift_type_selected = ref(SHIFT_OPTIONS.find(option => option.value == shift.value.type));
|
||||||
const select_ref = useTemplateRef<QSelect>('select');
|
const select_ref = ref<QSelect | null>(null);
|
||||||
const start_time_ref = useTemplateRef<QInput>('start_time');
|
const error_message = ref('');
|
||||||
const end_time_ref = useTemplateRef<QInput>('end_time');
|
|
||||||
|
|
||||||
const { dense = false, hasShiftAfter = false, isTimesheetApproved = false } = defineProps<{
|
const { errorMessage = undefined, isTimesheetApproved = false } = defineProps<{
|
||||||
dense?: boolean;
|
dense?: boolean;
|
||||||
hasShiftAfter?: boolean;
|
|
||||||
isTimesheetApproved?: boolean;
|
isTimesheetApproved?: boolean;
|
||||||
|
errorMessage?: string | undefined;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'saveComment': [comment: string, shift_id: number];
|
|
||||||
'requestDelete': [void];
|
'requestDelete': [void];
|
||||||
'onTimeFieldBlur': [void];
|
'onTimeFieldBlur': [void];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const onBlurShiftTypeSelect = () => {
|
const onTimeFieldBlur = (time_string: string) => {
|
||||||
if (shift_type_selected.value === undefined) {
|
if (time_string.length < 1 || !time_string) {
|
||||||
shift.value.type = 'REGULAR';
|
shift.value.has_error = true;
|
||||||
shift.value.id = 0;
|
error_message.value = 'timesheet.errors.SHIFT_TIME_REQUIRED'
|
||||||
emit('requestDelete');
|
} else {
|
||||||
}
|
shift.value.has_error = false;
|
||||||
};
|
error_message.value = '';
|
||||||
|
emit('onTimeFieldBlur');
|
||||||
const slideDeleteShift = async (reset: () => void) => {
|
}
|
||||||
timer = setTimeout(() => {
|
}
|
||||||
reset();
|
|
||||||
emit('requestDelete');
|
|
||||||
}, 200);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCommentCounterColor = (comment_length: number) => {
|
const getCommentCounterColor = (comment_length: number) => {
|
||||||
if (comment_length < 200) return 'primary';
|
if (comment_length < 200) return 'primary';
|
||||||
|
|
@ -62,43 +52,193 @@
|
||||||
shift_type_selected.value = undefined;
|
shift_type_selected.value = undefined;
|
||||||
ui_store.focus_next_component = false;
|
ui_store.focus_next_component = false;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
if (errorMessage)
|
||||||
clearTimeout(timer);
|
error_message.value = errorMessage;
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(() => [start_time_ref.value?.hasError, end_time_ref.value?.hasError], ([start_error, end_error]) => {
|
|
||||||
shift.value.has_error = (start_error || end_error) ?? false;
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-slide-item
|
<div :class="ui_store.is_mobile_mode ? 'column' : 'row'">
|
||||||
right-color="negative"
|
<div
|
||||||
class="rounded-5 transparent"
|
class="row items-center text-uppercase rounded-5"
|
||||||
:class="ui_store.is_mobile_mode ? 'q-my-md' : 'q-mr-xs'"
|
:class="ui_store.is_mobile_mode ? 'col q-mb-xs q-px-xs' : 'col-4'"
|
||||||
@right="details => slideDeleteShift(details.reset)"
|
|
||||||
>
|
|
||||||
<template
|
|
||||||
#right
|
|
||||||
v-if="ui_store.is_mobile_mode"
|
|
||||||
>
|
>
|
||||||
<q-icon name="delete" />
|
<!-- shift type -->
|
||||||
</template>
|
<q-select
|
||||||
|
ref="select"
|
||||||
<div :class="ui_store.is_mobile_mode ? 'column' : 'row'">
|
v-model="shift_type_selected"
|
||||||
<div
|
:standout="$q.dark.isActive ? 'bg-blue-grey-3' : 'bg-blue-grey-9'"
|
||||||
class="row items-center text-uppercase rounded-5"
|
dense
|
||||||
:class="ui_store.is_mobile_mode ? 'col q-mb-xs q-px-xs' : 'col-4'"
|
:borderless="(shift.is_approved && isTimesheetApproved)"
|
||||||
|
:readonly="(shift.is_approved && isTimesheetApproved)"
|
||||||
|
:options-dense="!ui_store.is_mobile_mode"
|
||||||
|
hide-dropdown-icon
|
||||||
|
:menu-offset="[0, 10]"
|
||||||
|
menu-anchor="bottom middle"
|
||||||
|
menu-self="top middle"
|
||||||
|
:options="SHIFT_OPTIONS"
|
||||||
|
class="col rounded-5 q-mx-xs bg-dark"
|
||||||
|
:class="!shift.is_approved && !isTimesheetApproved ? '' : 'inset-shadow'"
|
||||||
|
popup-content-class="text-uppercase text-weight-bold text-center rounded-5"
|
||||||
|
:style="shift.is_approved ? 'background-color: #0a7d32 !important;' : ''"
|
||||||
|
popup-content-style="border: 2px solid var(--q-accent)"
|
||||||
|
@update:model-value="option => shift.type = option.value"
|
||||||
>
|
>
|
||||||
<!-- mobile comment button -->
|
<template #selected-item="scope">
|
||||||
|
<div
|
||||||
|
class="row items-center text-weight-bold q-ma-none q-pa-none no-wrap ellipsis full-width"
|
||||||
|
:class="ui_store.is_mobile_mode ? 'full-height' : ''"
|
||||||
|
:tabindex="scope.tabindex"
|
||||||
|
>
|
||||||
|
<q-icon
|
||||||
|
:name="scope.opt.icon"
|
||||||
|
:color="scope.opt.icon_color"
|
||||||
|
size="sm"
|
||||||
|
class="col-auto"
|
||||||
|
:class="shift.is_approved ? 'q-mx-xs' : 'q-mr-xs'"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
style="line-height: 1.2em;"
|
||||||
|
class="col-auto ellipsis"
|
||||||
|
:class="!shift.is_approved ? '' : 'text-white'"
|
||||||
|
>
|
||||||
|
{{ $t(scope.opt.label) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #after>
|
||||||
|
<q-icon
|
||||||
|
v-if="shift.is_approved"
|
||||||
|
:name="shift.is_remote ? 'las la-laptop' : 'las la-building'"
|
||||||
|
size="1.2em"
|
||||||
|
color="white"
|
||||||
|
class="q-mr-sm"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
anchor="top middle"
|
||||||
|
self="bottom middle"
|
||||||
|
:offset="[0, 10]"
|
||||||
|
class="text-uppercase text-weight-bold text-white bg-primary"
|
||||||
|
>
|
||||||
|
{{ shift.is_remote ? $t('timesheet.shift.types.REMOTE') :
|
||||||
|
$t('timesheet.shift.types.OFFICE') }}
|
||||||
|
</q-tooltip>
|
||||||
|
</q-icon>
|
||||||
|
|
||||||
|
<q-toggle
|
||||||
|
v-else
|
||||||
|
v-model="shift.is_remote"
|
||||||
|
:disable="shift.is_approved"
|
||||||
|
dense
|
||||||
|
keep-color
|
||||||
|
size="3em"
|
||||||
|
color="accent"
|
||||||
|
icon="las la-building"
|
||||||
|
checked-icon="las la-laptop"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
anchor="top middle"
|
||||||
|
self="bottom middle"
|
||||||
|
:offset="[0, 10]"
|
||||||
|
class="text-uppercase text-weight-medium text-white bg-accent"
|
||||||
|
>
|
||||||
|
{{ shift.is_remote ? $t('timesheet.shift.types.REMOTE') :
|
||||||
|
$t('timesheet.shift.types.OFFICE') }}
|
||||||
|
</q-tooltip>
|
||||||
|
</q-toggle>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col row items-start text-uppercase rounded-5 q-pa-xs">
|
||||||
|
<!-- punch in field -->
|
||||||
|
<q-input
|
||||||
|
ref="start_time"
|
||||||
|
v-model="shift.start_time"
|
||||||
|
dense
|
||||||
|
:borderless="(shift.is_approved && isTimesheetApproved)"
|
||||||
|
:readonly="(shift.is_approved && isTimesheetApproved)"
|
||||||
|
type="time"
|
||||||
|
:standout="$q.dark.isActive ? 'bg-blue-grey-3' : 'bg-blue-grey-9'"
|
||||||
|
label-slot
|
||||||
|
lazy-rules
|
||||||
|
no-error-icon
|
||||||
|
hide-bottom-space
|
||||||
|
:error="shift.has_error"
|
||||||
|
:error-message="errorMessage || error_message !== '' ? $t(errorMessage ?? error_message) : ''"
|
||||||
|
:label-color="!shift.is_approved ? 'accent' : 'white'"
|
||||||
|
class="col rounded-5 bg-dark q-mx-xs"
|
||||||
|
:class="(shift.id === -2 ? 'bg-negative ' : ' ') + (!shift.is_approved && !isTimesheetApproved ? '' : 'cursor-not-allowed inset-shadow')"
|
||||||
|
:input-class="'text-weight-medium ' + (shift.id === -2 ? 'text-white ' : ' ') + (shift.is_approved ? 'text-white cursor-not-allowed q-px-sm' : '')"
|
||||||
|
input-style="font-size: 1.2em;"
|
||||||
|
:style="shift.is_approved ? 'background-color: #0a7d32 !important;' : ''"
|
||||||
|
@blur="onTimeFieldBlur(shift.start_time)"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<span
|
||||||
|
class="text-weight-bolder"
|
||||||
|
:class="shift.is_approved ? ' q-ml-md' : ''"
|
||||||
|
style="font-size: 0.95em;"
|
||||||
|
>{{ $t('shared.misc.in') }}</span>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
|
||||||
|
<!-- punch out field -->
|
||||||
|
<q-input
|
||||||
|
ref="end_time"
|
||||||
|
v-model="shift.end_time"
|
||||||
|
:standout="$q.dark.isActive ? 'bg-blue-grey-3' : 'bg-blue-grey-9'"
|
||||||
|
dense
|
||||||
|
:borderless="(shift.is_approved && isTimesheetApproved)"
|
||||||
|
:readonly="(shift.is_approved && isTimesheetApproved)"
|
||||||
|
type="time"
|
||||||
|
label-slot
|
||||||
|
no-error-icon
|
||||||
|
hide-bottom-space
|
||||||
|
:error="shift.has_error"
|
||||||
|
:error-message="errorMessage || error_message !== '' ? $t(errorMessage ?? error_message) : ''"
|
||||||
|
:label-color="!shift.is_approved ? 'accent' : 'white'"
|
||||||
|
:input-class="'text-weight-medium ' + (shift.id === -2 ? 'text-white ' : ' ') + (shift.is_approved ? 'text-white cursor-not-allowed q-px-sm' : '')"
|
||||||
|
input-style="font-size: 1.2em;"
|
||||||
|
class="col rounded-5 bg-dark q-mx-xs"
|
||||||
|
:class="(shift.id === -2 ? 'bg-negative ' : ' ') + (shift.is_approved ? 'cursor-not-allowed q-px-xs transparent inset-shadow' : (isTimesheetApproved ? 'inset-shadow' : ''))"
|
||||||
|
:style="shift.is_approved ? 'background-color: #0a7d32 !important;' : ''"
|
||||||
|
@blur="onTimeFieldBlur(shift.end_time)"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<span
|
||||||
|
class="text-weight-bolder"
|
||||||
|
:class="shift.is_approved ? ' q-ml-md' : ''"
|
||||||
|
style="font-size: 0.95em;"
|
||||||
|
>{{ $t('shared.misc.out') }}</span>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
|
||||||
|
<!-- comment and delete buttons -->
|
||||||
|
<div
|
||||||
|
class="row full-height"
|
||||||
|
:class="ui_store.is_mobile_mode ? 'col-12' : 'col-auto flex-center'"
|
||||||
|
>
|
||||||
|
<!-- desktop comment button -->
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="ui_store.is_mobile_mode && !dense"
|
v-if="!ui_store.is_mobile_mode"
|
||||||
|
push
|
||||||
|
dense
|
||||||
|
:color="shift.is_approved ? 'white' : 'accent'"
|
||||||
:icon="shift.comment ? 'chat' : 'chat_bubble_outline'"
|
:icon="shift.comment ? 'chat' : 'chat_bubble_outline'"
|
||||||
:text-color="shift.comment ? ((shift.is_approved && isTimesheetApproved) ? 'white' : 'accent') : 'grey-5'"
|
:text-color="shift.is_approved ? 'accent' : 'white'"
|
||||||
class="col-auto full-height q-mx-xs rounded-5 shadow-1"
|
class="col"
|
||||||
|
:class="ui_store.is_mobile_mode ? 'q-mt-xs bg-dark' : ''"
|
||||||
>
|
>
|
||||||
|
<q-badge
|
||||||
|
v-if="shift.comment"
|
||||||
|
floating
|
||||||
|
rounded
|
||||||
|
color="negative"
|
||||||
|
/>
|
||||||
|
|
||||||
<q-popup-edit
|
<q-popup-edit
|
||||||
v-model="shift.comment"
|
v-model="shift.comment"
|
||||||
:title="$t('timesheet.shift.fields.header_comment')"
|
:title="$t('timesheet.shift.fields.header_comment')"
|
||||||
|
|
@ -110,13 +250,13 @@
|
||||||
color="white"
|
color="white"
|
||||||
v-model="scope.value"
|
v-model="scope.value"
|
||||||
dense
|
dense
|
||||||
:readonly="(shift.is_approved || isTimesheetApproved)"
|
:readonly="shift.is_approved"
|
||||||
autofocus
|
autofocus
|
||||||
counter
|
counter
|
||||||
bottom-slots
|
bottom-slots
|
||||||
:maxlength="COMMENT_LENGTH_MAX"
|
:maxlength="COMMENT_LENGTH_MAX"
|
||||||
class="q-pb-lg"
|
class="q-pb-lg"
|
||||||
:class="(shift.is_approved || isTimesheetApproved) ? 'cursor-not-allowed' : ''"
|
:class="shift.is_approved ? 'cursor-not-allowed' : ''"
|
||||||
@keyup.enter="scope.set"
|
@keyup.enter="scope.set"
|
||||||
>
|
>
|
||||||
<template #append>
|
<template #append>
|
||||||
|
|
@ -145,245 +285,23 @@
|
||||||
</q-popup-edit>
|
</q-popup-edit>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
|
|
||||||
<!-- shift type -->
|
<q-btn
|
||||||
<q-select
|
v-if="!shift.is_approved"
|
||||||
ref="select"
|
flat
|
||||||
v-model="shift_type_selected"
|
|
||||||
:standout="$q.dark.isActive ? 'bg-blue-grey-3' : 'bg-blue-grey-9'"
|
|
||||||
dense
|
dense
|
||||||
:borderless="(shift.is_approved && isTimesheetApproved)"
|
:disable="shift.is_approved"
|
||||||
:readonly="(shift.is_approved && isTimesheetApproved)"
|
tabindex="-1"
|
||||||
:options-dense="!ui_store.is_mobile_mode"
|
icon="las la-trash"
|
||||||
hide-dropdown-icon
|
text-color="negative"
|
||||||
:menu-offset="[0, 10]"
|
class="col"
|
||||||
menu-anchor="bottom middle"
|
size="1.2em"
|
||||||
menu-self="top middle"
|
:class="shift.is_approved ? 'invisible' : ''"
|
||||||
:options="SHIFT_OPTIONS"
|
@click="$emit('requestDelete')"
|
||||||
class="col rounded-5 q-mx-xs bg-dark"
|
|
||||||
:class="!shift.is_approved && !isTimesheetApproved ? '' : 'inset-shadow'"
|
|
||||||
popup-content-class="text-uppercase text-weight-bold text-center rounded-5"
|
|
||||||
:style="shift.is_approved ? 'background-color: #0a7d32 !important;' : ''"
|
|
||||||
popup-content-style="border: 2px solid var(--q-accent)"
|
|
||||||
@blur="onBlurShiftTypeSelect"
|
|
||||||
@update:model-value="option => shift.type = option.value"
|
|
||||||
>
|
>
|
||||||
<template #selected-item="scope">
|
</q-btn>
|
||||||
<div
|
|
||||||
class="row items-center text-weight-bold q-ma-none q-pa-none no-wrap ellipsis full-width"
|
|
||||||
:class="ui_store.is_mobile_mode ? 'full-height' : ''"
|
|
||||||
:tabindex="scope.tabindex"
|
|
||||||
>
|
|
||||||
<q-icon
|
|
||||||
:name="scope.opt.icon"
|
|
||||||
:color="scope.opt.icon_color"
|
|
||||||
size="sm"
|
|
||||||
class="col-auto"
|
|
||||||
:class="shift.is_approved ? 'q-mx-xs': 'q-mr-xs'"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
style="line-height: 1.2em;"
|
|
||||||
class="col-auto ellipsis"
|
|
||||||
:class="!shift.is_approved ? '' : 'text-white'"
|
|
||||||
>
|
|
||||||
{{ $t(scope.opt.label) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #after>
|
|
||||||
<q-icon
|
|
||||||
v-if="shift.is_approved"
|
|
||||||
:name="shift.is_remote ? 'las la-laptop' : 'las la-building'"
|
|
||||||
size="1.2em"
|
|
||||||
color="white"
|
|
||||||
class="q-mr-sm"
|
|
||||||
>
|
|
||||||
<q-tooltip
|
|
||||||
anchor="top middle"
|
|
||||||
self="bottom middle"
|
|
||||||
:offset="[0, 10]"
|
|
||||||
class="text-uppercase text-weight-bold text-white bg-primary"
|
|
||||||
>
|
|
||||||
{{ shift.is_remote ? $t('timesheet.shift.types.REMOTE') :
|
|
||||||
$t('timesheet.shift.types.OFFICE') }}
|
|
||||||
</q-tooltip>
|
|
||||||
</q-icon>
|
|
||||||
|
|
||||||
<q-toggle
|
|
||||||
v-else
|
|
||||||
v-model="shift.is_remote"
|
|
||||||
:disable="shift.is_approved"
|
|
||||||
dense
|
|
||||||
keep-color
|
|
||||||
size="3em"
|
|
||||||
color="accent"
|
|
||||||
icon="las la-building"
|
|
||||||
checked-icon="las la-laptop"
|
|
||||||
>
|
|
||||||
<q-tooltip
|
|
||||||
anchor="top middle"
|
|
||||||
self="bottom middle"
|
|
||||||
:offset="[0, 10]"
|
|
||||||
class="text-uppercase text-weight-medium text-white bg-accent"
|
|
||||||
>
|
|
||||||
{{ shift.is_remote ? $t('timesheet.shift.types.REMOTE') :
|
|
||||||
$t('timesheet.shift.types.OFFICE') }}
|
|
||||||
</q-tooltip>
|
|
||||||
</q-toggle>
|
|
||||||
</template>
|
|
||||||
</q-select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col row items-start text-uppercase rounded-5 q-pa-xs">
|
|
||||||
<!-- punch in field -->
|
|
||||||
<q-input
|
|
||||||
ref="start_time"
|
|
||||||
v-model="shift.start_time"
|
|
||||||
dense
|
|
||||||
:borderless="(shift.is_approved && isTimesheetApproved)"
|
|
||||||
:readonly="(shift.is_approved && isTimesheetApproved)"
|
|
||||||
type="time"
|
|
||||||
:standout="$q.dark.isActive ? 'bg-blue-grey-3' : 'bg-blue-grey-9'"
|
|
||||||
label-slot
|
|
||||||
lazy-rules
|
|
||||||
no-error-icon
|
|
||||||
hide-bottom-space
|
|
||||||
:label-color="!shift.is_approved ? 'accent' : 'white'"
|
|
||||||
class="col rounded-5 bg-dark"
|
|
||||||
:class="(shift.id === -2 ? 'bg-negative ' : ' ') + (ui_store.is_mobile_mode ? 'q-mr-xs ' : 'q-mx-xs ') + (!shift.is_approved && !isTimesheetApproved ? '' : 'cursor-not-allowed inset-shadow')"
|
|
||||||
:input-class="'text-weight-medium ' + (shift.id === -2 ? 'text-white ' : ' ') + (shift.is_approved ? 'text-white cursor-not-allowed q-px-sm' : '')"
|
|
||||||
input-style="font-size: 1.2em;"
|
|
||||||
:style="shift.is_approved ? 'background-color: #0a7d32 !important;' : ''"
|
|
||||||
@blur="emit('onTimeFieldBlur')"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
<span
|
|
||||||
class="text-weight-bolder"
|
|
||||||
:class="shift.is_approved ? ' q-ml-md' : ''"
|
|
||||||
style="font-size: 0.95em;"
|
|
||||||
>{{ $t('shared.misc.in') }}</span>
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
|
|
||||||
<!-- punch out field -->
|
|
||||||
<q-input
|
|
||||||
ref="end_time"
|
|
||||||
v-model="shift.end_time"
|
|
||||||
:standout="$q.dark.isActive ? 'bg-blue-grey-3' : 'bg-blue-grey-9'"
|
|
||||||
dense
|
|
||||||
:borderless="(shift.is_approved && isTimesheetApproved)"
|
|
||||||
:readonly="(shift.is_approved && isTimesheetApproved)"
|
|
||||||
type="time"
|
|
||||||
label-slot
|
|
||||||
no-error-icon
|
|
||||||
hide-bottom-space
|
|
||||||
:label-color="!shift.is_approved ? 'accent' : 'white'"
|
|
||||||
:input-class="'text-weight-medium ' + (shift.id === -2 ? 'text-white ' : ' ') + (shift.is_approved ? 'text-white cursor-not-allowed q-px-sm' : '')"
|
|
||||||
input-style="font-size: 1.2em;"
|
|
||||||
class="col rounded-5 bg-dark"
|
|
||||||
:class="(shift.id === -2 ? 'bg-negative ' : ' ') + (ui_store.is_mobile_mode ? 'q-mr-xs ' : 'q-mx-xs ') + (shift.is_approved ? 'cursor-not-allowed q-px-xs transparent inset-shadow' : (isTimesheetApproved ? 'inset-shadow' : ''))"
|
|
||||||
:style="shift.is_approved ? 'background-color: #0a7d32 !important;' : ''"
|
|
||||||
@blur="emit('onTimeFieldBlur')"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
<span
|
|
||||||
class="text-weight-bolder"
|
|
||||||
:class="shift.is_approved ? ' q-ml-md' : ''"
|
|
||||||
style="font-size: 0.95em;"
|
|
||||||
>{{ $t('shared.misc.out') }}</span>
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
|
|
||||||
<!-- comment and delete buttons -->
|
|
||||||
<div class="row full-height" :class="ui_store.is_mobile_mode ? 'col-12' : 'col-auto flex-center'">
|
|
||||||
<!-- desktop comment button -->
|
|
||||||
<q-btn
|
|
||||||
v-if="!ui_store.is_mobile_mode"
|
|
||||||
push
|
|
||||||
dense
|
|
||||||
:color="shift.is_approved ? 'white' : 'accent'"
|
|
||||||
:icon="shift.comment ? 'chat' : 'chat_bubble_outline'"
|
|
||||||
:text-color="shift.is_approved ? 'accent' : 'white'"
|
|
||||||
class="col"
|
|
||||||
:class="ui_store.is_mobile_mode ? 'q-mt-xs bg-dark' : ''"
|
|
||||||
>
|
|
||||||
<q-badge
|
|
||||||
v-if="shift.comment"
|
|
||||||
floating
|
|
||||||
rounded
|
|
||||||
color="negative"
|
|
||||||
/>
|
|
||||||
<q-popup-edit
|
|
||||||
v-model="shift.comment"
|
|
||||||
:title="$t('timesheet.shift.fields.header_comment')"
|
|
||||||
auto-save
|
|
||||||
v-slot="scope"
|
|
||||||
class="bg-dark"
|
|
||||||
>
|
|
||||||
<q-input
|
|
||||||
color="white"
|
|
||||||
v-model="scope.value"
|
|
||||||
dense
|
|
||||||
:readonly="shift.is_approved"
|
|
||||||
autofocus
|
|
||||||
counter
|
|
||||||
bottom-slots
|
|
||||||
:maxlength="COMMENT_LENGTH_MAX"
|
|
||||||
class="q-pb-lg"
|
|
||||||
:class="shift.is_approved ? 'cursor-not-allowed' : ''"
|
|
||||||
@keyup.enter="scope.set"
|
|
||||||
>
|
|
||||||
<template #append>
|
|
||||||
<q-icon name="edit" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #counter>
|
|
||||||
<div class="row flex-center">
|
|
||||||
<q-space />
|
|
||||||
<q-knob
|
|
||||||
:model-value="scope.value?.length"
|
|
||||||
readonly
|
|
||||||
:max="COMMENT_LENGTH_MAX"
|
|
||||||
size="1.6em"
|
|
||||||
:thickness="0.4"
|
|
||||||
:color="getCommentCounterColor(scope.value?.length ?? 0)"
|
|
||||||
track-color="grey-4"
|
|
||||||
class="col-auto q-mr-xs"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
:class="'col-auto text-weight-bolder text-' + getCommentCounterColor(scope.value?.length ?? 0)"
|
|
||||||
>{{ 280 - (scope.value?.length ?? 0) }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</q-popup-edit>
|
|
||||||
</q-btn>
|
|
||||||
|
|
||||||
<q-btn
|
|
||||||
v-if="!ui_store.is_mobile_mode && !shift.is_approved"
|
|
||||||
flat
|
|
||||||
dense
|
|
||||||
:disable="shift.is_approved"
|
|
||||||
tabindex="-1"
|
|
||||||
icon="las la-trash"
|
|
||||||
text-color="negative"
|
|
||||||
class="col"
|
|
||||||
size="1.2em"
|
|
||||||
:class="shift.is_approved ? 'invisible' : ''"
|
|
||||||
@click="$emit('requestDelete')"
|
|
||||||
>
|
|
||||||
</q-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-slide-item>
|
</div>
|
||||||
|
|
||||||
<q-separator
|
|
||||||
v-if="hasShiftAfter && ui_store.is_mobile_mode"
|
|
||||||
spaced
|
|
||||||
color="accent"
|
|
||||||
class="q-mx-md"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,11 @@
|
||||||
import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api';
|
import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api';
|
||||||
import type { Shift } from 'src/modules/timesheets/models/shift.models';
|
import type { Shift } from 'src/modules/timesheets/models/shift.models';
|
||||||
import type { TimesheetDay } from 'src/modules/timesheets/models/timesheet.models';
|
import type { TimesheetDay } from 'src/modules/timesheets/models/timesheet.models';
|
||||||
|
import { isShiftOverlap } from 'src/modules/timesheets/utils/shift.util';
|
||||||
|
|
||||||
const shift_api = useShiftApi();
|
const shift_api = useShiftApi();
|
||||||
const timesheet_api = useTimesheetApi();
|
const timesheet_api = useTimesheetApi();
|
||||||
|
const shift_error_message = ref<string | undefined>();
|
||||||
|
|
||||||
const { day, dense = false, approved = false } = defineProps<{
|
const { day, dense = false, approved = false } = defineProps<{
|
||||||
timesheetId: number;
|
timesheetId: number;
|
||||||
|
|
@ -36,6 +38,15 @@
|
||||||
}
|
}
|
||||||
await shift_api.deleteShiftById(shift.id);
|
await shift_api.deleteShiftById(shift.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onTimeFieldBlur = () => {
|
||||||
|
const is_error = isShiftOverlap(day.shifts);
|
||||||
|
day.shifts.map(shift => shift.has_error = is_error);
|
||||||
|
if (is_error)
|
||||||
|
shift_error_message.value = 'timesheet.errors.SHIFT_OVERLAP_SHORT';
|
||||||
|
else
|
||||||
|
shift_error_message.value = undefined;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -73,24 +84,26 @@
|
||||||
<div
|
<div
|
||||||
v-for="shift, shift_index in day.shifts"
|
v-for="shift, shift_index in day.shifts"
|
||||||
:key="shift_index"
|
:key="shift_index"
|
||||||
class="col"
|
class="col-auto"
|
||||||
>
|
>
|
||||||
<ShiftListDayRowMobile
|
<ShiftListDayRowMobile
|
||||||
v-if="$q.platform.is.mobile"
|
v-if="$q.platform.is.mobile"
|
||||||
v-model:shift="day.shifts[shift_index]!"
|
v-model:shift="day.shifts[shift_index]!"
|
||||||
:is-timesheet-approved="approved"
|
:is-timesheet-approved="approved"
|
||||||
|
:error-message="shift_error_message"
|
||||||
:dense="dense"
|
:dense="dense"
|
||||||
:has-shift-after="shift_index < day.shifts.length - 1"
|
:has-shift-after="shift_index < day.shifts.length - 1"
|
||||||
@request-delete="deleteCurrentShift(shift)"
|
@request-delete="deleteCurrentShift(shift)"
|
||||||
|
@on-time-field-blur="onTimeFieldBlur()"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ShiftListDayRow
|
<ShiftListDayRow
|
||||||
v-else
|
v-else
|
||||||
v-model:shift="day.shifts[shift_index]!"
|
v-model:shift="day.shifts[shift_index]!"
|
||||||
:is-timesheet-approved="approved"
|
:is-timesheet-approved="approved"
|
||||||
:dense="dense"
|
:error-message="shift_error_message"
|
||||||
:has-shift-after="shift_index < day.shifts.length - 1"
|
|
||||||
@request-delete="deleteCurrentShift(shift)"
|
@request-delete="deleteCurrentShift(shift)"
|
||||||
|
@on-time-field-blur="onTimeFieldBlur()"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@
|
||||||
<div
|
<div
|
||||||
v-for="timesheet, timesheet_index of timesheet_store.timesheets"
|
v-for="timesheet, timesheet_index of timesheet_store.timesheets"
|
||||||
:key="timesheet.timesheet_id"
|
:key="timesheet.timesheet_id"
|
||||||
class="col fit"
|
class="col column fit flex-center"
|
||||||
>
|
>
|
||||||
<transition
|
<transition
|
||||||
appear
|
appear
|
||||||
|
|
@ -108,7 +108,7 @@
|
||||||
flat
|
flat
|
||||||
dense
|
dense
|
||||||
:label="$t('timesheet.apply_preset_week')"
|
:label="$t('timesheet.apply_preset_week')"
|
||||||
class="text-uppercase text-weight-bold text-accent q-mx-lg q-py-none rounded-5"
|
class="col-auto text-uppercase text-weight-bold text-accent q-mx-lg q-py-none rounded-5"
|
||||||
:class="timesheet.days.every(day => day.shifts.length < 1) ? '' : 'invisible'"
|
:class="timesheet.days.every(day => day.shifts.length < 1) ? '' : 'invisible'"
|
||||||
@click="timesheet_api.applyPreset(timesheet.timesheet_id)"
|
@click="timesheet_api.applyPreset(timesheet.timesheet_id)"
|
||||||
>
|
>
|
||||||
|
|
@ -127,7 +127,7 @@
|
||||||
<div
|
<div
|
||||||
v-for="day, day_index in timesheet.days"
|
v-for="day, day_index in timesheet.days"
|
||||||
:key="day.date"
|
:key="day.date"
|
||||||
class="col-auto row q-ma-sm"
|
class="col-auto row q-pa-sm fit"
|
||||||
:style="`animation-delay: ${day_index / 15}s;`"
|
:style="`animation-delay: ${day_index / 15}s;`"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|
@ -135,7 +135,7 @@
|
||||||
class="col column full-width q-px-md q-py-sm"
|
class="col column full-width q-px-md q-py-sm"
|
||||||
>
|
>
|
||||||
<q-card
|
<q-card
|
||||||
class="rounded-5 shadow-12"
|
class="rounded-10 shadow-12"
|
||||||
:class="(getDayApproval(day) || timesheet.is_approved) ? 'bg-accent' : 'bg-dark'"
|
:class="(getDayApproval(day) || timesheet.is_approved) ? 'bg-accent' : 'bg-dark'"
|
||||||
>
|
>
|
||||||
|
|
||||||
|
|
@ -174,7 +174,7 @@
|
||||||
/>
|
/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
<q-card-actions class="q-pa-none">
|
<q-card-section class="q-pa-none">
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="!(getDayApproval(day) || timesheet.is_approved)"
|
v-if="!(getDayApproval(day) || timesheet.is_approved)"
|
||||||
square
|
square
|
||||||
|
|
@ -183,15 +183,16 @@
|
||||||
color="accent"
|
color="accent"
|
||||||
icon="more_time"
|
icon="more_time"
|
||||||
class="full-width"
|
class="full-width"
|
||||||
|
style="border-radius: 0 0 10px 10px;"
|
||||||
@click="addNewShift(day.shifts, day.date, timesheet.timesheet_id)"
|
@click="addNewShift(day.shifts, day.date, timesheet.timesheet_id)"
|
||||||
/>
|
/>
|
||||||
</q-card-actions>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="col row full-width"
|
class="col row full-width shadow-8 rounded-10"
|
||||||
:class="(getDayApproval(day) || timesheet.is_approved) ? 'rounded-10 bg-accent' : ''"
|
:class="(getDayApproval(day) || timesheet.is_approved) ? 'rounded-10 bg-accent' : ''"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
import PayPeriodNavigator from 'src/modules/shared/components/pay-period-navigator.vue';
|
import PayPeriodNavigator from 'src/modules/shared/components/pay-period-navigator.vue';
|
||||||
import TimesheetErrorWidget from 'src/modules/timesheets/components/timesheet-error-widget.vue';
|
import TimesheetErrorWidget from 'src/modules/timesheets/components/timesheet-error-widget.vue';
|
||||||
import LoadingOverlay from 'src/modules/shared/components/loading-overlay.vue';
|
import LoadingOverlay from 'src/modules/shared/components/loading-overlay.vue';
|
||||||
|
import ShiftListWeeklyOverview from 'src/modules/timesheets/components/mobile/shift-list-weekly-overview.vue';
|
||||||
|
|
||||||
import { computed, onMounted } from 'vue';
|
import { computed, onMounted } from 'vue';
|
||||||
import { useShiftApi } from 'src/modules/timesheets/composables/use-shift-api';
|
import { useShiftApi } from 'src/modules/timesheets/composables/use-shift-api';
|
||||||
|
|
@ -38,9 +39,10 @@
|
||||||
<div class="column items-center full-height">
|
<div class="column items-center full-height">
|
||||||
<LoadingOverlay v-model="timesheet_store.is_loading" />
|
<LoadingOverlay v-model="timesheet_store.is_loading" />
|
||||||
|
|
||||||
|
<!-- top menu -->
|
||||||
<div
|
<div
|
||||||
class="col-auto row items-center full-width q-px-lg"
|
class="col-auto row items-center full-width q-px-lg"
|
||||||
:class="$q.platform.is.mobile && ($q.screen.width < $q.screen.height) ? 'justify-between' : 'q-mt-md q-px-md'"
|
:class="$q.platform.is.mobile && ($q.screen.width < $q.screen.height) ? 'justify-between' : 'q-mt-md q-pb-sm q-px-md'"
|
||||||
>
|
>
|
||||||
<!-- navigation btn -->
|
<!-- navigation btn -->
|
||||||
<PayPeriodNavigator
|
<PayPeriodNavigator
|
||||||
|
|
@ -90,80 +92,11 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- error message widget for potential backend-provided errors -->
|
||||||
<TimesheetErrorWidget class="col-auto" />
|
<TimesheetErrorWidget class="col-auto" />
|
||||||
|
|
||||||
<!-- mobile weekly overview widget -->
|
<!-- mobile weekly overview widget -->
|
||||||
<div
|
<ShiftListWeeklyOverview />
|
||||||
v-if="$q.platform.is.mobile && $q.screen.width < $q.screen.height"
|
|
||||||
class="col-auto row items-start q-px-sm q-pt-sm full-width"
|
|
||||||
>
|
|
||||||
<!-- per timesheet -->
|
|
||||||
<div
|
|
||||||
v-for="timesheet, timesheet_index in timesheet_store.timesheets"
|
|
||||||
:key="timesheet_index"
|
|
||||||
class="col column flex-center q-pa-sm"
|
|
||||||
>
|
|
||||||
<!-- container -->
|
|
||||||
<div
|
|
||||||
class="rounded-5 relative-position q-px-sm q-pt-sm q-pb-xs full-width shadow-4"
|
|
||||||
style="border: 1px solid var(--q-accent);"
|
|
||||||
>
|
|
||||||
<!-- label for week number -->
|
|
||||||
<div
|
|
||||||
class="self-start text-uppercase text-weight-bolder text-accent bg-secondary absolute-top-left q-px-xs"
|
|
||||||
style="font-size: 0.8em; top: -7px; left: 10px; line-height: 1em;"
|
|
||||||
>{{ $t('timesheet.week') + ` ${timesheet_index + 1}` }}</div>
|
|
||||||
|
|
||||||
<!-- hours worked in the week -->
|
|
||||||
<div class="col-auto row">
|
|
||||||
<span class="text-weight-bolder text-uppercase text-accent text-caption q-mr-sm">{{
|
|
||||||
$t('timesheet.total_hours') }}</span>
|
|
||||||
<span>{{
|
|
||||||
(timesheet.weekly_hours.regular +
|
|
||||||
timesheet.weekly_hours.evening +
|
|
||||||
timesheet.weekly_hours.emergency +
|
|
||||||
timesheet.weekly_hours.overtime).toFixed(2)
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- label for current shifts preview -->
|
|
||||||
<div
|
|
||||||
class="col-auto full-width text-center text-weight-medium text-caption text-uppercase q-mt-xs"
|
|
||||||
style="font-size: 0.65em; line-height: 1.2em;"
|
|
||||||
> {{ $t('timesheet.current_shifts') }}</div>
|
|
||||||
|
|
||||||
<!-- preview of current number of shifts -->
|
|
||||||
<div
|
|
||||||
class="col row flex-center"
|
|
||||||
style="height: 20px;"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-for="day, day_index in timesheet.days"
|
|
||||||
:key="day_index"
|
|
||||||
class="col row flex-center"
|
|
||||||
>
|
|
||||||
<q-badge :color="day.shifts.length > 0 ? 'accent' : 'blue-grey-4'">
|
|
||||||
<span class="text-weight-bolder">{{ day.shifts.length > 0 ? day.shifts.length : ''
|
|
||||||
}}</span>
|
|
||||||
</q-badge>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- button to apply weekly schedule preset -->
|
|
||||||
<div class="col-auto flex-center row q-pt-xs full-width">
|
|
||||||
<q-btn
|
|
||||||
v-if="timesheet.days.every(day => day.shifts.length < 1)"
|
|
||||||
push
|
|
||||||
dense
|
|
||||||
color="accent"
|
|
||||||
:label="$t('timesheet.apply_preset')"
|
|
||||||
class="full-width"
|
|
||||||
@click="timesheet_api.applyPreset(timesheet.timesheet_id)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ShiftList />
|
<ShiftList />
|
||||||
|
|
||||||
|
|
@ -180,6 +113,6 @@
|
||||||
@click="shift_api.saveShiftChanges"
|
@click="shift_api.saveShiftChanges"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ExpenseDialog :is-approved="is_timesheets_approved" />
|
<ExpenseDialog :is-approved="is_timesheets_approved" class="z-top"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -1,19 +1,43 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-layout view="hHh lpR fFf">
|
<q-layout view="hHh lpR fFf">
|
||||||
<q-page-container>
|
<q-page-container>
|
||||||
<q-page padding class="column justify-center items-center bg-secondary">
|
<q-page class="row justify-center bg-secondary">
|
||||||
<q-card class="col-shrink rounded-20">
|
<div class=" column justify-center" :class="$q.platform.is.mobile ? 'col-11' : 'col-8'">
|
||||||
<q-img src="src/assets/line-truck-1.jpg" height="20vh">
|
<div class="column rounded-20 q-pa-xs bg-accent" :class="$q.platform.is.mobile ? 'col-5' : 'col-4'">
|
||||||
<div
|
<div class="col">
|
||||||
class="absolute-bottom text-h4 text-center text-weight-bolder justify-center items-center row">
|
<q-img src="src/assets/line-truck-1.jpg" fit="cover" class="relative-position fit border-radius-inherit">
|
||||||
<div class="q-pr-md text-primary text-h3 text-weight-bolder">404</div>
|
<div class="absolute-bottom text-center column flex-center">
|
||||||
PAGE NOT FOUND
|
<div class="q-pr-md text-white text-h2 text-weight-bolder">404</div>
|
||||||
|
<div class="q-pr-md text-white text-h5 text-weight-bold">{{
|
||||||
|
$t('error.not_found_header')
|
||||||
|
}}</div>
|
||||||
|
</div>
|
||||||
|
</q-img>
|
||||||
</div>
|
</div>
|
||||||
</q-img>
|
|
||||||
<q-card-section class="text-center text-h5 text-primary">
|
<div class="col-auto text-center text-h6 text-weight-light bg-dark q-pa-md">
|
||||||
{{ $t('notFoundPage.pageText') }}
|
<div>{{ $t('error.not_found_description') }}</div>
|
||||||
</q-card-section>
|
</div>
|
||||||
</q-card>
|
</div>
|
||||||
|
|
||||||
|
<div class="col-auto row self-center q-py-md">
|
||||||
|
<q-btn
|
||||||
|
push
|
||||||
|
dense
|
||||||
|
color="accent"
|
||||||
|
:label="$t('error.go_back')"
|
||||||
|
class="col-auto q-px-md q-py-xs"
|
||||||
|
@click="router.go(-2)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</q-page>
|
</q-page>
|
||||||
</q-page-container>
|
</q-page-container>
|
||||||
</q-layout>
|
</q-layout>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { createMemoryHistory, createRouter, createWebHashHistory, createWebHisto
|
||||||
import routes from './routes';
|
import routes from './routes';
|
||||||
import { useAuthStore } from 'src/stores/auth-store';
|
import { useAuthStore } from 'src/stores/auth-store';
|
||||||
import { RouteNames } from 'src/router/router-constants';
|
import { RouteNames } from 'src/router/router-constants';
|
||||||
|
import type { UserModuleAccess } from 'src/modules/shared/models/user.models';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If not building with SSR mode, you can
|
* If not building with SSR mode, you can
|
||||||
|
|
@ -28,14 +29,19 @@ export default defineRouter(function (/* { store, ssrContext } */) {
|
||||||
history: createHistory(process.env.VUE_ROUTER_BASE),
|
history: createHistory(process.env.VUE_ROUTER_BASE),
|
||||||
});
|
});
|
||||||
|
|
||||||
Router.beforeEach(async (destinationPage) => {
|
Router.beforeEach(async (destination_page) => {
|
||||||
const authStore = useAuthStore();
|
const auth_store = useAuthStore();
|
||||||
const result = await authStore.getProfile() ?? { status: 400, message: 'unknown error occured' };
|
const result = await auth_store.getProfile() ?? { status: 400, message: 'unknown error occured' };
|
||||||
|
|
||||||
if ((destinationPage.meta.requiresAuth && !authStore.isAuthorizedUser) || (result.status >= 400 && destinationPage.name !== RouteNames.LOGIN)) {
|
if (destination_page.meta.requires_auth && !auth_store.user || (result.status >= 400 && destination_page.name !== RouteNames.LOGIN)) {
|
||||||
console.error('no user account found');
|
console.error('no user account found');
|
||||||
return { name: 'login' };
|
return { name: 'login' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (destination_page.meta.required_module && auth_store.user) {
|
||||||
|
if (!auth_store.user.user_module_access.includes(destination_page.meta.required_module as UserModuleAccess))
|
||||||
|
return {name: 'error'};
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return Router;
|
return Router;
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,8 @@ export enum RouteNames {
|
||||||
EMPLOYEE_LIST = 'employee_list',
|
EMPLOYEE_LIST = 'employee_list',
|
||||||
EMPLOYEE_MANAGEMENT = 'employee_management',
|
EMPLOYEE_MANAGEMENT = 'employee_management',
|
||||||
PROFILE = 'personal_profile',
|
PROFILE = 'personal_profile',
|
||||||
TIMESHEET = 'timesheets'
|
TIMESHEET = 'timesheets',
|
||||||
|
HELP = 'help',
|
||||||
|
|
||||||
|
ERROR = 'error',
|
||||||
}
|
}
|
||||||
|
|
@ -1,36 +1,42 @@
|
||||||
import type { RouteRecordRaw } from 'vue-router';
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
import { RouteNames } from './router-constants';
|
import { RouteNames } from './router-constants';
|
||||||
|
import { ModuleNames } from 'src/modules/shared/models/user.models';
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
component: () => import('src/layouts/main-layout.vue'),
|
component: () => import('src/layouts/main-layout.vue'),
|
||||||
meta: { requiresAuth: true },
|
meta: { requires_auth: true },
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
name: RouteNames.DASHBOARD,
|
name: RouteNames.DASHBOARD,
|
||||||
component: () => import('src/pages/dashboard-page.vue'),
|
component: () => import('src/pages/dashboard-page.vue'),
|
||||||
|
meta: { required_module: ModuleNames.DASHBOARD }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'timesheet-approvals',
|
path: 'timesheet-approvals',
|
||||||
name: RouteNames.TIMESHEET_APPROVALS,
|
name: RouteNames.TIMESHEET_APPROVALS,
|
||||||
component: () => import('src/pages/timesheet-approval-page.vue'),
|
component: () => import('src/pages/timesheet-approval-page.vue'),
|
||||||
|
meta: { required_module: ModuleNames.TIMESHEETS_APPROVAL }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'employees',
|
path: 'employees',
|
||||||
name: RouteNames.EMPLOYEE_LIST,
|
name: RouteNames.EMPLOYEE_LIST,
|
||||||
component: () => import('src/pages/employee-list-page.vue'),
|
component: () => import('src/pages/employee-list-page.vue'),
|
||||||
|
meta: { required_module: ModuleNames.EMPLOYEE_LIST }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'timesheet',
|
path: 'timesheet',
|
||||||
name: RouteNames.TIMESHEET,
|
name: RouteNames.TIMESHEET,
|
||||||
component: () => import('src/pages/timesheet-page.vue')
|
component: () => import('src/pages/timesheet-page.vue'),
|
||||||
|
meta: { required_module: ModuleNames.TIMESHEETS },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'user/profile',
|
path: 'user/profile',
|
||||||
name: RouteNames.PROFILE,
|
name: RouteNames.PROFILE,
|
||||||
component: () => import('src/pages/profile-page.vue'),
|
component: () => import('src/pages/profile-page.vue'),
|
||||||
|
meta: { required_module: ModuleNames.PERSONAL_PROFILE },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -39,22 +45,23 @@ const routes: RouteRecordRaw[] = [
|
||||||
path: '/v1/login',
|
path: '/v1/login',
|
||||||
name: RouteNames.LOGIN,
|
name: RouteNames.LOGIN,
|
||||||
component: () => import('src/pages/login-page.vue'),
|
component: () => import('src/pages/login-page.vue'),
|
||||||
meta: { requiresAuth: false },
|
meta: { requires_auth: false },
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: '/login-success',
|
path: '/login-success',
|
||||||
name: RouteNames.LOGIN_SUCCESS,
|
name: RouteNames.LOGIN_SUCCESS,
|
||||||
component: () => import('src/modules/auth/pages/auth-login-popup-success.vue'),
|
component: () => import('src/modules/auth/pages/auth-login-popup-success.vue'),
|
||||||
meta: { requiresAuth: false },
|
meta: { requires_auth: false },
|
||||||
},
|
},
|
||||||
|
|
||||||
// Always leave this as last one,
|
// Always leave this as last one,
|
||||||
// but you can also remove it
|
// but you can also remove it
|
||||||
{
|
{
|
||||||
path: '/:catchAll(.*)*',
|
path: '/:catchAll(.*)*',
|
||||||
|
name: RouteNames.ERROR,
|
||||||
component: () => import('src/pages/error-page.vue'),
|
component: () => import('src/pages/error-page.vue'),
|
||||||
meta: { requiresAuth: false },
|
meta: { requires_auth: false },
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
import { computed, ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
import { Notify } from "quasar";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { AuthService } from "../modules/auth/services/services-auth";
|
import { AuthService } from "../modules/auth/services/services-auth";
|
||||||
import { CAN_APPROVE_PAY_PERIODS, type User } from "src/modules/shared/models/user.models";
|
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { Notify } from "quasar";
|
import type { User } from "src/modules/shared/models/user.models";
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', () => {
|
export const useAuthStore = defineStore('auth', () => {
|
||||||
const user = ref<User>();
|
const user = ref<User>();
|
||||||
const authError = ref("");
|
const authError = ref("");
|
||||||
const isAuthorizedUser = computed(() => CAN_APPROVE_PAY_PERIODS.includes(user.value?.role ?? 'GUEST'));
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const login = () => {
|
const login = () => {
|
||||||
|
|
@ -62,6 +61,13 @@ export const useAuthStore = defineStore('auth', () => {
|
||||||
return { status: 400, message: 'unknown error occured' };
|
return { status: 400, message: 'unknown error occured' };
|
||||||
}
|
}
|
||||||
|
|
||||||
return { user, authError, isAuthorizedUser, login, oidcLogin, logout, getProfile };
|
return {
|
||||||
|
user,
|
||||||
|
authError,
|
||||||
|
login,
|
||||||
|
oidcLogin,
|
||||||
|
logout,
|
||||||
|
getProfile
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user