BREAKING(approvals): begin process of merging and DRYing timesheet with timesheet approvals, adjust imports, WIP - DO NOT MERGE

This commit is contained in:
Nicolas Drolet 2025-09-26 17:03:19 -04:00
parent 89cce4f73f
commit 655a7ecff1
38 changed files with 424 additions and 689 deletions

View File

@ -1,27 +1,28 @@
<script setup lang="ts">
/* eslint-disable */
import { ref } from 'vue';
import { computed, ref } from 'vue';
import { date} from 'quasar';
import type { QDateDetails } from 'src/modules/shared/types/q-date-details';
import { useTimesheetStore } from 'src/stores/timesheet-store';
const timesheet_store = useTimesheetStore();
const is_showing_calendar_picker = ref(false);
const calendar_date = ref(date.formatDate( Date.now(), 'YYYY-MM-DD' ));
const props = defineProps<{
isDisabled?: boolean;
isPreviousLimit:boolean;
}>();
const emit = defineEmits<{
'date-selected': [value: string, reason?: string, details?: QDateDetails]
'date-selected': [ value: string ]
'pressed-previous-button': []
'pressed-next-button': []
}>();
const onDateSelected = (value: string, reason: string, details: QDateDetails) => {
const is_previous_pay_period_limit = computed( ()=>
timesheet_store.pay_period.pay_year === 2024 &&
timesheet_store.pay_period.pay_period_no <= 1
);
const onDateSelected = (value: string) => {
calendar_date.value = value;
is_showing_calendar_picker.value = false;
emit('date-selected', value, reason, details);
emit('date-selected', value);
};
</script>
@ -33,39 +34,43 @@
icon="keyboard_arrow_left"
color="primary"
@click="emit('pressed-previous-button')"
:disable="props.isPreviousLimit || props.isDisabled"
:disable="is_previous_pay_period_limit || timesheet_store.is_loading"
class="q-mr-sm q-px-sm"
>
<q-tooltip
anchor="top middle"
self="center middle"
class="bg-primary text-uppercase text-weight-bold"
> {{ $t( 'timesheet.nav_button.previous_week' )}}
>
{{ $t( 'timesheet.nav_button.previous_week' )}}
</q-tooltip>
</q-btn>
<!-- navigation through calendar date picker -->
<q-btn
push rounded
icon="calendar_month"
color="primary"
@click="is_showing_calendar_picker = true"
:disable="props.isDisabled"
:disable="timesheet_store.is_loading"
class="q-px-lg"
>
<q-tooltip
anchor="top middle"
self="center middle"
class="bg-primary text-uppercase text-weight-bold"
>{{ $t('timesheet.nav_button.calendar_date_picker') }}
>
{{ $t('timesheet.nav_button.calendar_date_picker') }}
</q-tooltip>
</q-btn>
<!-- navigation to next week -->
<q-btn
push rounded
icon="keyboard_arrow_right"
color="primary"
@click="emit('pressed-next-button')"
:disable="props.isDisabled"
:disable="timesheet_store.is_loading"
class="q-ml-sm q-px-sm"
>
<q-tooltip

View File

@ -3,9 +3,9 @@
import { Bar } from 'vue-chartjs';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import { useTimesheetStore } from 'src/stores/timesheet-store';
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale, TimeScale, type ChartData, type ChartDataset } from 'chart.js';
import type { PayPeriodEmployeeDetails } from 'src/modules/timesheet-approval/types/pay-period-employee-details';
import type { Expense } from 'src/modules/timesheets/types/expense.interfaces';
import type { Expense } from 'src/modules/timesheets/models/expense.models';
const { t } = useI18n();
const $q = useQuasar();
@ -15,44 +15,44 @@
ChartJS.defaults.maintainAspectRatio = false;
ChartJS.defaults.color = $q.dark.isActive ? '#F5F5F5' : '#616161';
const props = defineProps<{
rawData: PayPeriodEmployeeDetails | undefined;
defineProps<{
}>();
const expenses_dataset = ref<ChartDataset<'bar'>[]>([]);
const expenses_labels = ref<string[]>([]);
// const expenses_dataset = ref<ChartDataset<'bar'>[]>([]);
// const expenses_labels = ref<string[]>([]);
const getExpensesData = (): ChartData<'bar'> => {
if (props.rawData) {
const all_weeks = [props.rawData.week1, props.rawData.week2];
const all_days = all_weeks.flatMap( week => Object.values(week.expenses));
const all_days_dates = all_weeks.flatMap( week => Object.values(week.shifts))
// const getExpensesData = (): ChartData<'bar'> => {
// if (timesheetDetails) {
// const all_weeks = [timesheetDetails.week1, timesheetDetails.week2];
// const all_days = all_weeks.flatMap( week => Object.values(week.expenses));
// const all_days_dates = all_weeks.flatMap( week => Object.values(week.shifts))
const all_costs = all_days.map( day => getTotalAmounts(day.cash));
const all_mileage = all_days.map( day => getTotalAmounts(day.km));
// const all_costs = all_days.map( day => getTotalAmounts(day.cash));
// const all_mileage = all_days.map( day => getTotalAmounts(day.km));
expenses_dataset.value = [
{
label: t('timesheet_approvals.table.expenses'),
data: all_costs,
backgroundColor: getComputedStyle(document.body).getPropertyValue('--q-primary').trim(),
},
{
label: t('timesheet_approvals.table.mileage'),
data: all_mileage,
backgroundColor: getComputedStyle(document.body).getPropertyValue('--q-info').trim(),
}
]
// expenses_dataset.value = [
// {
// label: t('timesheet_approvals.table.expenses'),
// data: all_costs,
// backgroundColor: getComputedStyle(document.body).getPropertyValue('--q-primary').trim(),
// },
// {
// label: t('timesheet_approvals.table.mileage'),
// data: all_mileage,
// backgroundColor: getComputedStyle(document.body).getPropertyValue('--q-info').trim(),
// }
// ]
expenses_labels.value = all_days_dates.map( day => day.short_date);
}
// expenses_labels.value = all_days_dates.map( day => day.short_date);
// }
return {
datasets: expenses_dataset.value,
labels: expenses_labels.value
};
};
// return {
// datasets: expenses_dataset.value,
// labels: expenses_labels.value
// };
// };
const getTotalAmounts = (expenses: Expense[]): number => {
let total_amount = 0;

View File

@ -1,25 +1,23 @@
<script setup lang="ts">
import { ref } from 'vue';
import DetailedShiftList from 'src/modules/timesheet-approval/components/detailed-shift-list.vue';
import DetailedChartHoursWorked from 'src/modules/timesheet-approval/components/graphs/detailed-chart-hours-worked.vue';
import DetailedChartShiftTypes from 'src/modules/timesheet-approval/components/graphs/detailed-chart-shift-types.vue';
import DetailedChartExpenses from 'src/modules/timesheet-approval/components/graphs/detailed-chart-expenses.vue';
import type { PayPeriodEmployeeOverview } from 'src/modules/timesheet-approval/types/pay-period-employee-overview';
import type { PayPeriodEmployeeDetails } from 'src/modules/timesheet-approval/types/pay-period-employee-details';
import { shift_type_legend } from 'src/modules/timesheet-approval/types/detailed-shift-color';
import { useTimesheetStore } from 'src/stores/timesheet-store';
// import { useTimesheetStore } from 'src/stores/timesheet-store';
import { shift_type_legend } from 'src/modules/timesheet-approval/models/detailed-dialog-shift-color.model';
import DetailedDialogChartHoursWorked from 'src/modules/timesheet-approval/components/graphs/detailed-chart-hours-worked.vue';
import DetailedDialogChartShiftTypes from 'src/modules/timesheet-approval/components/graphs/detailed-chart-shift-types.vue';
import DetailedDialogChartExpenses from 'src/modules/timesheet-approval/components/graphs/detailed-chart-expenses.vue';
import type { TimesheetApprovalOverviewCrewMember } from 'src/modules/timesheet-approval/models/timesheet-approval-overview.models';
import type { TimesheetDetails } from 'src/modules/timesheets/models/timesheet.models';
const dialog_model = defineModel<boolean>('dialog', { default: false });
defineProps<{
isLoading: boolean;
employeeOverview: PayPeriodEmployeeOverview;
employeeDetails: PayPeriodEmployeeDetails;
employeeOverview: TimesheetApprovalOverviewCrewMember;
timesheetDetails: TimesheetDetails;
}>();
const timesheet_store = useTimesheetStore();
const is_showing_graph = ref<boolean>(true);
// const timesheet_store = useTimesheetStore();
const is_showing_graph = ref(true);
</script>
<template>
@ -54,7 +52,7 @@
v-if="!isLoading"
class="text-h5 text-weight-bolder text-center text-primary q-pa-none text-uppercase col-auto"
>
<span> {{ employeeDetails.employee_full_name }} </span>
<span> {{ timesheetDetails.employee_full_name }} </span>
<q-separator
spaced
@ -78,7 +76,7 @@
</q-card-actions>
</q-card-section>
<!-- employee timesheet details edit -->
<!-- employee timesheet for supervisor editting -->
<q-card-section
v-if="!is_showing_graph"
class="q-pa-none"
@ -101,10 +99,7 @@
:horizontal="$q.screen.gt.sm"
class="q-pa-none bg-secondary rounded-10"
>
<DetailedShiftList
:raw-data="employeeDetails"
:current-pay-period="timesheet_store.pay_period"
/>
<!-- IMPORTANT: Timesheet shift list goes here!!! -->
</q-card-section>
</q-card-section>
@ -118,8 +113,8 @@
class="q-pa-none col no-wrap"
style="min-height: 300px;"
>
<DetailedChartHoursWorked
:raw-data="employeeDetails"
<DetailedDialogChartHoursWorked
:raw-data="timesheetDetails"
class="col-7"
/>
@ -129,7 +124,7 @@
/>
<div class="column col justify-center no-wrap q-pa-none">
<DetailedChartShiftTypes
<DetailedDialogChartShiftTypes
:raw-data="employeeOverview"
class="col-5"
/>
@ -139,8 +134,8 @@
:vertical="!$q.screen.lt.md"
/>
<DetailedChartExpenses
:raw-data="employeeDetails"
<DetailedDialogChartExpenses
:raw-data="timesheetDetails"
class="col"
/>
</div>

View File

@ -1,33 +0,0 @@
<template>
<q-card-section
horizontal
class="text-uppercase text-center items-center q-pa-none"
>
<!-- shift row itself -->
<q-card-section class="col q-pa-none">
<q-card-section horizontal class="col q-pa-none">
<!-- punch-in timestamps -->
<q-card-section class="col q-pa-none">
<q-item-label class="text-weight-bolder text-primary" style="font-size: 0.7em;">
{{ $t('shared.misc.in') }}
</q-item-label>
</q-card-section>
<!-- arrows pointing to punch-out timestamps -->
<q-card-section class="col q-py-none q-px-sm">
</q-card-section>
<!-- punch-out timestamps -->
<q-card-section class="col q-pa-none">
<q-item-label class="text-weight-bolder text-primary" style="font-size: 0.7em;">
{{ $t('shared.misc.out') }}
</q-item-label>
</q-card-section>
<!-- comment button -->
<q-card-section class="col column q-pa-none">
</q-card-section>
</q-card-section>
</q-card-section>
</q-card-section>
</template>

View File

@ -1,99 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue';
import type { Shift } from 'src/modules/timesheets/types/shift.interfaces';
const props = defineProps<{
shift: Shift;
}>();
const is_showing_time_popup = ref<boolean>(false);
const getShiftColor = (type: string): string => {
switch(type) {
case 'REGULAR': return 'secondary';
case 'EVENING': return 'warning';
case 'EMERGENCY': return 'amber-10';
case 'OVERTIME': return 'negative';
case 'VACATION': return 'purple-10';
case 'HOLIDAY': return 'purple-10';
case 'SICK': return 'grey-8';
default : return 'transparent';
}
};
</script>
<template>
<q-card-section
horizontal
class="q-pa-none text-uppercase text-center items-center cursor-pointer rounded-10"
style="line-height: 1;"
@click="is_showing_time_popup = true"
>
<!-- punch-in timestamps -->
<q-card-section class="q-pa-none col">
<q-item-label
class="text-weight-bolder q-pa-xs rounded-5"
:class="'bg-' + getShiftColor(props.shift.type) + (!$q.dark.isActive && props.shift.type === 'REGULAR' ? '' : ' text-white')"
style="font-size: 1.5em; line-height: 80% !important;"
>
{{ props.shift.start_time }}
</q-item-label>
</q-card-section>
<!-- arrows pointing to punch-out timestamps -->
<q-card-section
horizontal
class="items-center justify-center q-mx-sm col"
>
<div
v-for="icon_data, index in [
{ transform: 'transform: translateX(5px);', color: 'accent' },
{ transform: 'transform: translateX(-5px);', color: 'primary' }]"
:key="index"
>
<q-icon
v-if="props.shift.type"
name="double_arrow"
:color="icon_data.color"
size="24px"
:style="icon_data.transform"
/>
</div>
</q-card-section>
<!-- punch-out timestamps -->
<q-card-section class="q-pa-none col">
<q-item-label
class="text-weight-bolder q-pa-xs rounded-5"
:class="'bg-' + getShiftColor(props.shift.type) + (!$q.dark.isActive && props.shift.type === 'REGULAR' ? '' : ' text-white')"
style="font-size: 1.5em; line-height: 80% !important;"
>
{{ props.shift.end_time }}
</q-item-label>
</q-card-section>
<!-- comment and expenses buttons -->
<q-card-section
class="col q-pa-none text-right"
>
<!-- chat_bubble_outline or announcement -->
<q-btn
v-if="props.shift.type"
flat
dense
icon="chat_bubble_outline"
class="q-pa-none"
/>
<!-- insert_drive_file or request_quote -->
<q-btn
v-if="props.shift.type"
flat
dense
icon="attach_money"
class="q-pa-none q-mx-xs"
/>
</q-card-section>
</q-card-section>
</template>

View File

@ -1,82 +0,0 @@
<script setup lang="ts">
import DetailedShiftListRow from 'src/modules/timesheet-approval/components/detailed-shift-list-row.vue';
import DetailedShiftListHeader from 'src/modules/timesheet-approval/components/detailed-shift-list-header.vue';
import type { PayPeriod } from 'src/modules/shared/types/pay-period-interface';
import type { Shift } from 'src/modules/timesheets/types/shift.interfaces';
import type { PayPeriodEmployeeDetails } from 'src/modules/timesheet-approval/types/pay-period-employee-details';
import { default_shift } from 'src/modules/timesheets/types/shift.defaults';
const { rawData, currentPayPeriod } = defineProps<{
rawData: PayPeriodEmployeeDetails;
currentPayPeriod: PayPeriod;
}>();
const weeks = [ rawData.week1, rawData.week2 ];
const shifts_or_placeholder = (shifts: Shift[]): Shift[] => {
return shifts.length > 0 ? shifts : [default_shift];
};
const getDate = (shift_date: string): Date => {
return new Date(currentPayPeriod.pay_year.toString() + '/' + shift_date);
};
</script>
<template>
<div
v-for="week, index in weeks"
:key="index"
class="q-px-xs q-pt-xs rounded-5 col"
>
<q-card
v-for="day, day_index in week.shifts"
:key="day_index"
flat
bordered
class="row items-center rounded-10 q-mb-xs"
>
<q-card-section class="col-auto q-pa-xs text-white">
<div
class="bg-primary rounded-10 q-pa-xs text-center"
:style="$q.screen.lt.md ? '' : 'width: 75px;'"
>
<q-item-label
style="font-size: 0.7em;"
class="text-uppercase"
>
{{ $d(getDate(day.short_date), {weekday: $q.screen.lt.md ? 'short' : 'long'}) }}
</q-item-label>
<q-item-label
class="text-weight-bolder"
style="font-size: 2.5em; line-height: 90% !important;"
>
{{ day.short_date.split('/')[1] }}
</q-item-label>
<q-item-label
style="font-size: 0.7em;"
class="text-uppercase"
>
{{ $d(getDate(day.short_date), {month: $q.screen.lt.md ? 'short' : 'long'}) }}
</q-item-label>
</div>
</q-card-section>
<q-card-section class="col q-pa-none">
<DetailedShiftListHeader />
<DetailedShiftListRow
v-for="shift, shift_index in shifts_or_placeholder(day.shifts)"
:key="shift_index"
:shift="shift"
/>
</q-card-section>
<q-card-section class="q-pr-xs col-auto">
<q-btn
push
color="primary"
icon="more_time"
class="q-pa-sm"
/>
</q-card-section>
</q-card>
</div>
</template>

View File

@ -1,7 +1,6 @@
import { useTimesheetStore } from "src/stores/timesheet-store";
import { useAuthStore } from "src/stores/auth-store";
import type { PayPeriodReportFilters } from "src/modules/timesheet-approval/types/pay-period-report";
import { default_pay_period_employee_overview, type PayPeriodEmployeeOverview } from "src/modules/timesheet-approval/types/pay-period-employee-overview";
import type { TimesheetApprovalCSVReportFilters } from "src/modules/timesheet-approval/models/timesheet-approval-csv-report.models";
import { date } from "quasar";
export const useTimesheetApprovalApi = () => {
@ -20,10 +19,6 @@ export const useTimesheetApprovalApi = () => {
}
};
const getPayPeriodOverviewByEmployeeEmail = (email: string): void => {
const employee_overview = timesheet_store.getPayPeriodOverviewByEmployeeEmail(email);
};
/* This method attempts to get the next or previous pay period.
It checks if pay period number is within a certain range, adjusts pay period and year accordingly.
It then requests the matching pay period object to set as current pay period from server.
@ -56,9 +51,9 @@ export const useTimesheetApprovalApi = () => {
const [ targo, solucom ] = report_filter_company;
const [ shifts, expenses, holiday, vacation ] = report_filter_type;
const options = {
company: { targo, solucom },
types: { shifts, expenses, holiday, vacation }
} as PayPeriodReportFilters;
types: { shifts, expenses, holiday, vacation },
companies: { targo, solucom },
} as TimesheetApprovalCSVReportFilters;
await timesheet_store.getTimesheetApprovalCSVReport(options);
};
@ -66,7 +61,6 @@ export const useTimesheetApprovalApi = () => {
return {
getPayPeriodOverviewByDate,
getNextOrPreviousPayPeriodOverviewList,
getPayPeriodOverviewByEmployeeEmail,
getTimesheetApprovalCSVReport,
}
};

View File

@ -1,4 +1,4 @@
export interface PayPeriodReportFilters {
export interface TimesheetApprovalCSVReportFilters {
types: {
shifts: boolean;
expenses: boolean;
@ -9,9 +9,9 @@ export interface PayPeriodReportFilters {
targo: boolean;
solucom: boolean;
};
};
}
export const default_pay_period_report_filters: PayPeriodReportFilters = {
export const default_pay_period_report_filters: TimesheetApprovalCSVReportFilters = {
types: {
shifts: true,
expenses: true,

View File

@ -1,4 +1,14 @@
export interface PayPeriodEmployeeOverview {
export interface TimesheetApprovalOverviewCrew {
pay_period_no: number;
pay_year: number;
payday: string;
period_start: string;
period_end: string;
label: string;
overview_crew: TimesheetApprovalOverviewCrewMember[];
};
export interface TimesheetApprovalOverviewCrewMember {
email: string;
employee_name: string;
regular_hours: number;
@ -11,7 +21,7 @@ export interface PayPeriodEmployeeOverview {
is_approved: boolean;
};
export const default_pay_period_employee_overview: PayPeriodEmployeeOverview = {
export const default_timesheet_approval_overview_crew_member: TimesheetApprovalOverviewCrewMember = {
email: '',
employee_name: '',
regular_hours: -1,
@ -24,7 +34,17 @@ export const default_pay_period_employee_overview: PayPeriodEmployeeOverview = {
is_approved: false
}
export const pay_period_employee_overview_columns = [
export const default_timesheet_approval_overview_crew: TimesheetApprovalOverviewCrew = {
pay_period_no: -1,
pay_year: -1,
payday: '',
period_start: '',
period_end: '',
label: '',
overview_crew: []
}
export const timesheet_approval_overview_crew_columns = [
{
name: 'employee_name',
label: 'timesheet_approvals.table.full_name',

View File

@ -3,9 +3,9 @@
import { date } from 'quasar';
import { useTimesheetApprovalApi } from 'src/modules/timesheet-approval/composables/use-timesheet-approval-api';
import { useTimesheetStore } from 'src/stores/timesheet-store';
import EmployeeOverviewList from 'src/modules/timesheet-approval/components/employee-overview/overview-list.vue';
import TimesheetApprovalDetailed from 'src/modules/timesheet-approval/pages/timesheet-approval-detailed.vue';
import PageHeaderTemplate from 'src/modules/shared/components/page-header-template.vue';
import EmployeeOverviewList from 'src/modules/timesheet-approval/components/employee-overview/overview-list.vue';
import DetailedDialog from 'src/modules/timesheet-approval/components/detailed-dialog.vue';
const timesheet_approval_api = useTimesheetApprovalApi();
const timesheet_store = useTimesheetStore();
@ -26,10 +26,10 @@
:end-date="timesheet_store.pay_period.period_end"
/>
<TimesheetApprovalDetailed
<DetailedDialog
:is-loading="timesheet_store.is_loading"
:employee-overview="timesheet_store.pay_period_employee_overview"
:employee-details="timesheet_store.pay_period_employee_details"
:timesheet-details="timesheet_store.pay_period_employee_details"
/>
<EmployeeOverviewList />

View File

@ -1,15 +0,0 @@
import { defaultTimesheetDetailsWeek } from "src/modules/timesheets/types/timesheet.defaults";
import type { TimesheetDetailsWeek } from "src/modules/timesheets/types/timesheet.interfaces";
export interface PayPeriodEmployeeDetails {
week1: TimesheetDetailsWeek;
week2: TimesheetDetailsWeek;
employee_full_name: string;
};
export const default_pay_period_employee_details = {
week1: defaultTimesheetDetailsWeek(),
week2: defaultTimesheetDetailsWeek(),
employee_full_name: "",
}

View File

@ -1,11 +0,0 @@
import type { PayPeriodEmployeeOverview } from "./pay-period-employee-overview";
export interface PayPeriodOverview {
pay_period_no: number;
pay_year: number;
payday: string;
period_start: string;
period_end: string;
label: string;
employees_overview: PayPeriodEmployeeOverview[];
};

View File

@ -1,16 +0,0 @@
// export interface PayPeriodReport {
// }
export interface PayPeriodReportFilters {
company: {
targo: boolean;
solucom: boolean;
};
types: {
shifts: boolean;
expenses: boolean;
holiday: boolean;
vacation: boolean;
};
}

View File

@ -5,7 +5,6 @@ import { useI18n } from 'vue-i18n';
import { SHIFT_KEY, type ShiftKey, type ShiftPayload, type ShiftSelectOption } from '../../types/shift.types';
import type { UpsertShiftsBody } from '../../types/shift.interfaces';
import { upsertShiftsByDate } from '../../composables/api/use-shift-api';
/* eslint-disable */
const { t } = useI18n();

View File

@ -0,0 +1,78 @@
// export const EXPENSE_TYPE = [
// 'PER_DIEM',
// 'MILEAGE',
// 'EXPENSES',
// 'PRIME_GARDE',
// ] as const;
// export type ExpenseType = (typeof EXPENSE_TYPE)[number];
// export const TYPES_WITH_MILEAGE_ONLY: Readonly<ExpenseType[]> = ['MILEAGE'];
// export const TYPES_WITH_AMOUNT_ONLY: Readonly<ExpenseType[]> = [
// 'PER_DIEM',
// 'EXPENSES',
// 'PRIME_GARDE',
// ];
export type ExpenseType = 'PER_DIEM' | 'MILEAGE' | 'EXPENSES' | 'PRIME_GARDE';
export type ExpenseTotals = {
amount: number;
mileage: number;
};
// export type ExpenseSavePayload = {
// pay_period_no: number;
// pay_year: number;
// email: string;
// expenses: TimesheetExpense[];
// };
export interface Expense {
// is_approved: boolean;
// comment: string;
// amount: number;
// supervisor_comment: string;
// }
// export interface TimesheetExpense {
date: string;
type: string;
amount?: number;
mileage?: number;
comment?: string;
supervisor_comment?: string;
is_approved?: boolean;
}
// export interface PayPeriodExpenses {
export interface TimesheetExpenses {
pay_period_no: number;
pay_year: number;
employee_email: string;
is_approved: boolean;
// expenses: TimesheetExpense[];
expenses: Expense[];
totals?: {
amount: number;
mileage: number;
reimbursable_total?: number;
}
}
// export interface ExpensePayload{
// date: string;
// type: ExpenseType;
// amount?: number;
// mileage?: number;
// comment: string;
// }
// export interface UpsertExpensesBody {
// expenses: ExpensePayload[];
// }
// export interface UpsertExpensesResponse {
// data: PayPeriodExpenses;
// }

View File

@ -0,0 +1,83 @@
// export const SHIFT_KEY = [
// 'REGULAR',
// 'EVENING',
// 'EMERGENCY',
// 'HOLIDAY',
// 'VACATION',
// 'SICK'
// ] as const;
// export type ShiftKey = typeof SHIFT_KEY[number];
// export type ShiftSelectOption = { value: ShiftKey; label: string };
// export type ShiftPayload = {
// start_time: string;
// end_time: string;
// type: ShiftKey;
// is_remote: boolean;
// comment?: string;
// }
export type ShiftKey = 'REGULAR' | 'EVENING' | 'EMERGENCY' | 'HOLIDAY' | 'VACATION' | 'SICK';
export type UpsertAction = 'created' | 'updated' | 'deleted';
export type ShiftLegendItem = {
type: ShiftKey;
color: string;
label_key: string;
text_color?: string;
};
export interface Shift {
date: string;
type: ShiftKey;
start_time: string;
end_time: string;
comment: string;
is_approved: boolean;
is_remote: boolean;
}
export interface UpsertShiftsResponse {
action: UpsertAction;
// day: DayShift[];
day: Shift[];
}
// export interface CreateShiftPayload {
// date: string;
// type: ShiftKey;
// start_time: string;
// end_time: string;
// comment?: string;
// is_remote?: boolean;
// }
// export interface CreateWeekShiftPayload {
// shifts: CreateShiftPayload[];
// }
// export interface UpsertShiftsBody {
// old_shift?: ShiftPayload;
// new_shift?: ShiftPayload;
// }
// export interface DayShift {
// start_time: string;
// end_time: string;
// type: string;
// is_remote: boolean;
// comment?: string | null;
// }
export const default_shift: Readonly<Shift> = {
date: '',
start_time: '--:--',
end_time: '--:--',
type:'REGULAR',
comment: '',
is_approved: false,
is_remote: false,
};

View File

@ -0,0 +1,125 @@
import type { Shift } from "./shift.models";
import type { Expense } from "src/modules/timesheets/models/expense.models";
// import type {
// TimesheetExpenseEntry,
// TimesheetShiftEntry,
// Week
// } from "./timesheet.types";
// export interface Timesheet {
// is_approved: boolean;
// start_day: string;
// end_day: string;
// label: string;
// shifts: TimesheetShiftEntry[];
// expenses: TimesheetExpenseEntry[];
// }
// export type TimesheetShiftEntry = {
// bank_type: string;
// date: string;
// start_time: string;
// end_time: string;
// comment: string;
// is_approved: boolean;
// is_remote: boolean;
// };
// export type TimesheetExpenseEntry = {
// bank_type: string;
// date: string;
// amount: number;
// km: number;
// comment: string;
// is_approved: boolean;
// supervisor_comment: string;
// };
export type Week<T> = {
sun: T;
mon: T;
tue: T;
wed: T;
thu: T;
fri: T;
sat: T;
};
export interface TimesheetDetails {
week1: TimesheetDetailsWeek;
week2: TimesheetDetailsWeek;
employee_full_name: string;
}
export interface TimesheetDetailsWeek {
is_approved: boolean;
shifts: Week<TimesheetDetailsWeekDayShifts>
expenses: Week<TimesheetDetailsWeekDayExpenses>;
}
export interface TimesheetDetailsWeekDayShifts {
shifts: Shift[];
regular_hours: number;
evening_hours: number;
emergency_hours: number;
overtime_hours: number;
total_hours: number;
short_date: string;
break_duration?: number;
}
export interface TimesheetDetailsWeekDayExpenses {
cash: Expense[];
km: Expense[];
[otherType: string]: Expense[];
}
// export interface DailyExpense {
// is_approved: boolean;
// comment: string;
// amount: number;
// supervisor_comment: string;
// }
// export interface TimesheetPayPeriodDetailsOverview {
// week1: TimesheetDetailsWeek;
// week2: TimesheetDetailsWeek;
// }
const makeWeek = <T>(factory: ()=> T): Week<T> => ({
sun: factory(),
mon: factory(),
tue: factory(),
wed: factory(),
thu: factory(),
fri: factory(),
sat: factory(),
});
const emptyDailySchedule = (): TimesheetDetailsWeekDayShifts => ({
shifts: [],
regular_hours: 0,
evening_hours: 0,
emergency_hours: 0,
overtime_hours: 0,
total_hours: 0,
short_date: "",
break_duration: 0,
});
const emptyDailyExpenses = (): TimesheetDetailsWeekDayExpenses => ({
cash: [],
km: [],
});
export const defaultTimesheetDetailsWeek = (): TimesheetDetailsWeek => ({
is_approved: false,
shifts: makeWeek(emptyDailySchedule),
expenses: makeWeek(emptyDailyExpenses),
});
export const default_timesheet_details: TimesheetDetails = {
week1: defaultTimesheetDetailsWeek(),
week2: defaultTimesheetDetailsWeek(),
employee_full_name: "",
}

View File

@ -13,9 +13,9 @@ import TimesheetNavigation from '../components/timesheet/timesheet-naviga
import ShiftsLegend from '../components/shift/shifts-legend.vue';
import ShiftCrudDialog from '../components/shift/shift-crud-dialog.vue';
import TimesheetDetailsExpenses from '../components/expenses/timesheet-details-expenses.vue';
import { SHIFT_KEY } from '../types/shift.types';
import DetailedShiftList from '../components/shift/detailed-shift-list.vue';
import type { ShiftKey } from '../models/shift.models';
import type { TimesheetExpense } from '../types/expense.interfaces';
import DetailedShiftList from '../components/shift/detailed-shift-list.vue';
/* eslint-disable */
//------------------- stores -------------------
@ -29,14 +29,13 @@ const timesheet_api = useTimesheetApi();
//------------------- expenses -------------------
const openExpensesDialog = () => expenses_store.openDialog({
email: auth_store.user.email,
pay_year: timesheet_store.current_pay_period.pay_year,
pay_period_no: timesheet_store.current_pay_period.pay_period_no,
pay_year: timesheet_store.pay_period.pay_year,
pay_period_no: timesheet_store.pay_period.pay_period_no,
t,
});
const onSaveExpenses = async ( payload: { email: string; pay_year: number; pay_period_no: number; expenses: TimesheetExpense[] }) => {
await expenses_store.saveExpenses({...payload, t});
await timesheet_store.refreshCurrentPeriodForUser(auth_store.user.email);
};
const onCloseExpenses = () => expenses_store.closeDialog();
@ -45,7 +44,7 @@ const onCloseExpenses = () => expenses_store.closeDialog();
const date_options: Intl.DateTimeFormatOptions = { day: 'numeric', month: 'long', year: 'numeric' };
const pay_period_label = computed(() => formatPayPeriodLabel(
timesheet_store.current_pay_period?.label,
timesheet_store.pay_period.label,
locale.value,
date.extractDate,
date_options
@ -55,11 +54,6 @@ const pay_period_label = computed(() => formatPayPeriodLabel(
//------------------- q-select Shift options -------------------
const shift_options = computed(() => buildShiftOptions(SHIFT_KEY, t));
//------------------- navigation by date -------------------
const onDateSelected = async (date_string: string) => {
await timesheet_store.loadByIsoDate(date_string, auth_store.user.email);
};
onMounted(async () => {
await timesheet_store.loadToday(auth_store.user.email);
});

View File

@ -6,7 +6,7 @@ import type { PayPeriodReportFilters } from "src/modules/timesheet-approval/type
import type { Timesheet } from "../types/timesheet.interfaces";
import type { CreateShiftPayload, CreateWeekShiftPayload } from "../types/shift.interfaces";
export const timesheetTempService = {
export const timesheetService = {
//GET
getTimesheetsByEmail: async ( email: string, offset = 0): Promise<Timesheet> => {
const response = await api.get(`/timesheets/${encodeURIComponent(email)}`, {params: offset ? { offset } : undefined});

View File

@ -1,47 +0,0 @@
import type { ExpenseType } from "./expense.types";
export interface Expense {
is_approved: boolean;
comment: string;
amount: number;
supervisor_comment: string;
}
export interface TimesheetExpense {
date: string;
type: string;
amount?: number;
mileage?: number;
comment?: string;
supervisor_comment?: string;
is_approved?: boolean;
}
export interface PayPeriodExpenses {
pay_period_no: number;
pay_year: number;
employee_email: string;
is_approved: boolean;
expenses: TimesheetExpense[];
totals: {
amount: number;
mileage: number;
reimbursable_total?: number;
}
}
export interface ExpensePayload{
date: string;
type: ExpenseType;
amount?: number;
mileage?: number;
comment: string;
}
export interface UpsertExpensesBody {
expenses: ExpensePayload[];
}
export interface UpsertExpensesResponse {
data: PayPeriodExpenses;
}

View File

@ -1,29 +0,0 @@
import type { TimesheetExpense } from "./expense.interfaces";
export const EXPENSE_TYPE = [
'PER_DIEM',
'MILEAGE',
'EXPENSES',
'PRIME_GARDE',
] as const;
export type ExpenseType = (typeof EXPENSE_TYPE)[number];
export const TYPES_WITH_MILEAGE_ONLY: Readonly<ExpenseType[]> = ['MILEAGE'];
export const TYPES_WITH_AMOUNT_ONLY: Readonly<ExpenseType[]> = [
'PER_DIEM',
'EXPENSES',
'PRIME_GARDE',
];
export type ExpenseTotals = {
amount: number;
mileage: number;
};
export type ExpenseSavePayload = {
pay_period_no: number;
pay_year: number;
email: string;
expenses: TimesheetExpense[];
};

View File

@ -1,11 +0,0 @@
import type { Shift } from "./shift.interfaces";
export const default_shift: Readonly<Shift> = {
date: '',
start_time: '--:--',
end_time: '--:--',
type:'REGULAR',
comment: '',
is_approved: false,
is_remote: false,
};

View File

@ -1,44 +0,0 @@
import type { ShiftKey, ShiftPayload, UpsertAction } from "./shift.types";
export interface Shift {
date: string;
type: ShiftKey;
start_time: string;
end_time: string;
comment: string;
is_approved: boolean;
is_remote: boolean;
}
export interface CreateShiftPayload {
date: string;
type: ShiftKey;
start_time: string;
end_time: string;
comment?: string;
is_remote?: boolean;
}
export interface CreateWeekShiftPayload {
shifts: CreateShiftPayload[];
}
export interface UpsertShiftsBody {
old_shift?: ShiftPayload;
new_shift?: ShiftPayload;
}
export interface DayShift {
start_time: string;
end_time: string;
type: string;
is_remote: boolean;
comment?: string | null;
}
export interface UpsertShiftsResponse {
action: UpsertAction;
day: DayShift[];
}

View File

@ -1,30 +0,0 @@
export const SHIFT_KEY = [
'REGULAR',
'EVENING',
'EMERGENCY',
'HOLIDAY',
'VACATION',
'SICK'
] as const;
export type ShiftKey = typeof SHIFT_KEY[number];
export type ShiftSelectOption = { value: ShiftKey; label: string };
export type ShiftPayload = {
start_time: string;
end_time: string;
type: ShiftKey;
is_remote: boolean;
comment?: string;
}
export type ShiftLegendItem = {
type: 'REGULAR'|'EVENING'|'EMERGENCY'|'OVERTIME'|'VACATION'|'HOLIDAY'|'SICK';
color: string;
label_key: string;
text_color?: string;
};
export type UpsertAction = 'created' | 'updated' | 'deleted';

View File

@ -1,39 +0,0 @@
import type { WeekDay } from "./timesheet.types";
import type {
TimesheetDetailsDailyExpenses,
TimesheetDetailsDailySchedule,
TimesheetDetailsWeek
} from "./timesheet.interfaces";
const makeWeek = <T>(factory: ()=> T): WeekDay<T> => ({
sun: factory(),
mon: factory(),
tue: factory(),
wed: factory(),
thu: factory(),
fri: factory(),
sat: factory(),
});
const emptyDailySchedule = (): TimesheetDetailsDailySchedule => ({
shifts: [],
regular_hours: 0,
evening_hours: 0,
emergency_hours: 0,
overtime_hours: 0,
total_hours: 0,
comment: "",
short_date: "",
break_duration: 0,
});
const emptyDailyExpenses = (): TimesheetDetailsDailyExpenses => ({
cash: [],
km: [],
});
export const defaultTimesheetDetailsWeek = (): TimesheetDetailsWeek => ({
is_approved: false,
shifts: makeWeek(emptyDailySchedule),
expenses: makeWeek(emptyDailyExpenses),
});

View File

@ -1,54 +0,0 @@
import type { Shift } from "./shift.interfaces";
import type {
TimesheetExpenseEntry,
TimesheetShiftEntry,
WeekDay
} from "./timesheet.types";
export interface Timesheet {
is_approved: boolean;
start_day: string;
end_day: string;
label: string;
shifts: TimesheetShiftEntry[];
expenses: TimesheetExpenseEntry[];
}
export interface TimesheetDetailsWeek {
is_approved: boolean;
shifts: WeekDay<TimesheetDetailsDailySchedule>
expenses: WeekDay<TimesheetDetailsDailyExpenses>;
}
export interface TimesheetDetailsDailySchedule {
shifts: Shift[];
regular_hours: number;
evening_hours: number;
emergency_hours: number;
overtime_hours: number;
total_hours: number;
comment: string;
short_date: string;
break_duration?: number;
}
export interface DailyExpense {
is_approved: boolean;
comment: string;
amount: number;
supervisor_comment: string;
}
export interface TimesheetDetailsDailyExpenses {
cash: DailyExpense[];
km: DailyExpense[];
[otherType: string]: DailyExpense[];
}
export interface TimesheetPayPeriodDetailsOverview {
week1: TimesheetDetailsWeek;
week2: TimesheetDetailsWeek;
}

View File

@ -1,29 +0,0 @@
export type TimesheetShiftEntry = {
bank_type: string;
date: string;
start_time: string;
end_time: string;
comment: string;
is_approved: boolean;
is_remote: boolean;
};
export type TimesheetExpenseEntry = {
bank_type: string;
date: string;
amount: number;
km: number;
comment: string;
is_approved: boolean;
supervisor_comment: string;
};
export type WeekDay<T> = {
sun: T;
mon: T;
tue: T;
wed: T;
thu: T;
fri: T;
sat: T;
};

View File

@ -1,42 +1,23 @@
import { date } from 'quasar';
import { defineStore } from 'pinia';
import { computed, ref } from 'vue';
import { withLoading } from 'src/utils/store-helpers';
import { timesheetApprovalService } from 'src/modules/timesheet-approval/services/timesheet-approval-service';
import { timesheetTempService } from 'src/modules/timesheets/services/timesheet-services';
import { default_pay_period_employee_details, type PayPeriodEmployeeDetails } from 'src/modules/timesheet-approval/types/pay-period-employee-details';
import { default_pay_period_employee_overview, type PayPeriodEmployeeOverview } from "src/modules/timesheet-approval/types/pay-period-employee-overview";
import type { Timesheet } from 'src/modules/timesheets/types/timesheet.interfaces';
import type { PayPeriod } from 'src/modules/shared/types/pay-period-interface';
import { timesheetService } from 'src/modules/timesheets/services/timesheet-service';
import { default_timesheet_approval_overview_crew, type TimesheetApprovalOverviewCrew } from "src/modules/timesheet-approval/models/timesheet-approval-overview.models";
// import type { Timesheet } from 'src/modules/timesheets/types/timesheet.interfaces';
import type { TimesheetDetails } from 'src/modules/timesheets/models/timesheet.models';
import { default_timesheet_details } from 'src/modules/timesheets/types/timesheet.defaults';
import { default_pay_period, type PayPeriod } from 'src/modules/shared/types/pay-period-interface';
import type { PayPeriodReportFilters } from 'src/modules/timesheet-approval/types/pay-period-report';
const default_pay_period: PayPeriod = {
pay_period_no: -1,
period_start: '',
period_end: '',
payday: '',
pay_year: -1,
label: ''
};
//employee timesheet
const default_timesheet: Timesheet = {
start_day: '',
end_day: '',
label: '',
is_approved: false,
shifts: [],
expenses: [],
};
export const useTimesheetStore = defineStore('timesheet', () => {
const is_loading = ref<boolean>(false);
const pay_period = ref<PayPeriod>(default_pay_period);
const pay_period_employee_overview_list = ref<PayPeriodEmployeeOverview[]>([]);
const pay_period_employee_overview = ref<PayPeriodEmployeeOverview>(default_pay_period_employee_overview);
const pay_period_employee_details = ref<PayPeriodEmployeeDetails>(default_pay_period_employee_details);
const timesheet_approval_overview_list = ref<TimesheetApprovalOverview[]>([]);
const timesheet_aproval_overview = ref<TimesheetApprovalOverview>(default_pay_period_employee_overview);
const pay_period_employee_details = ref<TimesheetDetails>(default_timesheet_details);
const pay_period_report = ref();
const timesheet = ref<Timesheet>(default_timesheet);
// const timesheet = ref<Timesheet>(default_timesheet);
const is_calendar_limit = computed( ()=>
pay_period.value.pay_year === 2024 &&
pay_period.value.pay_period_no <= 1
@ -97,21 +78,21 @@ export const useTimesheetStore = defineStore('timesheet', () => {
return pay_period_employee_overview.value;
};
const getTimesheetByEmail = async (employee_email: string) => {
return withLoading( is_loading, async () => {
try{
const response = await timesheetTempService.getTimesheetsByEmail(employee_email);
timesheet.value = response;
// const getTimesheetByEmail = async (employee_email: string) => {
// return withLoading( is_loading, async () => {
// try{
// const response = await timesheetTempService.getTimesheetsByEmail(employee_email);
// timesheet.value = response;
return true;
}catch (error) {
console.error('There was an error retrieving timesheet details for this employee: ', error);
timesheet.value = { ...default_timesheet }
}
// return true;
// }catch (error) {
// console.error('There was an error retrieving timesheet details for this employee: ', error);
// timesheet.value = { ...default_timesheet }
// }
return false;
});
};
// return false;
// });
// };
const getPayPeriodEmployeeDetailsByEmployeeEmail = async (employee_email: string) => {
return withLoading( is_loading, async () => {
@ -163,7 +144,7 @@ export const useTimesheetStore = defineStore('timesheet', () => {
is_loading,
is_calendar_limit,
getPayPeriodByDateOrYearAndNumber,
getTimesheetByEmail,
// getTimesheetByEmail,
getPayPeriodEmployeeOverviewListBySupervisorEmail,
getPayPeriodOverviewByEmployeeEmail,
getPayPeriodEmployeeDetailsByEmployeeEmail,