Merge branch 'dev/nicolas/timesheet-approval-staging-prep' of git.targo.ca:Targo/targo_frontend into dev/nicolas/timesheet-approval-staging-prep

This commit is contained in:
Nicolas Drolet 2025-12-29 08:54:16 -05:00
commit f66934cc4f
9 changed files with 90 additions and 104 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 KiB

View File

@ -8,28 +8,26 @@
</script> </script>
<template> <template>
<div class="column text-weight-medium"> <div class="column bg-primary text-uppercase">
<q-separator
color="accent"
size="5px"
class="col-auto"
/>
<div class="col row"> <div class="col row">
<q-checkbox <q-checkbox
v-model="filters.is_showing_inactive" v-model="filters.is_showing_inactive"
keep-color
size="lg" size="lg"
color="accent" color="accent"
label="show inactive" label="show inactive"
class="col" class="col"
:class="filters.is_showing_inactive ? 'text-accent text-weight-bolder' : 'text-white text-weight-medium'"
/> />
<q-checkbox <q-checkbox
v-model="filters.is_showing_team_only" v-model="filters.is_showing_team_only"
keep-color
size="lg" size="lg"
val="team" val="team"
color="accent" color="accent"
label="show team only" label="show team only"
class="col" class="col"
:class="filters.is_showing_team_only ? 'text-accent text-weight-bolder' : 'text-white text-weight-medium'"
/> />
</div> </div>
</div> </div>

View File

@ -2,17 +2,17 @@
setup setup
lang="ts" lang="ts"
> >
import type { TimesheetOverview } from 'src/modules/timesheet-approval/models/timesheet-overview.models'; import type { TimesheetApprovalOverview } from 'src/modules/timesheet-approval/models/timesheet-overview.models';
const modelApproval = defineModel<boolean>(); const modelApproval = defineModel<boolean>();
const { row, index = 0 } = defineProps<{ const { row, index = 0 } = defineProps<{
row: TimesheetOverview; row: TimesheetApprovalOverview;
index?: number; index?: number;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
'clickDetails': [overview: TimesheetOverview]; 'clickDetails': [overview: TimesheetApprovalOverview];
'clickApprovalAll' : [is_approved: boolean]; 'clickApprovalAll' : [is_approved: boolean];
}>(); }>();
@ -37,7 +37,7 @@
> >
<q-card <q-card
class="rounded-10 shadow-5" class="rounded-10 shadow-5"
:style="`animation-delay: ${index / 15}s; opacity: ${row.is_active ? '1' : '0.5'}; transform: scale(${row.is_active ? '1' : '0.9'})`" :style="`animation-delay: ${index / 15}s; opacity: ${row.is_active ? '1' : '0.75'}; transform: scale(${row.is_active ? '1' : '0.9'})`"
> >
<!-- Card header with employee name and details button--> <!-- Card header with employee name and details button-->
<q-card-section <q-card-section
@ -49,9 +49,9 @@
class="text-h5 text-uppercase text-weight-medium q-mr-xs" class="text-h5 text-uppercase text-weight-medium q-mr-xs"
:class="row.is_active ? 'text-accent' : 'text-negative'" :class="row.is_active ? 'text-accent' : 'text-negative'"
> >
{{ row.employee_name.split(' ')[0] }} {{ row.employee_first_name }}
</span> </span>
<span class="text-uppercase text-weight-light">{{ row.employee_name.split(' ')[1] }}</span> <span class="text-uppercase text-weight-light">{{ row.employee_last_name }}</span>
</div> </div>
<!-- Buttons to view detailed shifts or view employee timesheet --> <!-- Buttons to view detailed shifts or view employee timesheet -->

View File

