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

View File

@ -241,6 +241,7 @@ export default {
download: "télécharger",
open: "ouvrir",
day: "jour",
empty: "vide",
},
misc: {
or: "ou",
@ -327,6 +328,9 @@ export default {
empty_list: 'Aucun dépense enregistrée',
employee_comment: 'Commentaire',
supervisor_comment: 'Note du Superviseur',
actions: {
delete_confirm: "Supprimer cette dépense?",
},
hints: {
amount_or_mileage: "Soit dépense ou kilométrage, pas les deux",
comment_required: "un commentaire est requis",

View File

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

View File

@ -12,15 +12,24 @@
import type { Expense } from 'src/modules/timesheets/models/expense.models';
import ExpenseDialogFormMobile from 'src/modules/timesheets/components/mobile/expense-dialog-form-mobile.vue';
// =========== state =====================================
const expense = defineModel<Expense>({ required: true })
const expenses_store = useExpensesStore();
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_delete_confirm = ref(false);
// =========== computed ==================================
const approved_class = computed(() => expense.value.is_approved ? ' bg-accent text-white' : '')
// =========== methods ===================================
const requestExpenseDeletion = async () => {
showDeleteConfirmation(false);
await expenses_api.deleteExpenseById(expense.value.id);
}
@ -31,84 +40,139 @@
expenses_store.current_expense = expense.value;
expenses_store.initial_expense = unwrapAndClone(expense.value);
}
const showDeleteConfirmation = (state: boolean) => {
is_showing_delete_confirm.value = state;
}
</script>
<template>
<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
v-model="is_showing_update_form"
hide-expand-icon
dense
group="expenses"
header-class="q-px-none"
class="rounded-5"
:class="expense.is_approved ? ' bg-accent text-white' : ''"
@before-show="onUpdateClicked()"
>
<template #header>
<div class="column col">
<!-- date label -->
<div class="col-auto row items-center q-pl-xs">
<q-icon
name="calendar_month"
size="sm"
class="col-auto"
/>
<div class="row full-width">
<div class="column col q-px-sm q-py-xs">
<!-- date label and delete button -->
<div class="col-auto row items-center q-pl-xs">
<q-icon
name="calendar_month"
size="sm"
class="col-auto"
/>
<span
class="col text-uppercase text-weight-light full-width q-pl-sm text-h6"
:class="approved_class"
>
{{ $d(
date.extractDate(expense.date, 'YYYY-MM-DD'),
{ month: 'long', day: 'numeric' }
) }}
</span>
</div>
<span
class="col text-uppercase text-weight-light full-width q-pl-sm text-h6"
:class="approved_class"
>
{{ $d(
date.extractDate(expense.date, 'YYYY-MM-DD'),
{ month: 'long', day: 'numeric' }
) }}
</span>
<div class="col row full-width items-center">
<!-- 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"
/>
<!-- 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>
<q-btn
flat
dense
icon="las la-trash"
color="primary"
size="lg"
class="col-auto bg-dark q-px-xs"
style="border-radius: 0 2px 2px 0;"
@click.stop="showDeleteConfirmation(true)"
/>
</div>
<!-- attachment file icon -->
<div class="col-auto q-px-xs">
<div class="col row full-width items-center 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
push
:color="expense.is_approved ? 'white' : 'accent'"
:text-color="expense.is_approved ? 'accent' : 'white'"
class="col-auto q-mx-sm q-px-sm q-pb-sm"
icon="attach_file"
/>
</div>
<div class="col-auto">
<q-icon
v-if="expense.is_approved"
name="verified"
color="white"
size="lg"
class="full-height"
icon="las la-paperclip"
: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>
<q-icon
v-if="expense.is_approved"
name="verified"
color="white"
size="lg"
class="full-height q-px-none"
/>
</div>
</template>
<div class="q-px-sm">
<ExpenseDialogFormMobile />
<ExpenseDialogFormMobile v-model="expense" />
</div>
</q-expansion-item>
</div>

View File

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