feat(csv): added a dialog to personnalize the csv to download

This commit is contained in:
Matthieu Haineault 2025-12-09 11:48:44 -05:00
parent 36612b5f4a
commit 0c6fd3289e
7 changed files with 156 additions and 194 deletions

View File

@ -1,38 +0,0 @@
<script setup lang="ts">
import { useTimesheetStore } from 'src/stores/timesheet-store';
const timesheet_store = useTimesheetStore();
</script>
<template>
<q-dialog
v-model="timesheet_store.is_csv_dialog_open"
persistent
transition-show="jump-down"
transition-hide="jump-down"
>
<q-card
class="q-pa-none rounded-10 shadow-10"
:class="$q.screen.lt.md ? ' bg-primary' : 'bg-secondary'"
style=" min-width: 70vw;"
:style="$q.dark.isActive ? 'border: solid 2px var(--q-accent);' : ''"
>
<q-inner-loading :showing="timesheet_store.is_loading">
<q-spinner size="32px" />
</q-inner-loading>
<q-card-section class="q-pa-none">
<q-separator
v-if="$q.screen.lt.md"
spaced
color="accent"
size="2px"
class="q-mx-md"
/>
</q-card-section>
</q-card>
</q-dialog>
</template>

View File

@ -1,21 +1,14 @@
<script <script setup lang="ts">
setup /* eslint-disable */
lang="ts" import { computed, ref } from 'vue';
> import OverviewListItem from 'src/modules/timesheet-approval/components/overview-list-item.vue';
/* eslint-disable */ import LoadingOverlay from 'src/modules/shared/components/loading-overlay.vue';
import { computed, ref } from 'vue'; import { useTimesheetStore } from 'src/stores/timesheet-store';
import OverviewListItem from 'src/modules/timesheet-approval/components/overview-list-item.vue'; import { overview_column_names, pay_period_overview_columns, type TimesheetOverview } from 'src/modules/timesheet-approval/models/timesheet-overview.models';
import LoadingOverlay from 'src/modules/shared/components/loading-overlay.vue';
import { useExpensesStore } from 'src/stores/expense-store';
import { useTimesheetStore } from 'src/stores/timesheet-store';
import { useTimesheetApprovalApi } from 'src/modules/timesheet-approval/composables/use-timesheet-approval-api';
import { overview_column_names, pay_period_overview_columns, type TimesheetOverview } from 'src/modules/timesheet-approval/models/timesheet-overview.models';
const expenses_store = useExpensesStore(); const timesheet_store = useTimesheetStore();
const timesheet_store = useTimesheetStore();
const timesheet_approval_api = useTimesheetApprovalApi();
const visible_columns = ref<string[]>([ const visible_columns = ref<string[]>([
overview_column_names.REGULAR, overview_column_names.REGULAR,
overview_column_names.EVENING, overview_column_names.EVENING,
overview_column_names.EMERGENCY, overview_column_names.EMERGENCY,
@ -24,19 +17,21 @@
overview_column_names.HOLIDAY, overview_column_names.HOLIDAY,
overview_column_names.OVERTIME, overview_column_names.OVERTIME,
overview_column_names.IS_APPROVED, overview_column_names.IS_APPROVED,
]); ]);
const overview_rows = computed(() => timesheet_store.pay_period_overviews[0]?.regular_hours === -1 ? const overview_rows = computed(() => timesheet_store.pay_period_overviews[0]?.regular_hours === -1 ?
[] : [] :
timesheet_store.pay_period_overviews timesheet_store.pay_period_overviews
) )
const onClickedDetails = async (row: TimesheetOverview) => { const onClickedDetails = async (row: TimesheetOverview) => {
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);
timesheet_store.is_details_dialog_open = true; timesheet_store.is_details_dialog_open = true;
}; };
</script> </script>
<template> <template>

View File

