Merge pull request 'fix(all): fix scroll issue in timesheet mobile, add approval for individual expenses in approvals' (#39) from dev/nicolas/staging-prep into main
Reviewed-on: Targo/targo_frontend#39
This commit is contained in:
commit
00bc56ea61
|
|
@ -123,6 +123,8 @@ export default {
|
|||
page_header: "account login",
|
||||
email: "e-mail",
|
||||
password: "password",
|
||||
connected: "Connected",
|
||||
redirecting: "Redirecting...",
|
||||
button: {
|
||||
connect: "connect",
|
||||
employee: "employee",
|
||||
|
|
@ -323,6 +325,7 @@ export default {
|
|||
TIMESHEET_NOT_FOUND: "No timesheet found with provided data",
|
||||
INVALID_EXPENSE: "An expense contains missing or corrupted data",
|
||||
EXPENSE_NOT_FOUND: "No expense found with provided data",
|
||||
UPDATE_ERROR: "Error while updating data",
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -355,6 +358,8 @@ export default {
|
|||
},
|
||||
tooltip: {
|
||||
button_detailed_view: "detailed view",
|
||||
approve: "Approve",
|
||||
unapprove: "remove approval",
|
||||
},
|
||||
},
|
||||
descriptions: {
|
||||
|
|
|
|||
|
|
@ -123,6 +123,8 @@ export default {
|
|||
page_header: "connexion au compte",
|
||||
email: "courriel",
|
||||
password: "mot de passe",
|
||||
connected: "Connecté",
|
||||
redirecting: "redirection en cours...",
|
||||
button: {
|
||||
connect: "connecter",
|
||||
employee: "employé",
|
||||
|
|
@ -324,6 +326,7 @@ export default {
|
|||
TIMESHEET_NOT_FOUND: "Aucune feuille de temps ne correspond au détails fournis",
|
||||
INVALID_EXPENSE: "Une dépense contient des données manquantes ou corrompues",
|
||||
EXPENSE_NOT_FOUND: "Aucune dépense ne correspond aux détails fournis",
|
||||
UPDATE_ERROR: "Une erreur est survenu lors de la mise à jour",
|
||||
},
|
||||
},
|
||||
|
||||
|
|
@ -356,6 +359,8 @@ export default {
|
|||
},
|
||||
tooltip: {
|
||||
button_detailed_view: "vue détaillée",
|
||||
approve: "mettre status approuvé",
|
||||
unapprove: "enlever status approuvé",
|
||||
},
|
||||
},
|
||||
descriptions: {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
<script
|
||||
setup
|
||||
lang="ts"
|
||||
>
|
||||
import { onMounted } from 'vue';
|
||||
|
||||
onMounted(() => {
|
||||
|
|
@ -6,7 +9,7 @@
|
|||
setTimeout(() => {
|
||||
window.opener.postMessage({ type: 'authSuccess' });
|
||||
window.close();
|
||||
}, 1500);
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
@ -15,18 +18,35 @@
|
|||
<q-layout class="bg-secondary">
|
||||
<q-page-container>
|
||||
<q-page class="column items-center justify-center q-pa-xl">
|
||||
<transition appear enter-active-class="animated slow flipInX" leave-active-class="animated flipOutX">
|
||||
<q-card class="col-3 items-center">
|
||||
<q-card-section class="row justify-center ">
|
||||
<q-icon name="check_circle" color="accent" size="xl" />
|
||||
<transition
|
||||
appear
|
||||
enter-active-class="animated flipInX"
|
||||
>
|
||||
<q-card class="col-3 items-center rounded-10 q-px-lg">
|
||||
<q-card-section class="row justify-center ">
|
||||
<q-icon
|
||||
name="check_circle"
|
||||
color="accent"
|
||||
size="xl"
|
||||
/>
|
||||
</q-card-section>
|
||||
<q-separator inset color="accent" />
|
||||
<q-card-section class="row justify-center">
|
||||
<span class="row text-h3">Login Successful!</span>
|
||||
<q-separator
|
||||
inset
|
||||
color="accent"
|
||||
/>
|
||||
<q-card-section class="column items-center">
|
||||
<span class="col-auto text-h4 text-uppercase">{{ $t('login.connected') }}</span>
|
||||
<span class="col-auto text-h6 text-uppercase">{{ $t('login.redirecting') }}</span>
|
||||
<q-spinner
|
||||
color="accent"
|
||||
size="5em"
|
||||
:thickness="4"
|
||||
class="col-auto"
|
||||
/>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</transition>
|
||||
</q-page>
|
||||
</q-page-container>
|
||||
</q-layout>
|
||||
</q-layout>
|
||||
</template>
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
</div>
|
||||
|
||||
<div class="col-auto">
|
||||
<ExpenseDialogList />
|
||||
<ExpenseDialogList mode="approval" />
|
||||
</div>
|
||||
|
||||
<!-- list of shifts -->
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
lang="ts"
|
||||
>
|
||||
import type { TimesheetApprovalOverview } from 'src/modules/timesheet-approval/models/timesheet-overview.models';
|
||||
import { getHoursMinutesStringFromHoursFloat, getMinutes } from 'src/utils/date-and-time-utils';
|
||||
import { getHoursMinutesStringFromHoursFloat, getMinutes } from 'src/utils/date-and-time-utils';
|
||||
|
||||
const modelApproval = defineModel<boolean>();
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ import { getHoursMinutesStringFromHoursFloat, getMinutes } from 'src/utils/date-
|
|||
|
||||
const emit = defineEmits<{
|
||||
'clickDetails': [overview: TimesheetApprovalOverview];
|
||||
'clickApprovalAll' : [is_approved: boolean];
|
||||
'clickApprovalAll': [is_approved: boolean];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
|
|
@ -45,11 +45,10 @@ import { getHoursMinutesStringFromHoursFloat, getMinutes } from 'src/utils/date-
|
|||
|
||||
<!-- Buttons to view detailed shifts or view employee timesheet -->
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
square
|
||||
unelevated
|
||||
class="col-auto q-pa-none q-ma-none"
|
||||
class="col-auto q-ma-none rounded-5"
|
||||
color="accent"
|
||||
icon="las la-chart-pie"
|
||||
@click="emit('clickDetails', row)"
|
||||
|
|
@ -57,6 +56,7 @@ import { getHoursMinutesStringFromHoursFloat, getMinutes } from 'src/utils/date-
|
|||
<q-tooltip
|
||||
anchor="top middle"
|
||||
self="center middle"
|
||||
:offset="[0, 20]"
|
||||
class="bg-accent text-uppercase text-weight-bold"
|
||||
>
|
||||
{{ $t('timesheet_approvals.tooltip.button_detailed_view') }}
|
||||
|
|
@ -64,7 +64,7 @@ import { getHoursMinutesStringFromHoursFloat, getMinutes } from 'src/utils/date-
|
|||
|
||||
<q-icon
|
||||
name="las la-chart-bar"
|
||||
color="accent"
|
||||
color="white"
|
||||
/>
|
||||
</q-btn>
|
||||
</q-card-section>
|
||||
|
|
@ -152,9 +152,7 @@ import { getHoursMinutesStringFromHoursFloat, getMinutes } from 'src/utils/date-
|
|||
v-if="row.is_active"
|
||||
class="col row full-width"
|
||||
>
|
||||
<div
|
||||
class="col text-uppercase"
|
||||
>
|
||||
<div class="col text-uppercase">
|
||||
<span class="text-h6 q-ml-sm text-weight-bolder">{{ 'Total : ' + Math.floor(row.total_hours)
|
||||
}}</span>
|
||||
<span class="text-uppercase text-weight-medium text-caption">H</span>
|
||||
|
|
@ -176,7 +174,17 @@ import { getHoursMinutesStringFromHoursFloat, getMinutes } from 'src/utils/date-
|
|||
class="text-uppercase"
|
||||
:class="row.is_approved ? '' : 'text-accent'"
|
||||
@update:model-value="value => $emit('clickApprovalAll', value)"
|
||||
/>
|
||||
>
|
||||
<q-tooltip
|
||||
anchor="top middle"
|
||||
self="center middle"
|
||||
:offset="[0, 20]"
|
||||
class="bg-accent text-uppercase text-weight-bold"
|
||||
>
|
||||
{{ row.is_approved ? $t('timesheet_approvals.tooltip.unapprove') :
|
||||
$t('timesheet_approvals.tooltip.approve') }}
|
||||
</q-tooltip>
|
||||
</q-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -190,8 +198,10 @@ import { getHoursMinutesStringFromHoursFloat, getMinutes } from 'src/utils/date-
|
|||
class="col-auto"
|
||||
size="lg"
|
||||
/>
|
||||
<span class="col q-pl-sm text-uppercase text-weight-bold text-h5">{{
|
||||
$t('timesheet_approvals.table.inactive') }}</span>
|
||||
|
||||
<span class="col q-pl-sm text-uppercase text-weight-bold text-h5">
|
||||
{{ $t('timesheet_approvals.table.inactive') }}
|
||||
</span>
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
|
|
|
|||
|
|
@ -4,32 +4,57 @@
|
|||
>
|
||||
import ExpenseDialogForm from 'src/modules/timesheets/components/expense-dialog-form.vue';
|
||||
|
||||
import { date } from 'quasar';
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { date, Notify } from 'quasar';
|
||||
import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
|
||||
import { useExpensesStore } from 'src/stores/expense-store';
|
||||
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||
import { useExpensesApi } from 'src/modules/timesheets/composables/use-expense-api';
|
||||
import { getExpenseIcon } from 'src/modules/timesheets/utils/expense.util';
|
||||
import type { Expense } from 'src/modules/timesheets/models/expense.models';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const expense = defineModel<Expense>({ required: true });
|
||||
|
||||
const expenses_store = useExpensesStore();
|
||||
const expenses_api = useExpensesApi();
|
||||
const expenses_store = useExpensesStore();
|
||||
const timesheet_store = useTimesheetStore();
|
||||
|
||||
const is_showing_update_form = ref(false);
|
||||
|
||||
const { mode = 'normal' } = defineProps<{
|
||||
mode?: 'approval' | 'normal';
|
||||
}>();
|
||||
|
||||
const requestExpenseDeletion = async () => {
|
||||
await expenses_api.deleteExpenseById(expense.value.id);
|
||||
}
|
||||
|
||||
const onClickExpenseUpdate = () => {
|
||||
if (expense.value.is_approved) return;
|
||||
|
||||
|
||||
expenses_store.mode = 'update';
|
||||
expenses_store.current_expense = expense.value;
|
||||
expenses_store.initial_expense = unwrapAndClone(expense.value);
|
||||
}
|
||||
|
||||
const onClickApproval = async () => {
|
||||
expenses_store.current_expense = unwrapAndClone(expense.value);
|
||||
expenses_store.current_expense.is_approved = !expenses_store.current_expense.is_approved;
|
||||
|
||||
const success = await expenses_store.upsertExpense(expenses_store.current_expense, timesheet_store.current_pay_period_overview?.email);
|
||||
if (success) {
|
||||
expense.value.is_approved = !expense.value.is_approved;
|
||||
} else {
|
||||
expenses_store.current_expense.is_approved = !expenses_store.current_expense.is_approved;
|
||||
Notify.create({
|
||||
message: t('timesheet.errors.UPDATE_ERROR'),
|
||||
color: "negative"
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -127,11 +152,31 @@
|
|||
|
||||
|
||||
<div class="col-auto">
|
||||
<q-btn
|
||||
v-if="mode === 'approval'"
|
||||
flat
|
||||
size="lg"
|
||||
:icon="expense.is_approved ? 'lock' : 'lock_open'"
|
||||
:color="expense.is_approved ? 'white' : 'accent'"
|
||||
class="relative-position"
|
||||
@click.stop="onClickApproval"
|
||||
>
|
||||
<q-tooltip
|
||||
anchor="top middle"
|
||||
self="center middle"
|
||||
:offset="[0, 20]"
|
||||
class="bg-accent text-uppercase text-weight-bold"
|
||||
>
|
||||
{{ expense.is_approved ? $t('timesheet_approvals.tooltip.unapprove') : $t('timesheet_approvals.tooltip.approve') }}
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
|
||||
<q-icon
|
||||
v-if="expense.is_approved"
|
||||
name="verified"
|
||||
color="white"
|
||||
size="lg"
|
||||
size="2.5em"
|
||||
class="q-px-sm"
|
||||
/>
|
||||
|
||||
<q-btn
|
||||
|
|
@ -141,7 +186,7 @@
|
|||
size="lg"
|
||||
icon="close"
|
||||
color="negative"
|
||||
class="q-py-none q-my-xs"
|
||||
class="q-py-xs q-px-sm"
|
||||
@click.stop="requestExpenseDeletion"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@
|
|||
|
||||
const timesheet_store = useTimesheetStore();
|
||||
|
||||
const { horizontal = false } = defineProps<{
|
||||
horizontal?: boolean;
|
||||
const { mode = 'normal' } = defineProps<{
|
||||
mode?: 'approval' | 'normal';
|
||||
}>();
|
||||
|
||||
const expenses_list = computed(() => {
|
||||
|
|
@ -26,7 +26,6 @@
|
|||
<q-list
|
||||
padding
|
||||
class="q-px-lg"
|
||||
:class="horizontal ? 'row flex-center' : ''"
|
||||
>
|
||||
<q-item-label
|
||||
v-if="expenses_list.length < 1"
|
||||
|
|
@ -50,6 +49,7 @@
|
|||
v-else
|
||||
v-model="expenses_list[index]!"
|
||||
:index="index"
|
||||
:mode="mode"
|
||||
/>
|
||||
</div>
|
||||
</q-list>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
const handleSwipe: TouchSwipeValue = (details) => {
|
||||
mobile_animation_direction.value = details.direction === 'left' ? 'fadeInRight' : 'fadeInLeft';
|
||||
if (details.distance && details.distance.x && Math.abs(details.distance.x) > 10) {
|
||||
if (details.distance && details.distance.x && Math.abs(details.distance.x) > 30) {
|
||||
timesheet_api.getTimesheetsBySwiping(details.direction === 'left' ? 1 : -1).catch(error => console.error(error));
|
||||
}
|
||||
};
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
<div
|
||||
class="column fit relative-position"
|
||||
:style="$q.platform.is.mobile && $q.screen.width < $q.screen.height ? 'margin-bottom: 40px' : ''"
|
||||
v-touch-swipe="handleSwipe"
|
||||
v-touch-swipe.horizontal="handleSwipe"
|
||||
>
|
||||
<q-scroll-area
|
||||
ref="timesheet_page"
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ export const ExpenseService = {
|
|||
return response.data;
|
||||
},
|
||||
|
||||
updateExpense: async (expense: Expense): Promise<{success: boolean, data: Expense, error?: unknown}> => {
|
||||
const response = await api.patch(`expense/update`, expense);
|
||||
updateExpense: async (expense: Expense, email?: string): Promise<{success: boolean, data: Expense, error?: unknown}> => {
|
||||
const response = await api.patch(`expense/update${email ? '?employee_email=' + email : ''}`, expense);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -29,13 +29,13 @@ export const useExpensesStore = defineStore('expenses', () => {
|
|||
is_showing_create_form.value = true;
|
||||
};
|
||||
|
||||
const upsertExpense = async (expense: Expense): Promise<boolean> => {
|
||||
const upsertExpense = async (expense: Expense, email?: string): Promise<boolean> => {
|
||||
try {
|
||||
if (expense.id < 0) {
|
||||
const data = await ExpenseService.createExpense(expense);
|
||||
return data.success;
|
||||
}
|
||||
const data = await ExpenseService.updateExpense(expense);
|
||||
const data = await ExpenseService.updateExpense(expense, email);
|
||||
return data.success;
|
||||
} catch (err) {
|
||||
// setErrorFrom(err);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user