eat(approvals): add and define components and other files related to timesheet approval page
This commit is contained in:
parent
c1ce7e36cb
commit
1f94d6a900
BIN
src/assets/targo-default-avatar.png
Normal file
BIN
src/assets/targo-default-avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
0
src/modules/employee-list/composables/use-users.ts
Normal file
0
src/modules/employee-list/composables/use-users.ts
Normal file
0
src/modules/employee-list/employee-constants.ts
Normal file
0
src/modules/employee-list/employee-constants.ts
Normal file
0
src/modules/employee-list/employee-store.ts
Normal file
0
src/modules/employee-list/employee-store.ts
Normal file
0
src/modules/employee-list/pages/user-add-page.vue
Normal file
0
src/modules/employee-list/pages/user-add-page.vue
Normal file
0
src/modules/employee-list/services/user-service.ts
Normal file
0
src/modules/employee-list/services/user-service.ts
Normal file
12
src/modules/shared/components/navigation/footer-bar.vue
Normal file
12
src/modules/shared/components/navigation/footer-bar.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import LanguageSwitch from '../language-switch.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-footer elevated class="bg-primary text-white">
|
||||
<q-toolbar>
|
||||
<q-toolbar-title>© 2025 Targo Communications inc.</q-toolbar-title>
|
||||
<LanguageSwitch class="q-mr-xs text-white" />
|
||||
</q-toolbar>
|
||||
</q-footer>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<script setup lang="ts">
|
||||
import { useAuthStore } from 'src/stores/auth-store';
|
||||
import { useUiStore } from 'src/stores/ui-store';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const uiStore = useUiStore();
|
||||
const currentUser = authStore.user;
|
||||
|
||||
// Will need to implement this eventually, just testing the look for now
|
||||
const notifAmount = ref(7);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-item clickable v-ripple dark @click="uiStore.toggleRightDrawer">
|
||||
<q-item-section side>
|
||||
<q-avatar rounded >
|
||||
<q-img src="src/assets/targo-default-avatar.png" />
|
||||
<q-badge floating color="red" v-if="notifAmount > 0" >{{ notifAmount }}</q-badge>
|
||||
</q-avatar>
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section>
|
||||
<q-item-label>{{ currentUser.firstName }} {{ currentUser.lastName }}</q-item-label>
|
||||
<q-item-label caption>{{ notifAmount }} new messages</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
22
src/modules/shared/components/navigation/header-bar.vue
Normal file
22
src/modules/shared/components/navigation/header-bar.vue
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<script lang="ts" setup>
|
||||
import { useUiStore } from 'src/stores/ui-store';
|
||||
import HeaderBarAvatar from './header-bar-avatar.vue';
|
||||
|
||||
const uiStore = useUiStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-header elevated>
|
||||
<q-toolbar>
|
||||
<q-toolbar-title>
|
||||
<q-btn flat dense color="white" icon="menu" @click="uiStore.toggleRightDrawer">
|
||||
<q-img src="src/assets/logo-targo-white.svg" fit="contain" width="150px" height="30px"/>
|
||||
</q-btn>
|
||||
</q-toolbar-title>
|
||||
<q-item class="q-pa-none">
|
||||
<HeaderBarAvatar />
|
||||
</q-item>
|
||||
</q-toolbar>
|
||||
</q-header>
|
||||
</template>
|
||||
|
||||
88
src/modules/shared/components/navigation/right-drawer.vue
Normal file
88
src/modules/shared/components/navigation/right-drawer.vue
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useAuthStore } from 'src/stores/auth-store';
|
||||
import { hasRequiredRole } from 'src/utils/has-required-role';
|
||||
import { useUiStore } from 'src/stores/ui-store';
|
||||
import { ref } from 'vue';
|
||||
import { RouteNames } from 'src/router/router-constants';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const uiStore = useUiStore();
|
||||
const router = useRouter();
|
||||
const miniState = ref(true);
|
||||
|
||||
const goToPageName = (pageName: string) => {
|
||||
router.push({ name: pageName }).catch(err => {
|
||||
console.error('Error with Vue Router: ', err);
|
||||
});
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
authStore.logout();
|
||||
|
||||
router.push({ name: 'login' }).catch(err => {
|
||||
console.log('could not log you out: ', err);
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-drawer overlay elevated side="left" :mini="miniState" @mouseenter="miniState = false"
|
||||
@mouseleave="miniState = true" v-model="uiStore.isRightDrawerOpen">
|
||||
<q-scroll-area class="fit">
|
||||
<q-list>
|
||||
<!-- Timesheet Validation -- Supervisor and Accounting only -->
|
||||
<q-item v-ripple clickable side @click="goToPageName(RouteNames.TIMESHEET_APPROVALS)"
|
||||
v-if="hasRequiredRole('supervisor', 'accounting')">
|
||||
<q-item-section avatar>
|
||||
<q-icon name="event_available" color="primary" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ $t('navBar.userMenuShiftValidation') }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<!-- Employee List -- Supervisor, Accounting and HR only -->
|
||||
<q-item v-ripple clickable side @click="goToPageName(RouteNames.EMPLOYEE_LIST)"
|
||||
v-if="hasRequiredRole('supervisor', 'human resources', 'accounting')">
|
||||
<q-item-section avatar>
|
||||
<q-icon name="view_list" color="primary" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ $t('navBar.userMenuEmployeeList') }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<!-- Profile -->
|
||||
<q-item v-ripple clickable side @click="goToPageName(RouteNames.PROFILE)">
|
||||
<q-item-section avatar>
|
||||
<q-icon name="account_box" color="primary" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ $t('navBar.userMenuProfile') }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<!-- Help -->
|
||||
<q-item v-ripple clickable @click="goToPageName('help')">
|
||||
<q-item-section avatar>
|
||||
<q-icon name="contact_support" color="primary" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ $t('navBar.userMenuHelp') }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
|
||||
<!-- Logout -->
|
||||
<q-item v-ripple clickable @click="handleLogout" class="absolute-bottom">
|
||||
<q-item-section avatar>
|
||||
<q-icon name="exit_to_app" color="primary" />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ $t('navBar.userMenuLogout') }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-scroll-area>
|
||||
</q-drawer>
|
||||
</template>
|
||||
7
src/modules/shared/types/pay-period-interface.ts
Normal file
7
src/modules/shared/types/pay-period-interface.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export interface PayPeriod {
|
||||
period_number: number;
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
year: number;
|
||||
label: string;
|
||||
};
|
||||
6
src/modules/shared/types/user-interface.ts
Normal file
6
src/modules/shared/types/user-interface.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export interface User {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
role: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<script setup lang="ts">
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
|
||||
const timesheetStore = useTimesheetStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row">
|
||||
<q-btn-group outline rounded>
|
||||
<q-btn outline icon="arrow_circle_left"/>
|
||||
<q-btn outline icon="calendar_month" :label="timesheetStore.currentPayPeriod?.label"/>
|
||||
<q-btn outline icon="arrow_circle_right"/>
|
||||
</q-btn-group>
|
||||
<div class="text-primary text-h4">{{ timesheetStore.currentPayPeriod?.label }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
const testDates: string[] = ['25 Juin 2025', '12 Juillet 2025', '13 Juillet 2025', '26 Juillet 2025', '27 Juillet 2025', '9 Aout 2025']
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-page padding class="q-pa-md bg-secondary">
|
||||
|
||||
</q-page>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import { api } from "src/boot/axios";
|
||||
import { mock_pay_period_employee_overviews, mock_pay_periods } from "../timesheet-approval-test-constants";
|
||||
import { PayPeriod } from "src/modules/shared/types/pay-period-interface";
|
||||
|
||||
export const timesheetApprovalService = {
|
||||
getCurrentPayPeriod: async () :Promise<PayPeriod> => {
|
||||
// TODO: REMOVE MOCK DATA PEFORE PUSHING TO PROD
|
||||
return await api.get(`/pay-periods/date/${new Date()}`) || mock_pay_periods[0];
|
||||
},
|
||||
|
||||
getAllPayPeriods: async () => {
|
||||
// TODO: REMOVE MOCK DATA PEFORE PUSHING TO PROD
|
||||
return await api.get(`/pay-periods/`) || mock_pay_periods;
|
||||
},
|
||||
|
||||
getPayPeriodEmployeeOverviews: async (period_number: number) => {
|
||||
// TODO: REMOVE MOCK DATA PEFORE PUSHING TO PROD
|
||||
return await api.get(`/pay-periods/${period_number}/overview`) || mock_pay_period_employee_overviews;
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
import { PayPeriod } from "../shared/types/pay-period-interface";
|
||||
import { PayPeriodEmployeeOverview } from "./types/timesheet-approval-pay-period-employee-overview-interface"
|
||||
|
||||
export const mock_pay_period_employee_overviews: PayPeriodEmployeeOverview[] = [
|
||||
{
|
||||
"employee_id": 'EMP-001',
|
||||
"employee_name": 'Alice Johnson',
|
||||
"regular_hours": 75,
|
||||
"evening_hours": 12,
|
||||
"emergency_hours": 3,
|
||||
"overtime_hours": 5,
|
||||
"expenses": 120.50,
|
||||
"mileage": 45,
|
||||
"is_approved": false
|
||||
},
|
||||
{
|
||||
"employee_id": 'EMP-002',
|
||||
"employee_name": 'Brian Smith',
|
||||
"regular_hours": 80,
|
||||
"evening_hours": 8,
|
||||
"emergency_hours": 0,
|
||||
"overtime_hours": 2,
|
||||
"expenses": 75.00,
|
||||
"mileage": 12,
|
||||
"is_approved": true
|
||||
},
|
||||
{
|
||||
"employee_id": 'EMP-003',
|
||||
"employee_name": 'Chloe Ramirez',
|
||||
"regular_hours": 68,
|
||||
"evening_hours": 15,
|
||||
"emergency_hours": 1,
|
||||
"overtime_hours": 0,
|
||||
"expenses": 200.00,
|
||||
"mileage": 88,
|
||||
"is_approved": false
|
||||
},
|
||||
{
|
||||
"employee_id": 'EMP-004',
|
||||
"employee_name": 'David Lee',
|
||||
"regular_hours": 82,
|
||||
"evening_hours": 5,
|
||||
"emergency_hours": 4,
|
||||
"overtime_hours": 6,
|
||||
"expenses": 50.75,
|
||||
"mileage": 20,
|
||||
"is_approved": true
|
||||
},
|
||||
{
|
||||
"employee_id": 'EMP-005',
|
||||
"employee_name": 'Emily Carter',
|
||||
"regular_hours": 78,
|
||||
"evening_hours": 10,
|
||||
"emergency_hours": 2,
|
||||
"overtime_hours": 3,
|
||||
"expenses": 95.25,
|
||||
"mileage": 60,
|
||||
"is_approved": false
|
||||
}
|
||||
];
|
||||
|
||||
export const mock_pay_periods: PayPeriod[] = [
|
||||
{
|
||||
"period_number": 15,
|
||||
"start_date": "2025-07-27",
|
||||
"end_date": "2025-08-09",
|
||||
"year": 2025,
|
||||
"label": "2025-07-27 → 2025-08-09"
|
||||
},
|
||||
{
|
||||
"period_number": 14,
|
||||
"start_date": "2025-07-13",
|
||||
"end_date": "2025-07-26",
|
||||
"year": 2025,
|
||||
"label": "2025-07-13 → 2025-07-26"
|
||||
},
|
||||
{
|
||||
"period_number": 13,
|
||||
"start_date": "2025-06-29",
|
||||
"end_date": "2025-07-12",
|
||||
"year": 2025,
|
||||
"label": "2025-06-29 → 2025-07-12"
|
||||
},
|
||||
{
|
||||
"period_number": 12,
|
||||
"start_date": "2025-06-15",
|
||||
"end_date": "2025-06-28",
|
||||
"year": 2025,
|
||||
"label": "2025-06-15 → 2025-06-28"
|
||||
},
|
||||
{
|
||||
"period_number": 11,
|
||||
"start_date": "2025-06-01",
|
||||
"end_date": "2025-06-14",
|
||||
"year": 2025,
|
||||
"label": "2025-06-01 → 2025-06-14"
|
||||
},
|
||||
{
|
||||
"period_number": 10,
|
||||
"start_date": "2025-05-18",
|
||||
"end_date": "2025-05-31",
|
||||
"year": 2025,
|
||||
"label": "2025-05-18 → 2025-05-31"
|
||||
},
|
||||
{
|
||||
"period_number": 9,
|
||||
"start_date": "2025-05-04",
|
||||
"end_date": "2025-05-17",
|
||||
"year": 2025,
|
||||
"label": "2025-05-04 → 2025-05-17"
|
||||
},
|
||||
{
|
||||
"period_number": 8,
|
||||
"start_date": "2025-04-20",
|
||||
"end_date": "2025-05-03",
|
||||
"year": 2025,
|
||||
"label": "2025-04-20 → 2025-05-03"
|
||||
},
|
||||
{
|
||||
"period_number": 7,
|
||||
"start_date": "2025-04-06",
|
||||
"end_date": "2025-04-19",
|
||||
"year": 2025,
|
||||
"label": "2025-04-06 → 2025-04-19"
|
||||
},
|
||||
{
|
||||
"period_number": 6,
|
||||
"start_date": "2025-03-23",
|
||||
"end_date": "2025-04-05",
|
||||
"year": 2025,
|
||||
"label": "2025-03-23 → 2025-04-05"
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
export interface PayPeriodEmployeeOverview {
|
||||
employee_id: string;
|
||||
employee_name: string;
|
||||
regular_hours: number;
|
||||
evening_hours: number;
|
||||
emergency_hours: number;
|
||||
overtime_hours: number;
|
||||
expenses: number;
|
||||
mileage: number;
|
||||
is_approved: boolean;
|
||||
};
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
export interface PayPeriodOverview {
|
||||
period_number: number;
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
label: string;
|
||||
};
|
||||
9
src/router/router-constants.ts
Normal file
9
src/router/router-constants.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
export enum RouteNames {
|
||||
/* eslint-disable */
|
||||
LOGIN = 'login',
|
||||
LOGIN_SUCCESS = 'login-success',
|
||||
DASHBOARD = 'dashboard',
|
||||
TIMESHEET_APPROVALS = 'timesheet-approvals',
|
||||
EMPLOYEE_LIST = 'employee-list',
|
||||
PROFILE = 'user/profile',
|
||||
}
|
||||
39
src/stores/auth-store.ts
Normal file
39
src/stores/auth-store.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { computed, ref } from "vue";
|
||||
import { defineStore } from "pinia";
|
||||
import { AuthService } from "../modules/auth/services/services-auth";
|
||||
import type { User } from "src/modules/shared/types/user-interface";
|
||||
|
||||
const defaultUser: User = {
|
||||
firstName: 'Unknown',
|
||||
lastName: 'Unknown',
|
||||
email: 'guest@guest.com',
|
||||
role: 'guest'
|
||||
};
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const user = ref (defaultUser);
|
||||
const authError = ref("");
|
||||
const isAuthorizedUser = computed(() => user.value.role !== 'guest');
|
||||
|
||||
const login = () => {
|
||||
//TODO: manage customer login process
|
||||
};
|
||||
|
||||
const oidcLogin = () => {
|
||||
const oidcPopup = AuthService.oidcLogin();
|
||||
if (!oidcPopup) {
|
||||
authError.value = "You have popups blocked on this website!";
|
||||
}
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
user.value = defaultUser;
|
||||
};
|
||||
|
||||
const setUser = (currentUser: User) => {
|
||||
user.value = currentUser;
|
||||
};
|
||||
|
||||
return { user, authError, isAuthorizedUser, login, oidcLogin, logout, setUser };
|
||||
});
|
||||
|
||||
18
src/stores/timesheet-store.ts
Normal file
18
src/stores/timesheet-store.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import type { PayPeriod } from 'src/modules/shared/types/pay-period-interface';
|
||||
import { timesheetApprovalService } from 'src/modules/timesheet-approval/services/services-timesheet-approval';
|
||||
|
||||
export const useTimesheetStore = defineStore('timesheet', () => {
|
||||
const payPeriods = ref<PayPeriod[]>([]);
|
||||
const currentPayPeriod = ref<PayPeriod>();
|
||||
|
||||
const getCurrentPayPeriod = async () => {
|
||||
if (!currentPayPeriod.value) {
|
||||
currentPayPeriod.value = await timesheetApprovalService.getCurrentPayPeriod();
|
||||
}
|
||||
return currentPayPeriod.value;
|
||||
}
|
||||
|
||||
return { payPeriods, currentPayPeriod, getCurrentPayPeriod};
|
||||
});
|
||||
11
src/stores/ui-store.ts
Normal file
11
src/stores/ui-store.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
|
||||
export const useUiStore = defineStore('ui', () => {
|
||||
const isRightDrawerOpen = ref(true);
|
||||
const toggleRightDrawer = () => {
|
||||
isRightDrawerOpen.value = !isRightDrawerOpen.value;
|
||||
}
|
||||
|
||||
return { isRightDrawerOpen, toggleRightDrawer };
|
||||
});
|
||||
7
src/utils/has-required-role.ts
Normal file
7
src/utils/has-required-role.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { useAuthStore } from "src/stores/auth-store";
|
||||
|
||||
export const hasRequiredRole = (...requiredRoles: string[] ) => {
|
||||
const currentUserRole = useAuthStore().user.role;
|
||||
|
||||
return requiredRoles.includes(currentUserRole);
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user