@ -1,93 +1,92 @@
<script <script setup lang="ts">
setup import { computed, ref } from 'vue';
lang="ts" import { useTimesheetStore } from 'src/stores/timesheet-store';
> import { TimesheetApprovalCSVReportFilters } from 'src/modules/timesheet-approval/models/timesheet-approval-csv-report.models';
import { default_timesheet_approval_cvs_report_filters, type TimesheetApprovalCSVReportFilters } from 'src/modules/timesheet-approval/models/timesheet-approval-csv-report.models';
import { ref, computed } from 'vue';
const report_filter_options = ref<TimesheetApprovalCSVReportFilters>(default_timesheet_approval_cvs_report_filters); const report_filter_options = ref<TimesheetApprovalCSVReportFilters>(new TimesheetApprovalCSVReportFilters);
const company_options = [ const selected_report_filters = ref<(keyof TimesheetApprovalCSVReportFilters)[]>(
{ label: 'Targo', value: report_filter_options.value.companies.targo }, Object.entries(report_filter_options.value).filter(([_key, value]) => value).map(([key]) => key as keyof TimesheetApprovalCSVReportFilters)
{ label: 'Solucom', value: report_filter_options.value.companies.solucom }, );
];
const type_options = [ interface ReportOptions {
{ label: 'timesheet_approvals.print_report.shifts', value: report_filter_options.value.types.shifts }, label: string;
{ label: 'timesheet_approvals.print_report.expenses', value: report_filter_options.value.types.expenses }, value: keyof TimesheetApprovalCSVReportFilters;
{ label: 'shared.shift_type.holiday', value: report_filter_options.value.types.holiday }, };
{ label: 'shared.shift_type.vacation', value: report_filter_options.value.types.vacation },
];
const is_download_button_disabled = computed(() => { const timesheet_store = useTimesheetStore();
return company_options.map(option => option.value).filter(value => value === true).length > 0 ||
type_options.map(option => option.value).filter(value => value === true).length > 0; const company_options: ReportOptions[] = [
}); { label: 'Targo', value: 'targo' },
{ label: 'Solucom', value: 'solucom' },
];
const type_options: ReportOptions[] = [
{ label: 'timesheet_approvals.print_report.shifts', value: 'shifts' },
{ label: 'timesheet_approvals.print_report.expenses', value: 'expenses' },
{ label: 'shared.shift_type.holiday', value: 'holiday' },
{ label: 'shared.shift_type.vacation', value: 'vacation' },
];
const is_download_button_enable = computed(() =>
company_options.map(option => option.value).some(option => selected_report_filters.value.includes(option)) &&
type_options.map(option => option.value).some(option => selected_report_filters.value.includes(option))
);
const onClickedDownload = async () => {
await timesheet_store.getPayPeriodReport(report_filter_options.value);
}
</script> </script>
<template> <template>
<q-btn-group <q-dialog v-model="timesheet_store.is_report_dialog_open">
rounded <div class="bg-dark column full-width">
push <div class="row col-auto q-py-md q-px-sm">
> <q-checkbox
<q-btn v-for="company, index in company_options"
rounded :key="index"
push v-model="selected_report_filters"
color="primary" left-label
icon="print" :label="$t(company.label)"
:disable="is_download_button_disabled" :val="company.value"
/> class="col-auto q-px-md q-mx-xs shadow-1 rounded-25"
dense
<q-btn-dropdown :class="selected_report_filters.includes(company.value) ? 'bg-accent text-white' : ''"
rounded
push
color="white" color="white"
text-color="primary" checked-icon="download"
icon="checklist" unchecked-icon="highlight_off"
> />
<q-list class="row"> </div>
<q-item class="col">
<q-item-label class="text-weight-bolder text-primary q-ma-none q-pa-none text-uppercase">
{{ $t('timesheet_approvals.print_report.company') }}
</q-item-label>
<q-item-section <div class="row col-auto q-py-md q-px-sm">
row
no-wrap
>
<q-checkbox <q-checkbox
v-for="option, index in company_options" v-for="type, index in type_options"
:key="index" :key="index"
v-model="option.value" v-model="selected_report_filters"
:val="option.label" left-label
:label="option.label" color="white"
class="col-auto q-px-md q-mx-xs shadow-1 rounded-25"
dense
:class="selected_report_filters.includes(type.value) ? 'bg-accent text-white' : ''"
:label="$t(type.label)"
:val="type.value"
checked-icon="download"
unchecked-icon="highlight_off"
/> />
</q-item-section> </div>
</q-item> <div>
<q-btn
<q-separator :disable="!is_download_button_enable"
spaced dense
vertical push
color="primary" size="md"
icon="download"
class=""
:color="is_download_button_enable ? 'accent' : 'grey-5'"
:label="$t('shared.label.download')"
@click="onClickedDownload()"
/> />
</div>
<q-item class="col"> </div>
<q-item-section </q-dialog>
row
no-wrap
>
<p class="text-weight-bolder text-primary q-ma-none q-pa-none text-uppercase">
{{ $t('timesheet_approvals.print_report.type') }}</p>
<q-checkbox
v-for="option, index in type_options"
:key="index"
v-model="option.value"
:val="option.label"
:label="option.label"
/>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</q-btn-group>
</template> </template>

