refactor(timesheet): more work on plugging in backend, managing expenses
This commit is contained in:
parent
18aa4c08f4
commit
f0ef88a16c
|
|
@ -104,7 +104,6 @@ export default defineConfig((ctx) => {
|
||||||
config: {
|
config: {
|
||||||
notify: {
|
notify: {
|
||||||
color: 'primary',
|
color: 'primary',
|
||||||
avatar: 'https://cdn.quasar.dev/img/boy-avatar.png',
|
|
||||||
},
|
},
|
||||||
dark: false,
|
dark: false,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
$primary : #019547;
|
$primary : #019547;
|
||||||
$secondary : #DAE0E7;
|
$secondary : #DAE0E7;
|
||||||
$accent : #AAD5C4;
|
$accent : #83f29f7d;
|
||||||
|
|
||||||
$dark-shadow-color : #00220f;
|
$dark-shadow-color : #00220f;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,12 +44,28 @@
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
|
|
||||||
<q-card-section class="q-ma-none q-pa-none text-uppercase text-caption text-weight-medium">
|
<q-card-section
|
||||||
|
horizontal
|
||||||
|
class="q-mb-md q-pa-none text-uppercase text-caption text-weight-medium"
|
||||||
|
>
|
||||||
<q-toggle
|
<q-toggle
|
||||||
v-model="is_remembered"
|
v-model="is_remembered"
|
||||||
|
size="sm"
|
||||||
color="primary"
|
color="primary"
|
||||||
:label="$t('login.button.remember_me')"
|
class="col-auto"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<transition
|
||||||
|
enter-active-class="animated rubberBand slow"
|
||||||
|
leave-active-class=""
|
||||||
|
mode="out-in"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:key="is_remembered ? 'yep' : 'nope'"
|
||||||
|
class="col-auto text-weight-bold self-center q-ml-sm"
|
||||||
|
:class="is_remembered ? 'text-primary' : ''"
|
||||||
|
>{{ $t('login.button.remember_me') }}</span>
|
||||||
|
</transition>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
<q-card-actions>
|
<q-card-actions>
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@
|
||||||
lang="ts"
|
lang="ts"
|
||||||
>
|
>
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
import { inject, ref } from 'vue';
|
import { computed, inject, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useExpensesStore } from 'src/stores/expense-store';
|
import { useExpensesStore } from 'src/stores/expense-store';
|
||||||
import { empty_expense, EXPENSE_TYPE, TYPES_WITH_AMOUNT_ONLY } from 'src/modules/timesheets/models/expense.models';
|
import { Expense, EXPENSE_TYPE, TYPES_WITH_AMOUNT_ONLY } from 'src/modules/timesheets/models/expense.models';
|
||||||
import { useExpenseRules } from 'src/modules/timesheets/utils/expense.util';
|
import { useExpenseRules } from 'src/modules/timesheets/utils/expense.util';
|
||||||
import { useExpensesApi } from 'src/modules/timesheets/composables/use-expense-api';
|
import { useExpensesApi } from 'src/modules/timesheets/composables/use-expense-api';
|
||||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||||
|
|
@ -18,45 +18,44 @@
|
||||||
const expenses_api = useExpensesApi();
|
const expenses_api = useExpensesApi();
|
||||||
const files = defineModel<File[] | null>('files');
|
const files = defineModel<File[] | null>('files');
|
||||||
const is_navigator_open = ref(false);
|
const is_navigator_open = ref(false);
|
||||||
const mode = ref<'create' | 'update' | 'delete'>('create');
|
|
||||||
|
|
||||||
const COMMENT_MAX_LENGTH = 280;
|
const COMMENT_MAX_LENGTH = 280;
|
||||||
const employee_email = inject<string>('employeeEmail');
|
const employee_email = inject<string>('employeeEmail');
|
||||||
const rules = useExpenseRules(t);
|
const rules = useExpenseRules(t);
|
||||||
|
const background_color = computed(() => expenses_store.mode === 'update' ? 'accent' : '');
|
||||||
|
|
||||||
const cancelUpdateMode = () => {
|
const cancelUpdateMode = () => {
|
||||||
expenses_store.current_expense = empty_expense;
|
expenses_store.current_expense = new Expense;
|
||||||
expenses_store.initial_expense = empty_expense;
|
expenses_store.initial_expense = new Expense;
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestExpenseCreationOrUpdate = async () => {
|
const requestExpenseCreationOrUpdate = async () => {
|
||||||
if (mode.value === 'create') await expenses_api.createExpenseByEmployeeEmail(employee_email ?? '', expenses_store.current_expense?.date ?? '');
|
if (expenses_store.mode === 'create') await expenses_api.createExpenseByEmployeeEmail(employee_email ?? '', expenses_store.current_expense?.date ?? '');
|
||||||
else await expenses_api.updateExpenseByEmployeeEmail(employee_email ?? '', expenses_store.current_expense?.date ?? '');
|
else await expenses_api.updateExpenseByEmployeeEmail(employee_email ?? '', expenses_store.current_expense?.date ?? '');
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-form
|
<q-form
|
||||||
flat
|
|
||||||
v-if="!timesheet_store.timesheets?.every(timesheet => timesheet.is_approved)"
|
v-if="!timesheet_store.timesheets?.every(timesheet => timesheet.is_approved)"
|
||||||
|
:key="expenses_store.mode"
|
||||||
|
flat
|
||||||
@submit.prevent="requestExpenseCreationOrUpdate"
|
@submit.prevent="requestExpenseCreationOrUpdate"
|
||||||
>
|
>
|
||||||
<div class="text-subtitle2 q-py-sm">
|
<div class="text-subtitle2 q-py-sm">
|
||||||
{{ $t('timesheet.expense.add_expense') }}
|
{{ $t('timesheet.expense.add_expense') }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div class="row justify-between rounded-5">
|
||||||
class="row justify-between rounded-5"
|
|
||||||
:class="mode === 'update' ? 'bg-accent' : ''"
|
|
||||||
>
|
|
||||||
|
|
||||||
<!-- date selection input -->
|
<!-- date selection input -->
|
||||||
<q-input
|
<q-input
|
||||||
v-model="expenses_store.current_expense.date"
|
v-model="expenses_store.current_expense.date"
|
||||||
dense
|
dense
|
||||||
filled
|
outlined
|
||||||
readonly
|
readonly
|
||||||
stack-label
|
stack-label
|
||||||
class="col q-px-xs"
|
class="col q-px-xs"
|
||||||
|
:bg-color="background_color"
|
||||||
color="primary"
|
color="primary"
|
||||||
:label="$t('timesheet.expense.date')"
|
:label="$t('timesheet.expense.date')"
|
||||||
>
|
>
|
||||||
|
|
@ -85,6 +84,7 @@
|
||||||
filled
|
filled
|
||||||
dense
|
dense
|
||||||
class="col q-px-xs"
|
class="col q-px-xs"
|
||||||
|
:bg-color="background_color"
|
||||||
color="primary"
|
color="primary"
|
||||||
emit-value
|
emit-value
|
||||||
map-options
|
map-options
|
||||||
|
|
@ -105,6 +105,7 @@
|
||||||
clearable
|
clearable
|
||||||
color="primary"
|
color="primary"
|
||||||
class="col q-px-xs"
|
class="col q-px-xs"
|
||||||
|
:bg-color="background_color"
|
||||||
:label="$t('timesheet.expense.amount')"
|
:label="$t('timesheet.expense.amount')"
|
||||||
suffix="$"
|
suffix="$"
|
||||||
lazy-rules="ondemand"
|
lazy-rules="ondemand"
|
||||||
|
|
@ -124,6 +125,7 @@
|
||||||
clearable
|
clearable
|
||||||
color="primary"
|
color="primary"
|
||||||
class="col q-px-xs"
|
class="col q-px-xs"
|
||||||
|
:bg-color="background_color"
|
||||||
:label="$t('timesheet.expense.mileage')"
|
:label="$t('timesheet.expense.mileage')"
|
||||||
suffix="km"
|
suffix="km"
|
||||||
lazy-rules="ondemand"
|
lazy-rules="ondemand"
|
||||||
|
|
@ -135,12 +137,13 @@
|
||||||
<q-input
|
<q-input
|
||||||
v-model="expenses_store.current_expense.comment"
|
v-model="expenses_store.current_expense.comment"
|
||||||
filled
|
filled
|
||||||
color="primary"
|
|
||||||
type="text"
|
|
||||||
class="col q-px-sm"
|
|
||||||
dense
|
dense
|
||||||
stack-label
|
stack-label
|
||||||
clearable
|
clearable
|
||||||
|
color="primary"
|
||||||
|
type="text"
|
||||||
|
class="col q-px-sm"
|
||||||
|
:bg-color="background_color"
|
||||||
:counter="true"
|
:counter="true"
|
||||||
:maxlength="COMMENT_MAX_LENGTH"
|
:maxlength="COMMENT_MAX_LENGTH"
|
||||||
lazy-rules="ondemand"
|
lazy-rules="ondemand"
|
||||||
|
|
@ -156,14 +159,15 @@
|
||||||
<!-- import attach file section -->
|
<!-- import attach file section -->
|
||||||
<q-file
|
<q-file
|
||||||
v-model="files"
|
v-model="files"
|
||||||
:label="$t('timesheet.expense.hints.attach_file')"
|
dense
|
||||||
filled
|
filled
|
||||||
use-chips
|
use-chips
|
||||||
multiple
|
multiple
|
||||||
stack-label
|
stack-label
|
||||||
|
:label="$t('timesheet.expense.hints.attach_file')"
|
||||||
class="col"
|
class="col"
|
||||||
|
:bg-color="background_color"
|
||||||
style="max-width: 300px;"
|
style="max-width: 300px;"
|
||||||
dense
|
|
||||||
>
|
>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<q-icon
|
<q-icon
|
||||||
|
|
@ -175,13 +179,14 @@
|
||||||
</q-file>
|
</q-file>
|
||||||
|
|
||||||
<!-- add btn section -->
|
<!-- add btn section -->
|
||||||
<div>
|
<div class="col-auto column">
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="mode === 'update'"
|
v-if="expenses_store.mode === 'update'"
|
||||||
flat
|
push
|
||||||
dense
|
dense
|
||||||
size="sm"
|
size="sm"
|
||||||
class="q-mt-sm q-ml-sm"
|
class="col q-ml-sm"
|
||||||
|
icon="cancel"
|
||||||
@click="cancelUpdateMode"
|
@click="cancelUpdateMode"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
@ -189,9 +194,10 @@
|
||||||
push
|
push
|
||||||
dense
|
dense
|
||||||
color="primary"
|
color="primary"
|
||||||
icon="add"
|
:icon="expenses_store.mode === 'update' ? 'save' : 'add'"
|
||||||
|
:label="$q.screen.gt.sm ? (expenses_store.mode === 'update' ? $t('shared.label.update') : $t('shared.label.add')) : ''"
|
||||||
size="sm"
|
size="sm"
|
||||||
class="q-mt-sm q-ml-sm"
|
class="col q-mx-xs q-my-sm q-pr-sm"
|
||||||
type="submit"
|
type="submit"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -2,43 +2,37 @@
|
||||||
setup
|
setup
|
||||||
lang="ts"
|
lang="ts"
|
||||||
>
|
>
|
||||||
/* eslint-disable */
|
|
||||||
import { computed, inject, ref } from 'vue';
|
import { computed, inject, ref } from 'vue';
|
||||||
import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
|
import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
|
||||||
import { useExpensesApi } from 'src/modules/timesheets/composables/use-expense-api';
|
import { useExpensesApi } from 'src/modules/timesheets/composables/use-expense-api';
|
||||||
import { useExpensesStore } from 'src/stores/expense-store';
|
import { useExpensesStore } from 'src/stores/expense-store';
|
||||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
|
||||||
import { getExpenseIcon } from 'src/modules/timesheets/utils/expense.util';
|
import { getExpenseIcon } from 'src/modules/timesheets/utils/expense.util';
|
||||||
import { useAuthStore } from 'src/stores/auth-store';
|
import { useAuthStore } from 'src/stores/auth-store';
|
||||||
import { CAN_APPROVE_PAY_PERIODS } from 'src/modules/shared/models/user.models';
|
import { CAN_APPROVE_PAY_PERIODS } from 'src/modules/shared/models/user.models';
|
||||||
import { empty_expense, type Expense } from 'src/modules/timesheets/models/expense.models';
|
import { Expense } from 'src/modules/timesheets/models/expense.models';
|
||||||
|
import { deepEqual } from 'src/utils/deep-equal';
|
||||||
|
|
||||||
const { expense, horizontal = false } = defineProps<{
|
const { expense, horizontal = false } = defineProps<{
|
||||||
expense: Expense;
|
expense: Expense;
|
||||||
index: number;
|
index: number;
|
||||||
horizontal?: boolean;
|
horizontal?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
const is_approved = defineModel<boolean>({ required: true });
|
||||||
|
|
||||||
const timesheet_store = useTimesheetStore();
|
|
||||||
const expenses_store = useExpensesStore();
|
const expenses_store = useExpensesStore();
|
||||||
const auth_store = useAuthStore();
|
const auth_store = useAuthStore();
|
||||||
const expenses_api = useExpensesApi();
|
const expenses_api = useExpensesApi();
|
||||||
|
const employee_email = inject<string>('employeeEmail') ?? '';
|
||||||
|
|
||||||
const is_approved = defineModel<boolean>({ required: true });
|
|
||||||
const is_selected = ref(false);
|
|
||||||
const refresh_key = ref(1);
|
const refresh_key = ref(1);
|
||||||
|
const background_class = computed(() => deepEqual(expense, expenses_store.current_expense) ? 'bg-accent' : '');
|
||||||
|
const approved_class = computed(() => horizontal ? ' q-mx-xs q-pa-xs cursor-pointer' : '')
|
||||||
|
const expense_item_style = computed(() => is_approved.value ? 'border: solid 2px var(--q-primary);' : 'border: solid 2px grey;');
|
||||||
const is_authorized_to_approve = computed(() => CAN_APPROVE_PAY_PERIODS.includes(auth_store.user?.role ?? 'GUEST'))
|
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 setExpenseToUpdate = () => {
|
||||||
// const highlightClass = computed(() => (expenses_store.mode === 'update' && is_selected) ? 'bg-accent' : '');
|
expenses_store.mode = 'update';
|
||||||
const approvedClass = computed(() => horizontal ? ' q-mx-xs q-pa-xs cursor-pointer' : '')
|
// if (expense.is_approved) return;
|
||||||
|
|
||||||
|
|
||||||
const employeeEmail = inject<string>('employeeEmail') ?? '';
|
|
||||||
|
|
||||||
|
|
||||||
const setExpenseToModify = () => {
|
|
||||||
// expenses_store.mode = 'update';
|
|
||||||
expenses_store.current_expense = expense;
|
expenses_store.current_expense = expense;
|
||||||
expenses_store.initial_expense = unwrapAndClone(expense);
|
expenses_store.initial_expense = unwrapAndClone(expense);
|
||||||
};
|
};
|
||||||
|
|
@ -46,16 +40,26 @@
|
||||||
const requestExpenseDeletion = async () => {
|
const requestExpenseDeletion = async () => {
|
||||||
// expenses_store.mode = 'delete';
|
// expenses_store.mode = 'delete';
|
||||||
expenses_store.initial_expense = expense;
|
expenses_store.initial_expense = expense;
|
||||||
expenses_store.current_expense = empty_expense;
|
expenses_store.current_expense = new Expense;
|
||||||
await expenses_api.deleteExpenseByEmployeeEmail(employeeEmail, expenses_store.initial_expense.date);
|
await expenses_api.deleteExpenseByEmployeeEmail(employee_email, expenses_store.initial_expense.date);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onExpenseClicked() {
|
const onExpenseClicked = () => {
|
||||||
if (is_authorized_to_approve.value) {
|
if (is_authorized_to_approve.value) {
|
||||||
is_approved.value = !is_approved.value;
|
is_approved.value = !is_approved.value;
|
||||||
refresh_key.value += 1;
|
refresh_key.value += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onUpdateClicked = () => {
|
||||||
|
if (expenses_store.mode === 'update') {
|
||||||
|
expenses_store.mode = 'create';
|
||||||
|
expenses_store.current_expense = new Expense;
|
||||||
|
expenses_store.initial_expense = new Expense;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setExpenseToUpdate();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -67,7 +71,8 @@
|
||||||
:key="refresh_key"
|
:key="refresh_key"
|
||||||
:clickable="horizontal"
|
:clickable="horizontal"
|
||||||
class="row col-4 q-ma-xs shadow-2"
|
class="row col-4 q-ma-xs shadow-2"
|
||||||
:style="expenseItemStyle + approvedClass"
|
:class="background_class + approved_class"
|
||||||
|
:style="expense_item_style"
|
||||||
@click="onExpenseClicked"
|
@click="onExpenseClicked"
|
||||||
>
|
>
|
||||||
<q-badge
|
<q-badge
|
||||||
|
|
@ -104,7 +109,7 @@
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
|
|
||||||
<!-- amount or mileage section -->
|
<!-- amount or mileage section -->
|
||||||
<q-item-section class="col-auto">
|
<q-item-section class="col col-md-2">
|
||||||
<q-item-label v-if="String(expense.type).trim().toUpperCase() === 'MILEAGE'">
|
<q-item-label v-if="String(expense.type).trim().toUpperCase() === 'MILEAGE'">
|
||||||
<template v-if="typeof expense.mileage === 'number'">
|
<template v-if="typeof expense.mileage === 'number'">
|
||||||
{{ expense.mileage?.toFixed(1) }} km
|
{{ expense.mileage?.toFixed(1) }} km
|
||||||
|
|
@ -121,9 +126,9 @@
|
||||||
<q-item-label
|
<q-item-label
|
||||||
caption
|
caption
|
||||||
lines="1"
|
lines="1"
|
||||||
|
class="text-uppercase"
|
||||||
>
|
>
|
||||||
<!-- {{ $d(new Date(expense.date), { year: 'numeric', month: 'short', day: 'numeric', weekday: 'short' }) }} -->
|
{{ $d(new Date(expense.date), { month: 'short', day: 'numeric', weekday: 'long' }) }}
|
||||||
{{ expense.date }}
|
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
|
|
||||||
|
|
@ -174,24 +179,21 @@
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
|
|
||||||
<q-item-section
|
<q-item-section :side="$q.screen.gt.sm">
|
||||||
side
|
|
||||||
class="q-pa-none"
|
|
||||||
>
|
|
||||||
<q-btn
|
<q-btn
|
||||||
push
|
push
|
||||||
dense
|
dense
|
||||||
size="xs"
|
|
||||||
color="primary"
|
color="primary"
|
||||||
icon="edit"
|
icon="edit"
|
||||||
class="q-mb-xs z-top"
|
class="z-top"
|
||||||
@click.stop="setExpenseToModify"
|
@click.stop="onUpdateClicked"
|
||||||
/>
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
|
||||||
|
<q-item-section :side="$q.screen.gt.sm">
|
||||||
<q-btn
|
<q-btn
|
||||||
push
|
push
|
||||||
dense
|
dense
|
||||||
size="xs"
|
|
||||||
color="negative"
|
color="negative"
|
||||||
icon="close"
|
icon="close"
|
||||||
class="z-top"
|
class="z-top"
|
||||||
|
|
|
||||||
|
|
@ -36,13 +36,20 @@
|
||||||
|
|
||||||
<ExpenseDialogList />
|
<ExpenseDialogList />
|
||||||
|
|
||||||
<ExpenseDialogForm v-if="!expense_store.current_expense.is_approved" />
|
<transition
|
||||||
<q-icon
|
appear
|
||||||
v-else
|
enter-active-class="animated fadeInDown faster"
|
||||||
name="block"
|
leave-active-class="animated fadeOutDown faster"
|
||||||
color="negative"
|
mode="out-in"
|
||||||
size="lg"
|
>
|
||||||
/>
|
<ExpenseDialogForm v-if="!expense_store.current_expense.is_approved" />
|
||||||
|
<q-icon
|
||||||
|
v-else
|
||||||
|
name="block"
|
||||||
|
color="negative"
|
||||||
|
size="lg"
|
||||||
|
/>
|
||||||
|
</transition>
|
||||||
|
|
||||||
<q-separator spaced />
|
<q-separator spaced />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,10 +46,17 @@
|
||||||
time_picker_model.value = time;
|
time_picker_model.value = time;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onBlurShiftTypeSelect = () => {
|
||||||
|
if (shift_type_selected.value === undefined) {
|
||||||
|
shift.value.shift_id = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (ui_store.focus_next_component) {
|
if (ui_store.focus_next_component) {
|
||||||
select_ref.value?.focus();
|
select_ref.value?.focus();
|
||||||
select_ref.value?.showPopup();
|
select_ref.value?.showPopup();
|
||||||
|
shift_type_selected.value = undefined;
|
||||||
ui_store.focus_next_component = false;
|
ui_store.focus_next_component = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -58,7 +65,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="shift.shift_id !== 0"
|
v-if="shift.shift_id !== 0"
|
||||||
class="col row flex-center text-uppercase rounded-10"
|
class="row col flex-center text-uppercase rounded-10"
|
||||||
|
:class="$q.screen.lt.md ? 'q-pa-xs' : ''"
|
||||||
>
|
>
|
||||||
<!-- shift type -->
|
<!-- shift type -->
|
||||||
<q-select
|
<q-select
|
||||||
|
|
@ -66,19 +74,22 @@
|
||||||
v-model="shift_type_selected"
|
v-model="shift_type_selected"
|
||||||
standout="bg-blue-grey-9"
|
standout="bg-blue-grey-9"
|
||||||
dense
|
dense
|
||||||
options-dense
|
:options-dense="!ui_store.is_mobile_mode"
|
||||||
hide-dropdown-icon
|
hide-dropdown-icon
|
||||||
:menu-offset="[0, 10]"
|
:menu-offset="[0, 10]"
|
||||||
|
menu-anchor="bottom middle"
|
||||||
|
menu-self="top middle"
|
||||||
:options="options"
|
:options="options"
|
||||||
class="rounded-5 q-mx-xs shadow-1"
|
class="rounded-5 shadow-1"
|
||||||
:class="ui_store.is_mobile_mode ? 'col-auto' : 'col'"
|
:class="ui_store.is_mobile_mode ? 'col-auto q-mx-xs' : 'col q-mx-xs'"
|
||||||
popup-content-class="text-uppercase text-weight-bold text-center rounded-5"
|
popup-content-class="text-uppercase text-weight-bold text-center rounded-5"
|
||||||
popup-content-style="border: 2px solid var(--q-primary)"
|
popup-content-style="border: 2px solid var(--q-primary)"
|
||||||
|
@blur="onBlurShiftTypeSelect"
|
||||||
>
|
>
|
||||||
<template #selected-item="scope">
|
<template #selected-item="scope">
|
||||||
<div
|
<div
|
||||||
class="row text-weight-bold q-ma-none q-pa-none no-wrap ellipsis"
|
class="row text-weight-bold q-ma-none q-pa-none no-wrap ellipsis"
|
||||||
:class="ui_store.is_mobile_mode ? 'items-center' : 'flex-center'"
|
:class="ui_store.is_mobile_mode ? 'items-center full-height' : 'flex-center'"
|
||||||
:tabindex="scope.tabindex"
|
:tabindex="scope.tabindex"
|
||||||
>
|
>
|
||||||
<q-icon
|
<q-icon
|
||||||
|
|
@ -96,7 +107,7 @@
|
||||||
</template>
|
</template>
|
||||||
</q-select>
|
</q-select>
|
||||||
|
|
||||||
<!-- punch-in timestamp -->
|
<!-- punch in field -->
|
||||||
<q-input
|
<q-input
|
||||||
v-model="shift.start_time"
|
v-model="shift.start_time"
|
||||||
dense
|
dense
|
||||||
|
|
@ -127,7 +138,7 @@
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
|
|
||||||
<!-- punch-out timestamps -->
|
<!-- punch out field -->
|
||||||
<q-input
|
<q-input
|
||||||
v-model="shift.end_time"
|
v-model="shift.end_time"
|
||||||
dense
|
dense
|
||||||
|
|
@ -169,7 +180,7 @@
|
||||||
:name="shift.comment ? 'comment' : ''"
|
:name="shift.comment ? 'comment' : ''"
|
||||||
color="primary"
|
color="primary"
|
||||||
:size="dense ? 'xs' : 'sm'"
|
:size="dense ? 'xs' : 'sm'"
|
||||||
class="col-auto q-pa-none q-mr-xs"
|
class="col-auto q-pa-none"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<q-btn
|
<q-btn
|
||||||
|
|
@ -178,7 +189,7 @@
|
||||||
dense
|
dense
|
||||||
:icon="shift.comment ? 'chat' : 'chat_bubble_outline'"
|
:icon="shift.comment ? 'chat' : 'chat_bubble_outline'"
|
||||||
:text-color="shift.comment ? 'primary' : 'grey-8'"
|
:text-color="shift.comment ? 'primary' : 'grey-8'"
|
||||||
class="col-auto q-ma-none q-pl-md full-height"
|
class="col-auto q-ma-none full-height"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<q-btn
|
<q-btn
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,10 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="$q.screen.lt.md ? 'column' : 'row'">
|
<div
|
||||||
|
:class="$q.screen.lt.md ? 'column full-width' : 'row'"
|
||||||
|
:style="$q.screen.lt.md ? 'width: 90vw !important;' : ''"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-for="timesheet in timesheet_store.timesheets"
|
v-for="timesheet in timesheet_store.timesheets"
|
||||||
:key="timesheet.timesheet_id"
|
:key="timesheet.timesheet_id"
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
import ShiftList from 'src/modules/timesheets/components/shift-list.vue';
|
import ShiftList from 'src/modules/timesheets/components/shift-list.vue';
|
||||||
import ExpenseDialog from 'src/modules/timesheets/components/expense-dialog.vue';
|
import ExpenseDialog from 'src/modules/timesheets/components/expense-dialog.vue';
|
||||||
import PayPeriodNavigator from 'src/modules/shared/components/pay-period-navigator.vue';
|
import PayPeriodNavigator from 'src/modules/shared/components/pay-period-navigator.vue';
|
||||||
// import ShiftListLegend from 'src/modules/timesheets/components/shift-list-legend.vue';
|
|
||||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||||
import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api';
|
import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api';
|
||||||
import { useExpensesStore } from 'src/stores/expense-store';
|
import { useExpensesStore } from 'src/stores/expense-store';
|
||||||
|
|
|
||||||
|
|
@ -4,44 +4,45 @@ export const EXPENSE_TYPE: ExpenseType[] = ['PER_DIEM', 'MILEAGE', 'EXPENSES', '
|
||||||
export const TYPES_WITH_MILEAGE_ONLY: Readonly<ExpenseType[]> = ['MILEAGE'];
|
export const TYPES_WITH_MILEAGE_ONLY: Readonly<ExpenseType[]> = ['MILEAGE'];
|
||||||
export const TYPES_WITH_AMOUNT_ONLY: Readonly<ExpenseType[]> = ['PER_DIEM', 'EXPENSES', 'ON_CALL',];
|
export const TYPES_WITH_AMOUNT_ONLY: Readonly<ExpenseType[]> = ['PER_DIEM', 'EXPENSES', 'ON_CALL',];
|
||||||
|
|
||||||
export interface Expense {
|
export class Expense {
|
||||||
id: number;
|
id: number;
|
||||||
date: string; //YYYY-MM-DD
|
date: string; //YYYY-MM-DD
|
||||||
type: ExpenseType;
|
type: ExpenseType;
|
||||||
amount: number;
|
amount: number;
|
||||||
mileage?: number;
|
mileage?: number;
|
||||||
comment: string;
|
comment: string;
|
||||||
supervisor_comment?: string;
|
supervisor_comment?: string;
|
||||||
is_approved: boolean;
|
is_approved: boolean;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.id = -1;
|
||||||
|
this.date = '';
|
||||||
|
this.type = 'EXPENSES';
|
||||||
|
this.amount = 0;
|
||||||
|
this.comment = '';
|
||||||
|
this.is_approved = false;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const empty_expense: Expense = {
|
|
||||||
id: -1,
|
|
||||||
date: '',
|
|
||||||
type: 'EXPENSES',
|
|
||||||
amount: 0,
|
|
||||||
comment: '',
|
|
||||||
is_approved: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const test_expenses: Expense[] = [
|
export const test_expenses: Expense[] = [
|
||||||
{
|
{
|
||||||
id: 201,
|
id: 201,
|
||||||
date: '2025-01-06',
|
date: '2025-01-06',
|
||||||
type: 'EXPENSES',
|
type: 'EXPENSES',
|
||||||
amount: 15.5,
|
amount: 15.5,
|
||||||
comment: 'Lunch receipt',
|
comment: 'Lunch receipt',
|
||||||
is_approved: false,
|
is_approved: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 202,
|
id: 202,
|
||||||
date: '2025-01-07',
|
date: '2025-01-07',
|
||||||
type: 'MILEAGE',
|
type: 'MILEAGE',
|
||||||
amount: 0,
|
amount: 0,
|
||||||
mileage: 32.4,
|
mileage: 32.4,
|
||||||
comment: 'Travel to client site',
|
comment: 'Travel to client site',
|
||||||
is_approved: true,
|
is_approved: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ export const useAuthStore = defineStore('auth', () => {
|
||||||
void handleAuthMessage(event);
|
void handleAuthMessage(event);
|
||||||
});
|
});
|
||||||
|
|
||||||
const oidc_popup = window.open(`${import.meta.env.VITE_TARGO_BACKEND_AUTH_URL}auth/v1/login`, 'authPopup', 'width=600,height=800');
|
const oidc_popup = window.open(`${import.meta.env.VITE_TARGO_BACKEND_URL}auth/v1/login`, 'authPopup', 'width=600,height=800');
|
||||||
|
|
||||||
if (!oidc_popup)
|
if (!oidc_popup)
|
||||||
Notify.create({
|
Notify.create({
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { ExpensesApiError, type GenericApiError } from "src/modules/timesheets/models/expense-validation.models";
|
import { ExpensesApiError, type GenericApiError } from "src/modules/timesheets/models/expense-validation.models";
|
||||||
import { empty_expense, test_expenses, type Expense } from "src/modules/timesheets/models/expense.models";
|
import { test_expenses, Expense } from "src/modules/timesheets/models/expense.models";
|
||||||
import { ExpenseService } from "src/modules/timesheets/services/expense-service";
|
import { ExpenseService } from "src/modules/timesheets/services/expense-service";
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -9,9 +9,10 @@ import { ExpenseService } from "src/modules/timesheets/services/expense-service"
|
||||||
export const useExpensesStore = defineStore('expenses', () => {
|
export const useExpensesStore = defineStore('expenses', () => {
|
||||||
const is_open = ref(false);
|
const is_open = ref(false);
|
||||||
const is_loading = ref(false);
|
const is_loading = ref(false);
|
||||||
|
const mode = ref<'create' | 'update' | 'delete'>('create');
|
||||||
const pay_period_expenses = ref<Expense[]>(test_expenses);
|
const pay_period_expenses = ref<Expense[]>(test_expenses);
|
||||||
const current_expense = ref<Expense>(empty_expense);
|
const current_expense = ref<Expense>(new Expense);
|
||||||
const initial_expense = ref<Expense>(empty_expense);
|
const initial_expense = ref<Expense>(new Expense);
|
||||||
const error = ref<string | null>(null);
|
const error = ref<string | null>(null);
|
||||||
|
|
||||||
// const setErrorFrom = (err: unknown) => {
|
// const setErrorFrom = (err: unknown) => {
|
||||||
|
|
@ -21,13 +22,10 @@ export const useExpensesStore = defineStore('expenses', () => {
|
||||||
|
|
||||||
const open = (): void => {
|
const open = (): void => {
|
||||||
is_open.value = true;
|
is_open.value = true;
|
||||||
is_loading.value = true;
|
|
||||||
error.value = null;
|
error.value = null;
|
||||||
current_expense.value = empty_expense;
|
current_expense.value = new Expense;
|
||||||
initial_expense.value = empty_expense;
|
initial_expense.value = new Expense;
|
||||||
|
mode.value = 'create';
|
||||||
// await getPayPeriodExpensesByTimesheetId(timesheet_id);
|
|
||||||
is_loading.value = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
|
|
@ -80,6 +78,7 @@ export const useExpensesStore = defineStore('expenses', () => {
|
||||||
return {
|
return {
|
||||||
is_open,
|
is_open,
|
||||||
is_loading,
|
is_loading,
|
||||||
|
mode,
|
||||||
pay_period_expenses,
|
pay_period_expenses,
|
||||||
current_expense,
|
current_expense,
|
||||||
initial_expense,
|
initial_expense,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user