Change timesheet UI to better fit current app model and avoid adding extra clicks and interactions to add new shifts and expenses. Also refactoring calls to backend to be more efficient and use recently-finalized OIDC implementation and integration.
203 lines
7.3 KiB
Vue
203 lines
7.3 KiB
Vue
<script
|
|
setup
|
|
lang="ts"
|
|
>
|
|
/* eslint-disable */
|
|
import { computed, inject, ref } from 'vue';
|
|
import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
|
|
import { useExpensesApi } from 'src/modules/timesheets/composables/use-expense-api';
|
|
import { useExpensesStore } from 'src/stores/expense-store';
|
|
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
|
import { getExpenseIcon } from 'src/modules/timesheets/utils/expense.util';
|
|
import { useAuthStore } from 'src/stores/auth-store';
|
|
import { CAN_APPROVE_PAY_PERIODS } from 'src/modules/shared/models/user.models';
|
|
import { empty_expense, type Expense } from 'src/modules/timesheets/models/expense.models';
|
|
|
|
const { expense, horizontal = false } = defineProps<{
|
|
expense: Expense;
|
|
index: number;
|
|
horizontal?: boolean;
|
|
}>();
|
|
|
|
const timesheet_store = useTimesheetStore();
|
|
const expenses_store = useExpensesStore();
|
|
const auth_store = useAuthStore();
|
|
const expenses_api = useExpensesApi();
|
|
|
|
const is_approved = defineModel<boolean>({ required: true });
|
|
const is_selected = ref(false);
|
|
const refresh_key = ref(1);
|
|
const is_authorized_to_approve = computed(() => CAN_APPROVE_PAY_PERIODS.includes(auth_store.user?.role ?? 'GUEST'))
|
|
|
|
const expenseItemStyle = computed(() => is_approved.value ? 'border: solid 2px var(--q-primary);' : 'border: solid 2px grey;');
|
|
// const highlightClass = computed(() => (expenses_store.mode === 'update' && is_selected) ? 'bg-accent' : '');
|
|
const approvedClass = computed(() => horizontal ? ' q-mx-xs q-pa-xs cursor-pointer' : '')
|
|
|
|
|
|
const employeeEmail = inject<string>('employeeEmail') ?? '';
|
|
|
|
|
|
const setExpenseToModify = () => {
|
|
// expenses_store.mode = 'update';
|
|
expenses_store.current_expense = expense;
|
|
expenses_store.initial_expense = unwrapAndClone(expense);
|
|
};
|
|
|
|
const requestExpenseDeletion = async () => {
|
|
// expenses_store.mode = 'delete';
|
|
expenses_store.initial_expense = expense;
|
|
expenses_store.current_expense = empty_expense;
|
|
await expenses_api.deleteExpenseByEmployeeEmail(employeeEmail, expenses_store.initial_expense.date);
|
|
}
|
|
|
|
function onExpenseClicked() {
|
|
if (is_authorized_to_approve.value) {
|
|
is_approved.value = !is_approved.value;
|
|
refresh_key.value += 1;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<transition
|
|
enter-active-class="animated pulse"
|
|
mode="out-in"
|
|
>
|
|
<q-item
|
|
:key="refresh_key"
|
|
:clickable="horizontal"
|
|
class="row col-4 q-ma-xs shadow-2"
|
|
:style="expenseItemStyle + approvedClass"
|
|
@click="onExpenseClicked"
|
|
>
|
|
<q-badge
|
|
v-if="expense.is_approved"
|
|
class="absolute z-top rounded-20 bg-white q-pa-none"
|
|
style="transform: translate(-15px, -15px);"
|
|
>
|
|
<q-icon
|
|
name="verified"
|
|
color="primary"
|
|
size="md"
|
|
/>
|
|
</q-badge>
|
|
|
|
<!-- avatar type icon section -->
|
|
<q-item-section avatar>
|
|
<q-icon
|
|
:name="getExpenseIcon(expense.type)"
|
|
:color="expense.is_approved ? 'primary' : ($q.dark.isActive ? 'blue-grey-2' : 'grey-8')"
|
|
size="lg"
|
|
>
|
|
<q-badge
|
|
v-if="expense.type === 'ON_CALL'"
|
|
floating
|
|
class="q-pa-none rounded-50 bg-white z-top"
|
|
>
|
|
<q-icon
|
|
name="shield"
|
|
size="xs"
|
|
:color="expense.is_approved ? 'primary' : ($q.dark.isActive ? 'blue-grey-2' : 'grey-8')"
|
|
/>
|
|
</q-badge>
|
|
</q-icon>
|
|
</q-item-section>
|
|
|
|
<!-- amount or mileage section -->
|
|
<q-item-section class="col-auto">
|
|
<q-item-label v-if="String(expense.type).trim().toUpperCase() === 'MILEAGE'">
|
|
<template v-if="typeof expense.mileage === 'number'">
|
|
{{ expense.mileage?.toFixed(1) }} km
|
|
</template>
|
|
<template v-else>
|
|
${{ expense.amount.toFixed(2) }}
|
|
</template>
|
|
</q-item-label>
|
|
<q-item-label v-else>
|
|
${{ expense.amount.toFixed(2) }}
|
|
</q-item-label>
|
|
|
|
<!-- date label -->
|
|
<q-item-label
|
|
caption
|
|
lines="1"
|
|
>
|
|
<!-- {{ $d(new Date(expense.date), { year: 'numeric', month: 'short', day: 'numeric', weekday: 'short' }) }} -->
|
|
{{ expense.date }}
|
|
</q-item-label>
|
|
</q-item-section>
|
|
|
|
<q-space v-if="horizontal" />
|
|
|
|
<!-- attachment file icon -->
|
|
<q-item-section side>
|
|
<q-btn
|
|
push
|
|
dense
|
|
size="md"
|
|
color="primary"
|
|
class="q-mx-lg"
|
|
icon="attach_file"
|
|
/>
|
|
</q-item-section>
|
|
|
|
<!-- comment section -->
|
|
<q-item-section
|
|
v-if="!horizontal"
|
|
top
|
|
>
|
|
<q-item-label lines="1">
|
|
{{ $t('timesheet.expense.employee_comment') }}
|
|
</q-item-label>
|
|
<q-item-label
|
|
caption
|
|
lines="1"
|
|
>
|
|
{{ expense.comment }}
|
|
</q-item-label>
|
|
</q-item-section>
|
|
|
|
<!-- supervisor comment section -->
|
|
<q-item-section
|
|
v-if="expense.supervisor_comment && !horizontal"
|
|
top
|
|
>
|
|
<q-item-label lines="1">
|
|
{{ $t('timesheet.expense.supervisor_comment') }}
|
|
</q-item-label>
|
|
<q-item-label
|
|
v-if="expense.supervisor_comment"
|
|
caption
|
|
lines="2"
|
|
>
|
|
{{ expense.supervisor_comment }}
|
|
</q-item-label>
|
|
</q-item-section>
|
|
|
|
<q-item-section
|
|
side
|
|
class="q-pa-none"
|
|
>
|
|
<q-btn
|
|
push
|
|
dense
|
|
size="xs"
|
|
color="primary"
|
|
icon="edit"
|
|
class="q-mb-xs z-top"
|
|
@click.stop="setExpenseToModify"
|
|
/>
|
|
|
|
<q-btn
|
|
push
|
|
dense
|
|
size="xs"
|
|
color="negative"
|
|
icon="close"
|
|
class="z-top"
|
|
@click.stop="requestExpenseDeletion"
|
|
/>
|
|
</q-item-section>
|
|
</q-item>
|
|
</transition>
|
|
</template> |