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