Merge pull request 'release/nicolas/v1.1' (#79) from release/nicolas/v1.1 into main

Reviewed-on: Targo/targo_frontend#79
This commit is contained in:
Nicolas 2026-02-09 09:53:57 -05:00
commit 877353307a
10 changed files with 86 additions and 26 deletions

View File

@ -331,6 +331,8 @@ export default {
empty_list: 'No registered expenses', empty_list: 'No registered expenses',
employee_comment: 'Comment', employee_comment: 'Comment',
supervisor_comment: 'Supervisor note', supervisor_comment: 'Supervisor note',
no_attachment: "no image attached",
temp_attachment_msg: "attachments are temporarily down, you can omit them from submissions and forward them to the finance department",
actions: { actions: {
delete_confirm: "Delete this expense?", delete_confirm: "Delete this expense?",
}, },

View File

@ -331,6 +331,8 @@ export default {
empty_list: 'Aucun dépense enregistrée', empty_list: 'Aucun dépense enregistrée',
employee_comment: 'Commentaire', employee_comment: 'Commentaire',
supervisor_comment: 'Note du Superviseur', supervisor_comment: 'Note du Superviseur',
no_attachment: "aucune pièce jointe",
temp_attachment_msg: "Les pièces jointes sont désactivés temporairement, vous pouvez laisser le champ vide et acheminez vos recus au département de la comptabilité",
actions: { actions: {
delete_confirm: "Supprimer cette dépense?", delete_confirm: "Supprimer cette dépense?",
}, },

View File

@ -60,7 +60,9 @@
const requestExpenseCreationOrUpdate = async () => { const requestExpenseCreationOrUpdate = async () => {
if (file.value) if (file.value)
await expenses_api.upsertExpense(expenses_store.current_expense, file.value, employeeEmail ?? auth_store.user?.email ?? 'MISSING_EMAIL'); await expenses_api.upsertExpense(expenses_store.current_expense, employeeEmail ?? auth_store.user?.email ?? 'MISSING_EMAIL', file.value);
else
await expenses_api.upsertExpense(expenses_store.current_expense, employeeEmail ?? auth_store.user?.email ?? 'MISSING_EMAIL');
expenses_store.is_showing_create_form = true; expenses_store.is_showing_create_form = true;
expenses_store.mode = 'create'; expenses_store.mode = 'create';

View File

@ -24,6 +24,7 @@
<template> <template>
<div class="column items-center q-pa-none"> <div class="column items-center q-pa-none">
<!-- title bar with close button -->
<div class="col row full-width bg-primary"> <div class="col row full-width bg-primary">
<q-item-label class="col text-h6 text-weight-bolder text-uppercase text-white q-py-sm q-px-md"> <q-item-label class="col text-h6 text-weight-bolder text-uppercase text-white q-py-sm q-px-md">
{{ $t('timesheet.expense.title') }} {{ $t('timesheet.expense.title') }}
@ -41,6 +42,19 @@
/> />
</div> </div>
<!-- REMOVE: ONCE ATTACHMENTS ARE FULLY IMPLEMENTED -->
<div class="col-auto row bg-warning flex-center q-px-md q-py-xs full-width">
<q-icon
name="las la-exclamation-triangle"
size="md"
class="q-px-md"
/>
<span class="text-bold text-center">
{{ $t('timesheet.expense.temp_attachment_msg') }}
</span>
</div>
<div class="col row flex-center full-width q-pt-sm q-px-md"> <div class="col row flex-center full-width q-pt-sm q-px-md">
<div class="col-auto row items-center q-px-md"> <div class="col-auto row items-center q-px-md">
<span <span

View File

@ -4,7 +4,7 @@
> >
import ExpenseDialogForm from 'src/modules/timesheets/components/expense-dialog-form.vue'; import ExpenseDialogForm from 'src/modules/timesheets/components/expense-dialog-form.vue';
import { ref } from 'vue'; import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { date, Notify } from 'quasar'; import { date, Notify } from 'quasar';
import { unwrapAndClone } from 'src/utils/unwrap-and-clone'; import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
@ -28,6 +28,12 @@
const timesheet_store = useTimesheetStore(); const timesheet_store = useTimesheetStore();
const is_showing_update_form = ref(false); const is_showing_update_form = ref(false);
// ========== computed ===================================
const attachmentButtonColor = computed(() => expense.value.attachment_name ?
(expense.value.is_approved ? 'white' : 'accent') :
'grey-5');
// ===================== methods ========================= // ===================== methods =========================
const requestExpenseDeletion = async () => { const requestExpenseDeletion = async () => {
@ -46,7 +52,11 @@
expenses_store.current_expense = unwrapAndClone(expense.value); expenses_store.current_expense = unwrapAndClone(expense.value);
expenses_store.current_expense.is_approved = !expenses_store.current_expense.is_approved; expenses_store.current_expense.is_approved = !expenses_store.current_expense.is_approved;
const success = await expenses_store.upsertExpense(expenses_store.current_expense, timesheet_store.current_pay_period_overview?.email); const success = await expenses_store.upsertExpense(
expenses_store.current_expense,
timesheet_store.current_pay_period_overview?.email
);
if (success) { if (success) {
expense.value.is_approved = !expense.value.is_approved; expense.value.is_approved = !expense.value.is_approved;
} else { } else {
@ -75,7 +85,6 @@
<div class="col-auto"> <div class="col-auto">
<q-icon <q-icon
:name="getExpenseIcon(expense.type)" :name="getExpenseIcon(expense.type)"
:color="expense.is_approved ? 'white' : ($q.dark.isActive ? 'white' : 'blue-grey-8')"
size="lg" size="lg"
class="q-pr-md" class="q-pr-md"
/> />
@ -117,14 +126,24 @@
<div class="col row items-center justify-start"> <div class="col row items-center justify-start">
<q-btn <q-btn
push push
:color="expense.is_approved ? 'white' : 'accent'" :disable="expense.attachment_name === undefined"
:color="attachmentButtonColor"
:text-color="expense.is_approved ? 'accent' : 'white'" :text-color="expense.is_approved ? 'accent' : 'white'"
class="col-auto q-px-sm q-mr-sm" class="col-auto q-px-sm q-mr-sm"
icon="attach_file" icon="attach_file"
/> />
<q-item-label class="col"> <q-item-label class="col">
attachment_name.jpg <span v-if="expense.attachment_name">
{{ expense.attachment_name }}
</span>
<span
v-else
class="text-italic text-blue-grey-5 text-uppercase"
>
{{ $t('timesheet.expense.no_attachment') }}
</span>
</q-item-label> </q-item-label>
</div> </div>

View File

@ -58,7 +58,8 @@
if (file.value) if (file.value)
await expenses_api.upsertExpense( await expenses_api.upsertExpense(
expenses_store.current_expense, expenses_store.current_expense,
file.value, employeeEmail ?? auth_store.user?.email ?? 'MISSING_EMAIL' employeeEmail ?? auth_store.user?.email ?? 'MISSING_EMAIL',
file.value
); );
emit('onUpdateClicked'); emit('onUpdateClicked');

View File

@ -134,7 +134,6 @@
<!-- avatar type icon section --> <!-- avatar type icon section -->
<q-icon <q-icon
:name="getExpenseIcon(expense.type)" :name="getExpenseIcon(expense.type)"
:color="expense.is_approved ? 'white' : ($q.dark.isActive ? 'white' : 'primary')"
size="lg" size="lg"
class="col-auto q-pr-sm" class="col-auto q-pr-sm"
/> />

View File

@ -81,13 +81,30 @@
<LoadingOverlay v-model="timesheet_store.is_loading" /> <LoadingOverlay v-model="timesheet_store.is_loading" />
<!-- label for approval mode to delimit that this is the timesheet --> <!-- label for approval mode to delimit that this is the timesheet -->
<span <div
v-if="mode === 'approval'" v-if="mode === 'approval'"
class="col-auto text-uppercase text-bold text-h5" class="col-auto row full-width q-px-xl"
> >
<span class="col-auto text-uppercase text-bold text-h5">
{{ $t('timesheet.page_header') }} {{ $t('timesheet.page_header') }}
</span> </span>
<q-space />
<!-- desktop save timesheet changes button -->
<q-btn
v-if="!is_timesheets_approved && $q.screen.width > $q.screen.height"
push
rounded
:disable="timesheet_store.is_loading || has_shift_errors"
:color="timesheet_store.is_loading || has_shift_errors ? 'grey-5' : 'accent'"
icon="upload"
:label="$t('shared.label.save')"
:class="$q.platform.is.mobile && ($q.screen.width < $q.screen.height) ? 'full-width' : 'q-ml-md'"
@click="onClickSaveTimesheets"
/>
</div>
<!-- weekly overview --> <!-- weekly overview -->
<div class="col-auto row q-px-lg full-width"> <div class="col-auto row q-px-lg full-width">

View File

@ -8,9 +8,12 @@ export const useExpensesApi = () => {
const expenses_store = useExpensesStore(); const expenses_store = useExpensesStore();
const timesheet_store = useTimesheetStore(); const timesheet_store = useTimesheetStore();
const upsertExpense = async (expense: Expense, file: File, employee_email: string): Promise<string> => { const upsertExpense = async (expense: Expense, employee_email: string, file?: File): Promise<string> => {
if (file) {
const presignedURL = expenses_store.uploadAttachment(file); const presignedURL = expenses_store.uploadAttachment(file);
if (!presignedURL) return 'PRESIGN_FAILED'; if (!presignedURL) return 'PRESIGN_FAILED';
}
const success = await expenses_store.upsertExpense(expense, employee_email); const success = await expenses_store.upsertExpense(expense, employee_email);

View File

@ -2,31 +2,32 @@ import { useShiftStore } from "src/stores/shift-store";
import { useTimesheetStore } from "src/stores/timesheet-store"; import { useTimesheetStore } from "src/stores/timesheet-store";
export const useShiftApi = () => { export const useShiftApi = () => {
const timesheet_store = useTimesheetStore(); const timesheetStore = useTimesheetStore();
const shift_store = useShiftStore(); const shift_store = useShiftStore();
const deleteShiftById = async (shift_id: number, employee_email?: string) => { const deleteShiftById = async (shift_id: number, employee_email?: string) => {
timesheet_store.is_loading = true; timesheetStore.is_loading = true;
const success = await shift_store.deleteShiftById(shift_id, employee_email); const success = await shift_store.deleteShiftById(shift_id, employee_email);
if (success) { if (success) {
await timesheet_store.getTimesheetsByOptionalEmployeeEmail(employee_email); await timesheetStore.getTimesheetsByOptionalEmployeeEmail(employee_email);
} }
timesheet_store.is_loading = false; timesheetStore.is_loading = false;
}; };
const saveShiftChanges = async (employee_email?: string) => { const saveShiftChanges = async (employee_email?: string) => {
timesheet_store.is_loading = true; timesheetStore.is_loading = true;
const update_success = await shift_store.updateShifts(employee_email); const update_success = await shift_store.updateShifts(employee_email);
const create_success = await shift_store.createNewShifts(employee_email); const create_success = await shift_store.createNewShifts(employee_email);
if (create_success || update_success){ if (create_success || update_success){
await timesheet_store.getTimesheetsByOptionalEmployeeEmail(employee_email); await timesheetStore.getTimesheetsByOptionalEmployeeEmail(employee_email);
await timesheetStore.getPaidTimeOffTotalsWithOptionalEmployeeEmail();
} }
timesheet_store.is_loading = false; timesheetStore.is_loading = false;
} }
return { return {