fix(timesheet): fix issue mobile epxense issue. Mobile expense UI overhaul.

- fix issue where mobile version of expense wouldn't allow you to change expense type and keep defaulting to type EXPENSES

revamped mobile expense UI/UX to be more intuitive and less crowded.
This commit is contained in:
Nic D 2026-02-02 10:56:52 -05:00
parent 8998918002
commit 1c6f7fd155
5 changed files with 126 additions and 55 deletions

View File

@ -241,6 +241,7 @@ export default {
download: "download", download: "download",
open: "open", open: "open",
day: "day", day: "day",
empty: "empty",
}, },
misc: { misc: {
or: "or", or: "or",
@ -327,6 +328,9 @@ 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',
actions: {
delete_confirm: "Delete this expense?",
},
hints: { hints: {
amount_or_mileage: "Either amount or mileage, not both", amount_or_mileage: "Either amount or mileage, not both",
comment_required: "A comment required", comment_required: "A comment required",

View File

@ -241,6 +241,7 @@ export default {
download: "télécharger", download: "télécharger",
open: "ouvrir", open: "ouvrir",
day: "jour", day: "jour",
empty: "vide",
}, },
misc: { misc: {
or: "ou", or: "ou",
@ -327,6 +328,9 @@ 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',
actions: {
delete_confirm: "Supprimer cette dépense?",
},
hints: { hints: {
amount_or_mileage: "Soit dépense ou kilométrage, pas les deux", amount_or_mileage: "Soit dépense ou kilométrage, pas les deux",
comment_required: "un commentaire est requis", comment_required: "un commentaire est requis",

View File

@ -264,9 +264,6 @@
<q-file <q-file
v-model="file" v-model="file"
standout="bg-blue-grey-9" standout="bg-blue-grey-9"
dense
use-chips
multiple
stack-label stack-label
:label="$t('timesheet.expense.hints.attach_file')" :label="$t('timesheet.expense.hints.attach_file')"
class="col full-width q-my-xs" class="col full-width q-my-xs"

View File

@ -12,15 +12,24 @@
import type { Expense } from 'src/modules/timesheets/models/expense.models'; import type { Expense } from 'src/modules/timesheets/models/expense.models';
import ExpenseDialogFormMobile from 'src/modules/timesheets/components/mobile/expense-dialog-form-mobile.vue'; import ExpenseDialogFormMobile from 'src/modules/timesheets/components/mobile/expense-dialog-form-mobile.vue';
// =========== state =====================================
const expense = defineModel<Expense>({ required: true }) const expense = defineModel<Expense>({ required: true })
const expenses_store = useExpensesStore(); const expenses_store = useExpensesStore();
const expenses_api = useExpensesApi(); const expenses_api = useExpensesApi();
const approved_class = computed(() => expense.value.is_approved ? ' bg-accent text-white' : '')
const is_showing_update_form = ref(false); const is_showing_update_form = ref(false);
const is_showing_delete_confirm = ref(false);
// =========== computed ==================================
const approved_class = computed(() => expense.value.is_approved ? ' bg-accent text-white' : '')
// =========== methods ===================================
const requestExpenseDeletion = async () => { const requestExpenseDeletion = async () => {
showDeleteConfirmation(false);
await expenses_api.deleteExpenseById(expense.value.id); await expenses_api.deleteExpenseById(expense.value.id);
} }
@ -31,84 +40,139 @@
expenses_store.current_expense = expense.value; expenses_store.current_expense = expense.value;
expenses_store.initial_expense = unwrapAndClone(expense.value); expenses_store.initial_expense = unwrapAndClone(expense.value);
} }
const showDeleteConfirmation = (state: boolean) => {
is_showing_delete_confirm.value = state;
}
</script> </script>
<template> <template>
<div class="column bg-dark shadow-5 rounded-5 q-my-sm full-width"> <div class="column bg-dark shadow-5 rounded-5 q-my-sm full-width">
<!-- expense deletion confirmation -->
<q-dialog
v-model="is_showing_delete_confirm"
backdrop-filter="blur(4px)"
class="z-max"
>
<div class="column rounded-5 bg-dark">
<span class="col text-uppercase text-weight-light text-h6 q-py-sm q-px-md">
{{ $t('timesheet.expense.actions.delete_confirm') }}
</span>
<div class="row col">
<q-btn
flat
dense
square
size="lg"
color="negative"
:label="$t('shared.misc.no')"
class="col"
@click="showDeleteConfirmation(false)"
/>
<q-btn
flat
dense
square
size="lg"
color="accent"
:label="$t('shared.misc.yes')"
class="col"
@click="requestExpenseDeletion"
/>
</div>
</div>
</q-dialog>
<q-expansion-item <q-expansion-item
v-model="is_showing_update_form" v-model="is_showing_update_form"
hide-expand-icon hide-expand-icon
dense dense
group="expenses" group="expenses"
header-class="q-px-none"
class="rounded-5"
:class="expense.is_approved ? ' bg-accent text-white' : ''" :class="expense.is_approved ? ' bg-accent text-white' : ''"
@before-show="onUpdateClicked()" @before-show="onUpdateClicked()"
> >
<template #header> <template #header>
<div class="column col"> <div class="row full-width">
<!-- date label --> <div class="column col q-px-sm q-py-xs">
<div class="col-auto row items-center q-pl-xs"> <!-- date label and delete button -->
<q-icon <div class="col-auto row items-center q-pl-xs">
name="calendar_month" <q-icon
size="sm" name="calendar_month"
class="col-auto" size="sm"
/> class="col-auto"
/>
<span <span
class="col text-uppercase text-weight-light full-width q-pl-sm text-h6" class="col text-uppercase text-weight-light full-width q-pl-sm text-h6"
:class="approved_class" :class="approved_class"
> >
{{ $d( {{ $d(
date.extractDate(expense.date, 'YYYY-MM-DD'), date.extractDate(expense.date, 'YYYY-MM-DD'),
{ month: 'long', day: 'numeric' } { month: 'long', day: 'numeric' }
) }} ) }}
</span> </span>
</div>
<div class="col row full-width items-center"> <q-btn
<!-- avatar type icon section --> flat
<q-icon dense
:name="getExpenseIcon(expense.type)" icon="las la-trash"
:color="expense.is_approved ? 'white' : ($q.dark.isActive ? 'white' : 'primary')" color="primary"
size="lg" size="lg"
class="col-auto" class="col-auto bg-dark q-px-xs"
/> style="border-radius: 0 2px 2px 0;"
@click.stop="showDeleteConfirmation(true)"
<!-- amount or mileage section --> />
<div class="col text-weight-bold text-h6">
<q-item-label v-if="expense.type === 'MILEAGE'">
{{ expense.mileage?.toFixed(1) }} km
</q-item-label>
<q-item-label v-else>
$ {{ expense.amount.toFixed(2) }}
</q-item-label>
</div> </div>
<!-- attachment file icon --> <div class="col row full-width items-center q-px-xs">
<div class="col-auto q-px-xs"> <!-- avatar type icon section -->
<q-icon
:name="getExpenseIcon(expense.type)"
:color="expense.is_approved ? 'white' : ($q.dark.isActive ? 'white' : 'primary')"
size="lg"
class="col-auto q-pr-sm"
/>
<!-- amount or mileage section -->
<div class="col text-weight-bold text-h6">
<q-item-label v-if="expense.type === 'MILEAGE'">
{{ expense.mileage?.toFixed(1) }} km
</q-item-label>
<q-item-label v-else>
$ {{ expense.amount.toFixed(2) }}
</q-item-label>
</div>
</div>
<!-- attachment file -->
<div class="col-auto q-pa-xs full-width">
<q-btn <q-btn
push
:color="expense.is_approved ? 'white' : 'accent'" :color="expense.is_approved ? 'white' : 'accent'"
:text-color="expense.is_approved ? 'accent' : 'white'" :text-color="expense.is_approved ? 'accent' : 'white'"
class="col-auto q-mx-sm q-px-sm q-pb-sm" icon="las la-paperclip"
icon="attach_file" :label="expense.attachment_name ?? `( ${$t('shared.label.empty')} )`"
/> class="full-width text-lowercase q-mx-sm q-px-sm q-pb-sm inset-shadow"
</div>
<div class="col-auto">
<q-icon
v-if="expense.is_approved"
name="verified"
color="white"
size="lg"
class="full-height"
/> />
</div> </div>
</div> </div>
<q-icon
v-if="expense.is_approved"
name="verified"
color="white"
size="lg"
class="full-height q-px-none"
/>
</div> </div>
</template> </template>
<div class="q-px-sm"> <div class="q-px-sm">
<ExpenseDialogFormMobile /> <ExpenseDialogFormMobile v-model="expense" />
</div> </div>
</q-expansion-item> </q-expansion-item>
</div> </div>

View File

@ -11,6 +11,8 @@ export class Expense {
type: ExpenseType; type: ExpenseType;
amount: number; amount: number;
mileage?: number; mileage?: number;
attachment_name?: string;
attachment_key?: string;
comment: string; comment: string;
supervisor_comment?: string; supervisor_comment?: string;
is_approved: boolean; is_approved: boolean;