refactor(management): replace standard form with carousel to separate info and access into two panels

This commit is contained in:
Nicolas Drolet 2025-12-02 13:38:03 -05:00
parent a1b6748d95
commit d8a1a87e98
6 changed files with 289 additions and 158 deletions

View File

@ -12,6 +12,29 @@ export default {
},
},
employee_management: {
module_access: {
dashboard: "Dashboard",
employee_list: "employee list",
employee_management: "employee management",
personal_profile: "profile",
timesheets: "timesheets",
timesheets_approval: "timesheet approval",
user_access: "module access",
presets: "access presets",
preset_admin: "administrator",
preset_employee: "employee",
uncheck_all: "remove all",
admin_description: "Check all modules",
employee_description: "Only check modules that are relevant to standard employees with no management access",
none_description: "Uncheck all modules",
},
add_employee: "Add employee",
modify_employee: "Modify employee",
access_label: "access",
details_label: "details",
},
login: {
page_header: "account login",
email: "e-mail",
@ -59,20 +82,6 @@ export default {
supervisor: "supervisor",
hired_date: "hiring date",
bankroll_id: "payroll ID",
module_access: {
dashboard: "Dashboard",
employee_list: "employee list",
employee_management: "employee management",
personal_profile: "profile",
timesheets: "timesheets",
timesheets_approval: "timesheet approval",
user_access: "module access",
presets: "access presets",
preset_admin: "admin",
preset_employee: "employee",
uncheck_all: "remove all",
},
},
preferences: {
tab_title: "preferences",

View File

@ -12,6 +12,29 @@ export default {
},
},
employee_management: {
module_access: {
dashboard: "Dashboard",
employee_list: "employee list",
employee_management: "employee management",
personal_profile: "profile",
timesheets: "timesheets",
timesheets_approval: "timesheet approval",
user_access: "module access",
presets: "access presets",
preset_admin: "administrateur",
preset_employee: "employé",
uncheck_all: "Tout enlever",
admin_description: "Selectionner tous les modules",
employee_description: "Selectionner seulement les modules qui sont pertinents aux employés sans accès spéciaux",
none_description: "Enlever tous les accès",
},
add_employee: "Ajouter employé",
modify_employee: "Modifier employé",
access_label: "accès",
details_label: "détails",
},
login: {
page_header: "connexion au compte",
email: "courriel",
@ -59,19 +82,6 @@ export default {
supervisor: "nom du superviseur",
hired_date: "date d'embauche",
bankroll_id: "identifiant de paie",
module_access: {
dashboard: "accueil",
employee_list: "liste employés",
employee_management: "gestion employés",
personal_profile: "profil",
timesheets: "feuilles de temps",
timesheets_approval: "valider feuilles de temps",
user_access: "accès aux modules",
presets: "accès prédéfinis",
preset_admin: "administrateur",
preset_employee: "employé",
uncheck_all: "aucun accès",
},
},
preferences: {
tab_title: "préférences",

View File

@ -0,0 +1,123 @@
<script
setup
lang="ts"
>
import { ref } from 'vue';
import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
import { useEmployeeStore } from 'src/stores/employee-store';
import { employee_access_options, type ModuleAccessPreset, type ModuleAccessName, employee_access_presets } from 'src/modules/employee-list/models/employee-profile.models';
const employee_store = useEmployeeStore();
const preset_preview = ref<ModuleAccessPreset>();
const toggleInSelected = (value: ModuleAccessName) => {
const i = employee_store.employee.user_module_access.indexOf(value);
if (i === -1) employee_store.employee.user_module_access.push(value);
else employee_store.employee.user_module_access.splice(i, 1);
}
const applyAccessPreset = (preset: ModuleAccessPreset) => {
employee_store.employee.user_module_access = unwrapAndClone(employee_access_presets[preset]);
}
const getPreviewBackgroundColor = (name: ModuleAccessName) => {
if (employee_access_presets[preset_preview.value!].includes(name)) {
if (!employee_store.employee.user_module_access.includes(name)) return 'bg-info text-white';
return 'bg-accent text-white';
}
if (employee_store.employee.user_module_access.includes(name)) return 'bg-negative text-white';
return 'bg-dark';
};
const getBackgroundColor = (name: ModuleAccessName) => {
if (employee_store.employee.user_module_access.includes(name)) return 'bg-accent text-white';
return 'bg-dark';
};
</script>
<template>
<div class="row full-width">
<div class="column col-3 q-px-md">
<q-item
clickable
class="shadow-2 rounded-5 q-ma-sm bg-dark"
@click="applyAccessPreset('admin')"
@mouseover="preset_preview = 'admin'"
@mouseleave="preset_preview = undefined"
>
<q-item-section>
<q-item-label class="text-uppercase text-weight-bold">
{{ $t('employee_management.module_access.preset_admin') }}
</q-item-label>
<q-item-label caption>
{{ $t('employee_management.module_access.admin_description') }}
</q-item-label>
</q-item-section>
</q-item>
<q-item
clickable
class="shadow-2 rounded-5 q-ma-sm bg-dark"
@click="applyAccessPreset('employee')"
@mouseover="preset_preview = 'employee'"
@mouseleave="preset_preview = undefined"
>
<q-item-section>
<q-item-label class="text-uppercase text-weight-bold">
{{ $t('employee_management.module_access.preset_employee') }}
</q-item-label>
<q-item-label caption>
{{ $t('employee_management.module_access.employee_description') }}
</q-item-label>
</q-item-section>
</q-item>
<q-item
clickable
class="shadow-2 rounded-5 q-ma-sm bg-dark"
@click="applyAccessPreset('none')"
@mouseover="preset_preview = 'none'"
@mouseleave="preset_preview = undefined"
>
<q-item-section>
<q-item-label class="text-uppercase text-weight-bold">
{{ $t('employee_management.module_access.uncheck_all') }}
</q-item-label>
<q-item-label caption>
{{ $t('employee_management.module_access.none_description') }}
</q-item-label>
</q-item-section>
</q-item>
</div>
<div class="row col items-start content-start">
<div
v-for="option in employee_access_options"
:key="option.label"
class="col-lg-6 col-sm-12 col-xs-12 q-pa-xs"
>
<div
class="row full-width cursor-pointer flex-center q-pa-sm rounded-5 no-wrap shadow-5"
:class="preset_preview !== undefined ? getPreviewBackgroundColor(option.value) : getBackgroundColor(option.value)"
@click="toggleInSelected(option.value)"
>
<span class="text-uppercase text-weight-bold">
{{ $t('employee_management.module_access.' + option.value) }}
</span>
<q-space />
<q-icon
:name="employee_store.employee.user_module_access.includes(option.value) ? 'check' : ''"
size="sm"
/>
</div>
</div>
</div>
</div>
</template>

View File

@ -3,20 +3,8 @@
lang="ts"
>
import { useEmployeeStore } from 'src/stores/employee-store';
import { employee_access_options, type ModuleAccessPreset, type ModuleAccessName, employee_access_presets } from 'src/modules/employee-list/models/employee-profile.models';
import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
const employee_store = useEmployeeStore();
const toggleInSelected = (value: ModuleAccessName) => {
const i = employee_store.employee.user_module_access.indexOf(value);
if (i === -1) employee_store.employee.user_module_access.push(value);
else employee_store.employee.user_module_access.splice(i, 1);
}
const applyAccessPreset = (preset: ModuleAccessPreset) => {
employee_store.employee.user_module_access = unwrapAndClone(employee_access_presets[preset]);
}
</script>
<template>
@ -28,14 +16,16 @@ import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
>
<q-input
v-model="employee_store.employee.first_name"
standout="bg-accent"
dense
color="accent"
stack-label
label-slot
class="col q-ma-xs"
class="col q-mx-md"
>
<template #label>
<span class="text-weight-bolder text-uppercase" style="font-size: 0.85em;">
<span
class="text-weight-bolder text-uppercase"
style="font-size: 0.85em;"
>
{{ $t('profile.personal.first_name') }}
</span>
</template>
@ -43,14 +33,16 @@ import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
<q-input
v-model="employee_store.employee.last_name"
standout="bg-accent"
dense
color="accent"
stack-label
label-slot
class="col q-ma-xs"
class="col q-mx-md"
>
<template #label>
<span class="text-weight-bolder text-uppercase" style="font-size: 0.85em;">
<span
class="text-weight-bolder text-uppercase"
style="font-size: 0.85em;"
>
{{ $t('profile.personal.last_name') }}
</span>
</template>
@ -63,14 +55,16 @@ import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
>
<q-input
v-model="employee_store.employee.email"
standout="bg-accent"
dense
color="accent"
stack-label
label-slot
class="col q-ma-xs"
class="col q-mx-md"
>
<template #label>
<span class="text-weight-bolder text-uppercase" style="font-size: 0.85em;">
<span
class="text-weight-bolder text-uppercase"
style="font-size: 0.85em;"
>
{{ $t('profile.employee.email') }}
</span>
</template>
@ -78,14 +72,16 @@ import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
<q-input
v-model="employee_store.employee.phone_number"
standout="bg-accent"
dense
color="accent"
stack-label
label-slot
class="col q-ma-xs"
class="col q-mx-md"
>
<template #label>
<span class="text-weight-bolder text-uppercase" style="font-size: 0.85em;">
<span
class="text-weight-bolder text-uppercase"
style="font-size: 0.85em;"
>
{{ $t('profile.personal.phone_number') }}
</span>
</template>
@ -98,14 +94,16 @@ import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
>
<q-input
v-model="employee_store.employee.job_title"
standout="bg-accent"
dense
color="accent"
stack-label
label-slot
class="col q-ma-xs"
class="col q-mx-md"
>
<template #label>
<span class="text-weight-bolder text-uppercase" style="font-size: 0.85em;">
<span
class="text-weight-bolder text-uppercase"
style="font-size: 0.85em;"
>
{{ $t('profile.employee.job_title') }}
</span>
</template>
@ -113,14 +111,16 @@ import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
<q-input
v-model="employee_store.employee.company_name"
standout="bg-accent"
dense
color="accent"
stack-label
label-slot
class="col q-ma-xs"
class="col q-mx-md"
>
<template #label>
<span class="text-weight-bolder text-uppercase" style="font-size: 0.85em;">
<span
class="text-weight-bolder text-uppercase"
style="font-size: 0.85em;"
>
{{ $t('profile.employee.company') }}
</span>
</template>
@ -133,14 +133,16 @@ import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
>
<q-input
v-model="employee_store.employee.supervisor_full_name"
standout="bg-accent"
dense
color="accent"
stack-label
label-slot
class="col q-ma-xs"
class="col q-mx-md"
>
<template #label>
<span class="text-weight-bolder text-uppercase" style="font-size: 0.85em;">
<span
class="text-weight-bolder text-uppercase"
style="font-size: 0.85em;"
>
{{ $t('profile.employee.supervisor') }}
</span>
</template>
@ -148,97 +150,21 @@ import { unwrapAndClone } from 'src/utils/unwrap-and-clone';
<q-input
v-model="employee_store.employee.phone_number"
standout="bg-accent"
dense
color="accent"
stack-label
label-slot
class="col q-ma-xs"
class="col q-mx-md"
>
<template #label>
<span class="text-weight-bolder text-uppercase" style="font-size: 0.85em;">
<span
class="text-weight-bolder text-uppercase"
style="font-size: 0.85em;"
>
{{ $t('profile.employee.bankroll_id') }}
</span>
</template>
</q-input>
</div>
<q-field
v-model="employee_store.employee.user_module_access"
dense
stack-label
label-slot
standout="transparent"
class="col-12 q-ma-sm"
>
<template #label>
<span class="text-weight-bolder text-uppercase" style="font-size: 0.85em;">
{{ $t('profile.employee.module_access.user_access') }}
</span>
</template>
<div class="col-12 column full-width">
<div class="row col-auto flex-center">
<q-btn
dense
flat
color="accent"
:label="$t('profile.employee.module_access.preset_admin')"
class="col-3 q-mx-sm q-pa-xs text-weight-bold"
@click="applyAccessPreset('admin')"
/>
<q-btn
dense
flat
color="accent"
:label="$t('profile.employee.module_access.preset_employee')"
class="col-3 q-mx-sm q-pa-xs text-weight-bold"
@click="applyAccessPreset('employee')"
/>
<q-btn
dense
flat
color="negative"
:label="$t('profile.employee.module_access.uncheck_all')"
class="col-3 q-mx-sm q-pa-xs text-weight-bold"
@click="applyAccessPreset('none')"
/>
</div>
<div class="row">
<div
v-for="option in employee_access_options"
:key="option.label"
class="col-xl-4 col-lg-6 col-sm-12 col-xs-12 q-pa-xs"
>
<div
class="row full-width cursor-pointer flex-center q-pa-sm rounded-5 no-wrap shadow-1"
:class="employee_store.employee.user_module_access.includes(option.value) ? 'bg-accent text-white' : ''"
@click="toggleInSelected(option.value)"
>
<span class="text-uppercase text-caption">
{{ $t('profile.employee.module_access.' + option.value) }}
</span>
<q-space />
<q-icon
:name="employee_store.employee.user_module_access.includes(option.value) ? 'check' : ''"
size="sm"
/>
</div>
</div>
</div>
</div>
<!-- <q-option-group
v-model="selected_permissions"
dense
inline
left-label
type="checkbox"
color="accent"
:options="employee_access_options"
/> -->
</q-field>
</q-form>
</div>
</template>

View File

@ -3,20 +3,80 @@
lang="ts"
>
import AddModifyDialogForm from 'src/modules/employee-list/components/employee/add-modify-dialog-form.vue';
import AddModifyDialogAccess from 'src/modules/employee-list/components/employee/add-modify-dialog-access.vue';
import { useEmployeeStore } from 'src/stores/employee-store';
import { ref } from 'vue';
const employee_store = useEmployeeStore();
const current_step = ref<'form' | 'access'>('form');
</script>
<template>
<q-dialog v-model="employee_store.is_add_modify_dialog_open">
<q-card>
<q-card-section class="text-bolder text-white bg-primary text-center">
<span >ADD EMPLOYEE</span>
<q-dialog
v-model="employee_store.is_add_modify_dialog_open"
full-width
@beforeShow="current_step = 'form'"
>
<q-card
class="bg-secondary shadow-10 rounded-10"
:style="$q.screen.lt.md ? '' : 'max-width: 70vw !important;'"
>
<q-card-section
class="text-weight-bolder text-white text-h6 text-uppercase bg-primary text-center shadow-10 q-py-xs"
>
<span>{{ $t('employee_management.' + employee_store.management_mode) }}</span>
</q-card-section>
<q-card-section>
<q-carousel
v-model="current_step"
transition-prev="slide-right"
transition-next="slide-left"
animated
control-color="accent"
class="rounded-10 transparent"
>
<q-carousel-slide name="form">
<div class="rounded-5 q-pb-sm bg-dark">
<AddModifyDialogForm />
</div>
</q-carousel-slide>
<q-carousel-slide name="access">
<AddModifyDialogAccess />
</q-carousel-slide>
<template #control>
<q-carousel-control position="bottom">
<div class="row">
<q-btn
v-if="current_step === 'access'"
flat
size="lg"
color="accent"
icon="arrow_back"
:label="$t('employee_management.details_label')"
@click="current_step = 'form'"
/>
<q-space />
<q-btn
v-if="current_step === 'form'"
flat
size="lg"
color="accent"
icon-right="arrow_forward"
:label="$t('employee_management.access_label')"
@click="current_step = 'access'"
/>
</div>
</q-carousel-control>
</template>
</q-carousel>
</q-card-section>
<q-inner-loading :showing="employee_store.is_loading" />
</q-card>
</q-dialog>

View File

@ -7,18 +7,20 @@ export const useEmployeeStore = defineStore('employee', () => {
const employee = ref<EmployeeProfile>(new EmployeeProfile);
const employee_list = ref<EmployeeProfile[]>([]);
const is_add_modify_dialog_open = ref<boolean>(false);
const management_mode = ref<'modify_employee' | 'add_employee'>('add_employee');
const is_loading = ref(false);
const openAddModifyDialog = async (employee_email?: string) =>{
console.log('open window triggered');
is_add_modify_dialog_open.value = true;
if (employee_email === undefined) {
management_mode.value = 'add_employee'
employee.value = new EmployeeProfile();
return;
}
is_loading.value = true;
management_mode.value = 'modify_employee';
await getEmployeeDetails(employee_email);
is_loading.value = false;
}
@ -57,6 +59,7 @@ export const useEmployeeStore = defineStore('employee', () => {
employee,
employee_list,
is_add_modify_dialog_open,
management_mode,
is_loading,
getEmployeeList,
getEmployeeDetails,