feat(profile): finalize get/update of user preferences, begin planning for employee-management module
This commit is contained in:
parent
4c79820128
commit
a4904ee80d
|
|
@ -1,7 +1,9 @@
|
||||||
<script setup lang="ts">
|
<script
|
||||||
//
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<router-view />
|
<router-view />
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -61,10 +61,14 @@ export default {
|
||||||
},
|
},
|
||||||
preferences: {
|
preferences: {
|
||||||
tab_title: "preferences",
|
tab_title: "preferences",
|
||||||
display_options: "display options",
|
display_options: "Color mode",
|
||||||
language_options: "language options",
|
language_options: "language options",
|
||||||
|
'fr-FR': "French",
|
||||||
|
'en-CA': "English",
|
||||||
dark_mode: "dark",
|
dark_mode: "dark",
|
||||||
light_mode: "light",
|
light_mode: "light",
|
||||||
|
update_successful: "Preferences saved",
|
||||||
|
update_failed: "Failed to save preferences",
|
||||||
},
|
},
|
||||||
schedule_presets: {
|
schedule_presets: {
|
||||||
tab_title: "Schedule",
|
tab_title: "Schedule",
|
||||||
|
|
|
||||||
|
|
@ -61,10 +61,14 @@ export default {
|
||||||
},
|
},
|
||||||
preferences: {
|
preferences: {
|
||||||
tab_title: "préférences",
|
tab_title: "préférences",
|
||||||
display_options: "Options d'affichage",
|
display_options: "Mode d'affichage",
|
||||||
language_options: "Options de langue",
|
language_options: "Options de langue",
|
||||||
|
'fr-FR': "Français",
|
||||||
|
'en-CA': "Anglais",
|
||||||
dark_mode: "sombre",
|
dark_mode: "sombre",
|
||||||
light_mode: "clair",
|
light_mode: "clair",
|
||||||
|
update_successful: "Préférences enregistrées",
|
||||||
|
update_failed: "Échec de sauvegarde",
|
||||||
},
|
},
|
||||||
schedule_presets: {
|
schedule_presets: {
|
||||||
tab_title: "horaire",
|
tab_title: "horaire",
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,46 @@
|
||||||
<script lang="ts" setup>
|
<script
|
||||||
import { RouterView } from 'vue-router';
|
lang="ts"
|
||||||
import HeaderBar from 'src/layouts/components/main-layout-header-bar.vue';
|
setup
|
||||||
import FooterBar from 'src/layouts/components/main-layout-footer-bar.vue';
|
>
|
||||||
import LeftDrawer from 'src/layouts/components/main-layout-left-drawer.vue';
|
import { RouterView } from 'vue-router';
|
||||||
|
import HeaderBar from 'src/layouts/components/main-layout-header-bar.vue';
|
||||||
|
import FooterBar from 'src/layouts/components/main-layout-footer-bar.vue';
|
||||||
|
import LeftDrawer from 'src/layouts/components/main-layout-left-drawer.vue';
|
||||||
|
import { useUiStore } from 'src/stores/ui-store';
|
||||||
|
import { onMounted, watch, ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const ui_store = useUiStore();
|
||||||
|
const user_preferences = ref(ui_store.user_preferences);
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
console.log('current preferences on load: ', ui_store.user_preferences);
|
||||||
|
if (ui_store.user_preferences.id === -1) {
|
||||||
|
console.log('fetching preferences');
|
||||||
|
await ui_store.getUserPreferences();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(user_preferences, async () => {
|
||||||
|
if (ui_store.user_preferences.id !== -1) {
|
||||||
|
console.log('triggered watcher');
|
||||||
|
await ui_store.updateUserPreferences(t);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log('watcher triggered but store has no preferences')
|
||||||
|
await ui_store.getUserPreferences();
|
||||||
|
}, {deep: true});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-layout view="hHh lpR fFf">
|
<q-layout view="hHh lpR fFf">
|
||||||
<HeaderBar />
|
<HeaderBar />
|
||||||
<LeftDrawer />
|
<LeftDrawer />
|
||||||
<q-page-container>
|
<q-page-container>
|
||||||
<router-view class="q-pa-sm bg-secondary" />
|
<router-view class="q-pa-sm bg-secondary" />
|
||||||
</q-page-container>
|
</q-page-container>
|
||||||
<FooterBar />
|
<FooterBar />
|
||||||
</q-layout>
|
</q-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -55,18 +55,14 @@
|
||||||
color="accent"
|
color="accent"
|
||||||
class="col-auto"
|
class="col-auto"
|
||||||
/>
|
/>
|
||||||
|
<span
|
||||||
<transition
|
:key="is_remembered ? 'yep' : 'nope'"
|
||||||
enter-active-class="animated rubberBand fast"
|
class="col-auto text-weight-bold self-center q-ml-sm"
|
||||||
leave-active-class=""
|
:class="is_remembered ? 'text-accent' : ''"
|
||||||
mode="out-in"
|
|
||||||
>
|
>
|
||||||
<span
|
{{ $t('login.button.remember_me') }}
|
||||||
:key="is_remembered ? 'yep' : 'nope'"
|
</span>
|
||||||
class="col-auto text-weight-bold self-center q-ml-sm"
|
|
||||||
:class="is_remembered ? 'text-accent' : ''"
|
|
||||||
>{{ $t('login.button.remember_me') }}</span>
|
|
||||||
</transition>
|
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
<q-card-actions>
|
<q-card-actions>
|
||||||
|
|
@ -90,14 +86,14 @@
|
||||||
<q-card-section class="row q-pt-sm">
|
<q-card-section class="row q-pt-sm">
|
||||||
<q-separator
|
<q-separator
|
||||||
size="2px"
|
size="2px"
|
||||||
color="primary"
|
color="accent"
|
||||||
class="col self-center"
|
class="col self-center"
|
||||||
/>
|
/>
|
||||||
<span class="col text-primary text-weight-bolder text-center text-uppercase self-center">{{
|
<span class="col text-accent text-weight-bolder text-center text-uppercase self-center">{{
|
||||||
$t('shared.misc.or') }}</span>
|
$t('shared.misc.or') }}</span>
|
||||||
<q-separator
|
<q-separator
|
||||||
size="2px"
|
size="2px"
|
||||||
color="primary"
|
color="accent"
|
||||||
class="col self-center"
|
class="col self-center"
|
||||||
/>
|
/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,20 @@
|
||||||
setup
|
setup
|
||||||
lang="ts"
|
lang="ts"
|
||||||
>
|
>
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
import { useEmployeeListApi } from 'src/modules/employee-list/composables/use-employee-api';
|
|
||||||
import { useEmployeeStore } from 'src/stores/employee-store';
|
|
||||||
import EmployeeListTableItem from 'src/modules/employee-list/components/employee-list-table-item.vue';
|
import EmployeeListTableItem from 'src/modules/employee-list/components/employee-list-table-item.vue';
|
||||||
import { employee_list_columns } from 'src/modules/employee-list/models/employee-profile.models';
|
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
import { useUiStore } from 'src/stores/ui-store';
|
||||||
|
import { useEmployeeStore } from 'src/stores/employee-store';
|
||||||
|
import { useEmployeeListApi } from 'src/modules/employee-list/composables/use-employee-api';
|
||||||
|
import { employee_list_columns, getCompanyName } from 'src/modules/employee-list/models/employee-profile.models';
|
||||||
|
|
||||||
const employee_list_api = useEmployeeListApi();
|
const employee_list_api = useEmployeeListApi();
|
||||||
const employee_store = useEmployeeStore();
|
const employee_store = useEmployeeStore();
|
||||||
|
const ui_store = useUiStore();
|
||||||
const is_loading_list = ref<boolean>(true);
|
const is_loading_list = ref<boolean>(true);
|
||||||
|
|
||||||
const filter = ref("");
|
const filter = ref("");
|
||||||
const is_grid_mode = ref(true);
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
is_loading_list.value = true;
|
is_loading_list.value = true;
|
||||||
|
|
@ -26,7 +28,6 @@
|
||||||
<div class="q-pa-lg">
|
<div class="q-pa-lg">
|
||||||
<q-table
|
<q-table
|
||||||
dense
|
dense
|
||||||
flat
|
|
||||||
hide-pagination
|
hide-pagination
|
||||||
virtual-scroll
|
virtual-scroll
|
||||||
title=" "
|
title=" "
|
||||||
|
|
@ -36,14 +37,13 @@
|
||||||
row-key="name"
|
row-key="name"
|
||||||
:rows-per-page-options="[0]"
|
:rows-per-page-options="[0]"
|
||||||
:filter="filter"
|
:filter="filter"
|
||||||
class="q-pa-md bg-transparent"
|
class="bg-transparent no-shadow sticky-header-table"
|
||||||
:class="is_grid_mode ? '' : 'sticky-header-table'"
|
|
||||||
:style="$q.screen.lt.md ? '' : 'width: 80vw;'"
|
:style="$q.screen.lt.md ? '' : 'width: 80vw;'"
|
||||||
:table-class="$q.dark.isActive ? 'q-px-md q-py-none q-mx-md rounded-10 bg-dark' : 'q-px-md q-py-none q-mx-md rounded-10 bg-white'"
|
:table-class="$q.dark.isActive ? 'q-py-none q-mx-md rounded-10 bg-dark shadow-10' : 'q-py-none q-mx-md rounded-10 bg-white shadow-10'"
|
||||||
color="accent"
|
color="accent"
|
||||||
table-header-class="text-accent text-uppercase"
|
table-header-class="text-accent text-uppercase"
|
||||||
card-container-class="justify-center"
|
card-container-class="justify-center"
|
||||||
:grid="is_grid_mode"
|
:grid="ui_store.user_preferences.is_employee_list_grid"
|
||||||
:loading="is_loading_list"
|
:loading="is_loading_list"
|
||||||
:no-data-label="$t('shared.error.no_data_found')"
|
:no-data-label="$t('shared.error.no_data_found')"
|
||||||
:no-results-label="$t('shared.error.no_search_results')"
|
:no-results-label="$t('shared.error.no_search_results')"
|
||||||
|
|
@ -87,7 +87,7 @@
|
||||||
<q-space />
|
<q-space />
|
||||||
|
|
||||||
<q-btn-toggle
|
<q-btn-toggle
|
||||||
v-model="is_grid_mode"
|
v-model="ui_store.user_preferences.is_employee_list_grid"
|
||||||
push
|
push
|
||||||
rounded
|
rounded
|
||||||
color="white"
|
color="white"
|
||||||
|
|
@ -119,6 +119,16 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template #body-cell="scope">
|
||||||
|
<q-td
|
||||||
|
:props="scope"
|
||||||
|
class="text-weight-medium"
|
||||||
|
>
|
||||||
|
<span v-if="scope.col.name === 'company_name'"> {{ getCompanyName(scope.value) }}</span>
|
||||||
|
<span v-else>{{ scope.value }}</span>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- Template for custome failed-to-load state -->
|
<!-- Template for custome failed-to-load state -->
|
||||||
<template v-slot:no-data="{ message, filter }">
|
<template v-slot:no-data="{ message, filter }">
|
||||||
<div class="full-width column items-center text-accent q-gutter-sm">
|
<div class="full-width column items-center text-accent q-gutter-sm">
|
||||||
|
|
@ -138,14 +148,14 @@
|
||||||
<style lang="sass">
|
<style lang="sass">
|
||||||
.sticky-header-table
|
.sticky-header-table
|
||||||
thead tr:first-child th
|
thead tr:first-child th
|
||||||
background-color: var(--q-dark)
|
background-color: var(--q-accent)
|
||||||
margin-top: none
|
margin-top: none
|
||||||
|
|
||||||
thead tr th
|
thead tr th
|
||||||
position: sticky
|
position: sticky
|
||||||
z-index: 1
|
z-index: 1
|
||||||
thead tr:first-child th
|
thead tr:first-child th
|
||||||
top: 0
|
top: 0px
|
||||||
|
|
||||||
&.q-table--loading thead tr:last-child th
|
&.q-table--loading thead tr:last-child th
|
||||||
top: 48px
|
top: 48px
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useEmployeeStore } from 'src/stores/employee-store';
|
import { useEmployeeStore } from 'src/stores/employee-store';
|
||||||
|
|
||||||
const employeeStore = useEmployeeStore();
|
const employee_store = useEmployeeStore();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-dialog v-model="employeeStore.isShowingEmployeeAddModifyWindow">
|
<q-dialog v-model="employee_store.isShowingEmployeeAddModifyWindow">
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
LOL
|
LOL
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-inner-loading :showing="employeeStore.isLoadingEmployeeProfile"/>
|
<q-inner-loading :showing="employee_store.is_loading"/>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import type { QTableColumn } from "quasar";
|
import type { QTableColumn } from "quasar";
|
||||||
|
|
||||||
export interface EmployeeProfile {
|
export class EmployeeProfile {
|
||||||
first_name: string;
|
first_name: string;
|
||||||
last_name: string;
|
last_name: string;
|
||||||
supervisor_full_name: string;
|
supervisor_full_name: string;
|
||||||
|
|
@ -12,57 +12,65 @@ export interface EmployeeProfile {
|
||||||
last_work_day: string;
|
last_work_day: string;
|
||||||
residence: string;
|
residence: string;
|
||||||
birth_date: string;
|
birth_date: string;
|
||||||
}
|
|
||||||
|
constructor() {
|
||||||
export const default_employee_profile: EmployeeProfile = {
|
this.first_name = '';
|
||||||
first_name: '',
|
this.last_name = '';
|
||||||
last_name: '',
|
this.supervisor_full_name = '';
|
||||||
supervisor_full_name: '',
|
this.company_name = 271583;
|
||||||
company_name: -1,
|
this.job_title = '';
|
||||||
job_title: '',
|
this.email = '';
|
||||||
email: '',
|
this.phone_number = '';
|
||||||
phone_number: '',
|
this.first_work_day = '';
|
||||||
first_work_day: '',
|
this.last_work_day = '';
|
||||||
last_work_day: '',
|
this.residence = '';
|
||||||
residence: '',
|
this.birth_date = '';
|
||||||
birth_date: '',
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const employee_list_columns: QTableColumn<EmployeeProfile>[] = [
|
export const employee_list_columns: QTableColumn<EmployeeProfile>[] = [
|
||||||
{
|
{
|
||||||
name: 'first_name',
|
name: 'first_name',
|
||||||
label: 'employee_list.table.first_name',
|
label: 'employee_list.table.first_name',
|
||||||
field: 'first_name',
|
field: 'first_name',
|
||||||
align: 'left'
|
align: 'left'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'last_name',
|
name: 'last_name',
|
||||||
label: 'employee_list.table.last_name',
|
label: 'employee_list.table.last_name',
|
||||||
field: 'last_name',
|
field: 'last_name',
|
||||||
align: 'left'
|
align: 'left'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'email',
|
name: 'email',
|
||||||
label: 'employee_list.table.email',
|
label: 'employee_list.table.email',
|
||||||
field: 'email',
|
field: 'email',
|
||||||
align: 'left'
|
align: 'left'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'supervisor_full_name',
|
name: 'supervisor_full_name',
|
||||||
label: 'employee_list.table.supervisor',
|
label: 'employee_list.table.supervisor',
|
||||||
field: 'supervisor_full_name',
|
field: 'supervisor_full_name',
|
||||||
align: 'left'
|
align: 'left'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'company_name',
|
name: 'company_name',
|
||||||
label: 'employee_list.table.company',
|
label: 'employee_list.table.company',
|
||||||
field: 'company_name',
|
field: 'company_name',
|
||||||
align: 'left'
|
align: 'left'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'job_title',
|
name: 'job_title',
|
||||||
label: 'employee_list.table.role',
|
label: 'employee_list.table.role',
|
||||||
field: 'job_title',
|
field: 'job_title',
|
||||||
align: 'left'
|
align: 'left'
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const getCompanyName = (company_code: number) => {
|
||||||
|
switch (company_code) {
|
||||||
|
case 271583: return 'Targo';
|
||||||
|
case 271585: return 'Solucom';
|
||||||
|
default: return 'N / A';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,8 +7,13 @@ export const EmployeeListService = {
|
||||||
return response.data.data;
|
return response.data.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
getEmployeeDetails: async (email: string): Promise<EmployeeProfile> => {
|
getEmployeeDetails: async (): Promise<EmployeeProfile> => {
|
||||||
const response = await api.get<EmployeeProfile>('employees/profile/' + email);
|
const response = await api.get<{success: boolean, data: EmployeeProfile, error?: string}>('employees/profile');
|
||||||
return response.data;
|
return response.data.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getEmployeeDetailsWithEmployeeEmail: async (employee_email: string): Promise<EmployeeProfile> => {
|
||||||
|
const response = await api.get<{success: boolean, data: EmployeeProfile, error?: string}>(`employees/profile?employee_email=${employee_email}`);
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
import MenuPanelPreferences from 'src/modules/profile/components/shared/menu-panel-preferences.vue';
|
import MenuPanelPreferences from 'src/modules/profile/components/shared/menu-panel-preferences.vue';
|
||||||
import MenuPanelSchedulePresets from 'src/modules/profile/components/shared/menu-panel-schedule-presets.vue';
|
import MenuPanelSchedulePresets from 'src/modules/profile/components/shared/menu-panel-schedule-presets.vue';
|
||||||
import MenuTemplate from 'src/modules/profile/components/shared/menu-template.vue';
|
import MenuTemplate from 'src/modules/profile/components/shared/menu-template.vue';
|
||||||
import { default_employee_profile, type EmployeeProfile } from 'src/modules/employee-list/models/employee-profile.models';
|
import { EmployeeProfile } from 'src/modules/employee-list/models/employee-profile.models';
|
||||||
import { useAuthStore } from 'src/stores/auth-store';
|
import { useAuthStore } from 'src/stores/auth-store';
|
||||||
|
|
||||||
const auth_store = useAuthStore();
|
const auth_store = useAuthStore();
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
SCHEDULE_PRESETS: 'schedule_presets',
|
SCHEDULE_PRESETS: 'schedule_presets',
|
||||||
};
|
};
|
||||||
|
|
||||||
const employee_profile = defineModel<EmployeeProfile>({ default: default_employee_profile });
|
const employee_profile = defineModel<EmployeeProfile>({ default: new EmployeeProfile });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,38 +2,49 @@
|
||||||
setup
|
setup
|
||||||
lang="ts"
|
lang="ts"
|
||||||
>
|
>
|
||||||
import { ref } from 'vue';
|
|
||||||
import { deepEqual } from 'src/utils/deep-equal';
|
|
||||||
import MenuPanelInputField from 'src/modules/profile/components/shared/menu-panel-input-field.vue';
|
import MenuPanelInputField from 'src/modules/profile/components/shared/menu-panel-input-field.vue';
|
||||||
import MenuPanelSelectField from 'src/modules/profile/components/shared/menu-panel-select-field.vue';
|
import MenuPanelSelectField from 'src/modules/profile/components/shared/menu-panel-select-field.vue';
|
||||||
import type { EmployeeProfile } from 'src/modules/employee-list/models/employee-profile.models';
|
|
||||||
import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
|
|
||||||
|
|
||||||
const employee_profile = defineModel<EmployeeProfile>({required: true});
|
import { ref } from 'vue';
|
||||||
|
import { deepEqual } from 'src/utils/deep-equal';
|
||||||
|
import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
|
||||||
|
import { useAuthStore } from 'src/stores/auth-store';
|
||||||
|
import { useEmployeeStore } from 'src/stores/employee-store';
|
||||||
|
import { CAN_APPROVE_PAY_PERIODS } from 'src/modules/shared/models/user.models';
|
||||||
|
import type { EmployeeProfile } from 'src/modules/employee-list/models/employee-profile.models';
|
||||||
|
|
||||||
|
const COMPANY_OPTIONS = [
|
||||||
|
{ label: 'Targo', value: 271583 },
|
||||||
|
{ label: 'Solucom', value: 271585 }
|
||||||
|
];
|
||||||
|
const SUPERVISOR_OPTIONS = [{ label: 'AAA', value: '1' }, { label: 'BBB', value: '2' }, { label: 'CCC', value: '3' }, { label: 'DDD', value: '4' }];
|
||||||
|
|
||||||
|
const auth_store = useAuthStore();
|
||||||
|
const employee_store = useEmployeeStore();
|
||||||
|
|
||||||
const is_editing = ref<boolean>(false);
|
const is_editing = ref<boolean>(false);
|
||||||
let initial_info: EmployeeProfile = unwrapAndClone(employee_profile.value);
|
const current_company_option = ref(COMPANY_OPTIONS.find(option => option.value === employee_store.employee.company_name) ?? { label: '', value: 0 })
|
||||||
|
let initial_info: EmployeeProfile = unwrapAndClone(employee_store.employee);
|
||||||
|
|
||||||
const supervisor_options = [{ label: 'AAA', value: '1' }, { label: 'BBB', value: '2' }, { label: 'CCC', value: '3' }, { label: 'DDD', value: '4' }];
|
|
||||||
|
|
||||||
const onSubmit = () => {
|
const onSubmit = () => {
|
||||||
if (!is_editing.value) {
|
if (!is_editing.value) {
|
||||||
is_editing.value = true;
|
is_editing.value = true;
|
||||||
console.log('clicky!');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
is_editing.value = false;
|
is_editing.value = false;
|
||||||
initial_info = unwrapAndClone(employee_profile.value); // update initial value for future possible resets
|
initial_info = unwrapAndClone(employee_store.employee); // update initial value for future possible resets
|
||||||
|
employee_store.employee.company_name = current_company_option.value.value;
|
||||||
|
|
||||||
if (!deepEqual(employee_profile.value, initial_info)) {
|
if (!deepEqual(employee_store.employee, initial_info)) {
|
||||||
// save the new data here
|
// save the new data here
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onReset = () => {
|
const onReset = () => {
|
||||||
employee_profile.value = unwrapAndClone(initial_info);
|
employee_store.employee = unwrapAndClone(initial_info);
|
||||||
is_editing.value = false;
|
is_editing.value = false;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -46,23 +57,24 @@
|
||||||
>
|
>
|
||||||
<div :class="$q.screen.lt.md ? 'column' : 'row'">
|
<div :class="$q.screen.lt.md ? 'column' : 'row'">
|
||||||
<MenuPanelInputField
|
<MenuPanelInputField
|
||||||
v-model="employee_profile.job_title"
|
v-model="employee_store.employee.job_title"
|
||||||
class="col"
|
class="col"
|
||||||
:is-editing="is_editing"
|
:is-editing="is_editing"
|
||||||
:label-string="$t('profile.employee.job_title')"
|
:label-string="$t('profile.employee.job_title')"
|
||||||
/>
|
/>
|
||||||
<MenuPanelInputField
|
<MenuPanelSelectField
|
||||||
v-model="employee_profile.company_name"
|
v-model="current_company_option"
|
||||||
|
:options="COMPANY_OPTIONS"
|
||||||
class="col"
|
class="col"
|
||||||
:is-editing="is_editing"
|
:is-editing="is_editing"
|
||||||
:label-string="$t('profile.employee.company')"
|
:label-string="$t('profile.employee.company')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="q-mx-xs">
|
<div>
|
||||||
<MenuPanelSelectField
|
<MenuPanelSelectField
|
||||||
v-model="employee_profile.supervisor_full_name"
|
v-model="employee_store.employee.supervisor_full_name"
|
||||||
:options="supervisor_options"
|
:options="SUPERVISOR_OPTIONS"
|
||||||
:label-string="$t('profile.employee.supervisor')"
|
:label-string="$t('profile.employee.supervisor')"
|
||||||
:is-editing="is_editing"
|
:is-editing="is_editing"
|
||||||
/>
|
/>
|
||||||
|
|
@ -71,15 +83,15 @@
|
||||||
|
|
||||||
<div :class="$q.screen.lt.md ? 'column' : 'row'">
|
<div :class="$q.screen.lt.md ? 'column' : 'row'">
|
||||||
<MenuPanelInputField
|
<MenuPanelInputField
|
||||||
v-model="employee_profile.email"
|
v-model="employee_store.employee.email"
|
||||||
class="col"
|
class="col"
|
||||||
:is-editing="is_editing"
|
:is-editing="is_editing"
|
||||||
:label-string="$t('profile.employee.email')"
|
:label-string="$t('profile.employee.email')"
|
||||||
/>
|
/>
|
||||||
<MenuPanelInputField
|
<MenuPanelInputField
|
||||||
v-model="employee_profile.first_work_day"
|
v-model="employee_store.employee.first_work_day"
|
||||||
readonly
|
readonly
|
||||||
class="col"
|
class="col-auto"
|
||||||
type="date"
|
type="date"
|
||||||
:is-editing="is_editing"
|
:is-editing="is_editing"
|
||||||
:label-string="$t('profile.employee.hired_date')"
|
:label-string="$t('profile.employee.hired_date')"
|
||||||
|
|
@ -87,6 +99,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
v-if="CAN_APPROVE_PAY_PERIODS.includes(auth_store.user?.role ?? 'GUEST')"
|
||||||
class="absolute-bottom"
|
class="absolute-bottom"
|
||||||
:class="$q.screen.lt.md ? 'column' : 'row'"
|
:class="$q.screen.lt.md ? 'column' : 'row'"
|
||||||
>
|
>
|
||||||
|
|
@ -94,20 +107,18 @@
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="is_editing"
|
v-if="is_editing"
|
||||||
push
|
push
|
||||||
size="sm"
|
|
||||||
color="negative"
|
color="negative"
|
||||||
type="reset"
|
type="reset"
|
||||||
icon="cancel"
|
icon="cancel"
|
||||||
class="q-ma-sm"
|
class="q-ma-sm q-py-xs"
|
||||||
:label="$t('shared.label.cancel')"
|
:label="$t('shared.label.cancel')"
|
||||||
/>
|
/>
|
||||||
<q-btn
|
<q-btn
|
||||||
push
|
push
|
||||||
size="sm"
|
color="accent"
|
||||||
color="primary"
|
|
||||||
type="submit"
|
type="submit"
|
||||||
:icon="is_editing ? 'save_alt' : 'create'"
|
:icon="is_editing ? 'save_alt' : 'create'"
|
||||||
class="q-ma-sm"
|
class="q-ma-sm q-py-xs"
|
||||||
:label="is_editing ? $t('shared.label.save') : $t('shared.label.update')"
|
:label="is_editing ? $t('shared.label.save') : $t('shared.label.update')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -2,35 +2,36 @@
|
||||||
setup
|
setup
|
||||||
lang="ts"
|
lang="ts"
|
||||||
>
|
>
|
||||||
|
import MenuPanelInputField from 'src/modules/profile/components/shared/menu-panel-input-field.vue';
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { deepEqual } from 'src/utils/deep-equal';
|
import { deepEqual } from 'src/utils/deep-equal';
|
||||||
import MenuPanelInputField from 'src/modules/profile/components/shared/menu-panel-input-field.vue';
|
|
||||||
import type { EmployeeProfile } from 'src/modules/employee-list/models/employee-profile.models';
|
|
||||||
import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
|
import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
|
||||||
|
import { useEmployeeStore } from 'src/stores/employee-store';
|
||||||
|
import type { EmployeeProfile } from 'src/modules/employee-list/models/employee-profile.models';
|
||||||
|
|
||||||
const employee_profile = defineModel<EmployeeProfile>({required: true});
|
const employee_store = useEmployeeStore();
|
||||||
|
|
||||||
const is_editing = ref<boolean>(false);
|
const is_editing = ref<boolean>(false);
|
||||||
|
const initial_info = ref<EmployeeProfile>(unwrapAndClone(employee_store.employee));
|
||||||
let initial_info: EmployeeProfile = unwrapAndClone(employee_profile.value);
|
|
||||||
|
|
||||||
const onSubmit = () => {
|
const onSubmit = () => {
|
||||||
if (!is_editing.value) {
|
if (!is_editing.value) {
|
||||||
is_editing.value = true;
|
is_editing.value = true;
|
||||||
|
initial_info.value = unwrapAndClone(employee_store.employee);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
is_editing.value = false;
|
is_editing.value = false;
|
||||||
initial_info = unwrapAndClone(employee_profile.value); // update initial value for future possible resets
|
initial_info.value = unwrapAndClone(employee_store.employee); // update initial value for future possible resets
|
||||||
|
|
||||||
if (!deepEqual(employee_profile.value, initial_info)) {
|
if (!deepEqual(employee_store.employee, initial_info)) {
|
||||||
// save the new data here
|
// save the new data here
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onReset = () => {
|
const onReset = () => {
|
||||||
employee_profile.value = unwrapAndClone(initial_info);
|
employee_store.employee = unwrapAndClone(initial_info.value);
|
||||||
is_editing.value = false;
|
is_editing.value = false;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -43,14 +44,14 @@
|
||||||
>
|
>
|
||||||
<div :class="$q.screen.lt.md ? 'column' : 'row'">
|
<div :class="$q.screen.lt.md ? 'column' : 'row'">
|
||||||
<MenuPanelInputField
|
<MenuPanelInputField
|
||||||
v-model="employee_profile.first_name"
|
v-model="employee_store.employee.first_name"
|
||||||
type="text"
|
type="text"
|
||||||
class="col"
|
class="col"
|
||||||
:is-editing="is_editing"
|
:is-editing="is_editing"
|
||||||
:label-string="$t('profile.personal.first_name')"
|
:label-string="$t('profile.personal.first_name')"
|
||||||
/>
|
/>
|
||||||
<MenuPanelInputField
|
<MenuPanelInputField
|
||||||
v-model="employee_profile.last_name"
|
v-model="employee_store.employee.last_name"
|
||||||
class="col"
|
class="col"
|
||||||
type="text"
|
type="text"
|
||||||
:is-editing="is_editing"
|
:is-editing="is_editing"
|
||||||
|
|
@ -60,14 +61,14 @@
|
||||||
|
|
||||||
<div :class="$q.screen.lt.md ? 'column' : 'row'">
|
<div :class="$q.screen.lt.md ? 'column' : 'row'">
|
||||||
<MenuPanelInputField
|
<MenuPanelInputField
|
||||||
v-model="employee_profile.phone_number"
|
v-model="employee_store.employee.phone_number"
|
||||||
class="col"
|
class="col"
|
||||||
type="text"
|
type="text"
|
||||||
:is-editing="is_editing"
|
:is-editing="is_editing"
|
||||||
:label-string="$t('profile.personal.phone_number')"
|
:label-string="$t('profile.personal.phone_number')"
|
||||||
/>
|
/>
|
||||||
<MenuPanelInputField
|
<MenuPanelInputField
|
||||||
v-model="employee_profile.birth_date"
|
v-model="employee_store.employee.birth_date"
|
||||||
class="col"
|
class="col"
|
||||||
mask="#### / ## / ##"
|
mask="#### / ## / ##"
|
||||||
hint="ex: 1970 / 01 / 01"
|
hint="ex: 1970 / 01 / 01"
|
||||||
|
|
@ -78,7 +79,7 @@
|
||||||
|
|
||||||
<div :class="$q.screen.lt.md ? 'column' : 'row'">
|
<div :class="$q.screen.lt.md ? 'column' : 'row'">
|
||||||
<MenuPanelInputField
|
<MenuPanelInputField
|
||||||
v-model="employee_profile.residence"
|
v-model="employee_store.employee.residence"
|
||||||
class="col"
|
class="col"
|
||||||
:is-editing="is_editing"
|
:is-editing="is_editing"
|
||||||
:label-string="$t('profile.personal.address')"
|
:label-string="$t('profile.personal.address')"
|
||||||
|
|
@ -93,21 +94,19 @@
|
||||||
<q-space />
|
<q-space />
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="is_editing"
|
v-if="is_editing"
|
||||||
push
|
flat
|
||||||
size="sm"
|
|
||||||
color="negative"
|
color="negative"
|
||||||
type="reset"
|
type="reset"
|
||||||
icon="cancel"
|
icon="cancel"
|
||||||
class="q-ma-sm"
|
class="q-ma-sm q-py-xs"
|
||||||
:label="$t('timesheet.cancel_button')"
|
:label="$t('timesheet.cancel_button')"
|
||||||
/>
|
/>
|
||||||
<q-btn
|
<q-btn
|
||||||
push
|
push
|
||||||
size="sm"
|
color="accent"
|
||||||
color="primary"
|
|
||||||
type="submit"
|
type="submit"
|
||||||
:icon="is_editing ? 'save_alt' : 'create'"
|
:icon="is_editing ? 'save_alt' : 'create'"
|
||||||
class="q-ma-sm"
|
class="q-ma-sm q-py-xs"
|
||||||
:label="is_editing ? $t('shared.label.save') : $t('shared.label.update')"
|
:label="is_editing ? $t('shared.label.save') : $t('shared.label.update')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
<script setup lang="ts">
|
<script
|
||||||
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
import type { ValidationRule } from 'quasar';
|
import type { ValidationRule } from 'quasar';
|
||||||
|
|
||||||
const model = defineModel<string | number>({ required: true });
|
const model = defineModel<string | number | undefined>({ required: true });
|
||||||
|
|
||||||
const { readonly = false, hint = '' } = defineProps<{
|
const { readonly = false, hint = '' } = defineProps<{
|
||||||
labelString: string;
|
labelString: string;
|
||||||
|
|
@ -23,14 +26,19 @@
|
||||||
:standout="$q.dark.isActive ? 'bg-blue-grey-3' : 'bg-blue-grey-9'"
|
:standout="$q.dark.isActive ? 'bg-blue-grey-3' : 'bg-blue-grey-9'"
|
||||||
debounce="500"
|
debounce="500"
|
||||||
label-color="accent"
|
label-color="accent"
|
||||||
class="q-ma-xs text-uppercase"
|
label-slot
|
||||||
input-class="text-weight-medium text-h6"
|
class="q-ma-xs"
|
||||||
|
input-class="text-weight-light"
|
||||||
|
input-style="font-size: 1.2em"
|
||||||
:hide-hint="hint === ''"
|
:hide-hint="hint === ''"
|
||||||
:hint="isEditing ? hint : ''"
|
:hint="isEditing ? hint : ''"
|
||||||
:mask="mask"
|
:mask="mask"
|
||||||
:readonly="readonly || !isEditing"
|
:readonly="readonly || !isEditing"
|
||||||
:type="type"
|
:type="type"
|
||||||
:label="labelString"
|
|
||||||
:rules="rules"
|
:rules="rules"
|
||||||
/>
|
>
|
||||||
|
<template #label>
|
||||||
|
<span class="text-weight-bolder text-accent text-uppercase">{{ labelString }}</span>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -1,47 +1,40 @@
|
||||||
<script setup lang="ts">
|
<script
|
||||||
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
|
import LanguageSwitch from 'src/modules/shared/components/language-switch.vue';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { Dark } from 'quasar';
|
import { Dark } from 'quasar';
|
||||||
import LanguageSwitch from 'src/modules/shared/components/language-switch.vue';
|
import { useUiStore } from 'src/stores/ui-store';
|
||||||
|
const ui_store = useUiStore();
|
||||||
const initial_dark_mode_value = Dark.isActive;
|
const initial_dark_mode_value = Dark.isActive;
|
||||||
const is_dark_mode = ref<boolean>(initial_dark_mode_value);
|
const is_dark_mode = ref<boolean>(initial_dark_mode_value);
|
||||||
|
|
||||||
const toggle_dark_mode = (value: boolean) => {
|
const toggle_dark_mode = (value: boolean) => {
|
||||||
is_dark_mode.value = value;
|
if (ui_store.user_preferences) ui_store.user_preferences.is_dark_mode = value;
|
||||||
Dark.set(value);
|
Dark.set(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-form class="q-pa-md column fit">
|
<q-form class="q-pa-md column fit">
|
||||||
<div class="col-auto text-uppercase rounded-5" style="line-height: 1em;">{{ $t('profile.preferences.display_options') }}</div>
|
<q-card
|
||||||
<q-card
|
|
||||||
flat
|
flat
|
||||||
class="col-auto column justify-center items-center content-center q-mb-lg q-pa-md"
|
class="col-auto justify-center content-center q-mb-lg q-pa-none"
|
||||||
style="border: solid #AAA 1px;"
|
:style="$q.dark.isActive ? 'background-color: #FFF1;' : 'background-color: #0001;'"
|
||||||
>
|
>
|
||||||
|
<q-card-section class="q-py-none">
|
||||||
|
<span class="text-uppercase text-weight-bold text-accent">{{ $t('profile.preferences.display_options')
|
||||||
|
}}</span>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
<div class="row col">
|
<q-card-section
|
||||||
<q-card flat class="col column q-pa-xs bg-white" style="border: solid 1px var(--q-primary);">
|
horizontal
|
||||||
<div
|
class="flex-center text-uppercase"
|
||||||
class="col-auto column rounded-4 ellipsis"
|
>
|
||||||
style="height: 90px; min-width: 80px; background-color: #DAE0E7;"
|
<span class="q-mx-md text-weight-medium">{{ $t('profile.preferences.light_mode') }}</span>
|
||||||
>
|
<q-toggle
|
||||||
<div class="bg-primary col-1"></div>
|
|
||||||
<div class=" row col">
|
|
||||||
<div class="col-8 q-ma-xs rounded-borders" style="background-color: white;"></div>
|
|
||||||
<div class="col column q-gutter-xs q-py-xs q-pr-xs">
|
|
||||||
<div class="col rounded-borders" style="background-color: white;"></div>
|
|
||||||
<div class="col rounded-borders" style="background-color: white;"></div>
|
|
||||||
<div class="col rounded-borders" style="background-color: white;"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="bg-primary col-1"></div>
|
|
||||||
</div>
|
|
||||||
<span class="col-auto text-subtitle2 text-primary text-center text-uppercase">{{$t('profile.preferences.light_mode')}}</span>
|
|
||||||
</q-card>
|
|
||||||
|
|
||||||
<q-toggle
|
|
||||||
v-model="is_dark_mode"
|
v-model="is_dark_mode"
|
||||||
@update:model-value="value => toggle_dark_mode(value)"
|
@update:model-value="value => toggle_dark_mode(value)"
|
||||||
size="xl"
|
size="xl"
|
||||||
|
|
@ -49,36 +42,26 @@
|
||||||
checked-icon="dark_mode"
|
checked-icon="dark_mode"
|
||||||
unchecked-icon="light_mode"
|
unchecked-icon="light_mode"
|
||||||
/>
|
/>
|
||||||
|
<span class="q-mx-md text-weight-medium">{{ $t('profile.preferences.dark_mode') }}</span>
|
||||||
<q-card flat class="col column q-pa-xs bg-white" style="border: solid 1px var(--q-primary);">
|
</q-card-section>
|
||||||
<div
|
|
||||||
class="col-auto column rounded-4 ellipsis"
|
|
||||||
style="height: 90px; min-width: 80px; background-color: #0f1114;"
|
|
||||||
>
|
|
||||||
<div class="bg-primary col-1"></div>
|
|
||||||
<div class=" row col">
|
|
||||||
<div class="col-8 q-ma-xs rounded-borders" style="background-color: #333;"></div>
|
|
||||||
<div class="col column q-gutter-xs q-py-xs q-pr-xs">
|
|
||||||
<div class="col rounded-borders" style="background-color: #333;"></div>
|
|
||||||
<div class="col rounded-borders" style="background-color: #333;"></div>
|
|
||||||
<div class="col rounded-borders" style="background-color: #333;"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="bg-primary col-1"></div>
|
|
||||||
</div>
|
|
||||||
<span class="col-auto text-subtitle2 text-primary text-center text-uppercase">{{$t('profile.preferences.dark_mode')}}</span>
|
|
||||||
</q-card>
|
|
||||||
</div>
|
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
||||||
<div class="col-auto text-uppercase rounded-5" style="line-height: 1em;">{{ $t('profile.preferences.language_options') }}</div>
|
<q-card
|
||||||
<q-card
|
|
||||||
flat
|
flat
|
||||||
class="col-auto column justify-center items-center content-center q-mb-lg q-pa-md"
|
class="col-auto justify-center content-center q-mb-lg q-pa-none"
|
||||||
style="border: solid #AAA 1px;"
|
:style="$q.dark.isActive ? 'background-color: #FFF1;' : 'background-color: #0001;'"
|
||||||
>
|
>
|
||||||
|
<q-card-section class="q-py-none">
|
||||||
<LanguageSwitch class="q-mr-xs col-auto" />
|
<span class="text-uppercase text-weight-bold text-accent">{{ $t('profile.preferences.language_options')
|
||||||
|
}}</span>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-section
|
||||||
|
horizontal
|
||||||
|
class="flex-center"
|
||||||
|
>
|
||||||
|
<LanguageSwitch class="col-auto" />
|
||||||
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-form>
|
</q-form>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
<script setup lang="ts">
|
<script
|
||||||
const model = defineModel<string>();
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
|
const model = defineModel<string | number | {label: string, value: unknown} | undefined>();
|
||||||
|
|
||||||
const { readonly = false, localizeOptions = false } = defineProps<{
|
const { readonly = false, localizeOptions = false } = defineProps<{
|
||||||
options: { label: string, value: string }[];
|
options: { label: string, value: string | number }[];
|
||||||
labelString: string;
|
labelString: string;
|
||||||
isEditing: boolean;
|
isEditing: boolean;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
|
|
@ -12,20 +15,29 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-select
|
<q-select
|
||||||
v-model="model"
|
v-model="model"
|
||||||
dense
|
dense
|
||||||
:stack-label="!isEditing"
|
:stack-label="!isEditing"
|
||||||
:standout="$q.dark.isActive ? 'bg-blue-grey-3' : 'bg-blue-grey-9'"
|
:standout="$q.dark.isActive ? 'bg-blue-grey-3' : 'bg-blue-grey-9'"
|
||||||
label-color="accent"
|
label-color="accent"
|
||||||
class="q-ma-xs text-h6 text-uppercase"
|
class="q-ma-xs"
|
||||||
popup-content-class="text-weight-medium text-h6"
|
popup-content-class="text-weight-medium text-h6 rounded-5"
|
||||||
input-class="text-weight-medium"
|
options-selected-class="bg-accent text-white"
|
||||||
|
:menu-offset="[0, 10]"
|
||||||
:options="options"
|
:options="options"
|
||||||
:readonly="readonly || !isEditing"
|
:readonly="readonly || !isEditing"
|
||||||
:hide-dropdown-icon="!isEditing"
|
:hide-dropdown-icon="!isEditing"
|
||||||
:label="labelString"
|
:label="labelString"
|
||||||
:option-label="opt => localizeOptions ? $t(opt) : opt"
|
:option-label="opt => localizeOptions ? $t(opt.label) : opt.label ?? opt"
|
||||||
hint=''
|
hint=''
|
||||||
/>
|
>
|
||||||
|
<template #label>
|
||||||
|
<span class="text-weight-bolder text-accent text-uppercase">{{ labelString }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #selected-item="scope">
|
||||||
|
<span class="text-weight-light" style="font-size: 1.2em;">{{ scope.opt.label }}</span>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script
|
||||||
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import MenuHeader from 'src/modules/profile/components/shared/menu-header.vue';
|
import MenuHeader from 'src/modules/profile/components/shared/menu-header.vue';
|
||||||
|
|
||||||
|
|
@ -12,39 +15,32 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
:class="$q.screen.lt.md ? 'column no-wrap' : 'row'"
|
:class="$q.screen.lt.md ? 'column no-wrap flex-center' : 'row'"
|
||||||
:style="$q.screen.lt.md ? 'width: 90vw;' : 'width: 40vw;'"
|
:style="$q.screen.lt.md ? 'width: 90vw;' : 'width: 40vw;'"
|
||||||
>
|
>
|
||||||
<MenuHeader
|
<MenuHeader
|
||||||
:user-first-name="firstName"
|
:user-first-name="firstName"
|
||||||
:user-last-name="lastName"
|
:user-last-name="lastName"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<q-card
|
||||||
class="col-3 no-wrap"
|
class="col-auto q-pa-xs"
|
||||||
:class="$q.screen.lt.md ? '' : 'column'"
|
:class="$q.screen.lt.md ? 'q-mb-sm' : 'q-mr-sm'"
|
||||||
>
|
>
|
||||||
<q-card
|
<q-tabs
|
||||||
class="col-auto q-pa-xs"
|
v-model="current_menu"
|
||||||
:class="$q.screen.lt.md ? 'q-mb-sm' : 'q-mr-sm'"
|
:vertical="$q.screen.gt.sm"
|
||||||
|
active-color="accent"
|
||||||
|
indicator-color="accent"
|
||||||
>
|
>
|
||||||
<q-tabs
|
<slot name="tabs"></slot>
|
||||||
v-model="current_menu"
|
</q-tabs>
|
||||||
:vertical="$q.screen.gt.sm"
|
</q-card>
|
||||||
dense
|
|
||||||
active-color="accent"
|
|
||||||
indicator-color="accent"
|
|
||||||
>
|
|
||||||
<slot name="tabs"></slot>
|
|
||||||
</q-tabs>
|
|
||||||
</q-card>
|
|
||||||
<div class="col"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<q-card
|
<q-card
|
||||||
class="col"
|
class="col"
|
||||||
:class="$q.screen.lt.md ? '' : 'q-ml-sm'"
|
:class="$q.screen.lt.md ? 'full-width' : 'q-ml-sm'"
|
||||||
>
|
>
|
||||||
<q-tab-panels
|
<q-tab-panels
|
||||||
v-model="current_menu"
|
v-model="current_menu"
|
||||||
|
|
|
||||||
21
src/modules/profile/models/preferences.models.ts
Normal file
21
src/modules/profile/models/preferences.models.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import type { MessageLanguages } from "src/boot/i18n";
|
||||||
|
|
||||||
|
export class Preferences {
|
||||||
|
id: number;
|
||||||
|
notifications: boolean;
|
||||||
|
is_dark_mode: boolean | null;
|
||||||
|
display_language: MessageLanguages;
|
||||||
|
is_lefty_mode: boolean;
|
||||||
|
is_employee_list_grid: boolean;
|
||||||
|
is_timesheet_approval_grid: boolean;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.id = -1;
|
||||||
|
this.notifications = true;
|
||||||
|
this.is_dark_mode = null;
|
||||||
|
this.display_language = 'fr-FR';
|
||||||
|
this.is_lefty_mode = false;
|
||||||
|
this.is_employee_list_grid = true;
|
||||||
|
this.is_timesheet_approval_grid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/modules/profile/services/profile-service.ts
Normal file
15
src/modules/profile/services/profile-service.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { api } from "src/boot/axios";
|
||||||
|
import type { Preferences } from "src/modules/profile/models/preferences.models";
|
||||||
|
import type { BackendResponse } from "src/modules/shared/models/backend-response.models";
|
||||||
|
|
||||||
|
export const ProfileService = {
|
||||||
|
getUserPreferences: async (): Promise<BackendResponse<Preferences>> => {
|
||||||
|
const response = await api.get<BackendResponse<Preferences>>(`/preferences`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateUserPreferences: async (new_preferences: Preferences): Promise<BackendResponse<Preferences>> => {
|
||||||
|
const response = await api.patch<BackendResponse<Preferences>>(`/preferences/update`, new_preferences);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -1,36 +1,45 @@
|
||||||
<script lang="ts" setup>
|
<script
|
||||||
import { useI18n } from 'vue-i18n';
|
lang="ts"
|
||||||
|
setup
|
||||||
|
>
|
||||||
|
import { useUiStore } from 'src/stores/ui-store';
|
||||||
|
import type { MessageLanguages } from 'src/boot/i18n';
|
||||||
|
|
||||||
const { locale } = useI18n();
|
const ui_store = useUiStore();
|
||||||
const localeOptions = [
|
|
||||||
{ value: 'en-CA', label: 'English' },
|
const setDisplayLanguage = (locale: MessageLanguages) => {
|
||||||
{ value: 'fr-FR', label: 'Francais' },
|
if (ui_store.user_preferences !== undefined) {
|
||||||
];
|
ui_store.user_preferences.display_language = locale;
|
||||||
|
console.log('triggered language change: ', ui_store.user_preferences.display_language);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<q-list
|
||||||
<q-list dense class="row">
|
dense
|
||||||
<q-item v-for="option in localeOptions"
|
class="row full-width"
|
||||||
:key="option.value"
|
>
|
||||||
tag="label"
|
<q-item
|
||||||
v-ripple
|
v-for="locale in $i18n.availableLocales"
|
||||||
>
|
:key="locale"
|
||||||
<q-item-section avatar>
|
clickable
|
||||||
<q-radio v-model="locale" :val="option.value" />
|
dense
|
||||||
</q-item-section>
|
v-ripple
|
||||||
|
class="col rounded-5 q-ma-sm shadow-1 "
|
||||||
<q-item-section>
|
:class="locale === $i18n.locale ? 'bg-accent text-white text-weight-bolder' : ''"
|
||||||
<q-item-label>{{ option.label }}</q-item-label>
|
@click="setDisplayLanguage(locale as MessageLanguages)"
|
||||||
</q-item-section>
|
>
|
||||||
</q-item>
|
<q-item-section class="text-uppercase justify-center">
|
||||||
</q-list>
|
<q-item-label> {{ $t(`profile.preferences.${locale}`) }}</q-item-label>
|
||||||
</div>
|
</q-item-section>
|
||||||
<!-- <q-btn-dropdown push color="primary" :label="$t('shared.languageLabel')" icon="language">
|
<q-item-section side>
|
||||||
<q-list>
|
<q-icon
|
||||||
<q-item clickable v-close-popup v-for="option in localeOptions" :key="option.value" @click="locale = option.value">
|
v-if="locale === $i18n.locale"
|
||||||
<q-item-section>{{ option.label }}</q-item-section>
|
name="check"
|
||||||
</q-item>
|
color="white"
|
||||||
</q-list>
|
/>
|
||||||
</q-btn-dropdown> -->
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
</template>
|
</template>
|
||||||
5
src/modules/shared/models/backend-response.models.ts
Normal file
5
src/modules/shared/models/backend-response.models.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
export interface BackendResponse<T> {
|
||||||
|
success: boolean;
|
||||||
|
data?: T | undefined;
|
||||||
|
error?: string | undefined;
|
||||||
|
};
|
||||||
|
|
@ -4,19 +4,34 @@
|
||||||
>
|
>
|
||||||
import MenuEmployee from 'src/modules/profile/components/employee/menu-employee.vue';
|
import MenuEmployee from 'src/modules/profile/components/employee/menu-employee.vue';
|
||||||
import { useAuthStore } from 'src/stores/auth-store';
|
import { useAuthStore } from 'src/stores/auth-store';
|
||||||
// import type { EmployeeProfile } from 'src/modules/employee-list/models/employee-profile.models';
|
import { useEmployeeStore } from 'src/stores/employee-store';
|
||||||
|
import { onMounted } from 'vue';
|
||||||
|
|
||||||
const auth_store = useAuthStore();
|
const auth_store = useAuthStore();
|
||||||
|
const employee_store = useEmployeeStore();
|
||||||
const employee_roles = ['SUPERVISOR', 'EMPLOYEE', 'ADMIN', 'HR', 'ACCOUNTING'];
|
const employee_roles = ['SUPERVISOR', 'EMPLOYEE', 'ADMIN', 'HR', 'ACCOUNTING'];
|
||||||
|
|
||||||
// const employee_profile = defineModel<EmployeeProfile>({ required: true });
|
const is_employee = employee_roles.includes(auth_store.user?.role.toUpperCase() ?? 'GUEST');
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (is_employee) {
|
||||||
|
await employee_store.getEmployeeDetails();
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-page class="bg-secondary column items-center justify-center">
|
<q-page class="bg-secondary column items-center justify-center">
|
||||||
<MenuEmployee
|
<MenuEmployee
|
||||||
v-if="employee_roles.includes(auth_store.user?.role.toUpperCase() ?? 'GUEST')"
|
v-if="employee_roles.includes(auth_store.user?.role.toUpperCase() ?? 'GUEST')"
|
||||||
class="col-auto"
|
class="col-sm-12 col-md-10 col-lg-9 col-xl-8"
|
||||||
/>
|
/>
|
||||||
</q-page>
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.q-field--standout.q-field--readonly .q-field__control:before) {
|
||||||
|
border: none !important;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -3,8 +3,9 @@ export enum RouteNames {
|
||||||
LOGIN = 'login',
|
LOGIN = 'login',
|
||||||
LOGIN_SUCCESS = 'login-success',
|
LOGIN_SUCCESS = 'login-success',
|
||||||
DASHBOARD = 'dashboard',
|
DASHBOARD = 'dashboard',
|
||||||
TIMESHEET_APPROVALS = 'timesheet-approvals',
|
TIMESHEET_APPROVALS = 'timesheets_approval',
|
||||||
EMPLOYEE_LIST = 'employee-list',
|
EMPLOYEE_LIST = 'employee_list',
|
||||||
PROFILE = 'user/profile',
|
EMPLOYEE_MANAGEMENT = 'employee_management',
|
||||||
TIMESHEET = 'timesheet'
|
PROFILE = 'personal_profile',
|
||||||
|
TIMESHEET = 'timesheets'
|
||||||
}
|
}
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { EmployeeListService } from "src/modules/employee-list/services/employee-list-service";
|
import { EmployeeListService } from "src/modules/employee-list/services/employee-list-service";
|
||||||
import { default_employee_profile, type EmployeeProfile } from "src/modules/employee-list/models/employee-profile.models";
|
import { EmployeeProfile } from "src/modules/employee-list/models/employee-profile.models";
|
||||||
|
|
||||||
export const useEmployeeStore = defineStore('employee', () => {
|
export const useEmployeeStore = defineStore('employee', () => {
|
||||||
const employee = ref<EmployeeProfile>( default_employee_profile );
|
const employee = ref<EmployeeProfile>(new EmployeeProfile);
|
||||||
const employee_list = ref<EmployeeProfile[]>([]);
|
const employee_list = ref<EmployeeProfile[]>([]);
|
||||||
const isShowingEmployeeAddModifyWindow = ref<boolean>(false);
|
const isShowingEmployeeAddModifyWindow = ref<boolean>(false);
|
||||||
const isLoadingEmployeeProfile = ref(false);
|
const is_loading = ref(false);
|
||||||
const isLoadingEmployeeList = ref(false);
|
const isLoadingEmployeeList = ref(false);
|
||||||
|
|
||||||
const getEmployeeList = async () => {
|
const getEmployeeList = async () => {
|
||||||
|
|
@ -22,19 +22,24 @@ export const useEmployeeStore = defineStore('employee', () => {
|
||||||
isLoadingEmployeeList.value = false;
|
isLoadingEmployeeList.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getEmployeeDetails = async (email: string) => {
|
const getEmployeeDetails = async (email?: string) => {
|
||||||
isLoadingEmployeeProfile.value = true;
|
is_loading.value = true;
|
||||||
try {
|
try {
|
||||||
const response = await EmployeeListService.getEmployeeDetails(email);
|
if (email === undefined) {
|
||||||
employee.value = response;
|
const response = await EmployeeListService.getEmployeeDetails();
|
||||||
|
employee.value = response;
|
||||||
|
} else{
|
||||||
|
const response = await EmployeeListService.getEmployeeDetailsWithEmployeeEmail(email);
|
||||||
|
employee.value = response;
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('There was an error retrieving employee info: ', error);
|
console.error('There was an error retrieving employee info: ', error);
|
||||||
//TODO: trigger an alert window with an error message here!
|
//TODO: trigger an alert window with an error message here!
|
||||||
}
|
}
|
||||||
isLoadingEmployeeProfile.value = false;
|
is_loading.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { employee, employee_list, isShowingEmployeeAddModifyWindow, isLoadingEmployeeList, isLoadingEmployeeProfile, getEmployeeList, getEmployeeDetails };
|
return { employee, employee_list, isShowingEmployeeAddModifyWindow, isLoadingEmployeeList, is_loading, getEmployeeList, getEmployeeDetails };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,81 @@
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { useQuasar } from 'quasar';
|
import { Notify, LocalStorage, useQuasar, Dark } from 'quasar';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
import { Preferences } from 'src/modules/profile/models/preferences.models';
|
||||||
|
import { ProfileService } from 'src/modules/profile/services/profile-service';
|
||||||
|
import { useI18n, type ComposerTranslation } from 'vue-i18n';
|
||||||
|
|
||||||
|
|
||||||
export const useUiStore = defineStore('ui', () => {
|
export const useUiStore = defineStore('ui', () => {
|
||||||
const q = useQuasar();
|
const q = useQuasar();
|
||||||
const is_left_drawer_open = ref(true);
|
const { locale } = useI18n();
|
||||||
|
const is_left_drawer_open = ref(false);
|
||||||
const focus_next_component = ref(false);
|
const focus_next_component = ref(false);
|
||||||
const is_mobile_mode = computed(() => q.screen.lt.md);
|
const is_mobile_mode = computed(() => q.screen.lt.md);
|
||||||
|
const user_preferences = ref<Preferences>(new Preferences);
|
||||||
|
|
||||||
|
|
||||||
const toggleRightDrawer = () => {
|
const toggleRightDrawer = () => {
|
||||||
is_left_drawer_open.value = !is_left_drawer_open.value;
|
is_left_drawer_open.value = !is_left_drawer_open.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUserPreferences = async () => {
|
||||||
|
try {
|
||||||
|
const local_user_preferences = LocalStorage.getItem<Preferences>('user_preferences');
|
||||||
|
|
||||||
|
if (local_user_preferences !== null) {
|
||||||
|
if (local_user_preferences.id !== -1) {
|
||||||
|
Object.assign(user_preferences.value, local_user_preferences);
|
||||||
|
setPreferences();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await ProfileService.getUserPreferences();
|
||||||
|
|
||||||
|
if (response.success && response.data) {
|
||||||
|
LocalStorage.setItem('user_preferences', response.data);
|
||||||
|
Object.assign(user_preferences.value, response.data);
|
||||||
|
setPreferences();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
user_preferences.value = new Preferences;
|
||||||
|
console.error('Could not retrieve user preferences: ', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateUserPreferences = async (t: ComposerTranslation) => {
|
||||||
|
try {
|
||||||
|
if (user_preferences.value.id === -1) return;
|
||||||
|
|
||||||
|
const response = await ProfileService.updateUserPreferences(user_preferences.value);
|
||||||
|
if (response.success && response.data) {
|
||||||
|
Object.assign(user_preferences.value, response.data);
|
||||||
|
LocalStorage.setItem('user_preferences', response.data);
|
||||||
|
setPreferences();
|
||||||
|
Notify.create({ message: t('profile.preferences.update_successful'), color: 'accent' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Could not update user preferences: ', error);
|
||||||
|
}
|
||||||
|
Notify.create({ message: t('profile.preferences.update_failed'), color: 'negative' })
|
||||||
|
};
|
||||||
|
|
||||||
|
const setPreferences = () => {
|
||||||
|
if (user_preferences.value !== undefined) {
|
||||||
|
Dark.set(user_preferences.value.is_dark_mode ?? 'auto');
|
||||||
|
locale.value = user_preferences.value.display_language;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
is_mobile_mode,
|
is_mobile_mode,
|
||||||
focus_next_component,
|
focus_next_component,
|
||||||
is_left_drawer_open,
|
is_left_drawer_open,
|
||||||
toggleRightDrawer
|
user_preferences,
|
||||||
|
toggleRightDrawer,
|
||||||
|
getUserPreferences,
|
||||||
|
updateUserPreferences,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user