Merge pull request 'dev/nicolas/staging-prep' (#69) from dev/nicolas/staging-prep into main
Reviewed-on: Targo/targo_frontend#69
This commit is contained in:
commit
79c67b8637
|
|
@ -53,7 +53,11 @@
|
|||
|
||||
<!-- list of shifts -->
|
||||
<div class="col-auto column no-wrap">
|
||||
<TimesheetWrapper mode="approval" class="col-auto"/>
|
||||
<TimesheetWrapper
|
||||
mode="approval"
|
||||
:employee-email="timesheet_store.current_pay_period_overview?.email"
|
||||
class="col-auto"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-dialog>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
>
|
||||
import { date } from 'quasar';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { computed, inject, onMounted, ref } from 'vue';
|
||||
import { useUiStore } from 'src/stores/ui-store';
|
||||
import { useExpensesStore } from 'src/stores/expense-store';
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
|
|
@ -12,11 +12,16 @@
|
|||
import { getExpenseIcon, useExpenseRules } from 'src/modules/timesheets/utils/expense.util';
|
||||
import { Expense, type ExpenseType, TYPES_WITH_AMOUNT_ONLY } from 'src/modules/timesheets/models/expense.models';
|
||||
|
||||
// ================= state ======================
|
||||
|
||||
interface ExpenseOption {
|
||||
label: string;
|
||||
value: ExpenseType;
|
||||
icon: string;
|
||||
}
|
||||
const expense = defineModel<Expense>({ default: new Expense(new Date().toISOString().slice(0, 10)) })
|
||||
|
||||
const COMMENT_MAX_LENGTH = 280;
|
||||
|
||||
const { t } = useI18n();
|
||||
const ui_store = useUiStore();
|
||||
|
|
@ -24,16 +29,9 @@
|
|||
const expenses_store = useExpensesStore();
|
||||
const expenses_api = useExpensesApi();
|
||||
const files = defineModel<File[] | null>('files');
|
||||
|
||||
const is_navigator_open = ref(false);
|
||||
|
||||
const COMMENT_MAX_LENGTH = 280;
|
||||
const rules = useExpenseRules(t);
|
||||
|
||||
const period_start_date = computed(() => timesheet_store.pay_period?.period_start.replaceAll('-', '/') ?? '');
|
||||
const period_end_date = computed(() => timesheet_store.pay_period?.period_end.replaceAll('-', '/') ?? '');
|
||||
|
||||
const expense = defineModel<Expense>({ default: new Expense(new Date().toISOString().slice(0, 10)) })
|
||||
|
||||
const expense_options: ExpenseOption[] = [
|
||||
{ label: t('timesheet.expense.types.PER_DIEM'), value: 'PER_DIEM', icon: getExpenseIcon('PER_DIEM') },
|
||||
|
|
@ -41,8 +39,15 @@
|
|||
{ label: t('timesheet.expense.types.MILEAGE'), value: 'MILEAGE', icon: getExpenseIcon('MILEAGE') },
|
||||
{ label: t('timesheet.expense.types.ON_CALL'), value: 'ON_CALL', icon: getExpenseIcon('ON_CALL') },
|
||||
]
|
||||
|
||||
const expense_selected = ref<ExpenseOption | undefined>();
|
||||
const employeeEmail = inject<string>('employeeEmail');
|
||||
|
||||
// ================== computed ===================
|
||||
|
||||
const period_start_date = computed(() => timesheet_store.pay_period?.period_start.replaceAll('-', '/') ?? '');
|
||||
const period_end_date = computed(() => timesheet_store.pay_period?.period_end.replaceAll('-', '/') ?? '');
|
||||
|
||||
// ==================== method =======================
|
||||
|
||||
const openDatePicker = () => {
|
||||
is_navigator_open.value = true;
|
||||
|
|
@ -57,10 +62,7 @@
|
|||
}
|
||||
|
||||
const requestExpenseCreationOrUpdate = async () => {
|
||||
if (expenses_store.mode === 'update')
|
||||
await expenses_api.upsertExpense(expenses_store.current_expense, timesheet_store.current_pay_period_overview?.email);
|
||||
else
|
||||
await expenses_api.upsertExpense(expenses_store.current_expense);
|
||||
await expenses_api.upsertExpense(expenses_store.current_expense, employeeEmail);
|
||||
|
||||
expenses_store.is_showing_create_form = true;
|
||||
expenses_store.mode = 'create';
|
||||
|
|
|
|||
|
|
@ -2,23 +2,31 @@
|
|||
setup
|
||||
lang="ts"
|
||||
>
|
||||
import { computed } from 'vue';
|
||||
import { computed, inject } from 'vue';
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
import ExpenseDialogListItem from 'src/modules/timesheets/components/expense-dialog-list-item.vue';
|
||||
import ExpenseDialogListItemMobile from 'src/modules/timesheets/components/mobile/expense-dialog-list-item-mobile.vue';
|
||||
|
||||
// ================== state =======================
|
||||
|
||||
const timesheet_store = useTimesheetStore();
|
||||
|
||||
const { mode = 'normal' } = defineProps<{
|
||||
mode?: 'approval' | 'normal';
|
||||
}>();
|
||||
|
||||
// ========================== computed ==========================
|
||||
|
||||
const expenses_list = computed(() => {
|
||||
if (timesheet_store.timesheets !== undefined) {
|
||||
return timesheet_store.timesheets.flatMap(week => week.days).flatMap(day => day.expenses);
|
||||
}
|
||||
return [];
|
||||
})
|
||||
|
||||
// ==================== methods ========================
|
||||
|
||||
inject( 'employeeEmail', mode === 'approval' ? timesheet_store.current_pay_period_overview?.email : undefined);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -3,24 +3,16 @@
|
|||
lang="ts"
|
||||
>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { QSelect, QInput, useQuasar } from 'quasar';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { QSelect, QInput, useQuasar, QSelectProps } from 'quasar';
|
||||
import { useUiStore } from 'src/stores/ui-store';
|
||||
import { SHIFT_OPTIONS } from 'src/modules/timesheets/utils/shift.util';
|
||||
import type { Shift } from 'src/modules/timesheets/models/shift.models';
|
||||
|
||||
const q = useQuasar();
|
||||
const { t } = useI18n();
|
||||
const ui_store = useUiStore();
|
||||
// ================== State ==================
|
||||
|
||||
const COMMENT_LENGTH_MAX = 280;
|
||||
|
||||
const shift = defineModel<Shift>('shift', { required: true });
|
||||
const shift_type_selected = ref(SHIFT_OPTIONS.find(option => option.value == shift.value.type));
|
||||
const select_ref = ref<QSelect | null>(null);
|
||||
const error_message = ref<string | undefined>();
|
||||
const is_showing_delete_confirm = ref(false);
|
||||
|
||||
const { errorMessage = undefined, isTimesheetApproved = false, holiday = false } = defineProps<{
|
||||
dense?: boolean;
|
||||
isTimesheetApproved?: boolean;
|
||||
|
|
@ -28,7 +20,25 @@
|
|||
holiday?: boolean | undefined;
|
||||
}>();
|
||||
|
||||
const time_input_props = {
|
||||
const emit = defineEmits<{
|
||||
'requestDelete': [void];
|
||||
'onTimeFieldBlur': [void];
|
||||
}>();
|
||||
|
||||
const shift = defineModel<Shift>('shift', { required: true });
|
||||
|
||||
const q = useQuasar();
|
||||
const { t } = useI18n();
|
||||
const ui_store = useUiStore();
|
||||
|
||||
const shiftTypeSelected = ref(SHIFT_OPTIONS.find(option => option.value == shift.value.type));
|
||||
const selectRef = ref<QSelect | null>(null);
|
||||
const shiftErrorMessage = ref<string | undefined>();
|
||||
const is_showing_delete_confirm = ref(false);
|
||||
|
||||
// ================== Computed ==================
|
||||
|
||||
const timeInputProps = computed(() => ({
|
||||
dense: true,
|
||||
borderless: shift.value.is_approved && isTimesheetApproved,
|
||||
readonly: shift.value.is_approved && isTimesheetApproved,
|
||||
|
|
@ -38,32 +48,46 @@
|
|||
noErrorIcon: true,
|
||||
hideBottomSpace: true,
|
||||
error: shift.value.has_error,
|
||||
errorMessage: errorMessage ? t(errorMessage) : (error_message.value ? t(error_message.value) : undefined),
|
||||
errorMessage: errorMessage ? t(errorMessage) : (shiftErrorMessage.value ? t(shiftErrorMessage.value) : undefined),
|
||||
labelColor: shift.value.is_approved ? 'white' : (holiday ? 'purple-5' : 'accent'),
|
||||
class: `col rounded-5 bg-dark q-mx-xs ${shift.value.id === -2 ? 'bg-negative' : ''} ${shift.value.is_approved || isTimesheetApproved ? 'cursor-not-allowed inset-shadow' : ''}`,
|
||||
inputClass: `text-weight-medium ${shift.value.id === -2 ? 'text-white ' : ' '} ${shift.value.is_approved ? 'text-white cursor-not-allowed q-px-sm' : ''}`,
|
||||
style: shift.value.is_approved ? (holiday ? 'background-color: #7b1fa2 !important' : 'background-color: #0a7d32 !important;') : '',
|
||||
inputStyle: "font-size: 1.2em;"
|
||||
}
|
||||
}));
|
||||
|
||||
const emit = defineEmits<{
|
||||
'requestDelete': [void];
|
||||
'onTimeFieldBlur': [void];
|
||||
}>();
|
||||
const shiftTypeSelectProps = computed<Partial<QSelectProps>>(() => ({
|
||||
standout: q.dark.isActive ? 'bg-blue-grey-3' : 'bg-blue-grey-9',
|
||||
dense: true,
|
||||
borderless: shift.value.is_approved && isTimesheetApproved,
|
||||
readonly: shift.value.is_approved && isTimesheetApproved,
|
||||
optionsDense: !ui_store.is_mobile_mode,
|
||||
hideDropdownIcon: true,
|
||||
menuOffset: [0, 10],
|
||||
menuAnchor: "bottom middle",
|
||||
menuSelf: "top middle",
|
||||
options: SHIFT_OPTIONS,
|
||||
class: `col rounded-5 q-mx-xs bg-dark ${!shift.value.is_approved && !isTimesheetApproved ? '' : 'inset-shadow'}`,
|
||||
popupContentClass: "text-uppercase text-weight-bold text-center rounded-5",
|
||||
style: shift.value.is_approved ? (holiday ? 'background-color: #7b1fa2 !important' : 'background-color: #0a7d32 !important;') : '',
|
||||
popupContentStyle: "border: 2px solid var(--q-accent)",
|
||||
}));
|
||||
|
||||
// ================== Methods ==================
|
||||
|
||||
const onTimeFieldBlur = (time_string: string) => {
|
||||
if (time_string.length < 1 || !time_string) {
|
||||
shift.value.has_error = true;
|
||||
error_message.value = 'timesheet.errors.SHIFT_TIME_REQUIRED'
|
||||
shiftErrorMessage.value = 'timesheet.errors.SHIFT_TIME_REQUIRED'
|
||||
} else {
|
||||
shift.value.has_error = false;
|
||||
error_message.value = undefined;
|
||||
shiftErrorMessage.value = undefined;
|
||||
emit('onTimeFieldBlur');
|
||||
}
|
||||
}
|
||||
|
||||
const onBlurShiftTypeSelect = () => {
|
||||
if (shift_type_selected.value === undefined) {
|
||||
if (shiftTypeSelected.value === undefined) {
|
||||
shift.value.type = 'REGULAR';
|
||||
shift.value.id = 0;
|
||||
emit('requestDelete');
|
||||
|
|
@ -81,18 +105,22 @@
|
|||
return 'negative';
|
||||
};
|
||||
|
||||
const toggleIsShowingDeleteConfirm = (state: boolean) => {
|
||||
is_showing_delete_confirm.value = state;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (ui_store.focus_next_component) {
|
||||
select_ref.value?.focus();
|
||||
select_ref.value?.showPopup();
|
||||
shift_type_selected.value = undefined;
|
||||
selectRef.value?.focus();
|
||||
selectRef.value?.showPopup();
|
||||
shiftTypeSelected.value = undefined;
|
||||
ui_store.focus_next_component = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="ui_store.is_mobile_mode ? 'column' : 'row'">
|
||||
<div class="row">
|
||||
<!-- delete shift confirmation dialog -->
|
||||
<q-dialog
|
||||
v-model="is_showing_delete_confirm"
|
||||
|
|
@ -110,7 +138,7 @@
|
|||
color="negative"
|
||||
:label="$t('shared.misc.no')"
|
||||
class="col"
|
||||
@click="is_showing_delete_confirm = false"
|
||||
@click="toggleIsShowingDeleteConfirm(false)"
|
||||
/>
|
||||
|
||||
<q-btn
|
||||
|
|
@ -130,23 +158,9 @@
|
|||
>
|
||||
<!-- shift type -->
|
||||
<q-select
|
||||
ref="select_ref"
|
||||
v-model="shift_type_selected"
|
||||
:standout="$q.dark.isActive ? 'bg-blue-grey-3' : 'bg-blue-grey-9'"
|
||||
dense
|
||||
:borderless="(shift.is_approved && isTimesheetApproved)"
|
||||
:readonly="(shift.is_approved && isTimesheetApproved)"
|
||||
:options-dense="!ui_store.is_mobile_mode"
|
||||
hide-dropdown-icon
|
||||
:menu-offset="[0, 10]"
|
||||
menu-anchor="bottom middle"
|
||||
menu-self="top middle"
|
||||
:options="SHIFT_OPTIONS"
|
||||
class="col rounded-5 q-mx-xs bg-dark"
|
||||
:class="!shift.is_approved && !isTimesheetApproved ? '' : 'inset-shadow'"
|
||||
popup-content-class="text-uppercase text-weight-bold text-center rounded-5"
|
||||
:style="shift.is_approved ? (holiday ? 'background-color: #7b1fa2 !important' : 'background-color: #0a7d32 !important;') : ''"
|
||||
popup-content-style="border: 2px solid var(--q-accent)"
|
||||
ref="selectRef"
|
||||
v-model="shiftTypeSelected"
|
||||
v-bind="shiftTypeSelectProps"
|
||||
@blur="onBlurShiftTypeSelect"
|
||||
@update:model-value="option => shift.type = option.value"
|
||||
>
|
||||
|
|
@ -173,6 +187,22 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<template #option="scope">
|
||||
<q-item
|
||||
clickable
|
||||
v-bind="scope.itemProps"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-icon :name="scope.opt.icon" />
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section class="text-left">
|
||||
{{ $t(scope.label) }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
|
||||
<!-- work-from-home toggle -->
|
||||
<template #after>
|
||||
<q-icon
|
||||
v-if="shift.is_approved"
|
||||
|
|
@ -214,21 +244,6 @@
|
|||
</q-tooltip>
|
||||
</q-toggle>
|
||||
</template>
|
||||
|
||||
<template #option="scope">
|
||||
<q-item
|
||||
clickable
|
||||
v-bind="scope.itemProps"
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-icon :name="scope.opt.icon" />
|
||||
</q-item-section>
|
||||
|
||||
<q-item-section class="text-left">
|
||||
{{ $t(scope.label) }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
</div>
|
||||
|
||||
|
|
@ -237,7 +252,7 @@
|
|||
<q-input
|
||||
ref="start_time"
|
||||
v-model="shift.start_time"
|
||||
v-bind="time_input_props"
|
||||
v-bind="timeInputProps"
|
||||
type="time"
|
||||
@blur="onTimeFieldBlur(shift.start_time)"
|
||||
>
|
||||
|
|
@ -254,7 +269,7 @@
|
|||
<q-input
|
||||
ref="end_time"
|
||||
v-model="shift.end_time"
|
||||
v-bind="time_input_props"
|
||||
v-bind="timeInputProps"
|
||||
type="time"
|
||||
@blur="onTimeFieldBlur(shift.end_time)"
|
||||
>
|
||||
|
|
@ -267,12 +282,11 @@
|
|||
</template>
|
||||
</q-input>
|
||||
|
||||
<!-- comment and delete buttons -->
|
||||
<div
|
||||
class="row full-height"
|
||||
:class="ui_store.is_mobile_mode ? 'col-12' : 'col-auto flex-center'"
|
||||
>
|
||||
<!-- desktop comment button -->
|
||||
<!-- comment button -->
|
||||
<q-btn
|
||||
v-if="!ui_store.is_mobile_mode"
|
||||
push
|
||||
|
|
@ -291,6 +305,7 @@
|
|||
class="text-blue-9 text-weight-bolder"
|
||||
>!!</q-badge>
|
||||
|
||||
<!-- popup to edit comment, with visual indicator of character limit -->
|
||||
<q-popup-edit
|
||||
v-model="shift.comment"
|
||||
v-slot="scope"
|
||||
|
|
@ -339,6 +354,7 @@
|
|||
</q-popup-edit>
|
||||
</q-btn>
|
||||
|
||||
<!-- delete button -->
|
||||
<q-btn
|
||||
v-if="!shift.is_approved"
|
||||
flat
|
||||
|
|
@ -350,14 +366,15 @@
|
|||
class="col"
|
||||
size="1.2em"
|
||||
:class="shift.is_approved ? 'invisible' : ''"
|
||||
@click="is_showing_delete_confirm = true"
|
||||
>
|
||||
</q-btn>
|
||||
@click="toggleIsShowingDeleteConfirm(true)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- styling the error message component to ressemble a red tab that
|
||||
drops down, rather than the standard floating red text only -->
|
||||
<style scoped>
|
||||
:deep(.q-field--error) {
|
||||
background-color: var(--q-negative) !important;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
import ShiftListDayRow from 'src/modules/timesheets/components/shift-list-day-row.vue';
|
||||
import ShiftListDayRowMobile from 'src/modules/timesheets/components/mobile/shift-list-day-row-mobile.vue';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { inject, ref } from 'vue';
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
import { useShiftApi } from 'src/modules/timesheets/composables/use-shift-api';
|
||||
import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api';
|
||||
|
|
@ -13,27 +13,30 @@
|
|||
import type { TimesheetDay } from 'src/modules/timesheets/models/timesheet.models';
|
||||
import { isShiftOverlap } from 'src/modules/timesheets/utils/shift.util';
|
||||
|
||||
const shift_api = useShiftApi();
|
||||
const timesheet_api = useTimesheetApi();
|
||||
const timesheet_store = useTimesheetStore();
|
||||
const shift_error_message = ref<string | undefined>();
|
||||
// ================== State ==================
|
||||
|
||||
const { day, dense = false, approved = false, holiday = false, employeeEmail } = defineProps<{
|
||||
const { day, dense = false, approved = false, holiday = false } = defineProps<{
|
||||
timesheetId: number;
|
||||
weekDayIndex: number;
|
||||
day: TimesheetDay;
|
||||
dense?: boolean;
|
||||
approved?: boolean;
|
||||
holiday?: boolean;
|
||||
employeeEmail?: string;
|
||||
}>();
|
||||
|
||||
const preset_mouseover = ref(false);
|
||||
|
||||
const emit = defineEmits<{
|
||||
'deleteUnsavedShift': [void];
|
||||
}>();
|
||||
|
||||
const shift_api = useShiftApi();
|
||||
const timesheet_api = useTimesheetApi();
|
||||
const timesheet_store = useTimesheetStore();
|
||||
const preset_mouseover = ref(false);
|
||||
const shift_error_message = ref<string | undefined>();
|
||||
const employeeEmail = inject<string>('employeeEmail');
|
||||
|
||||
// ================== Methods ==================
|
||||
|
||||
const deleteCurrentShift = async (shift: Shift) => {
|
||||
if (shift.id <= 0) {
|
||||
shift.id = 0;
|
||||
|
|
|
|||
|
|
@ -12,19 +12,29 @@
|
|||
import ShiftListWeeklyOverview from 'src/modules/timesheets/components/shift-list-weekly-overview.vue';
|
||||
import ShiftListWeeklyOverviewMobile from 'src/modules/timesheets/components/mobile/shift-list-weekly-overview-mobile.vue';
|
||||
|
||||
import { date, Notify } from 'quasar';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { computed, onMounted } from 'vue';
|
||||
import { computed, onMounted, provide } from 'vue';
|
||||
import { useShiftApi } from 'src/modules/timesheets/composables/use-shift-api';
|
||||
import { useTimesheetApi } from 'src/modules/timesheets/composables/use-timesheet-api';
|
||||
import { useExpensesStore } from 'src/stores/expense-store';
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
import { date, Notify } from 'quasar';
|
||||
|
||||
// ================= state ====================
|
||||
|
||||
const { mode = 'normal', employeeEmail } = defineProps<{
|
||||
mode?: 'approval' | 'normal';
|
||||
employeeEmail?: string | undefined;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const expenses_store = useExpensesStore();
|
||||
const timesheet_store = useTimesheetStore();
|
||||
const timesheet_api = useTimesheetApi();
|
||||
const shift_api = useShiftApi();
|
||||
|
||||
// ================== computed ====================
|
||||
|
||||
const has_shift_errors = computed(() => timesheet_store.all_current_shifts.filter(shift => shift.has_error === true).length > 0);
|
||||
|
||||
const is_timesheets_approved = computed(() => timesheet_store.timesheets.every(timesheet => timesheet.is_approved))
|
||||
|
|
@ -44,9 +54,9 @@
|
|||
0 //initial value
|
||||
));
|
||||
|
||||
const { mode = 'normal' } = defineProps<{
|
||||
mode?: 'approval' | 'normal';
|
||||
}>();
|
||||
// =================== methods ==========================
|
||||
|
||||
provide('employeeEmail', employeeEmail);
|
||||
|
||||
const onClickSaveTimesheets = async () => {
|
||||
if (mode === 'normal') {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user