View File

@ -26,21 +26,16 @@ export const useTimesheetApprovalApi = () => {
timesheet_store.is_loading = false; timesheet_store.is_loading = false;
}; };
const getTimesheetApprovalCSVReport = async (report_filter_company: boolean[], report_filter_type: boolean[], year?: number, period_number?: number) => { const getTimesheetApprovalCSVReport = async (report_filter_company: boolean[], report_filter_type: boolean[]) => {
if (timesheet_store.pay_period === undefined) return; if (timesheet_store.pay_period === undefined) return;
const [targo, solucom] = report_filter_company; const [targo, solucom] = report_filter_company;
const [shifts, expenses, holiday, vacation] = report_filter_type; const [shifts, expenses, holiday, vacation] = report_filter_type;
const options = { const options = {
types: { shifts, expenses, holiday, vacation }, shifts, expenses, holiday, vacation, targo, solucom
companies: { targo, solucom },
} as TimesheetApprovalCSVReportFilters; } as TimesheetApprovalCSVReportFilters;
await timesheet_store.getPayPeriodReportByYearAndPeriodNumber( await timesheet_store.getPayPeriodReport(options);
year ?? timesheet_store.pay_period.pay_year,
period_number ?? timesheet_store.pay_period.pay_period_no,
options
);
}; };
return { return {

View File

@ -1,25 +1,18 @@
export interface TimesheetApprovalCSVReportFilters { export class TimesheetApprovalCSVReportFilters {
types: {
shifts: boolean; shifts: boolean;
expenses: boolean; expenses: boolean;
holiday: boolean; holiday: boolean;
vacation: boolean; vacation: boolean;
};
companies: {
targo: boolean; targo: boolean;
solucom: boolean; solucom: boolean;
constructor() {
this.shifts = true;
this.expenses = true;
this.holiday = false;
this.vacation = false;
this.targo = true;
this.solucom = true;
}; };
} }
export const default_timesheet_approval_cvs_report_filters: TimesheetApprovalCSVReportFilters = {
types: {
shifts: true,
expenses: true,
holiday: true,
vacation: true,
},
companies: {
targo: true,
solucom: true,
},
};

View File

@ -9,6 +9,7 @@ import OverviewList from 'src/modules/timesheet-approval/components/overview-lis
import DetailsDialog from 'src/modules/timesheet-approval/components/details-dialog.vue'; import DetailsDialog from 'src/modules/timesheet-approval/components/details-dialog.vue';
import QTableFilters from 'src/modules/shared/components/q-table-filters.vue'; import QTableFilters from 'src/modules/shared/components/q-table-filters.vue';
import PayPeriodNavigator from 'src/modules/shared/components/pay-period-navigator.vue'; import PayPeriodNavigator from 'src/modules/shared/components/pay-period-navigator.vue';
import OverviewReport from 'src/modules/timesheet-approval/components/overview-report.vue';
const timesheet_approval_api = useTimesheetApprovalApi(); const timesheet_approval_api = useTimesheetApprovalApi();
const timesheet_store = useTimesheetStore(); const timesheet_store = useTimesheetStore();
@ -74,12 +75,12 @@ onMounted(async () => {
color="accent" color="accent"
:label="$q.screen.lt.md ? '' : $t('shared.label.download')" :label="$q.screen.lt.md ? '' : $t('shared.label.download')"
class="col-auto q-mr-sm" class="col-auto q-mr-sm"
@click="timesheet_store.is_report_dialog_open = true"
/> />
<QTableFilters v-model:search="timesheet_store.search_filter" /> <QTableFilters v-model:search="timesheet_store.search_filter" />
</div> </div>
</div> </div>
<OverviewReport />
<OverviewList class="col" /> <OverviewList class="col" />
</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 { TimesheetOverview } from "src/modules/timesheet-approval/models/timesheet-overview.models"; import type { PayPeriodOverviewResponse, TimesheetOverview } 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';
@ -17,7 +17,8 @@ export const useTimesheetStore = defineStore('timesheet', () => {
const initial_timesheets = ref<Timesheet[]>([]); const initial_timesheets = ref<Timesheet[]>([]);
const pay_period_overviews = ref<TimesheetOverview[]>([]); const pay_period_overviews = ref<TimesheetOverview[]>([]);
const is_csv_dialog_open = ref(false); const pay_period_infos = ref<PayPeriodOverviewResponse>();
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>();
@ -94,9 +95,10 @@ export const useTimesheetStore = defineStore('timesheet', () => {
} }
}; };
const getPayPeriodReportByYearAndPeriodNumber = async (year: number, period_number: number, report_filters?: TimesheetApprovalCSVReportFilters) => { const getPayPeriodReport = async ( report_filters?: TimesheetApprovalCSVReportFilters) => {
try { try {
const response = await timesheetApprovalService.getPayPeriodReportByYearAndPeriodNumber(year, period_number, report_filters); if(!pay_period.value) return false;
const response = await timesheetApprovalService.getPayPeriodReportByYearAndPeriodNumber(pay_period.value.pay_year, pay_period.value.pay_period_no, report_filters);
pay_period_report.value = response; pay_period_report.value = response;
return true; return true;
} catch (error) { } catch (error) {
@ -107,15 +109,28 @@ export const useTimesheetStore = defineStore('timesheet', () => {
return false; return false;
}; };
const openReportDialog = () => {
is_report_dialog_open.value = true;
is_loading.value = true;
is_loading.value = false;
};
const closeReportDialog = () => {
is_report_dialog_open.value = false;
};
return { return {
is_loading, is_loading,
is_csv_dialog_open, is_report_dialog_open,
is_approval_grid_mode, is_approval_grid_mode,
is_details_dialog_open, is_details_dialog_open,
search_filter, search_filter,
pay_period, pay_period,
pay_period_overviews, pay_period_overviews,
current_pay_period_overview, current_pay_period_overview,
pay_period_infos,
selected_employee_name, selected_employee_name,
timesheets, timesheets,
all_current_shifts, all_current_shifts,
@ -123,6 +138,8 @@ export const useTimesheetStore = defineStore('timesheet', () => {
getPayPeriodByDateOrYearAndNumber, getPayPeriodByDateOrYearAndNumber,
getTimesheetOverviews, getTimesheetOverviews,
getTimesheetsByOptionalEmployeeEmail, getTimesheetsByOptionalEmployeeEmail,
getPayPeriodReportByYearAndPeriodNumber, getPayPeriodReport,
openReportDialog,
closeReportDialog,
}; };
}); });