From d1fc596b62bc3df4e0070c4c84cf2f0c62a86504 Mon Sep 17 00:00:00 2001 From: Matthieu Haineault Date: Mon, 22 Sep 2025 14:17:07 -0400 Subject: [PATCH] feat(expenses): expense's dialog 1st iteration --- src/i18n/en-ca/index.ts | 2 + src/i18n/fr-ca/index.ts | 2 + .../expenses/timesheet-details-expenses.vue | 445 ++++++++++-------- .../pages/timesheet-details-overview.vue | 10 +- .../types/timesheet-expenses-interface.ts | 4 +- 5 files changed, 265 insertions(+), 198 deletions(-) diff --git a/src/i18n/en-ca/index.ts b/src/i18n/en-ca/index.ts index e01b2c2..651696b 100644 --- a/src/i18n/en-ca/index.ts +++ b/src/i18n/en-ca/index.ts @@ -301,6 +301,8 @@ export default { amount:'Amount', date:'Date', empty_list:'No registered expenses', + employee_comment:'Comment', + supervisor_comment:'Supervisor note', errors: { date_required_or_invalid:'the date is missing or invalid', comment_required:'A comment required', diff --git a/src/i18n/fr-ca/index.ts b/src/i18n/fr-ca/index.ts index ccf79e3..0ab00ad 100644 --- a/src/i18n/fr-ca/index.ts +++ b/src/i18n/fr-ca/index.ts @@ -351,6 +351,8 @@ export default { amount:'Montant', date:'Date', empty_list:'Aucun dépense enregistrée', + employee_comment:'Commentaire', + supervisor_comment:'Note du Superviseur', errors: { date_required_or_invalid:'La date est manquante ou invalide', comment_required:'un commentaire est requis', diff --git a/src/modules/timesheets/components/expenses/timesheet-details-expenses.vue b/src/modules/timesheets/components/expenses/timesheet-details-expenses.vue index cda1de1..f2c7690 100644 --- a/src/modules/timesheets/components/expenses/timesheet-details-expenses.vue +++ b/src/modules/timesheets/components/expenses/timesheet-details-expenses.vue @@ -3,11 +3,10 @@ import { computed, ref } from 'vue'; import { EXPENSE_TYPE, type ExpenseType, type TimesheetExpense } from '../../types/timesheet-expenses-interface'; import { compute_expense_totals, ExpensesValidationError, normalize_expense, validate_expense_UI } from '../../utils/timesheet-expenses-validators'; -import { useI18n } from 'vue-i18n'; +// import { date } from 'quasar'; import { COMMENT_MAX_LENGTH } from '../../composables/use-shift-api'; /* eslint-disable */ -const { t } = useI18n(); //props const props = defineProps<{ @@ -31,27 +30,28 @@ const emit = defineEmits<{ }>(); //q-select mapper -const type_options = computed(()=> EXPENSE_TYPE.map((val)=> ({ - label: t(`timesheet.expense.types.${val}`, val), - value: val, -}))); +const type_options = computed(()=> EXPENSE_TYPE.map( val => ({ label: val, value: val }))); //refs & states const items = ref(Array.isArray(props.initial_expenses) ? props.initial_expenses.map(normalize_expense): []); +const formRef = ref | null>(null); +const triedSubmit = ref(false); + +const DEFAULT_TYPE: ExpenseType = 'EXPENSES' + const draft = ref>({ date:'', - type: 'EXPENSES', + type: DEFAULT_TYPE, comment:'', }); // computeds -const totals = computed(()=> compute_expense_totals(items.value)); -const remaining_comment_chars = computed(()=> { - const comment = String(draft.value.comment ?? ''); - return COMMENT_MAX_LENGTH - comment.length; -}); +const totals = computed(()=> compute_expense_totals(items.value)); +const is_readonly = computed(()=> !!props.is_approved); +const showMileage = computed(()=> (draft.value.type as string) === 'MILEAGE'); +const showAmount = computed(()=> !showMileage.value); -//actions +//helpers const reset_draft = () => { draft.value.date = ''; draft.value.type = 'EXPENSES'; @@ -60,10 +60,20 @@ const reset_draft = () => { draft.value.comment = ''; }; +const set_draft_type = (value: ExpenseType) => { + draft.value.type = value; + if (value === 'MILEAGE') { + delete draft.value.amount; + } else { + delete draft.value.mileage; + } +}; + +//actions const add_draft_as_item = () => { const candidate: TimesheetExpense = normalize_expense({ - date: String(draft.value.date ?? '').trim(), - type: String(draft.value.type ?? '').trim(), + date: draft.value.date, + type: normType(draft.value.type), ...(typeof draft.value.amount === 'number' ? { amount: draft.value.amount }: {}), ...(typeof draft.value.mileage === 'number' ? { mileage: draft.value.mileage }: {}), comment: String(draft.value.comment ?? '').trim(), @@ -117,217 +127,270 @@ const on_save = () => { } }; +const on_form_submit = async () => { + triedSubmit.value = true; + const ok = await formRef.value?.validate(true); + if(!ok) return; + add_draft_as_item(); +}; + const on_close = () => emit('close'); - -//read-only guard for supervisor comment and approved expenses -const is_readonly = computed(()=> !!props.is_approved); - - -const set_draft_type = (value: ExpenseType) => (draft.value.type = value); -const set_draft_amount = (value: number | null) => { - if(value === null || value === undefined || Number.isNaN(Number(value))) { - delete draft.value.amount; - } else { - draft.value.amount = Number(value); - } -}; -const set_draft_mileage = (value: number | null) => { - if(value === null || value === undefined || Number.isNaN(Number(value))) { - delete draft.value.mileage; - } else { - draft.value.mileage = Number(value); - } +//icons managament +type ExpensesType = 'MILEAGE' | 'EXPENSES' | 'PER_DIEM' | 'PRIME_GARDE' | string; +const normType = (type: unknown) => String(type ?? '').trim().toUpperCase(); +const expenseTypeIcon = (type: ExpensesType) => { + const t = normType(type); + const map: Record = { + MILEAGE: 'time_to_leave', + EXPENSES: 'receipt_long', + PER_DIEM: 'hotel', + PRIME_GARDE: 'admin_panel_settings', + }; + return map[String(t)] ?? 'help_outline'; }; \ No newline at end of file diff --git a/src/modules/timesheets/pages/timesheet-details-overview.vue b/src/modules/timesheets/pages/timesheet-details-overview.vue index 08bb5cc..d183998 100644 --- a/src/modules/timesheets/pages/timesheet-details-overview.vue +++ b/src/modules/timesheets/pages/timesheet-details-overview.vue @@ -261,19 +261,19 @@ const on_request_delete = async ({ date, shift }: { date: string; shift: any }) persistent > - {{ expenses_error }} - + --> = ['MILEAGE']; -export const TYPES_WITH_AMOUNT_ONLY: Readonly = ['PER_DIEM', 'EXPENSES', 'PRIME_DISPO'] \ No newline at end of file +export const TYPES_WITH_AMOUNT_ONLY: Readonly = ['PER_DIEM', 'EXPENSES', 'PRIME_GARDE'] \ No newline at end of file