@ -10,11 +10,13 @@
import OverviewListFilters from 'src/modules/timesheet-approval/components/overview-list-filters.vue'; import OverviewListFilters from 'src/modules/timesheet-approval/components/overview-list-filters.vue';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { useAuthStore } from 'src/stores/auth-store';
import { useTimesheetStore } from 'src/stores/timesheet-store'; import { useTimesheetStore } from 'src/stores/timesheet-store';
import { useTimesheetApprovalApi } from 'src/modules/timesheet-approval/composables/use-timesheet-approval-api'; import { useTimesheetApprovalApi } from 'src/modules/timesheet-approval/composables/use-timesheet-approval-api';
import { overview_column_names, pay_period_overview_columns, PayPeriodOverviewFilters, type TimesheetOverview } from 'src/modules/timesheet-approval/models/timesheet-overview.models'; import { overview_column_names, pay_period_overview_columns, PayPeriodOverviewFilters, type TimesheetApprovalOverview } from 'src/modules/timesheet-approval/models/timesheet-overview.models';
const auth_store = useAuthStore();
const timesheet_store = useTimesheetStore(); const timesheet_store = useTimesheetStore();
const timesheet_approval_api = useTimesheetApprovalApi(); const timesheet_approval_api = useTimesheetApprovalApi();
@ -39,7 +41,7 @@
name_search_string: search_string.value, name_search_string: search_string.value,
}); });
const onClickedDetails = async (row: TimesheetOverview) => { const onClickedDetails = async (row: TimesheetApprovalOverview) => {
timesheet_store.current_pay_period_overview = row; timesheet_store.current_pay_period_overview = row;
await timesheet_store.getTimesheetsByOptionalEmployeeEmail(row.email); await timesheet_store.getTimesheetsByOptionalEmployeeEmail(row.email);
@ -50,16 +52,23 @@
await timesheet_approval_api.toggleTimesheetsApprovalByEmployeeEmail(email, is_approved); await timesheet_approval_api.toggleTimesheetsApprovalByEmployeeEmail(email, is_approved);
} }
const filterEmployeeRows = (rows: readonly TimesheetOverview[], terms: PayPeriodOverviewFilters): TimesheetOverview[] => { const filterEmployeeRows = (rows: readonly TimesheetApprovalOverview[], terms: PayPeriodOverviewFilters): TimesheetApprovalOverview[] => {
let result = [...rows]; let result = [...rows];
if (!terms.is_showing_inactive) { if (!terms.is_showing_inactive) {
result = result.filter(row => row.is_active); result = result.filter(row => row.is_active);
} }
// if (terms.name_search_string) { if (terms.is_showing_team_only) {
// result = result.filter(row => row.employee_name.includes(terms.name_search_string ?? '')); result = result.filter(row => row.supervisor !== null && row.supervisor.email === (auth_store.user ? auth_store.user.email : '') );
// } }
if (terms.name_search_string.length > 0) {
const search_words = terms.name_search_string.trim().split(' ');
search_words.map(word => result = result.filter(row =>
row.employee_first_name.includes(word ?? '') || row.employee_last_name.includes(word ?? '')
));
}
return result; return result;
}; };
@ -146,7 +155,7 @@
<QTableFilters <QTableFilters
v-model:search="search_string" v-model:search="search_string"
class="col-auto q-mb-xs" class="col-auto q-mb-sm"
/> />
<q-btn <q-btn
@ -154,7 +163,7 @@
icon="filter_alt" icon="filter_alt"
color="white" color="white"
:label="$q.platform.is.mobile ? '' : $t('shared.label.filter')" :label="$q.platform.is.mobile ? '' : $t('shared.label.filter')"
class="col q-ml-sm self-stretch bg-accent" class="col q-ml-sm self-stretch bg-primary"
style="border-radius: 5px 5px 0 0;" style="border-radius: 5px 5px 0 0;"
@click="is_showing_filters = !is_showing_filters" @click="is_showing_filters = !is_showing_filters"
/> />
@ -170,9 +179,9 @@
</q-slide-transition> </q-slide-transition>
<q-separator <q-separator
color="accent" color="primary"
size="5px" size="5px"
class="q-mx-lg" class="q-mx-lg q-my-none q-pa-none"
/> />
</div> </div>
</template> </template>
@ -242,7 +251,7 @@
</template> </template>
<!-- Template for individual employee cards --> <!-- Template for individual employee cards -->
<template #item="props: { row: TimesheetOverview, rowIndex: number }"> <template #item="props: { row: TimesheetApprovalOverview, rowIndex: number }">
<OverviewListItem <OverviewListItem
v-model="props.row.is_approved" v-model="props.row.is_approved"
:key="props.row.email + timesheet_store.pay_period?.pay_period_no" :key="props.row.email + timesheet_store.pay_period?.pay_period_no"

View File

@ -1,8 +1,14 @@
import type { QTableColumn } from "quasar"; import type { QTableColumn } from "quasar";
export class TimesheetOverview { export class TimesheetApprovalOverview {
email: string; email: string;
employee_name: string; employee_first_name: string;
employee_last_name: string;
supervisor: {
first_name: string;
last_name: string;
email: string;
} | null;
is_active: boolean; is_active: boolean;
regular_hours: number; regular_hours: number;
other_hours: { other_hours: {
@ -20,7 +26,9 @@ export class TimesheetOverview {
constructor() { constructor() {
this.email = ''; this.email = '';
this.employee_name = 'John Doe'; this.employee_first_name = 'Unknown';
this.employee_last_name = 'Unknown';
this.supervisor = null;
this.is_active = true; this.is_active = true;
this.regular_hours = 0; this.regular_hours = 0;
this.other_hours = { this.other_hours = {
@ -45,14 +53,14 @@ export interface PayPeriodOverviewResponse {
period_end: string; period_end: string;
payday: string; payday: string;
label: string; label: string;
employees_overview: TimesheetOverview[]; employees_overview: TimesheetApprovalOverview[];
} }
export interface PayPeriodOverviewFilters { export interface PayPeriodOverviewFilters {
is_showing_inactive: boolean; is_showing_inactive: boolean;
is_showing_team_only: boolean; is_showing_team_only: boolean;
supervisors: string[]; supervisors: string[];
name_search_string: string | number | null; name_search_string: string;
} }
export const overview_column_names = { export const overview_column_names = {

View File

@ -1,7 +1,7 @@
import { api } from "src/boot/axios"; import { api } from "src/boot/axios";
import type { PayPeriod } from "src/modules/shared/models/pay-period.models"; import type { PayPeriod } from "src/modules/shared/models/pay-period.models";
import type { TimesheetResponse } from "src/modules/timesheets/models/timesheet.models"; import type { TimesheetResponse } from "src/modules/timesheets/models/timesheet.models";
import type { TimesheetOverview } from "src/modules/timesheet-approval/models/timesheet-overview.models"; import type { TimesheetApprovalOverview } from "src/modules/timesheet-approval/models/timesheet-overview.models";
import type { BackendResponse } from "src/modules/shared/models/backend-response.models"; import type { BackendResponse } from "src/modules/shared/models/backend-response.models";
export const timesheetService = { export const timesheetService = {
@ -15,8 +15,8 @@ export const timesheetService = {
return response.data.data; return response.data.data;
}, },
getTimesheetOverviewsByPayPeriodAndSupervisorEmail: async (year: number, period_number: number, supervisor_email: string): Promise<TimesheetOverview[]> => { getTimesheetOverviewsByPayPeriodAndSupervisorEmail: async (year: number, period_number: number, supervisor_email: string): Promise<TimesheetApprovalOverview[]> => {
const response = await api.get<{ success: boolean, data: TimesheetOverview[], error?: string }>(`pay-periods/${year}/${period_number}/${supervisor_email}`); const response = await api.get<{ success: boolean, data: TimesheetApprovalOverview[], error?: string }>(`pay-periods/${year}/${period_number}/${supervisor_email}`);
return response.data.data; return response.data.data;
}, },

View File

@ -3,11 +3,6 @@
lang="ts" lang="ts"
> >
import { ref } from 'vue'; import { ref } from 'vue';
import { Notify } from 'quasar';
const click_number = ref(1);
const icon = ref('las la-hand-peace');
const color = ref('accent');
const LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et \ const LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et \
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip \ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip \
@ -16,34 +11,19 @@
deserunt mollit anim id est laborum." deserunt mollit anim id est laborum."
const slide = ref<string>('welcome'); const slide = ref<string>('welcome');
const clickNotify = () => {
if (click_number.value % 7 === 0) {
icon.value = 'las la-hand-middle-finger';
color.value = 'negative';
}
else {
icon.value = 'las la-hand-peace';
color.value = 'accent';
}
Notify.create({
color: color.value,
icon: icon.value,
iconSize: '5em',
iconColor: 'white',
});
click_number.value += 1;
}
</script> </script>
<template> <template>
<q-page <q-page
padding padding
class="q-pa-md row justify-center" class="q-pa-md justify-center items-stretch"
:class="$q.platform.is.mobile ? 'column' : 'row'"
> >
<q-card flat class="column col-9 transparent "> <div class="column col flex-center q-pa-md">
<div class="col-1"></div>
</div>
<div class="column col-xs-12 col-md-8 col-xl-6 flex-center self-start q-pa-md">
<q-carousel <q-carousel
v-model="slide" v-model="slide"
transition-prev="jump-right" transition-prev="jump-right"
@ -53,13 +33,18 @@
control-color="accent" control-color="accent"
navigation-icon="radio_button_unchecked" navigation-icon="radio_button_unchecked"
navigation navigation
class="col-5 bg-dark rounded-15 shadow-2" class="col-auto bg-dark rounded-15 shadow-18"
> >
<!-- welcome slide -->
<q-carousel-slide <q-carousel-slide
name="welcome" name="welcome"
class="column no-wrap flex-center q-pa-none q-pb-xl" class="column no-wrap flex-center q-pa-none q-pb-xl"
> >
<q-img src="src/assets/line-truck-1.jpg" class="full-height"> <q-img
src="src/assets/targo_building.png"
height="25vh"
position="50% 25%"
>
<div class="absolute-bottom text-h5"> <div class="absolute-bottom text-h5">
Welcome to App Targo! Welcome to App Targo!
</div> </div>
@ -68,6 +53,8 @@
{{ LOREM_IPSUM }} {{ LOREM_IPSUM }}
</div> </div>
</q-carousel-slide> </q-carousel-slide>
<!-- help page slide -->
<q-carousel-slide <q-carousel-slide
name="tv" name="tv"
class="column no-wrap flex-center q-pa-none q-pb-xl" class="column no-wrap flex-center q-pa-none q-pb-xl"
@ -80,39 +67,23 @@
{{ LOREM_IPSUM }} {{ LOREM_IPSUM }}
</div> </div>
</q-carousel-slide> </q-carousel-slide>
<q-carousel-slide
name="layers"
class="column no-wrap flex-center q-pa-none q-pb-xl"
>
<q-icon
name="layers"
size="56px"
/>
<div class="q-mt-md text-center">
{{ LOREM_IPSUM }}
</div>
</q-carousel-slide>
<q-carousel-slide
name="map"
class="column no-wrap flex-center q-pa-none q-pb-xl"
>
<q-icon
name="terrain"
size="56px"
/>
<div class="q-mt-md text-center">
{{ LOREM_IPSUM }}
</div>
</q-carousel-slide>
</q-carousel> </q-carousel>
<div class="col column flex-center">
<q-btn
push
color="accent"
label="Click Me"
@click="clickNotify"
/>
</div> </div>
</q-card>
<div class="column col flex-center q-pt-md q-pl-md">
<div class="col"></div>
<div class="col-auto row justify-end full-width within-iframe">
<iframe
title="Environment Canada Weather"
height="100%"
width="100%"
src="https://weather.gc.ca/wxlink/wxlink.html?coords=45.159%2C-73.676&lang=e"
allowtransparency="true"
style="border: 0;"
class="col-auto"
></iframe>
</div>
</div>
</q-page> </q-page>
</template> </template>

View File

@ -3,7 +3,7 @@ import { defineStore } from 'pinia';
import { unwrapAndClone } from 'src/utils/unwrap-and-clone'; import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
import { timesheetApprovalService } from 'src/modules/timesheet-approval/services/timesheet-approval-service'; import { timesheetApprovalService } from 'src/modules/timesheet-approval/services/timesheet-approval-service';
import { timesheetService } from 'src/modules/timesheets/services/timesheet-service'; import { timesheetService } from 'src/modules/timesheets/services/timesheet-service';
import type { PayPeriodOverviewResponse, TimesheetOverview } from "src/modules/timesheet-approval/models/timesheet-overview.models"; import type { PayPeriodOverviewResponse, TimesheetApprovalOverview } from "src/modules/timesheet-approval/models/timesheet-overview.models";
import type { PayPeriod } from 'src/modules/shared/models/pay-period.models'; import type { PayPeriod } from 'src/modules/shared/models/pay-period.models';
import type { Timesheet } from 'src/modules/timesheets/models/timesheet.models'; import type { Timesheet } from 'src/modules/timesheets/models/timesheet.models';
import type { TimesheetApprovalCSVReportFilters } from 'src/modules/timesheet-approval/models/timesheet-approval-csv-report.models'; import type { TimesheetApprovalCSVReportFilters } from 'src/modules/timesheet-approval/models/timesheet-approval-csv-report.models';
@ -16,13 +16,13 @@ export const useTimesheetStore = defineStore('timesheet', () => {
const all_current_shifts = computed(() => timesheets.value.flatMap(week => week.days.flatMap(day => day.shifts)) ?? []); const all_current_shifts = computed(() => timesheets.value.flatMap(week => week.days.flatMap(day => day.shifts)) ?? []);
const initial_timesheets = ref<Timesheet[]>([]); const initial_timesheets = ref<Timesheet[]>([]);
const pay_period_overviews = ref<TimesheetOverview[]>([]); const pay_period_overviews = ref<TimesheetApprovalOverview[]>([]);
const pay_period_infos = ref<PayPeriodOverviewResponse>(); const pay_period_infos = ref<PayPeriodOverviewResponse>();
const is_report_dialog_open = ref(false); const is_report_dialog_open = ref(false);
const is_details_dialog_open = ref(false); const is_details_dialog_open = ref(false);
const selected_employee_name = ref<string>(); const selected_employee_name = ref<string>();
const current_pay_period_overview = ref<TimesheetOverview>(); const current_pay_period_overview = ref<TimesheetApprovalOverview>();
const is_approval_grid_mode = ref<boolean>(true); const is_approval_grid_mode = ref<boolean>(true);
const pay_period_report = ref(); const pay_period_report = ref();