Merge branch 'main' of git.targo.ca:Targo/targo_frontend into dev/lion/chatbot
20
Dockerfile
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Step 1 - Building the app
|
||||||
|
FROM node:22 AS build
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json ./
|
||||||
|
# Set environment variables
|
||||||
|
ARG BACKEND_URL
|
||||||
|
ENV VITE_TARGO_BACKEND_URL=$BACKEND_URL
|
||||||
|
# Install dependencies
|
||||||
|
RUN npm install -g @quasar/cli
|
||||||
|
COPY . .
|
||||||
|
RUN npm install
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Step 2 - Move Applicatin to Nginx
|
||||||
|
FROM nginx:alpine
|
||||||
|
COPY --from=build /app/dist/spa /usr/share/nginx/html
|
||||||
|
RUN mkdir /usr/share/nginx/html/src
|
||||||
|
COPY --from=build /app/src /usr/share/nginx/html/src
|
||||||
|
EXPOSE 80
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
26
docker-compose.yaml
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
---
|
||||||
|
name: app-targo
|
||||||
|
services:
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: https://git.targo.ca/Targo/targo_backend.git
|
||||||
|
args:
|
||||||
|
- CALLBACK_URL=http://${SERVER_IP}:${BACKEND_PUBLIC_PORT}/auth/callback
|
||||||
|
environment:
|
||||||
|
- REDIRECT_URL_DEV=http://${SERVER_IP}:${FRONTEND_PUBLIC_PORT}/#/login-success
|
||||||
|
env_file:
|
||||||
|
- stack.env
|
||||||
|
ports:
|
||||||
|
- ${BACKEND_PUBLIC_PORT}:3000
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: https://git.targo.ca/Targo/targo_frontend.git
|
||||||
|
args:
|
||||||
|
- BACKEND_URL=http://${SERVER_IP}:${BACKEND_PUBLIC_PORT}/
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
env_file:
|
||||||
|
- stack.env
|
||||||
|
ports:
|
||||||
|
- ${FRONTEND_PUBLIC_PORT}:80
|
||||||
|
|
@ -41,6 +41,11 @@ export default defineConfigWithVueTs(
|
||||||
'error',
|
'error',
|
||||||
{ prefer: 'type-imports' }
|
{ prefer: 'type-imports' }
|
||||||
],
|
],
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'warn',
|
||||||
|
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' }
|
||||||
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// https://github.com/vuejs/eslint-config-typescript
|
// https://github.com/vuejs/eslint-config-typescript
|
||||||
|
|
@ -63,15 +68,15 @@ export default defineConfigWithVueTs(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
files: ['**/*.ts', '**/*.vue'],
|
||||||
|
|
||||||
// add your custom rules here
|
// add your custom rules here
|
||||||
rules: {
|
rules: {
|
||||||
'prefer-promise-reject-errors': 'off',
|
'prefer-promise-reject-errors': 'off',
|
||||||
|
|
||||||
// warn about unused but underscored variables
|
// warn about unused but underscored variables
|
||||||
'no-unused-vars': [
|
|
||||||
'warn',
|
'no-unused-vars': 'off',
|
||||||
{ argsIgnorePattern: '^_' }
|
|
||||||
],
|
|
||||||
|
|
||||||
// allow debugger during development only
|
// allow debugger during development only
|
||||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,12 @@ export default defineConfig((ctx) => {
|
||||||
// 'fontawesome-v6',
|
// 'fontawesome-v6',
|
||||||
// 'eva-icons',
|
// 'eva-icons',
|
||||||
// 'themify',
|
// 'themify',
|
||||||
// 'line-awesome',
|
'line-awesome',
|
||||||
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
|
'material-icons',
|
||||||
|
'material-icons-outlined',
|
||||||
|
|
||||||
'roboto-font', // optional, you are not bound to it
|
'roboto-font',
|
||||||
'material-icons', // optional, you are not bound to it
|
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
|
||||||
],
|
],
|
||||||
|
|
||||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#build
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#build
|
||||||
|
|
@ -97,6 +98,7 @@ export default defineConfig((ctx) => {
|
||||||
devServer: {
|
devServer: {
|
||||||
// https: true,
|
// https: true,
|
||||||
open: true, // opens browser window automatically
|
open: true, // opens browser window automatically
|
||||||
|
allowedHosts: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#framework
|
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#framework
|
||||||
|
|
@ -104,9 +106,8 @@ export default defineConfig((ctx) => {
|
||||||
config: {
|
config: {
|
||||||
notify: {
|
notify: {
|
||||||
color: 'primary',
|
color: 'primary',
|
||||||
avatar: 'https://cdn.quasar.dev/img/boy-avatar.png',
|
|
||||||
},
|
},
|
||||||
dark: false,
|
dark: 'auto',
|
||||||
},
|
},
|
||||||
|
|
||||||
// iconSet: 'material-icons', // Quasar icon set
|
// iconSet: 'material-icons', // Quasar icon set
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script
|
||||||
//
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
BIN
src/assets/en-CA.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
src/assets/facturation_thumbnail.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
src/assets/fr-FR.png
Normal file
|
After Width: | Height: | Size: 880 B |
BIN
src/assets/google_thumbnail.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
src/assets/help-ss/access-management.png
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
src/assets/help-ss/calendar.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
src/assets/help-ss/commenting-shift-overview.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
src/assets/help-ss/commenting-shift.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
src/assets/help-ss/create-employee.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
src/assets/help-ss/create-shift-overview.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
src/assets/help-ss/create-shift.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
src/assets/help-ss/dashboard.png
Normal file
|
After Width: | Height: | Size: 419 KiB |
BIN
src/assets/help-ss/default-dashboard.png
Normal file
|
After Width: | Height: | Size: 816 KiB |
BIN
src/assets/help-ss/default-employee-list.png
Normal file
|
After Width: | Height: | Size: 778 KiB |
BIN
src/assets/help-ss/default-employee-management.png
Normal file
|
After Width: | Height: | Size: 802 KiB |
BIN
src/assets/help-ss/default-personnal_profile.png
Normal file
|
After Width: | Height: | Size: 804 KiB |
BIN
src/assets/help-ss/default-timesheet.png
Normal file
|
After Width: | Height: | Size: 739 KiB |
BIN
src/assets/help-ss/default-validation-page.png
Normal file
|
After Width: | Height: | Size: 681 KiB |
BIN
src/assets/help-ss/delete-expense.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
src/assets/help-ss/delete-shift.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
src/assets/help-ss/employee-list-grid.png
Normal file
|
After Width: | Height: | Size: 133 KiB |
BIN
src/assets/help-ss/employee-list.png
Normal file
|
After Width: | Height: | Size: 103 KiB |
BIN
src/assets/help-ss/expenses.png
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
src/assets/help-ss/personal_infos.png
Normal file
|
After Width: | Height: | Size: 130 KiB |
BIN
src/assets/help-ss/preferences.png
Normal file
|
After Width: | Height: | Size: 132 KiB |
BIN
src/assets/help-ss/professionnal_infos.png
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
src/assets/help-ss/schedule-preset-management.png
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
src/assets/help-ss/search-bar.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
src/assets/help-ss/terminated-employee-list.png
Normal file
|
After Width: | Height: | Size: 116 KiB |
BIN
src/assets/help-ss/timesheet-appovals-list.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
src/assets/help-ss/timesheet-approvals.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
src/assets/help-ss/timesheet-details.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
src/assets/help-ss/timesheets.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
src/assets/help-ss/update-employee.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
src/assets/help-ss/update-expense.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
src/assets/help-ss/update-shift.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
src/assets/info-pannes.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 938 KiB After Width: | Height: | Size: 578 KiB |
BIN
src/assets/map_targo_banner.png
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
src/assets/targo_building.png
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
src/assets/targo_help_banner.png
Normal file
|
After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 2.9 MiB After Width: | Height: | Size: 7.4 MiB |
|
|
@ -14,7 +14,10 @@ declare module 'vue' {
|
||||||
// good idea to move this instance creation inside of the
|
// good idea to move this instance creation inside of the
|
||||||
// "export default () => {}" function below (which runs individually
|
// "export default () => {}" function below (which runs individually
|
||||||
// for each client)
|
// for each client)
|
||||||
const api = axios.create({ baseURL: import.meta.env.VITE_TARGO_BACKEND_AUTH_URL });
|
const api = axios.create({
|
||||||
|
baseURL: import.meta.env.VITE_TARGO_BACKEND_URL,
|
||||||
|
withCredentials: true
|
||||||
|
});
|
||||||
|
|
||||||
export default defineBoot(({ app }) => {
|
export default defineBoot(({ app }) => {
|
||||||
// for use inside Vue files (Options API) through this.$axios and this.$api
|
// for use inside Vue files (Options API) through this.$axios and this.$api
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
// app global css in SCSS form
|
// app global css in SCSS form
|
||||||
@each $size in (1, 2, 3, 4, 5, 10, 15, 20, 25, 50, 75, 100) {
|
@each $size in (1, 2, 3, 4, 5, 10, 15, 20, 25, 50, 75, 100, 200) {
|
||||||
.rounded-#{$size} {
|
.rounded-#{$size} {
|
||||||
border-radius: #{$size}px !important;
|
border-radius: #{$size}px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.text-fb-blue {
|
.text-fb-blue {
|
||||||
color: #4267B2 !important;
|
color: #4267B2 !important;
|
||||||
}
|
}
|
||||||
|
|
@ -25,7 +24,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
body.body--dark {
|
body.body--dark {
|
||||||
--q-secondary: #0f1114;
|
--q-secondary: #151520;
|
||||||
color: $grey-2;
|
color: $grey-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,3 +32,45 @@ body.body--dark {
|
||||||
--q-dark: #FFF;
|
--q-dark: #FFF;
|
||||||
color: $blue-grey-8;
|
color: $blue-grey-8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.shift-highlight {
|
||||||
|
background: #0195462a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.frosted-glass {
|
||||||
|
background-color: #0008 !important;
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-btn--push::before {
|
||||||
|
border-bottom: 4px solid rgba(0,0,0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-btn--push:active {
|
||||||
|
transform: translateY(3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-btn--push:active::before {
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Chrome, Safari, Edge, Opera */
|
||||||
|
input::-webkit-outer-spin-button,
|
||||||
|
input::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
input[type=number] {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-field--dark .q-field__control::before {
|
||||||
|
border-color: #fff3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-field--dark .q-field__control:hover::before, .q-field--outlined .q-field__control:hover::before {
|
||||||
|
border-color: var(--q-accent);
|
||||||
|
border-width: 2px;
|
||||||
|
}
|
||||||
|
|
@ -12,21 +12,29 @@
|
||||||
// to match your app's branding.
|
// to match your app's branding.
|
||||||
// Tip: Use the "Theme Builder" on Quasar's documentation website.
|
// Tip: Use the "Theme Builder" on Quasar's documentation website.
|
||||||
|
|
||||||
$primary : #019547;
|
$primary : #30303A;
|
||||||
$secondary : #DAE0E7;
|
$secondary : #DAE0E7;
|
||||||
$accent : #AAD5C4;
|
$accent : #0c9a3b;
|
||||||
|
$accent2 : #0a7d32;
|
||||||
|
|
||||||
$dark-shadow-color : #019547;
|
$dark-shadow-color : #000000;
|
||||||
|
|
||||||
$elevation-dark-umbra : rgba($dark-shadow-color, 0.4);
|
$elevation-dark-umbra : rgba($dark-shadow-color, 1);
|
||||||
$elevation-dark-penumbra : rgba($dark-shadow-color, 0);
|
$elevation-dark-penumbra : rgba($dark-shadow-color, 0.75);
|
||||||
$elevation-dark-ambient : rgba($dark-shadow-color, 0);
|
$elevation-dark-ambient : rgba($dark-shadow-color, 0.53);
|
||||||
|
|
||||||
$dark-shadow-2 : 0 3px 5px -1px $elevation-dark-umbra, 0 5px 8px $elevation-dark-penumbra, 0 1px 14px $elevation-dark-ambient;
|
$dark-shadow-2 : 2px 3px $elevation-dark-umbra, 2px 3px 6px $elevation-dark-penumbra, 2px 3px 14px $elevation-dark-ambient;
|
||||||
$layout-shadow-dark : 0 0 10px 5px rgba($dark-shadow-color, 0.5);
|
$layout-shadow-dark : 0 0 5px 5px rgba($dark-shadow-color, 0.5);
|
||||||
|
|
||||||
$dark : #333;
|
$input-text-color : #455A64;
|
||||||
$dark-page : #343434;
|
$input-autofill-color : #AAD5C4;
|
||||||
|
$field-dense-label-top : 5px !default;
|
||||||
|
$field-dense-label-font-size : 16px !default;
|
||||||
|
|
||||||
|
$button-shadow : 0 0 0 transparent;
|
||||||
|
|
||||||
|
$dark : #40404C;
|
||||||
|
$dark-page : #343444;
|
||||||
|
|
||||||
$positive : #21ba45;
|
$positive : #21ba45;
|
||||||
$negative : #e6364b;
|
$negative : #e6364b;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,73 @@
|
||||||
export default {
|
export default {
|
||||||
chatbot: {
|
chatbot: {
|
||||||
chat_header: "AI Assistant",
|
chat_header: "AI Assistant",
|
||||||
chat_initial_message:
|
chat_initial_message: "Welcome to your technical assistant.\nPlease provide the Customer ID to get a diagnostic report",
|
||||||
"Welcome to your technical assistant.\nPlease provide the Customer ID to get a diagnostic report",
|
|
||||||
chat_placeholder: "Enter a Message",
|
chat_placeholder: "Enter a Message",
|
||||||
chat_thinking: "Thinking...",
|
chat_thinking: "Thinking...",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
dashboard: {
|
||||||
|
carousel: {
|
||||||
|
welcome_title: "Welcome to the new Targo Application!",
|
||||||
|
welcome_message: "Development is complete and the application is live! Things have remained mostly the same, but with a new coat of paint, a more streamlined user experience, and most importantly, drastically improved security and optimization!",
|
||||||
|
help_title: "We have a help page!",
|
||||||
|
help_message: "We've modernized the app while trying to make as few functional changes as possible, but if there's ever any part of the site that leaves you scratching your head, feel free to check out the help page.",
|
||||||
|
},
|
||||||
|
useful_links: "useful links",
|
||||||
|
},
|
||||||
|
|
||||||
|
help: {
|
||||||
|
label: "Centre d'aide",
|
||||||
|
tutorial: {
|
||||||
|
dashboard: {
|
||||||
|
title: "Home Page",
|
||||||
|
news_feed: "News Feed",
|
||||||
|
chat_bot: "Technical chat-bot",
|
||||||
|
notifications: "Notifications",
|
||||||
|
},
|
||||||
|
personal_profile: {
|
||||||
|
title: "Personnal Profile",
|
||||||
|
personal_info: "Personal informations",
|
||||||
|
professional_info: "Professional informations",
|
||||||
|
},
|
||||||
|
timesheets: {
|
||||||
|
title: "Timesheet",
|
||||||
|
create_shift: "Add a new shift",
|
||||||
|
update_shift: "Update an existing shift",
|
||||||
|
delete_shift: "Removing a shift from the timesheet",
|
||||||
|
comment_shift: "Commenting a shift",
|
||||||
|
create_expense: "Add a new expense",
|
||||||
|
update_expense: "Update an existing expense",
|
||||||
|
delete_expense: "Removing an expense from the list",
|
||||||
|
|
||||||
|
},
|
||||||
|
employee_list: {
|
||||||
|
title: "Employee List",
|
||||||
|
terminated_employees: "Inactive employees",
|
||||||
|
},
|
||||||
|
employee_management: {
|
||||||
|
title: "Employee Management",
|
||||||
|
create_employee: "Creating a new employee",
|
||||||
|
update_employee: "Updating an existing employee's informations",
|
||||||
|
module_access: "App managing access tool",
|
||||||
|
schedule_preset: "Schedule preset management",
|
||||||
|
terminating_employee: "terminate an employee",
|
||||||
|
},
|
||||||
|
timesheets_approval: {
|
||||||
|
title: "Timesheets approval",
|
||||||
|
approval: "timesheet approvals",
|
||||||
|
inspect: "Inspect timesheets",
|
||||||
|
comment_expense: "Commenting an expense",
|
||||||
|
},
|
||||||
|
shared: {
|
||||||
|
search: "Advance search",
|
||||||
|
preferences: "Display mode",
|
||||||
|
calendar: "Navigation using the calendar",
|
||||||
|
display: "Display as cards or as a list"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
employee_list: {
|
employee_list: {
|
||||||
page_header: "Employee Directory",
|
page_header: "Employee Directory",
|
||||||
table: {
|
table: {
|
||||||
|
|
@ -16,13 +78,76 @@ export default {
|
||||||
role: "Role",
|
role: "Role",
|
||||||
supervisor: "Supervisor",
|
supervisor: "Supervisor",
|
||||||
company: "Company",
|
company: "Company",
|
||||||
|
is_supervisor: "is a supervisor",
|
||||||
|
expected_daily_hours: "Expected Daily Hours",
|
||||||
|
active: "active",
|
||||||
|
inactive: "inactive",
|
||||||
},
|
},
|
||||||
|
errors: {
|
||||||
|
first_name_required: "Employee's first name is required",
|
||||||
|
last_name_required: "Employee's last name is required",
|
||||||
|
company_required: "Employee must be assigned to a company",
|
||||||
|
phone_number_required: "Employee's phone number required",
|
||||||
|
hire_date_required: "Employee's first work day must be entered",
|
||||||
|
daily_hours_required: "Provide employee's expected daily hours worked",
|
||||||
|
no_modules_warning: "All modules disabled. This will lock the user out.",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
employee_management: {
|
||||||
|
add_employee: "Add employee",
|
||||||
|
modify_employee: "Modify employee",
|
||||||
|
access_label: "access",
|
||||||
|
details_label: "details",
|
||||||
|
schedule_label: "schedule",
|
||||||
|
can_be_entered_later: "OPTIONAL: can be entered later",
|
||||||
|
enter_delete_input: "type 'DELETE' to remove",
|
||||||
|
banked_hours: "available banked hours",
|
||||||
|
sick_hours: "available PTO hours",
|
||||||
|
vacation_hours: "available vacation hours",
|
||||||
|
schedule_presets: {
|
||||||
|
preset_list_placeholder: "Select a schedule",
|
||||||
|
preset_name_placeholder: "schedule preset name",
|
||||||
|
delete_warning: "",
|
||||||
|
delete_warning_employee_1: "This schedule is used by",
|
||||||
|
delete_warning_employee_2: "Deleting this preset will not affect previous timesheets, but they will no longer be able to apply this preset to their timesheets going forward.",
|
||||||
|
},
|
||||||
|
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",
|
||||||
|
by_role: "by role",
|
||||||
|
by_module: "by module",
|
||||||
|
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",
|
||||||
|
usage_description: "You can use roles to enable preset modules, add or remove modules individually, or both",
|
||||||
|
},
|
||||||
|
filter: {
|
||||||
|
hide_terminated: "Hide inactive employees",
|
||||||
|
sort_by_tags: "sort by tags",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
error: {
|
||||||
|
not_found_header: "page not found",
|
||||||
|
not_found_description: "You may have entered the wrong URL, or you may not have access to this page",
|
||||||
|
go_back: "go back",
|
||||||
},
|
},
|
||||||
|
|
||||||
login: {
|
login: {
|
||||||
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",
|
||||||
|
|
@ -32,6 +157,10 @@ export default {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
coming_soon: "coming soon!",
|
coming_soon: "coming soon!",
|
||||||
},
|
},
|
||||||
|
error: {
|
||||||
|
login_failed: "Failed to login",
|
||||||
|
popups_blocked: "Popups are blocked on this device",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
nav_bar: {
|
nav_bar: {
|
||||||
|
|
@ -61,17 +190,29 @@ export default {
|
||||||
company: "company",
|
company: "company",
|
||||||
supervisor: "supervisor",
|
supervisor: "supervisor",
|
||||||
hired_date: "hiring date",
|
hired_date: "hiring date",
|
||||||
|
fired_date: "departure date",
|
||||||
|
bankroll_id: "payroll ID",
|
||||||
},
|
},
|
||||||
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': "Français",
|
||||||
|
'en-CA': "English",
|
||||||
dark_mode: "dark",
|
dark_mode: "dark",
|
||||||
light_mode: "light",
|
light_mode: "light",
|
||||||
|
auto_mode: "auto",
|
||||||
|
update_successful: "Preferences saved",
|
||||||
|
update_failed: "Failed to save preferences",
|
||||||
|
},
|
||||||
|
schedule_presets: {
|
||||||
|
tab_title: "Schedule",
|
||||||
|
selected_schedule: "Selected Schedule Preset",
|
||||||
|
new_preset: "Build a new preset",
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
must_enter_birthdate: "You must enter a valid birthdate",
|
must_enter_birthdate: "You must enter a valid birthdate",
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
shared: {
|
shared: {
|
||||||
|
|
@ -91,6 +232,8 @@ export default {
|
||||||
update: "update",
|
update: "update",
|
||||||
modify: "modify",
|
modify: "modify",
|
||||||
close: "close",
|
close: "close",
|
||||||
|
download: "download",
|
||||||
|
open: "open",
|
||||||
},
|
},
|
||||||
misc: {
|
misc: {
|
||||||
or: "or",
|
or: "or",
|
||||||
|
|
@ -113,28 +256,33 @@ export default {
|
||||||
remote: "remote work",
|
remote: "remote work",
|
||||||
},
|
},
|
||||||
weekday: {
|
weekday: {
|
||||||
sunday: "dimanche",
|
sun: "dimanche",
|
||||||
monday: "lundi",
|
mon: "lundi",
|
||||||
tuesday: "mardi",
|
tue: "mardi",
|
||||||
wednesday: "mercredi",
|
wed: "mercredi",
|
||||||
thursday: "jeudi",
|
thu: "jeudi",
|
||||||
friday: "vendredi",
|
fri: "vendredi",
|
||||||
saturday: "samedi",
|
sat: "samedi",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
timesheet: {
|
timesheet: {
|
||||||
page_header: "Timesheet",
|
page_header: "Timesheet",
|
||||||
|
week: "week",
|
||||||
|
total_hours: "total hours: ",
|
||||||
|
total_expenses: "total expenses: ",
|
||||||
|
vacation_available: "vacation time available: ",
|
||||||
|
sick_available: "sick time available: ",
|
||||||
|
current_shifts: "shifts worked",
|
||||||
|
apply_preset: "auto-fill",
|
||||||
|
apply_preset_day: "Apply schedule to day",
|
||||||
|
apply_preset_week: "Apply schedule to week",
|
||||||
nav_button: {
|
nav_button: {
|
||||||
calendar_date_picker: "Calendar",
|
calendar_date_picker: "Calendar",
|
||||||
current_week: "This week",
|
current_week: "This week",
|
||||||
next_week: "Next week",
|
next_week: "Next period",
|
||||||
previous_week: "Previous week",
|
previous_week: "Previous period",
|
||||||
},
|
},
|
||||||
save_button: "Save",
|
|
||||||
cancel_button: "Cancel",
|
|
||||||
remote_button: "Remote work",
|
|
||||||
delete_button: "Delete",
|
|
||||||
shift: {
|
shift: {
|
||||||
actions: {
|
actions: {
|
||||||
add: "Add Shift",
|
add: "Add Shift",
|
||||||
|
|
@ -151,15 +299,8 @@ export default {
|
||||||
REGULAR: "Regular",
|
REGULAR: "Regular",
|
||||||
SICK: "Sick Leave",
|
SICK: "Sick Leave",
|
||||||
VACATION: "Vacation",
|
VACATION: "Vacation",
|
||||||
REMOTE: "Remote work",
|
REMOTE: "Remote",
|
||||||
},
|
OFFICE: "Office",
|
||||||
errors: {
|
|
||||||
not_found: "Shift not found",
|
|
||||||
overlap: "An overlaps occured between 2 or more shifts",
|
|
||||||
invalid: "Invalid shift`s entry",
|
|
||||||
unknown: "Unknown error",
|
|
||||||
comment_required: "A comment is required",
|
|
||||||
comment_too_long: "Your comment is too long",
|
|
||||||
},
|
},
|
||||||
fields: {
|
fields: {
|
||||||
start: "Start (HH:mm)",
|
start: "Start (HH:mm)",
|
||||||
|
|
@ -169,29 +310,16 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expense: {
|
expense: {
|
||||||
add_expense: "Add Expense",
|
add_expense: 'Add Expense',
|
||||||
amount: "Amount",
|
amount: 'Amount',
|
||||||
date: "Date",
|
date: 'Date',
|
||||||
empty_list: "No registered expenses",
|
empty_list: 'No registered expenses',
|
||||||
employee_comment: "Comment",
|
employee_comment: 'Comment',
|
||||||
supervisor_comment: "Supervisor note",
|
supervisor_comment: 'Supervisor note',
|
||||||
errors: {
|
|
||||||
date_required_or_invalid: "the date is missing or invalid",
|
|
||||||
comment_required: "A comment required",
|
|
||||||
comment_too_long: "Your comment is too long",
|
|
||||||
amount_must_be_positive: "the amount cannot be under 0$",
|
|
||||||
mileave_must_be_positive: "the mileage cannot be under 0",
|
|
||||||
amount_xor_mileage:
|
|
||||||
"you cannot enter an amount and a mileage for the same expense",
|
|
||||||
mileage_required_for_type:
|
|
||||||
"you need to enter a value for mileage when you enter an expense of that type",
|
|
||||||
amount_required_for_type:
|
|
||||||
"you need to enter a value for amount when you enter an expense of that type",
|
|
||||||
},
|
|
||||||
hints: {
|
hints: {
|
||||||
amount_or_mileage: "Either amount or mileage, not both",
|
amount_or_mileage: "Either amount or mileage, not both",
|
||||||
comment_required: "A comment required",
|
comment_required: "A comment required",
|
||||||
attach_file: "Attach File",
|
attach_file: "Attach File"
|
||||||
},
|
},
|
||||||
mileage: "mileage",
|
mileage: "mileage",
|
||||||
open_btn: "list of expenses",
|
open_btn: "list of expenses",
|
||||||
|
|
@ -203,33 +331,142 @@ export default {
|
||||||
PER_DIEM: "Per Diem",
|
PER_DIEM: "Per Diem",
|
||||||
EXPENSES: "expense",
|
EXPENSES: "expense",
|
||||||
MILEAGE: "mileage",
|
MILEAGE: "mileage",
|
||||||
PRIME_GARDE: "on-call allowance",
|
ON_CALL: "on-call allowance",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
errors: {
|
||||||
|
INVALID_SHIFT_TIME: "In and Out shift times are reversed",
|
||||||
|
SHIFT_OVERLAP: "An overlaps occured between 2 or more shifts",
|
||||||
|
SHIFT_OVERLAP_SHORT: "Overlap",
|
||||||
|
INVALID_SHIFT: "A shift contains missing or corrupted data",
|
||||||
|
SHIFT_TIME_REQUIRED: "Time missing",
|
||||||
|
SHIFT_TYPE_REQUIRED: "Shift type required",
|
||||||
|
SHIFT_NOT_FOUND: "Shift missing or deleted",
|
||||||
|
PAY_PERIOD_NOT_FOUND: "No pay period matching given dates",
|
||||||
|
EMPLOYEE_NOT_FOUND: "No employee matching current login details",
|
||||||
|
INVALID_TIMESHEET: "Timesheet data is missing or corrupted",
|
||||||
|
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",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
timesheet_approvals: {
|
timesheet_approvals: {
|
||||||
page_title: "Validation cartes de temps",
|
page_title: "Validation cartes de temps",
|
||||||
table: {
|
|
||||||
full_name: "full name",
|
|
||||||
email: "email address",
|
|
||||||
expenses: "expenses",
|
|
||||||
mileage: "mileage",
|
|
||||||
verified: "approved",
|
|
||||||
unverified: "pending",
|
|
||||||
},
|
|
||||||
chart: {
|
chart: {
|
||||||
hours_worked_title: "hours worked",
|
hours_worked_title: "hours worked",
|
||||||
expenses_title: "expenses accrued",
|
expenses_title: "expenses accrued",
|
||||||
},
|
},
|
||||||
print_report: {
|
print_report: {
|
||||||
company: "company",
|
title: "Download options",
|
||||||
|
description: "Choose what to include in the report",
|
||||||
|
company: "companies",
|
||||||
type: "type",
|
type: "type",
|
||||||
shifts: "shifts",
|
shifts: "shifts",
|
||||||
expenses: "expenses",
|
expenses: "expenses",
|
||||||
|
options: "options",
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
full_name: "full name",
|
||||||
|
email: "email address",
|
||||||
|
is_approved: "approval",
|
||||||
|
expenses: "expenses",
|
||||||
|
mileage: "mileage",
|
||||||
|
verified: "approved",
|
||||||
|
unverified: "pending",
|
||||||
|
inactive: "inactive",
|
||||||
|
regular: "regular",
|
||||||
|
evening: "evening",
|
||||||
|
emergency: "emergency",
|
||||||
|
overtime: "overtime",
|
||||||
|
holiday: "holiday",
|
||||||
|
vacation: "vacation",
|
||||||
|
sick: "sick",
|
||||||
|
remote: "remote work",
|
||||||
|
weekly_hours_1: "1st week hours",
|
||||||
|
weekly_hours_2: "2nd week hours",
|
||||||
|
total_hours: "total hours",
|
||||||
|
filter_active: "show only active employees",
|
||||||
|
filter_team: "show my team only",
|
||||||
|
filter_columns: "Information displayed",
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
button_detailed_view: "detailed view",
|
button_detailed_view: "detailed view",
|
||||||
|
approve: "Approve",
|
||||||
|
unapprove: "remove approval",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
descriptions: {
|
||||||
|
dashboard: {
|
||||||
|
menu: "To access the main menu, click the button located in the upper-left corner. This menu allows you to navigate through the entire application.",
|
||||||
|
news_feed: "General announcements and important updates are displayed here. This view is the same for all employees.",
|
||||||
|
notifications: "Notifications are accessible via the bell icon located in the upper-right corner. They allow you to quickly view information relevant to you, \
|
||||||
|
such as leave, vacation, or absence requests, overtime hours worked during the current week, \
|
||||||
|
and comments left by your supervisor.",
|
||||||
|
chat_bot: "To access the bot, simply click on the \"bot\" bubble. The conversational bot allows you to quickly find information about a client, \
|
||||||
|
an invoice, or a device.",
|
||||||
|
},
|
||||||
|
personal_profile: {
|
||||||
|
personal_info: "In the \"Personal\" tab, you can view your personal information, such as your name, phone numbers, \
|
||||||
|
address, and date of birth.",
|
||||||
|
professional_info: "In the \"Career\" tab, you can view your professional information, including your position, the name of the company you work for, \
|
||||||
|
your supervisor's name and email address, as well as your hire date.",
|
||||||
|
preferences: "In the \"Preferences\" tab, you can adjust certain settings based on your personal preferences, such as dark mode, \
|
||||||
|
language, and notifications.",
|
||||||
|
},
|
||||||
|
timesheets: {
|
||||||
|
create_shift: "To add a work shift, click the green \"Add Time\" tab and enter the following required information: \
|
||||||
|
the shift type, start time, end time, and whether the shift is on-site or remote. \
|
||||||
|
Then click \"Save\", located in the upper-right corner of the time card.",
|
||||||
|
update_shift: "To modify a work shift, click the information you want to update, adjust the value, then click \"Save\", \
|
||||||
|
located in the upper-right corner of the time card.",
|
||||||
|
delete_shift: "To delete a work shift, click the red trash icon associated with the entry.",
|
||||||
|
comment_shift: "To leave a comment on a work shift, click the conversation bubble icon located to the right of the shift entry, \
|
||||||
|
then click \"Save\" in the upper-right corner of the time card.",
|
||||||
|
create_expense: "To add an expense, access the expense list using the button located in the upper-right corner. \
|
||||||
|
You must then complete the following fields: the date (today's date is selected by default), the expense type, \
|
||||||
|
the amount or mileage, a comment justifying the expense, and attach a supporting document. \
|
||||||
|
Then click the \"Add\" button.",
|
||||||
|
update_expense: "To modify an expense, access the expense list, select the expense you wish to edit, make the necessary changes, \
|
||||||
|
then click \"Update\" to save.",
|
||||||
|
delete_expense: "To delete an expense, access the expense list and click the red trash icon.",
|
||||||
|
},
|
||||||
|
employee_list: {
|
||||||
|
terminated_employees: "This option allows you to show or hide employees who are no longer employed.",
|
||||||
|
},
|
||||||
|
employee_management: {
|
||||||
|
create_employee: "To create an employee, access the \"Add Employee\" menu located in the upper-left corner of the employee list. \
|
||||||
|
In the \"Details\" tab, first enter all required information, excluding the termination date. \
|
||||||
|
Then assign the appropriate access rights to the employee. Optionally, you may assign a preset schedule. \
|
||||||
|
Click \"Save\" to confirm the employee creation.",
|
||||||
|
update_employee: "To update an employee's information, access rights, or schedule, select the employee's profile and navigate to the appropriate tab. \
|
||||||
|
Make the necessary changes, then click \"Update\" to confirm.",
|
||||||
|
module_access: "To manage access to different parts of the application, select the desired employee's profile and navigate to the \"Access\" tab. \
|
||||||
|
Two selection options are available: by role or by module. If the employee is a supervisor, the \"Administrator\" role is recommended. \
|
||||||
|
For a standard employee, the \"Employee\" role is appropriate. Specific modules can also be selected in special cases.",
|
||||||
|
schedule_preset: "To assign, modify, or create a schedule for an employee, first select the employee, then navigate to the \"Schedule\" tab in the edit menu. \
|
||||||
|
You may select an existing schedule, create a new one by assigning a unique name, or copy an existing schedule, modify it, and rename it. \
|
||||||
|
Once satisfied, click \"Update\" to confirm your selection.",
|
||||||
|
terminating_employee: "To terminate an employee, select the employee's profile (or row). Once the edit menu is displayed, \
|
||||||
|
enter the termination date and click \"Update\".",
|
||||||
|
},
|
||||||
|
timesheets_approval: {
|
||||||
|
approval: "To approve a timesheet, click the bottom of the card where the lock icon is located. In list view, \
|
||||||
|
click the lock icon on the right side of the corresponding row.",
|
||||||
|
inspect: "To review an employee's work shifts or expenses, click the briefcase icon located in the upper-right corner. \
|
||||||
|
You will find statistics on hours worked as well as expenses incurred. Within this window, you may edit work shifts \
|
||||||
|
and expenses. Click \"Save\" to confirm the changes.",
|
||||||
|
comment_expense: "To leave a comment on an expense submitted by an employee, click the briefcase icon on the desired employee's card. \
|
||||||
|
Navigate to the expense list and click the supervisor comment bubble. \
|
||||||
|
Click \"Save\" to confirm your comment.",
|
||||||
|
},
|
||||||
|
shared: {
|
||||||
|
display: "This option allows you to choose the display mode that best suits your needs, either card view or detailed list view.",
|
||||||
|
search: "An advanced keyword search is available. Simply separate each keyword with a space, and the search bar will return results \
|
||||||
|
that include all entered keywords.",
|
||||||
|
calendar: "The calendar speeds up navigation. It allows you to select a specific date and display the pay period that includes the selected date.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -1,11 +1,73 @@
|
||||||
export default {
|
export default {
|
||||||
chatbot: {
|
chatbot: {
|
||||||
chat_header: "Agent IA",
|
chat_header: "Agent IA",
|
||||||
chat_initial_message:
|
chat_initial_message: "Bienvenue à votre assistant technique.\nVeuillez fournir le ID du Client pour avoir un diagnostique",
|
||||||
"Bienvenue à votre assistant technique.\nVeuillez fournir le ID du Client pour avoir un diagnostique",
|
|
||||||
chat_placeholder: "Entré un Message",
|
chat_placeholder: "Entré un Message",
|
||||||
chat_thinking: "Réflexion en cours...",
|
chat_thinking: "Réflexion en cours...",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
dashboard: {
|
||||||
|
carousel: {
|
||||||
|
welcome_title: "Bienvenue dans la nouvelle application Targo!",
|
||||||
|
welcome_message: "La nouvelle application est officiellement en ligne ! Plus performante et plus sécuritaire, elle conserve l’essentiel avec un design rafraîchie.",
|
||||||
|
help_title: "Nous avons une page d'aide!",
|
||||||
|
help_message: "L’application a été pensée pour être plus intuitive et moderne. En cas de doute, la page d’aide est à votre disposition.",
|
||||||
|
},
|
||||||
|
useful_links: "liens utiles",
|
||||||
|
},
|
||||||
|
|
||||||
|
help: {
|
||||||
|
label: "Centre d'aide",
|
||||||
|
tutorial: {
|
||||||
|
dashboard: {
|
||||||
|
title: "Page d'accueil",
|
||||||
|
menu: "Menu Principal",
|
||||||
|
news_feed: "Fil d'actualités",
|
||||||
|
chat_bot: "Robot conversationnel d'aide technique",
|
||||||
|
notifications: "Notifications",
|
||||||
|
},
|
||||||
|
personal_profile: {
|
||||||
|
title: "Profil",
|
||||||
|
personal_info: "Informations personnelles",
|
||||||
|
professional_info: "Informations professionnelles",
|
||||||
|
},
|
||||||
|
timesheets: {
|
||||||
|
title: "Carte de temps",
|
||||||
|
create_shift: "Inscrire un nouveau quart de travail",
|
||||||
|
update_shift: "Modifier un quart de travail existant",
|
||||||
|
delete_shift: "Supprimer un quart de travail de la carte de temps",
|
||||||
|
comment_shift: "Commenter un quart de travail",
|
||||||
|
create_expense: "Inscrire une dépense",
|
||||||
|
update_expense: "Modifier une dépense",
|
||||||
|
delete_expense: "Supprimer une dépense",
|
||||||
|
},
|
||||||
|
employee_list: {
|
||||||
|
title: "Répertoire des employés ",
|
||||||
|
terminated_employees: "Employés inactifs",
|
||||||
|
},
|
||||||
|
employee_management: {
|
||||||
|
title: "Gestion des employés ",
|
||||||
|
create_employee: "Création d'un nouvel employé",
|
||||||
|
update_employee: "Modifier les informations d'un employé",
|
||||||
|
module_access: "Outils de gestion des accès de l'application",
|
||||||
|
schedule_preset: "Gestion des horaires prédéterminés",
|
||||||
|
terminating_employee: "Rendre un employé inactif",
|
||||||
|
},
|
||||||
|
timesheets_approval: {
|
||||||
|
title: "Validation de Carte de temps",
|
||||||
|
approval: "Validation des cartes de temps",
|
||||||
|
inspect: "Inspect timesheets",
|
||||||
|
comment_expense: "Commenter une dépense",
|
||||||
|
},
|
||||||
|
shared: {
|
||||||
|
search: "Recherche avancée",
|
||||||
|
preferences: "Options de préférences d'affichage",
|
||||||
|
calendar: "Navigation avec l'aide du calendrier",
|
||||||
|
display: "Affichage par Cartes ou par Liste"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
employee_list: {
|
employee_list: {
|
||||||
page_header: "Répertoire du personnel",
|
page_header: "Répertoire du personnel",
|
||||||
table: {
|
table: {
|
||||||
|
|
@ -16,13 +78,76 @@ export default {
|
||||||
role: "rôle",
|
role: "rôle",
|
||||||
supervisor: "superviseur",
|
supervisor: "superviseur",
|
||||||
company: "Compagnie",
|
company: "Compagnie",
|
||||||
|
is_supervisor: "est un superviseur",
|
||||||
|
expected_daily_hours: "Heures quotidiennes attendues",
|
||||||
|
active: "actif",
|
||||||
|
inactive: "inactif",
|
||||||
},
|
},
|
||||||
|
errors: {
|
||||||
|
first_name_required: "Vous devez spécifier le prénom",
|
||||||
|
last_name_required: "Vous devez spécifier le nom de famille",
|
||||||
|
company_required: "Vous devez assignerl'employé à une compagnie",
|
||||||
|
phone_number_required: "Vous devez entrer un numéro de téléphone",
|
||||||
|
hire_date_required: "Vous devez entrer une date d'embauche",
|
||||||
|
daily_hours_required: "Spécifiez le nombre d'heures quotidiennes travaillé",
|
||||||
|
no_modules_warning: "Tout les modules sont désactivés. L'utilisateur sera verrouillé hors de l'application.",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
employee_management: {
|
||||||
|
add_employee: "Ajouter employé",
|
||||||
|
modify_employee: "Modifier employé",
|
||||||
|
access_label: "accès",
|
||||||
|
details_label: "détails",
|
||||||
|
schedule_label: "horaire",
|
||||||
|
can_be_entered_later: "FACULTATIF: peut être entré plus tard",
|
||||||
|
enter_delete_input: "tappez 'SUPPRIMER' pour confirmer",
|
||||||
|
banked_hours: "heures en banque disponibles",
|
||||||
|
sick_hours: "heures d'absence payées disponibles",
|
||||||
|
vacation_hours: "heures de vacances disponibles",
|
||||||
|
schedule_presets: {
|
||||||
|
preset_list_placeholder: "Sélectionner un horaire",
|
||||||
|
preset_name_placeholder: "nom de l'horaire",
|
||||||
|
delete_warning: "Êtes-vous certain de vouloir supprimer cet horaire?",
|
||||||
|
delete_warning_employee_1: "Cet horaire est présentement utilisé par",
|
||||||
|
delete_warning_employee_2: "La suppression n'affectera pas leurs feuilles de temps antérieures, mais ils ne pourront plus appliquer cet horaire à leurs feuilles de temps à partir de maintenant.",
|
||||||
|
},
|
||||||
|
module_access: {
|
||||||
|
dashboard: "Accueil",
|
||||||
|
employee_list: "Répertoire du personnel",
|
||||||
|
employee_management: "Gestion employés",
|
||||||
|
personal_profile: "profil personnel",
|
||||||
|
timesheets: "carte de temps",
|
||||||
|
timesheets_approval: "validation cartes de temps",
|
||||||
|
user_access: "module access",
|
||||||
|
by_role: "par rôle",
|
||||||
|
by_module: "par module",
|
||||||
|
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",
|
||||||
|
usage_description: "Vous pouvez utiliser les rôles pour sélectionner des modules prédéfinis, enlever ou ajouter des modules individuellement, ou les deux",
|
||||||
|
},
|
||||||
|
filter: {
|
||||||
|
hide_terminated: "Cacher les employés inactifs",
|
||||||
|
sort_by_tags: "filtrer par identifiants",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
error: {
|
||||||
|
not_found_header: "page introuvable",
|
||||||
|
not_found_description: "Vous avez possiblement entré une mauvaise addresse URL, ou vous n'avez pas accès à cette section du site",
|
||||||
|
go_back: "retour en arrière",
|
||||||
},
|
},
|
||||||
|
|
||||||
login: {
|
login: {
|
||||||
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é",
|
||||||
|
|
@ -32,6 +157,10 @@ export default {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
coming_soon: "à venir!",
|
coming_soon: "à venir!",
|
||||||
},
|
},
|
||||||
|
error: {
|
||||||
|
login_failed: "Échec à la connexion",
|
||||||
|
popups_blocked: "Les fenêtres contextuelles sont bloqués sur cet appareil",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
nav_bar: {
|
nav_bar: {
|
||||||
|
|
@ -61,29 +190,41 @@ export default {
|
||||||
company: "compagnie",
|
company: "compagnie",
|
||||||
supervisor: "nom du superviseur",
|
supervisor: "nom du superviseur",
|
||||||
hired_date: "date d'embauche",
|
hired_date: "date d'embauche",
|
||||||
|
fired_date: "date de départ",
|
||||||
|
bankroll_id: "identifiant de paie",
|
||||||
},
|
},
|
||||||
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': "English",
|
||||||
dark_mode: "sombre",
|
dark_mode: "sombre",
|
||||||
light_mode: "clair",
|
light_mode: "clair",
|
||||||
|
auto_mode: "automatique",
|
||||||
|
update_successful: "Préférences enregistrées",
|
||||||
|
update_failed: "Échec de sauvegarde",
|
||||||
|
},
|
||||||
|
schedule_presets: {
|
||||||
|
tab_title: "horaire",
|
||||||
|
selected_schedule: "Horaire Sélectionné",
|
||||||
|
new_preset: "Construire un nouvel horaire",
|
||||||
},
|
},
|
||||||
errors: {
|
errors: {
|
||||||
must_enter_birthdate: "Vous devez entrer une date de naissance valide",
|
must_enter_birthdate: "Vous devez entrer une date de naissance valide",
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
shared: {
|
shared: {
|
||||||
error: {
|
error: {
|
||||||
no_data_found: "aucune donnée à afficher",
|
no_data_found: 'aucune donnée à afficher',
|
||||||
no_search_results: "aucun résultat ne correspond à la recherche",
|
no_search_results: 'aucun résultat ne correspond à la recherche',
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
search: "recherche",
|
search: 'recherche',
|
||||||
filter: "filtres",
|
filter: "filtres",
|
||||||
loading: "chargement en cours...",
|
loading: 'chargement en cours...',
|
||||||
language: "langue",
|
language: 'langue',
|
||||||
add: "ajouter",
|
add: "ajouter",
|
||||||
save: "sauvegarder",
|
save: "sauvegarder",
|
||||||
remove: "supprimer",
|
remove: "supprimer",
|
||||||
|
|
@ -91,6 +232,8 @@ export default {
|
||||||
update: "mettre à jour",
|
update: "mettre à jour",
|
||||||
modify: "modifier",
|
modify: "modifier",
|
||||||
close: "fermer",
|
close: "fermer",
|
||||||
|
download: "télécharger",
|
||||||
|
open: "ouvrir",
|
||||||
},
|
},
|
||||||
misc: {
|
misc: {
|
||||||
or: "ou",
|
or: "ou",
|
||||||
|
|
@ -113,28 +256,33 @@ export default {
|
||||||
remote: "télétravail",
|
remote: "télétravail",
|
||||||
},
|
},
|
||||||
weekday: {
|
weekday: {
|
||||||
sunday: "dimanche",
|
sun: "dimanche",
|
||||||
monday: "lundi",
|
mon: "lundi",
|
||||||
tuesday: "mardi",
|
tue: "mardi",
|
||||||
wednesday: "mercredi",
|
wed: "mercredi",
|
||||||
thursday: "jeudi",
|
thu: "jeudi",
|
||||||
friday: "vendredi",
|
fri: "vendredi",
|
||||||
saturday: "samedi",
|
sat: "samedi",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
timesheet: {
|
timesheet: {
|
||||||
page_header: "Carte de temps",
|
page_header: "Carte de temps",
|
||||||
|
week: "semaine",
|
||||||
|
total_hours: "heures totales: ",
|
||||||
|
total_expenses: "dépenses totales: ",
|
||||||
|
vacation_available: "vacances disponibles: ",
|
||||||
|
sick_available: "congés maladie disponible: ",
|
||||||
|
current_shifts: "quarts entrées",
|
||||||
|
apply_preset: "auto-remplir",
|
||||||
|
apply_preset_day: "Appliquer horaire pour la journée",
|
||||||
|
apply_preset_week: "Appliquer horaire pour la semaine",
|
||||||
nav_button: {
|
nav_button: {
|
||||||
calendar_date_picker: "Calendrier",
|
calendar_date_picker: "Calendrier",
|
||||||
current_week: "Semaine actuelle",
|
current_week: "Semaine actuelle",
|
||||||
next_week: "Prochaine semaine",
|
next_week: "Prochaine période",
|
||||||
previous_week: "Semaine précédente",
|
previous_week: "Période précédente",
|
||||||
},
|
},
|
||||||
save_button: "Enregistrer",
|
|
||||||
cancel_button: "Annuler",
|
|
||||||
remote_button: "Télétravail",
|
|
||||||
delete_button: "Supprimer",
|
|
||||||
shift: {
|
shift: {
|
||||||
actions: {
|
actions: {
|
||||||
add: "Ajouter un Quart",
|
add: "Ajouter un Quart",
|
||||||
|
|
@ -152,14 +300,7 @@ export default {
|
||||||
SICK: "Maladie",
|
SICK: "Maladie",
|
||||||
VACATION: "Vacance",
|
VACATION: "Vacance",
|
||||||
REMOTE: "Télétravail",
|
REMOTE: "Télétravail",
|
||||||
},
|
OFFICE: "Bureau",
|
||||||
errors: {
|
|
||||||
not_found: "Aucun quart trouvé",
|
|
||||||
overlap: "Il y a un chevauchement entre deux ou plusieurs quarts",
|
|
||||||
invalid: "Entrée du quart invalide",
|
|
||||||
unknown: "Erreur inconnue",
|
|
||||||
comment_required: "un commentaire est requis",
|
|
||||||
comment_too_long: "votre commentaire est trop long",
|
|
||||||
},
|
},
|
||||||
fields: {
|
fields: {
|
||||||
start: "Début (HH:mm)",
|
start: "Début (HH:mm)",
|
||||||
|
|
@ -169,29 +310,16 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expense: {
|
expense: {
|
||||||
add_expense: "Ajouter une dépense",
|
add_expense: 'Ajouter une dépense',
|
||||||
amount: "Montant",
|
amount: 'Montant',
|
||||||
date: "Date",
|
date: 'Date',
|
||||||
empty_list: "Aucun dépense enregistrée",
|
empty_list: 'Aucun dépense enregistrée',
|
||||||
employee_comment: "Commentaire",
|
employee_comment: 'Commentaire',
|
||||||
supervisor_comment: "Note du Superviseur",
|
supervisor_comment: 'Note du Superviseur',
|
||||||
errors: {
|
|
||||||
date_required_or_invalid: "La date est manquante ou invalide",
|
|
||||||
comment_required: "un commentaire est requis",
|
|
||||||
comment_too_long: "votre commentaire est trop long",
|
|
||||||
amount_must_be_positive: "le montant doit être suppérieur à 0$",
|
|
||||||
mileave_must_be_positive: "le kilométrage doit être suppérieur à 0",
|
|
||||||
amount_xor_mileage:
|
|
||||||
"Vous ne pouvez pas saisir un montant et un kilométrage pour une même dépense",
|
|
||||||
mileage_required_for_type:
|
|
||||||
"Vous devez entrer une valeur en kilométrage pour ce type de dépense",
|
|
||||||
amount_required_for_type:
|
|
||||||
"Vous devez entrer une valeur en montant $ pour ce type de dépense",
|
|
||||||
},
|
|
||||||
hints: {
|
hints: {
|
||||||
amount_or_mileage: "Soit dépense ou kilométrage, pas les deux",
|
amount_or_mileage: "Soit dépense ou kilométrage, pas les deux",
|
||||||
comment_required: "un commentaire est requis",
|
comment_required: "un commentaire est requis",
|
||||||
attach_file: "Pièce jointe",
|
attach_file: "Pièce jointe"
|
||||||
},
|
},
|
||||||
mileage: "Kilométrage",
|
mileage: "Kilométrage",
|
||||||
open_btn: "Liste des Dépenses",
|
open_btn: "Liste des Dépenses",
|
||||||
|
|
@ -203,33 +331,142 @@ export default {
|
||||||
PER_DIEM: "Per diem",
|
PER_DIEM: "Per diem",
|
||||||
EXPENSES: "dépense",
|
EXPENSES: "dépense",
|
||||||
MILEAGE: "kilométrage",
|
MILEAGE: "kilométrage",
|
||||||
PRIME_GARDE: "Prime de garde",
|
ON_CALL: "Prime de garde",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
errors: {
|
||||||
|
INVALID_SHIFT_TIME: "Les heures d'entrée et de sortie sont inversées",
|
||||||
|
SHIFT_OVERLAP: "Il y a un chevauchement entre deux ou plusieurs quarts",
|
||||||
|
SHIFT_OVERLAP_SHORT: "Chevauchement",
|
||||||
|
INVALID_SHIFT: "Un quart de travail contient des données manquantes ou corrompues",
|
||||||
|
SHIFT_TIME_REQUIRED: "Heures manquantes",
|
||||||
|
SHIFT_TYPE_REQUIRED: "Type requis",
|
||||||
|
SHIFT_NOT_FOUND: "Quart de travail manquant ou supprimé",
|
||||||
|
PAY_PERIOD_NOT_FOUND: "Aucune période de paie ne correspond aux dates fournies",
|
||||||
|
EMPLOYEE_NOT_FOUND: "Aucun employé ne correspond aux détails de votre connexion",
|
||||||
|
INVALID_TIMESHEET: "Une feuille de temps contient des données manquantes ou corrompues",
|
||||||
|
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",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
timesheet_approvals: {
|
timesheet_approvals: {
|
||||||
page_title: "Validation cartes de temps",
|
page_title: "Validation cartes de temps",
|
||||||
|
chart: {
|
||||||
|
hours_worked_title: "heures travaillées",
|
||||||
|
expenses_title: "dépenses encourues"
|
||||||
|
},
|
||||||
|
print_report: {
|
||||||
|
title: "options de téléchargement",
|
||||||
|
description: "Choisissez ce qui sera inclu dans le rapport",
|
||||||
|
company: "compagnies",
|
||||||
|
type: "types de données",
|
||||||
|
shifts: "quarts de travail",
|
||||||
|
expenses: "dépenses",
|
||||||
|
options: "options",
|
||||||
|
},
|
||||||
table: {
|
table: {
|
||||||
full_name: "nom complet",
|
full_name: "nom complet",
|
||||||
email: "courriel",
|
email: "courriel",
|
||||||
|
is_approved: "approuvé",
|
||||||
expenses: "dépenses",
|
expenses: "dépenses",
|
||||||
mileage: "kilométrage",
|
mileage: "kilométrage",
|
||||||
verified: "approuvé",
|
verified: "approuvé",
|
||||||
unverified: "à vérifier",
|
unverified: "à vérifier",
|
||||||
},
|
inactive: "inactif",
|
||||||
chart: {
|
regular: "régulier",
|
||||||
hours_worked_title: "heures travaillées",
|
evening: "soir",
|
||||||
expenses_title: "dépenses encourues",
|
emergency: "urgence",
|
||||||
},
|
overtime: "supplémentaire",
|
||||||
print_report: {
|
holiday: "férié",
|
||||||
company: "compagnie",
|
vacation: "vacances",
|
||||||
type: "types de données",
|
sick: "maladie",
|
||||||
shifts: "quarts de travail",
|
remote: "télétravail",
|
||||||
expenses: "dépenses",
|
weekly_hours_1: "heures semaine 1",
|
||||||
|
weekly_hours_2: "heures semaine 2",
|
||||||
|
total_hours: "heures totales",
|
||||||
|
filter_active: "montrer les employés inactifs",
|
||||||
|
filter_team: "montrer mon équipe seulement",
|
||||||
|
filter_columns: "informations affichés",
|
||||||
},
|
},
|
||||||
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: {
|
||||||
|
dashboard: {
|
||||||
|
menu: "Pour accéder au menu principal, cliquez sur le bouton situé dans le coin supérieur gauche. Ce menu vous permet de naviguer à travers l'ensemble de l'application.",
|
||||||
|
news_feed: "Des annonces générales et des points importants y sont présentés. Cet affichage est identique pour l'ensemble des employés.",
|
||||||
|
notifications: "Les notifications sont accessibles via la clochette située dans le coin supérieur droit. Elles permettent de consulter rapidement les informations qui vous concernent, \
|
||||||
|
par exemple : les demandes de congé, de vacances ou d'absence, les heures supplémentaires effectuées durant la semaine courante, \
|
||||||
|
ainsi que les commentaires laissés par votre superviseur.",
|
||||||
|
chat_bot: "Pour accéder au robot, il suffit de cliquer sur la bulle « robot ». Le robot conversationnel vous permet de trouver rapidement de l'information sur un client, \
|
||||||
|
une facture ou un appareil.",
|
||||||
|
},
|
||||||
|
personal_profile: {
|
||||||
|
personal_info: "Dans l'onglet « Personnel », vous pouvez consulter vos informations personnelles, telles que votre nom, vos numéros de téléphone, \
|
||||||
|
votre adresse et votre date de naissance.",
|
||||||
|
professional_info: "Dans l'onglet « Carrière », vous pouvez consulter vos informations professionnelles, comme votre poste, le nom de l'entreprise pour laquelle vous travaillez, \
|
||||||
|
le nom et l'adresse courriel de votre superviseur, ainsi que votre date d'embauche.",
|
||||||
|
preferences: "Dans l'onglet « Préférences », vous pouvez ajuster certains paramètres selon vos préférences personnelles, tels que le mode sombre, \
|
||||||
|
la langue et les notifications.",
|
||||||
|
},
|
||||||
|
timesheets: {
|
||||||
|
create_shift: "Pour ajouter un quart de travail, cliquez sur l'onglet vert « Ajouter du temps » et saisissez les informations obligatoires suivantes : \
|
||||||
|
le type de quart, l'heure de début, l'heure de fin, ainsi que le mode de travail (présentiel ou télétravail). \
|
||||||
|
Cliquez ensuite sur « Sauvegarder », situé dans le coin supérieur droit de la carte de temps.",
|
||||||
|
update_shift: "Pour modifier un quart de travail, cliquez sur l'information à modifier, ajustez la valeur souhaitée, puis cliquez sur « Sauvegarder », \
|
||||||
|
situé dans le coin supérieur droit de la carte de temps.",
|
||||||
|
delete_shift: "Pour supprimer un quart de travail, cliquez sur l'icône de poubelle rouge associée à l'entrée.",
|
||||||
|
comment_shift: "Pour laisser un commentaire sur un quart de travail, cliquez sur l'icône de bulle conversationnelle située à droite de l'entrée du quart de travail, \
|
||||||
|
puis cliquez sur « Sauvegarder », dans le coin supérieur droit de la carte de temps.",
|
||||||
|
create_expense: "Pour ajouter une dépense, accédez à la liste des dépenses à l'aide du bouton situé dans le coin supérieur droit. \
|
||||||
|
Vous devez ensuite remplir les champs suivants : la date (la date du jour est sélectionnée par défaut), le type de dépense, \
|
||||||
|
le montant ou le kilométrage effectué, un commentaire justifiant la dépense, et joindre une pièce justificative. \
|
||||||
|
Cliquez ensuite sur le bouton « Ajouter ».",
|
||||||
|
update_expense: "Pour modifier une dépense, accédez à la liste des dépenses, sélectionnez la dépense à modifier, apportez les changements nécessaires, \
|
||||||
|
puis cliquez sur « Modifier » pour sauvegarder.",
|
||||||
|
delete_expense: "Pour supprimer une dépense, accédez à la liste des dépenses et cliquez sur l'icône de poubelle rouge.",
|
||||||
|
},
|
||||||
|
employee_list: {
|
||||||
|
terminated_employees: "Cette option vous permet d'afficher ou de masquer les employés qui ne sont plus à l'emploi.",
|
||||||
|
},
|
||||||
|
employee_management: {
|
||||||
|
create_employee: "Pour créer un employé, accédez au menu « Ajouter un employé », situé dans le coin supérieur gauche de la liste des employés. \
|
||||||
|
Dans l'onglet « Détails », vous devez d'abord saisir l'ensemble des informations requises, à l'exception de la date de départ. \
|
||||||
|
Ensuite, attribuez les accès nécessaires à l'employé. Optionnellement, vous pouvez lui assigner un horaire préfabriqué. \
|
||||||
|
Cliquez sur « Sauvegarder » pour confirmer la création de l'employé.",
|
||||||
|
update_employee: "Pour mettre à jour les informations, les accès ou l'horaire d'un employé, sélectionnez son portrait et naviguez vers l'onglet approprié. \
|
||||||
|
Apportez les modifications nécessaires, puis cliquez sur « Mettre à jour » pour confirmer.",
|
||||||
|
module_access: "Pour gérer les accès aux différentes parties de l'application, sélectionnez le portrait de l'employé désiré, puis accédez à l'onglet « Accès ». \
|
||||||
|
Deux options sont disponibles : par rôle ou par module. Si l'employé est superviseur, le rôle « Administrateur » est suggéré. \
|
||||||
|
Pour un employé standard, le rôle « Employé » est recommandé. Il est également possible de sélectionner des modules spécifiques dans certains cas.",
|
||||||
|
schedule_preset: "Pour attribuer, modifier ou créer un horaire pour un employé, sélectionnez d'abord l'employé, puis accédez à l'onglet « Horaire » du menu de modification. \
|
||||||
|
Vous pouvez choisir un horaire existant, créer un nouvel horaire en lui donnant un nom unique, ou copier un horaire existant, le modifier et le renommer. \
|
||||||
|
Une fois satisfait, cliquez sur « Mettre à jour » pour confirmer votre choix.",
|
||||||
|
terminating_employee: "Pour mettre fin à l'emploi d'un employé, sélectionnez son portrait (ou sa ligne). Une fois le menu de modification affiché, \
|
||||||
|
entrez la date de départ et cliquez sur « Mettre à jour ».",
|
||||||
|
},
|
||||||
|
timesheets_approval: {
|
||||||
|
approval: "Pour approuver une feuille de temps, cliquez sur le bas de la carte où se trouve l'icône de cadenas. En mode liste, \
|
||||||
|
cliquez sur le cadenas situé à droite de la ligne correspondante.",
|
||||||
|
inspect: "Pour consulter les informations relatives aux quarts de travail ou aux dépenses d'un employé, cliquez sur l'icône de valise située dans le coin supérieur droit. \
|
||||||
|
Vous y trouverez des statistiques sur les heures travaillées ainsi que les dépenses effectuées. Dans cette fenêtre, il est possible de modifier des quarts de travail \
|
||||||
|
et des dépenses. Cliquez sur « Sauvegarder » pour confirmer les modifications.",
|
||||||
|
comment_expense: "Pour laisser un commentaire sur une dépense soumise par un employé, cliquez sur l'icône de valise associée à la carte de l'employé désiré. \
|
||||||
|
Accédez à la liste des dépenses et cliquez sur la bulle de commentaire du superviseur. \
|
||||||
|
Cliquez sur « Sauvegarder » pour confirmer votre commentaire.",
|
||||||
|
},
|
||||||
|
shared: {
|
||||||
|
display: "Cette option permet de choisir le mode d'affichage qui vous convient le mieux, soit par carte ou sous forme de liste détaillée.",
|
||||||
|
search: "Il est possible d'effectuer une recherche avancée par mots-clés. Il suffit de séparer chaque mot par un espace, et la barre de recherche affichera \
|
||||||
|
les résultats contenant l'ensemble des mots-clés saisis.",
|
||||||
|
calendar: "Le calendrier facilite la navigation. Il vous permet de sélectionner une date précise et d'afficher la période de paie qui inclut cette date.",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -1,10 +1,20 @@
|
||||||
|
<script
|
||||||
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
|
const CREATE_YEAR = 2025;
|
||||||
|
const today = new Date();
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-footer
|
<q-footer
|
||||||
elevated
|
elevated
|
||||||
class="bg-primary text-white"
|
class="bg-primary text-white"
|
||||||
>
|
>
|
||||||
<q-toolbar>
|
<div class="q-px-md q-py-xs full-width text-right">
|
||||||
<q-toolbar-title>© 2025 Targo Communications inc.</q-toolbar-title>
|
<span class="text-weight-light text-caption text-uppercase">
|
||||||
</q-toolbar>
|
© {{ CREATE_YEAR }} - {{ today.getFullYear() }} Targo Communications inc.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</q-footer>
|
</q-footer>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,29 @@
|
||||||
<script setup lang="ts">
|
<script
|
||||||
import { useAuthStore } from 'src/stores/auth-store';
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
|
||||||
const currentUser = authStore.user;
|
|
||||||
|
|
||||||
// Will need to implement this eventually, just testing the look for now
|
// Will need to implement this eventually, just testing the look for now
|
||||||
const notifAmount = ref(7);
|
const notification_count = ref(7);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-item clickable v-ripple dark class="q-pa-none">
|
<q-btn
|
||||||
<q-item-section :side="$q.screen.gt.sm">
|
flat
|
||||||
<q-avatar rounded >
|
transparent
|
||||||
<q-img src="src/assets/targo-default-avatar.png" />
|
dense
|
||||||
<q-badge floating color="negative" v-if="notifAmount > 0" >{{ notifAmount }}</q-badge>
|
:icon="notification_count > 0 ? 'notifications_active' : 'notifications_off'"
|
||||||
</q-avatar>
|
size="lg"
|
||||||
</q-item-section>
|
color="white"
|
||||||
|
>
|
||||||
<q-item-section v-if="$q.screen.gt.sm">
|
<q-badge
|
||||||
<q-item-label>{{ currentUser.firstName }} {{ currentUser.lastName }}</q-item-label>
|
v-if="notification_count > 0"
|
||||||
<q-item-label caption>{{ notifAmount }} new messages</q-item-label>
|
floating
|
||||||
</q-item-section>
|
color="negative"
|
||||||
</q-item>
|
class="text-weight-bolder q-mt-xs"
|
||||||
|
>
|
||||||
|
{{ notification_count }}
|
||||||
|
</q-badge>
|
||||||
|
</q-btn>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -1,22 +1,25 @@
|
||||||
<script lang="ts" setup>
|
<script
|
||||||
import { useUiStore } from 'src/stores/ui-store';
|
lang="ts"
|
||||||
import HeaderBarNotification from './main-layout-header-bar-notification.vue';
|
setup
|
||||||
import Chatbutton from "src/modules/chatbot/components/chat-button.vue";
|
>
|
||||||
|
import { useUiStore } from 'src/stores/ui-store';
|
||||||
|
// import HeaderBarNotification from './main-layout-header-bar-notification.vue';
|
||||||
|
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUiStore();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-header elevated>
|
<q-header elevated>
|
||||||
<q-toolbar>
|
<q-toolbar class="q-px-sm">
|
||||||
<q-toolbar-title>
|
<q-toolbar-title>
|
||||||
<q-btn
|
<q-btn
|
||||||
flat
|
flat
|
||||||
dense
|
dense
|
||||||
color="white"
|
color="white"
|
||||||
icon="menu"
|
|
||||||
@click="uiStore.toggleRightDrawer"
|
@click="uiStore.toggleRightDrawer"
|
||||||
|
class="q-px-none"
|
||||||
>
|
>
|
||||||
|
<q-icon name="menu" size="lg" class="q-mr-lg"/>
|
||||||
<q-img
|
<q-img
|
||||||
src="src/assets/logo-targo-white.svg"
|
src="src/assets/logo-targo-white.svg"
|
||||||
fit="contain"
|
fit="contain"
|
||||||
|
|
@ -26,12 +29,7 @@ const uiStore = useUiStore();
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</q-toolbar-title>
|
</q-toolbar-title>
|
||||||
<q-item class="q-pa-none">
|
<q-item class="q-pa-none">
|
||||||
|
<!-- <HeaderBarNotification /> -->
|
||||||
<Chatbutton>
|
|
||||||
</Chatbutton>
|
|
||||||
|
|
||||||
<HeaderBarNotification />
|
|
||||||
|
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-toolbar>
|
</q-toolbar>
|
||||||
</q-header>
|
</q-header>
|
||||||
|
|
|
||||||
|
|
@ -1,175 +1,113 @@
|
||||||
<script setup lang="ts">
|
<script
|
||||||
import { useRouter } from 'vue-router';
|
setup
|
||||||
import { useAuthStore } from 'src/stores/auth-store';
|
lang="ts"
|
||||||
import { useUiStore } from 'src/stores/ui-store';
|
>
|
||||||
import { ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import { RouteNames } from 'src/router/router-constants';
|
import { useQuasar } from 'quasar';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { useAuthStore } from 'src/stores/auth-store';
|
||||||
|
import { useUiStore } from 'src/stores/ui-store';
|
||||||
|
import { RouteNames } from 'src/router/router-constants';
|
||||||
|
import { ModuleNames, type UserModuleAccess } from 'src/modules/shared/models/user.models';
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const DRAWER_BUTTONS: { i18n_key: string, icon: string, route: RouteNames, required_module?: UserModuleAccess }[] = [
|
||||||
const uiStore = useUiStore();
|
{ i18n_key: 'nav_bar.home', icon: "home", route: RouteNames.DASHBOARD, required_module: ModuleNames.DASHBOARD },
|
||||||
const router = useRouter();
|
{ i18n_key: 'nav_bar.timesheet_approvals', icon: "event_available", route: RouteNames.TIMESHEET_APPROVALS, required_module: ModuleNames.TIMESHEETS_APPROVAL },
|
||||||
const miniState = ref(true);
|
{ i18n_key: 'nav_bar.employee_list', icon: "groups", route: RouteNames.EMPLOYEE_LIST, required_module: ModuleNames.EMPLOYEE_LIST },
|
||||||
|
{ i18n_key: 'nav_bar.timesheet', icon: "punch_clock", route: RouteNames.TIMESHEET, required_module: ModuleNames.TIMESHEETS },
|
||||||
|
{ i18n_key: 'nav_bar.profile', icon: "account_box", route: RouteNames.PROFILE, required_module: ModuleNames.PERSONAL_PROFILE },
|
||||||
|
{ i18n_key: 'nav_bar.help', icon: "contact_support", route: RouteNames.HELP },
|
||||||
|
]
|
||||||
|
|
||||||
|
const q = useQuasar();
|
||||||
|
const auth_store = useAuthStore();
|
||||||
|
const ui_store = useUiStore();
|
||||||
|
const router = useRouter();
|
||||||
|
const is_mini = ref(true);
|
||||||
|
|
||||||
const goToPageName = (pageName: string) => {
|
const onClickDrawerPage = (page_name: RouteNames) => {
|
||||||
router.push({ name: pageName }).catch(err => {
|
is_mini.value = true;
|
||||||
console.error('Error with Vue Router: ', err);
|
|
||||||
|
router.push({ name: page_name }).catch(error => {
|
||||||
|
console.error('failed to reach page: ', error);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
authStore.logout();
|
auth_store.logout();
|
||||||
|
|
||||||
router.push({ name: 'login' }).catch(err => {
|
router.push({ name: 'login' }).catch(err => {
|
||||||
console.log('could not log you out: ', err);
|
console.error('could not log you out: ', err);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (q.platform.is.mobile) {
|
||||||
|
ui_store.is_left_drawer_open = false;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-drawer
|
<q-drawer
|
||||||
v-model="uiStore.isRightDrawerOpen"
|
v-model="ui_store.is_left_drawer_open"
|
||||||
overlay
|
:persistent="!$q.platform.is.mobile"
|
||||||
|
mini-to-overlay
|
||||||
elevated
|
elevated
|
||||||
side="left"
|
side="left"
|
||||||
:mini="miniState"
|
:mini="is_mini"
|
||||||
@mouseenter="miniState = false"
|
@mouseenter="is_mini = false"
|
||||||
@mouseleave="miniState = true"
|
@mouseleave="is_mini = true"
|
||||||
class="bg-dark"
|
class="bg-dark z-max"
|
||||||
>
|
>
|
||||||
<q-scroll-area class="fit">
|
<q-scroll-area class="column fit">
|
||||||
<q-list>
|
<div
|
||||||
<!-- Home -->
|
v-for="button, index in DRAWER_BUTTONS"
|
||||||
<q-item
|
:key="index"
|
||||||
v-ripple
|
v-show="button.required_module ?? true"
|
||||||
clickable
|
@click="onClickDrawerPage(button.route)"
|
||||||
side
|
>
|
||||||
@click="goToPageName(RouteNames.DASHBOARD)"
|
<div
|
||||||
|
v-if="button.required_module ? auth_store.user?.user_module_access.includes(button.required_module) : true"
|
||||||
|
class="row items-center full-width q-py-sm cursor-pointer"
|
||||||
|
:class="$router.currentRoute.value.name === button.route ? ($q.dark.isActive ? 'bg-green-10' : 'bg-green-2') : ''"
|
||||||
>
|
>
|
||||||
<q-item-section avatar>
|
|
||||||
<q-icon
|
<q-icon
|
||||||
name="home"
|
:name="button.icon"
|
||||||
color="primary"
|
color="accent"
|
||||||
|
size="lg"
|
||||||
|
class="col-auto q-pl-sm"
|
||||||
/>
|
/>
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label class="text-uppercase text-weight-bold">{{ $t('nav_bar.home') }}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
|
|
||||||
<!-- Timesheet Validation -- Supervisor and Accounting only -->
|
<div
|
||||||
<q-item
|
class="col text-uppercase text-weight-bold text-h6 q-pl-sm"
|
||||||
v-ripple
|
:class="$q.platform.is.mobile ? '' : 'q-mini-drawer-hide'"
|
||||||
clickable
|
|
||||||
side
|
|
||||||
@click="goToPageName(RouteNames.TIMESHEET_APPROVALS)"
|
|
||||||
v-if="['supervisor', 'accounting'].includes(authStore.user.role)"
|
|
||||||
>
|
>
|
||||||
<q-item-section avatar>
|
{{ $t(button.i18n_key) }}
|
||||||
<q-icon
|
</div>
|
||||||
name="event_available"
|
</div>
|
||||||
color="primary"
|
</div>
|
||||||
/>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label class="text-uppercase text-weight-bold">{{ $t('nav_bar.timesheet_approvals')
|
|
||||||
}}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
|
|
||||||
<!-- Employee List -- Supervisor, Accounting and HR only -->
|
<q-separator spaced />
|
||||||
<q-item
|
|
||||||
v-ripple
|
|
||||||
clickable
|
|
||||||
side
|
|
||||||
@click="goToPageName(RouteNames.EMPLOYEE_LIST)"
|
|
||||||
v-if="['supervisor', 'accounting', 'human_resources'].includes(authStore.user.role)"
|
|
||||||
>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-icon
|
|
||||||
name="view_list"
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label class="text-uppercase text-weight-bold">{{ $t('nav_bar.employee_list')
|
|
||||||
}}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
|
|
||||||
<!-- Employee Timesheet temp -- Employee, Supervisor, Accounting only -->
|
<div
|
||||||
<q-item
|
class="row items-center full-width cursor-pointer q-py-sm"
|
||||||
v-ripple
|
|
||||||
clickable
|
|
||||||
side
|
|
||||||
@click="goToPageName(RouteNames.TIMESHEET_TEMP)"
|
|
||||||
v-if="['supervisor', 'accounting', 'employee'].includes(authStore.user.role)"
|
|
||||||
>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-icon
|
|
||||||
name="punch_clock"
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label class="text-uppercase text-weight-bold">{{ $t('nav_bar.timesheet')
|
|
||||||
}}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
|
|
||||||
<!-- Profile -->
|
|
||||||
<q-item
|
|
||||||
v-ripple
|
|
||||||
clickable
|
|
||||||
side
|
|
||||||
@click="goToPageName(RouteNames.PROFILE)"
|
|
||||||
>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-icon
|
|
||||||
name="account_box"
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label class="text-uppercase text-weight-bold">{{ $t('nav_bar.profile') }}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
|
|
||||||
<!-- Help -->
|
|
||||||
<q-item
|
|
||||||
v-ripple
|
|
||||||
clickable
|
|
||||||
@click="goToPageName('help')"
|
|
||||||
>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-icon
|
|
||||||
name="contact_support"
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label class="text-uppercase text-weight-bold">{{ $t('nav_bar.help') }}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
|
|
||||||
<!-- Logout -->
|
|
||||||
<q-item
|
|
||||||
v-ripple
|
|
||||||
clickable
|
|
||||||
@click="handleLogout"
|
@click="handleLogout"
|
||||||
class="absolute-bottom"
|
|
||||||
>
|
>
|
||||||
<q-item-section avatar>
|
|
||||||
<q-icon
|
<q-icon
|
||||||
name="exit_to_app"
|
name="exit_to_app"
|
||||||
color="primary"
|
color="accent"
|
||||||
|
size="lg"
|
||||||
|
class="col-auto q-pl-sm"
|
||||||
/>
|
/>
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
<div
|
||||||
<q-item-label class="text-uppercase text-weight-bold">{{ $t('nav_bar.logout') }}</q-item-label>
|
class="col text-uppercase text-weight-bold text-h6 q-pl-sm"
|
||||||
</q-item-section>
|
:class="$q.platform.is.mobile ? '' : 'q-mini-drawer-hide'"
|
||||||
</q-item>
|
>
|
||||||
|
{{ $t('nav_bar.logout') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</q-scroll-area>
|
</q-scroll-area>
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -1,20 +1,44 @@
|
||||||
<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 HeaderBar from 'src/layouts/components/main-layout-header-bar.vue';
|
||||||
import ChatbotPage from "src/modules/chatbot/pages/chatbot-page.vue";
|
import FooterBar from 'src/layouts/components/main-layout-footer-bar.vue';
|
||||||
|
import LeftDrawer from 'src/layouts/components/main-layout-left-drawer.vue';
|
||||||
|
|
||||||
|
import { onMounted, watch, ref } from 'vue';
|
||||||
|
import { RouterView } from 'vue-router';
|
||||||
|
import { useUiStore } from 'src/stores/ui-store';
|
||||||
|
|
||||||
|
|
||||||
|
const ui_store = useUiStore();
|
||||||
|
const user_preferences = ref(ui_store.user_preferences);
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (ui_store.user_preferences.id === -1) {
|
||||||
|
await ui_store.getUserPreferences();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(user_preferences, async () => {
|
||||||
|
if (ui_store.user_preferences.id !== -1) {
|
||||||
|
await ui_store.updateUserPreferences();
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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 />
|
||||||
<ChatbotPage />
|
|
||||||
</q-page-container>
|
</q-page-container>
|
||||||
<FooterBar />
|
|
||||||
|
<FooterBar v-if="!$q.platform.is.mobile" />
|
||||||
</q-layout>
|
</q-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,33 @@
|
||||||
<script setup lang="ts">
|
<script
|
||||||
import { computed, ref } from 'vue';
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
|
import { computed } from 'vue';
|
||||||
import { useAuthApi } from 'src/modules/auth/composables/use-auth-api';
|
import { useAuthApi } from 'src/modules/auth/composables/use-auth-api';
|
||||||
|
import LoginRockPaperScissor from 'src/modules/auth/components/login-rock-paper-scissor.vue';
|
||||||
|
|
||||||
const auth_api = useAuthApi();
|
const auth_api = useAuthApi();
|
||||||
|
|
||||||
const email = defineModel<string>('email', { default: '', });
|
const email = defineModel<string>('email', { default: '', });
|
||||||
const is_remembered = ref<boolean>(false);
|
// const is_remembered = ref<boolean>(false);
|
||||||
const is_employee_email = computed( () => email.value.includes('@targ'));
|
const is_employee_email = computed(() => email.value.includes('@targ'));
|
||||||
|
const is_game_time = computed(() => email.value.includes('allumette'));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<q-card class="rounded-15">
|
<q-card
|
||||||
<q-card-section class="text-center bg-primary q-pa-lg">
|
bordered
|
||||||
<q-img src="/src/assets/logo-targo-white.svg" ratio="4.6" fit="contain" />
|
class="rounded-15 shadow-10 full-width"
|
||||||
</q-card-section>
|
>
|
||||||
|
<div class="text-center bg-primary q-pa-lg">
|
||||||
|
<q-img
|
||||||
|
src="/src/assets/logo-targo-white.svg"
|
||||||
|
ratio="4.6"
|
||||||
|
fit="contain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="q-pt-sm q-px-xl q-pb-lg">
|
<div class="q-pt-sm q-px-xl q-pb-lg ">
|
||||||
<q-card-section class="text-center text-uppercase">
|
<q-card-section class="text-center text-uppercase">
|
||||||
<div class="text-h6 text-weight-bold">
|
<div class="text-h6 text-weight-bold">
|
||||||
{{ $t('login.page_header') }}
|
{{ $t('login.page_header') }}
|
||||||
|
|
@ -27,17 +39,36 @@
|
||||||
v-model="email"
|
v-model="email"
|
||||||
dense
|
dense
|
||||||
outlined
|
outlined
|
||||||
label-color="primary"
|
color="accent"
|
||||||
:label="$t('login.email')"
|
label-color="accent"
|
||||||
/>
|
class="rounded-5 inset-shadow bg-white"
|
||||||
|
label-slot
|
||||||
<q-card-section class="q-ma-none q-pa-none text-uppercase text-caption text-weight-medium">
|
input-class="text-h6 text-primary"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<span class="text-weight-bolder text-uppercase text-overline"> {{ $t('login.email') }} </span>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
<!-- Stay-logged-in section, removed temporarly until customer module is up -->
|
||||||
|
<!-- <q-card-section
|
||||||
|
horizontal
|
||||||
|
class="q-mb-md q-pa-none text-uppercase text-caption text-weight-medium"
|
||||||
|
>
|
||||||
<q-toggle
|
<q-toggle
|
||||||
v-model="is_remembered"
|
v-model="is_remembered"
|
||||||
color="primary"
|
size="sm"
|
||||||
:label="$t('login.button.remember_me')"
|
color="accent"
|
||||||
|
class="col-auto"
|
||||||
/>
|
/>
|
||||||
</q-card-section>
|
<span
|
||||||
|
:key="is_remembered ? 'yep' : 'nope'"
|
||||||
|
class="col-auto text-weight-bold self-center q-ml-sm"
|
||||||
|
:class="is_remembered ? 'text-accent' : ''"
|
||||||
|
>
|
||||||
|
{{ $t('login.button.remember_me') }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
</q-card-section> -->
|
||||||
|
|
||||||
<q-card-actions>
|
<q-card-actions>
|
||||||
<q-btn
|
<q-btn
|
||||||
|
|
@ -45,9 +76,9 @@
|
||||||
rounded
|
rounded
|
||||||
disabled
|
disabled
|
||||||
type="submit"
|
type="submit"
|
||||||
color="primary"
|
color="grey-5"
|
||||||
:label="$t('login.button.connect')"
|
:label="$t('login.button.connect')"
|
||||||
class="full-width"
|
class="full-width q-mt-lg"
|
||||||
/>
|
/>
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
|
|
||||||
|
|
@ -58,9 +89,18 @@
|
||||||
</q-form>
|
</q-form>
|
||||||
|
|
||||||
<q-card-section class="row q-pt-sm">
|
<q-card-section class="row q-pt-sm">
|
||||||
<q-separator color="primary" class="col self-center"/>
|
<q-separator
|
||||||
<span class="col text-primary text-weight-bolder text-center text-uppercase self-center">{{ $t('shared.misc.or') }}</span>
|
size="2px"
|
||||||
<q-separator color="primary" class="col self-center"/>
|
color="accent"
|
||||||
|
class="col self-center"
|
||||||
|
/>
|
||||||
|
<span class="col text-accent text-weight-bolder text-center text-uppercase self-center">{{
|
||||||
|
$t('shared.misc.or') }}</span>
|
||||||
|
<q-separator
|
||||||
|
size="2px"
|
||||||
|
color="accent"
|
||||||
|
class="col self-center"
|
||||||
|
/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
<q-card-section class="column q-px-sm q-pt-none">
|
<q-card-section class="column q-px-sm q-pt-none">
|
||||||
|
|
@ -68,12 +108,15 @@
|
||||||
rounded
|
rounded
|
||||||
push
|
push
|
||||||
disabled
|
disabled
|
||||||
color="fb-blue"
|
color="blue-grey-7"
|
||||||
icon="img:src/assets/Facebook-f_Logo-White-Logo.wine.svg"
|
icon="img:src/assets/Facebook-f_Logo-White-Logo.wine.svg"
|
||||||
:label="$t('login.button.facebook')"
|
:label="$t('login.button.facebook')"
|
||||||
class="full-width row q-mb-sm"
|
class="full-width row q-mb-sm"
|
||||||
>
|
>
|
||||||
<q-tooltip anchor="top middle" class="bg-primary">{{$t('login.tooltip.coming_soon')}}</q-tooltip>
|
<q-tooltip
|
||||||
|
anchor="top middle"
|
||||||
|
class="bg-primary"
|
||||||
|
>{{ $t('login.tooltip.coming_soon') }}</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-slide-transition>
|
<q-slide-transition>
|
||||||
<div v-if="is_employee_email">
|
<div v-if="is_employee_email">
|
||||||
|
|
@ -85,7 +128,7 @@
|
||||||
<q-btn
|
<q-btn
|
||||||
push
|
push
|
||||||
rounded
|
rounded
|
||||||
color="primary"
|
color="accent"
|
||||||
icon="img:src/assets/logo-targo-simple.svg"
|
icon="img:src/assets/logo-targo-simple.svg"
|
||||||
:label="$t('login.button.employee')"
|
:label="$t('login.button.employee')"
|
||||||
class="full-width row"
|
class="full-width row"
|
||||||
|
|
@ -97,4 +140,7 @@
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</div>
|
</div>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
<div v-if="is_game_time">
|
||||||
|
<LoginRockPaperScissor />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { useAuthApi } from '../composables/use-auth-api';
|
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
|
|
||||||
const auth_api = useAuthApi();
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const setBypassUser = (bypassRole: string) => {
|
|
||||||
auth_api.setUser(bypassRole);
|
|
||||||
|
|
||||||
router.push({ name: 'dashboard' }).catch( err => {
|
|
||||||
console.error('Router navigation failed: ', err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<q-card class="absolute-bottom-right q-ma-sm">
|
|
||||||
<q-card-section class="q-pa-sm text-uppercase text-center"> impersonate </q-card-section>
|
|
||||||
<q-card-actions vertical>
|
|
||||||
<q-btn
|
|
||||||
v-for="role, index in [ 'supervisor', 'accounting', 'human_resources', 'employee' ]"
|
|
||||||
:key="index"
|
|
||||||
push
|
|
||||||
color="primary"
|
|
||||||
text-color="white"
|
|
||||||
:label="role"
|
|
||||||
class="text-uppercase"
|
|
||||||
@click="setBypassUser(role)"
|
|
||||||
/>
|
|
||||||
</q-card-actions>
|
|
||||||
</q-card>
|
|
||||||
</template>
|
|
||||||
77
src/modules/auth/components/login-rock-paper-scissor.vue
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Notify } from 'quasar';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
const choices = ['rock', 'paper', 'scissors'] as const;
|
||||||
|
type Choice = typeof choices[number];
|
||||||
|
const app_choice = ref<Choice>();
|
||||||
|
|
||||||
|
const click_number = ref(1);
|
||||||
|
const icon = ref('');
|
||||||
|
const color = ref('accent');
|
||||||
|
|
||||||
|
const getRandmonChoice = (): Choice => {
|
||||||
|
const index = Math.floor(Math.random() * choices.length);
|
||||||
|
return choices[index]!;
|
||||||
|
};
|
||||||
|
const icon_map: Record<Choice, string> = {
|
||||||
|
rock: 'las la-hand-rock',
|
||||||
|
paper: 'las la-hand-paper',
|
||||||
|
scissors: 'las la-hand-scissors',
|
||||||
|
};
|
||||||
|
|
||||||
|
const fightingResult = () => {
|
||||||
|
if (click_number.value === 7) {
|
||||||
|
Notify.create({
|
||||||
|
icon: 'las la-hand-middle-finger',
|
||||||
|
color: 'negative',
|
||||||
|
iconSize: '5em',
|
||||||
|
iconColor: 'white',
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
app_choice.value = getRandmonChoice();
|
||||||
|
icon.value = icon_map[app_choice.value];
|
||||||
|
color.value = 'accent';
|
||||||
|
|
||||||
|
Notify.create({
|
||||||
|
color: color.value,
|
||||||
|
icon: icon.value,
|
||||||
|
iconSize: '5em',
|
||||||
|
iconColor: 'white',
|
||||||
|
});
|
||||||
|
|
||||||
|
click_number.value += 1;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="row flex-center q-mt-xl">
|
||||||
|
<div class="q-px-sm">
|
||||||
|
<q-btn
|
||||||
|
push
|
||||||
|
dense
|
||||||
|
color="accent"
|
||||||
|
icon="las la-hand-rock"
|
||||||
|
@click="fightingResult()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="q-px-sm">
|
||||||
|
<q-btn
|
||||||
|
push
|
||||||
|
dense
|
||||||
|
color="accent"
|
||||||
|
icon="las la-hand-paper"
|
||||||
|
@click="fightingResult()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="q-px-sm">
|
||||||
|
<q-btn
|
||||||
|
push
|
||||||
|
dense
|
||||||
|
color="accent"
|
||||||
|
icon="las la-hand-scissors"
|
||||||
|
@click="fightingResult()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -3,8 +3,6 @@ import { useAuthStore } from "../../../stores/auth-store";
|
||||||
export const useAuthApi = () => {
|
export const useAuthApi = () => {
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const login = () => {
|
const login = () => {
|
||||||
authStore.login();
|
authStore.login();
|
||||||
};
|
};
|
||||||
|
|
@ -17,19 +15,9 @@ export const useAuthApi = () => {
|
||||||
authStore.logout();
|
authStore.logout();
|
||||||
};
|
};
|
||||||
|
|
||||||
const isAuthorizedUser = () => {
|
|
||||||
return authStore.isAuthorizedUser;
|
|
||||||
};
|
|
||||||
|
|
||||||
const setUser = (bypassRole: string) => {
|
|
||||||
authStore.setUser(bypassRole);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
login,
|
login,
|
||||||
oidcLogin,
|
oidcLogin,
|
||||||
logout,
|
logout,
|
||||||
isAuthorizedUser,
|
|
||||||
setUser,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -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
|
||||||
|
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-card-section class="row justify-center ">
|
||||||
<q-icon name="check_circle" color="green" size="xl" />
|
<q-icon
|
||||||
|
name="check_circle"
|
||||||
|
color="accent"
|
||||||
|
size="xl"
|
||||||
|
/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-separator inset color="primary" />
|
<q-separator
|
||||||
<q-card-section class="row justify-center">
|
inset
|
||||||
<span class="row text-primary 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>
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import LoginConnectionPanel from 'src/modules/auth/components/login-connection-panel.vue';
|
|
||||||
import LoginDevBypass from 'src/modules/auth/components/login-dev-bypass.vue';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<q-layout view="hHh lpR fFf">
|
|
||||||
<q-page-container class="bg-dark">
|
|
||||||
<q-img src="src/assets/village.png" fit="cover" position="50% 100%" class="absolute-full" />
|
|
||||||
<q-page class="flex flex-center">
|
|
||||||
<transition appear slow enter-active-class="animated zoomIn" leave-active-class="animated zoomOut">
|
|
||||||
<LoginConnectionPanel />
|
|
||||||
</transition>
|
|
||||||
|
|
||||||
<!-- DEV TOOLS -->
|
|
||||||
<LoginDevBypass />
|
|
||||||
</q-page>
|
|
||||||
</q-page-container>
|
|
||||||
</q-layout>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
import { api } from 'src/boot/axios';
|
import { api } from 'src/boot/axios';
|
||||||
|
import type { User } from 'src/modules/shared/models/user.models';
|
||||||
|
|
||||||
export const AuthService = {
|
export const AuthService = {
|
||||||
// Will likely be deprecated and relegated to Authentik
|
// Will likely be deprecated and relegated to Authentik
|
||||||
|
|
@ -7,16 +8,6 @@ export const AuthService = {
|
||||||
//TODO: OIDC customer sign-in, eventually
|
//TODO: OIDC customer sign-in, eventually
|
||||||
},
|
},
|
||||||
|
|
||||||
oidcLogin: (): Window | null => {
|
|
||||||
window.addEventListener('message', (event) => {
|
|
||||||
if (event.data.type === 'authSuccess') {
|
|
||||||
//some kind of logic here to set user in store
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return window.open('http://localhost:3000/auth/v1/login', 'authPopup', 'width=600,height=800');
|
|
||||||
},
|
|
||||||
|
|
||||||
logout: () => {
|
logout: () => {
|
||||||
// TODO: logout logic
|
// TODO: logout logic
|
||||||
api.post('/auth/logout')
|
api.post('/auth/logout')
|
||||||
|
|
@ -27,8 +18,8 @@ export const AuthService = {
|
||||||
api.post('/auth/refresh')
|
api.post('/auth/refresh')
|
||||||
},
|
},
|
||||||
|
|
||||||
getProfile: () => {
|
getProfile: async (): Promise<User> => {
|
||||||
// TODO: user info fetch logic
|
const response = await api.get('/auth/me');
|
||||||
api.get('/auth/me')
|
return response.data;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
42
src/modules/dashboard/components/employee/shortcut-card.vue
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
<script
|
||||||
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
|
const { imageSource = "", title = "", description = "", route = "" } = defineProps<{
|
||||||
|
imageSource?: string,
|
||||||
|
title?: string,
|
||||||
|
description?: string,
|
||||||
|
route?: string,
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const onClickExternalShortcut = () => {
|
||||||
|
window.open(route, '_blank')?.focus();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-card
|
||||||
|
class="shortcut-card cursor-pointer shadow-12"
|
||||||
|
@click="onClickExternalShortcut"
|
||||||
|
>
|
||||||
|
<q-img
|
||||||
|
:src="imageSource"
|
||||||
|
fit="contain"
|
||||||
|
>
|
||||||
|
<div class="absolute-bottom text-uppercase text-weight-bolder text-center">{{ title }}</div>
|
||||||
|
</q-img>
|
||||||
|
|
||||||
|
<q-card-section v-if="description">
|
||||||
|
<span>{{ description }}</span>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style
|
||||||
|
lang="sass"
|
||||||
|
scoped
|
||||||
|
>
|
||||||
|
.shortcut-card
|
||||||
|
width: 100%
|
||||||
|
max-width: 250px
|
||||||
|
</style>
|
||||||
72
src/modules/dashboard/components/main-carousel.vue
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
<script
|
||||||
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
|
import { RouteNames } from 'src/router/router-constants';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const slide = ref<string>('welcome');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-carousel
|
||||||
|
v-model="slide"
|
||||||
|
transition-prev="jump-right"
|
||||||
|
transition-next="jump-left"
|
||||||
|
swipeable
|
||||||
|
animated
|
||||||
|
infinite
|
||||||
|
arrows
|
||||||
|
:autoplay="9001"
|
||||||
|
control-color="accent"
|
||||||
|
control-type="outline"
|
||||||
|
class="bg-dark full-width rounded-15 shadow-18"
|
||||||
|
>
|
||||||
|
<!-- welcome slide -->
|
||||||
|
<q-carousel-slide
|
||||||
|
name="welcome"
|
||||||
|
class="q-pa-none fit"
|
||||||
|
>
|
||||||
|
<div class="column fit">
|
||||||
|
<q-img
|
||||||
|
src="src/assets/targo_building.png"
|
||||||
|
position="50% 25%"
|
||||||
|
fit="cover"
|
||||||
|
class="col-9"
|
||||||
|
>
|
||||||
|
<div class="absolute-bottom text-h6 text-uppercase text-weight-light">
|
||||||
|
{{ $t('dashboard.carousel.welcome_title') }}
|
||||||
|
</div>
|
||||||
|
</q-img>
|
||||||
|
|
||||||
|
<div class="col column flex-center q-px-md">
|
||||||
|
<span class="col-auto text-center">{{ $t('dashboard.carousel.welcome_message') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-carousel-slide>
|
||||||
|
|
||||||
|
<!-- help page slide -->
|
||||||
|
<q-carousel-slide
|
||||||
|
name="tv"
|
||||||
|
class="q-pa-none cursor-pointer"
|
||||||
|
@click="$router.push(RouteNames.HELP)"
|
||||||
|
>
|
||||||
|
<div class="column fit">
|
||||||
|
<q-img
|
||||||
|
src="src/assets/targo_help_banner.png"
|
||||||
|
position="50% 25%"
|
||||||
|
fit="none"
|
||||||
|
class="col-9"
|
||||||
|
>
|
||||||
|
<div class="absolute-bottom text-h6 text-uppercase text-weight-light">
|
||||||
|
{{ $t('dashboard.carousel.help_title') }}
|
||||||
|
</div>
|
||||||
|
</q-img>
|
||||||
|
|
||||||
|
<div class="col column flex-center q-px-md">
|
||||||
|
<span class="col-auto text-center">{{ $t('dashboard.carousel.help_message') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-carousel-slide>
|
||||||
|
</q-carousel>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,194 @@
|
||||||
|
<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, employee_access_presets, getEmployeeAccessOptionIcon } from 'src/modules/employee-list/models/employee-profile.models';
|
||||||
|
import type { UserModuleAccess } from 'src/modules/shared/models/user.models';
|
||||||
|
|
||||||
|
const employee_store = useEmployeeStore();
|
||||||
|
const preset_preview = ref<ModuleAccessPreset>();
|
||||||
|
|
||||||
|
const toggleInSelected = (value: UserModuleAccess) => {
|
||||||
|
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: UserModuleAccess) => {
|
||||||
|
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: UserModuleAccess) => {
|
||||||
|
if (employee_store.employee.user_module_access.includes(name)) return 'bg-accent text-white';
|
||||||
|
|
||||||
|
return 'bg-dark';
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="column full-width items-start content-start overflow-hidden-y">
|
||||||
|
<!-- warning when all modules are disabled -->
|
||||||
|
<div class="col-auto row flex-center q-px-lg q-py-xs full-width">
|
||||||
|
<q-slide-transition>
|
||||||
|
<div
|
||||||
|
v-if="employee_store.employee.user_module_access.length === 0"
|
||||||
|
class="row flex-center q-px-md q-py-xs bg-dark"
|
||||||
|
style="border: 2px solid var(--q-warning);"
|
||||||
|
>
|
||||||
|
<q-icon
|
||||||
|
name="las la-exclamation-triangle"
|
||||||
|
color="warning"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span class="text-warning text-weight-medium q-px-sm">{{ $t('employee_list.errors.no_modules_warning') }}</span>
|
||||||
|
|
||||||
|
<q-icon
|
||||||
|
name="las la-exclamation-triangle"
|
||||||
|
color="warning"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-slide-transition>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- info line explaining how to customize access -->
|
||||||
|
<div class="col-auto row flex-center q-px-sm q-py-xs no-wrap">
|
||||||
|
<q-icon
|
||||||
|
name="info_outline"
|
||||||
|
size="sm"
|
||||||
|
color="accent"
|
||||||
|
class="col-auto q-mr-sm"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-item-label
|
||||||
|
caption
|
||||||
|
class="col-auto text-weight-medium"
|
||||||
|
>{{ $t('employee_management.module_access.usage_description') }}</q-item-label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="col"
|
||||||
|
:class="$q.platform.is.mobile ? 'column' : 'row'"
|
||||||
|
>
|
||||||
|
<!-- column to attribute access by roles -->
|
||||||
|
<div class="column col-4 overflow-hidden-y q-pr-sm">
|
||||||
|
<span class="text-uppercase text-weight-medium q-mx-sm">
|
||||||
|
{{ $t('employee_management.module_access.by_role') }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<q-separator
|
||||||
|
size="2px"
|
||||||
|
color="accent"
|
||||||
|
class="q-mx-sm"
|
||||||
|
style="transform: translateY(-4px);"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<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-btn
|
||||||
|
flat
|
||||||
|
color="negative"
|
||||||
|
icon="clear"
|
||||||
|
size="md"
|
||||||
|
align="left"
|
||||||
|
:label="$t('employee_management.module_access.uncheck_all')"
|
||||||
|
class="q-ma-sm q-px-xs rounded-5"
|
||||||
|
@click="applyAccessPreset('none')"
|
||||||
|
@mouseover="preset_preview = 'none'"
|
||||||
|
@mouseleave="preset_preview = undefined"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row col items-start content-start q-pl-sm">
|
||||||
|
<div class="col-12 q-mb-xs">
|
||||||
|
<span class="text-uppercase text-weight-medium q-mx-sm">
|
||||||
|
{{ $t('employee_management.module_access.by_module') }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<q-separator
|
||||||
|
size="2px"
|
||||||
|
color="accent"
|
||||||
|
class="q-mx-sm"
|
||||||
|
style="transform: translateY(-4px);"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<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)"
|
||||||
|
>
|
||||||
|
<q-icon
|
||||||
|
:name="getEmployeeAccessOptionIcon(option.value)"
|
||||||
|
size="sm"
|
||||||
|
class="q-mr-sm"
|
||||||
|
/>
|
||||||
|
<span class="text-uppercase text-weight-bold non-selectable">
|
||||||
|
{{ $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>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
<script
|
||||||
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
|
const model = defineModel<string | number | null | undefined>({ required: true });
|
||||||
|
const is_date_picker_open = defineModel<boolean>('isDatePickerOpen', {default: false});
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
label?: string | undefined;
|
||||||
|
requiresDatePicker?: boolean | undefined;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-input
|
||||||
|
v-model="model"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
color="accent"
|
||||||
|
stack-label
|
||||||
|
label-slot
|
||||||
|
no-error-icon
|
||||||
|
hide-bottom-space
|
||||||
|
class="col q-mx-sm q-my-xs rounded-5 bg-dark shadow-12"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<span
|
||||||
|
class="text-weight-bolder text-uppercase"
|
||||||
|
style="font-size: 0.85em;"
|
||||||
|
>
|
||||||
|
{{ label }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #append v-if="requiresDatePicker">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
size="lg"
|
||||||
|
icon="calendar_month"
|
||||||
|
color="accent"
|
||||||
|
@click="is_date_picker_open = true"
|
||||||
|
>
|
||||||
|
<q-dialog
|
||||||
|
v-model="is_date_picker_open"
|
||||||
|
backdrop-filter="none"
|
||||||
|
>
|
||||||
|
<q-date
|
||||||
|
v-model="model"
|
||||||
|
mask="YYYY-MM-DD"
|
||||||
|
color="accent"
|
||||||
|
@update:model-value="is_date_picker_open = false"
|
||||||
|
/>
|
||||||
|
</q-dialog>
|
||||||
|
</q-btn>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
<script
|
||||||
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
|
const model = defineModel<string>({ required: true });
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
label?: string | undefined;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-select
|
||||||
|
v-model="model"
|
||||||
|
dense
|
||||||
|
outlined
|
||||||
|
color="accent"
|
||||||
|
stack-label
|
||||||
|
label-slot
|
||||||
|
lazy-rules
|
||||||
|
no-error-icon
|
||||||
|
hide-bottom-space
|
||||||
|
options-selected-class="text-white text-bold bg-accent"
|
||||||
|
class="col q-mx-sm q-my-xs bg-dark rounded-5 shadow-12"
|
||||||
|
popup-content-class="text-uppercase text-weight-medium rounded-5"
|
||||||
|
popup-content-style="border: 2px solid var(--q-accent)"
|
||||||
|
menu-anchor="bottom middle"
|
||||||
|
menu-self="top middle"
|
||||||
|
:menu-offset="[0, 5]"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<span
|
||||||
|
class="text-weight-bolder text-uppercase"
|
||||||
|
style="font-size: 0.85em;"
|
||||||
|
>
|
||||||
|
{{ label }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
</template>
|
||||||
210
src/modules/employee-list/components/add-modify-dialog-form.vue
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
<script
|
||||||
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
|
import AddModifyDialogFormInput from 'src/modules/employee-list/components/add-modify-dialog-form-input.vue';
|
||||||
|
import AddModifyDialogFormSelect from 'src/modules/employee-list/components/add-modify-dialog-form-select.vue';
|
||||||
|
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { useEmployeeStore } from 'src/stores/employee-store';
|
||||||
|
import { type QuasarRules, useEmployeeProfileRules, company_options } from 'src/modules/employee-list/employee-list-utils';
|
||||||
|
|
||||||
|
const employee_store = useEmployeeStore();
|
||||||
|
const last_work_day = computed(() => employee_store.employee.last_work_day ?? '---');
|
||||||
|
const is_first_day_picker_open = ref(false);
|
||||||
|
const is_last_day_picker_open = ref(false);
|
||||||
|
|
||||||
|
const form_rules = useEmployeeProfileRules();
|
||||||
|
|
||||||
|
const supervisor_options = computed(() => {
|
||||||
|
const supervisors = employee_store.employee_list.filter(employee => employee.is_supervisor === true && employee.last_work_day === null);
|
||||||
|
return supervisors.map(supervisor => supervisor.first_name + ' ' + supervisor.last_name);
|
||||||
|
})
|
||||||
|
|
||||||
|
const setLastWorkDay = (date: string | number | null | undefined) => {
|
||||||
|
if (typeof date === 'string' && date.length > 0) {
|
||||||
|
employee_store.employee.last_work_day = date;
|
||||||
|
}
|
||||||
|
employee_store.employee.last_work_day = null;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<q-form>
|
||||||
|
<div class="row flex-center">
|
||||||
|
<transition
|
||||||
|
enter-active-class="animated pulse fast"
|
||||||
|
mode="out-in"
|
||||||
|
>
|
||||||
|
<q-checkbox
|
||||||
|
v-model="employee_store.employee.is_supervisor"
|
||||||
|
:key="employee_store.employee.is_supervisor ? '1' : '0'"
|
||||||
|
dense
|
||||||
|
left-label
|
||||||
|
:label="$t('employee_list.table.is_supervisor')"
|
||||||
|
size="lg"
|
||||||
|
color="accent"
|
||||||
|
class="col-auto text-uppercase q-py-xs q-px-lg q-ma-xs rounded-25"
|
||||||
|
:class="employee_store.employee.is_supervisor ? 'bg-accent text-white text-weight-bold' : ''"
|
||||||
|
/>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="q-ma-xs"
|
||||||
|
:class="$q.screen.lt.sm ? 'column' : 'row'"
|
||||||
|
>
|
||||||
|
<AddModifyDialogFormInput
|
||||||
|
v-model="employee_store.employee.first_name"
|
||||||
|
:label="$t('profile.personal.first_name')"
|
||||||
|
:rules="[(value: string, rules: QuasarRules) => form_rules.isNotEmpty(value, rules, $t('employee_list.errors.first_name_required'))]"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AddModifyDialogFormInput
|
||||||
|
v-model="employee_store.employee.last_name"
|
||||||
|
:label="$t('profile.personal.last_name')"
|
||||||
|
:rules="[(value: string, rules: QuasarRules) => form_rules.isNotEmpty(value, rules, $t('employee_list.errors.last_name_required'))]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="q-ma-xs"
|
||||||
|
:class="$q.screen.lt.sm ? 'column' : 'row'"
|
||||||
|
>
|
||||||
|
<AddModifyDialogFormInput
|
||||||
|
v-model="employee_store.employee.email"
|
||||||
|
:label="$t('profile.employee.email')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AddModifyDialogFormInput
|
||||||
|
v-model="employee_store.employee.phone_number"
|
||||||
|
:label="$t('profile.personal.phone_number')"
|
||||||
|
mask="(###) ### - ####"
|
||||||
|
:rules="[(value: string, rules: QuasarRules) => form_rules.isNotEmpty(value, rules, $t('employee_list.errors.phone_number_required'))]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="q-ma-xs"
|
||||||
|
:class="$q.screen.lt.sm ? 'column' : 'row'"
|
||||||
|
>
|
||||||
|
<AddModifyDialogFormInput
|
||||||
|
v-model="employee_store.employee.job_title"
|
||||||
|
:label="$t('profile.employee.job_title')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AddModifyDialogFormSelect
|
||||||
|
v-model="employee_store.employee.company_name"
|
||||||
|
:options="company_options"
|
||||||
|
:label="$t('profile.employee.company')"
|
||||||
|
:rules="[(value: string, rules: QuasarRules) => form_rules.isNotEmpty(value, rules, $t('employee_list.errors.company_required'))]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="q-ma-xs"
|
||||||
|
:class="$q.screen.lt.sm ? 'column' : 'row'"
|
||||||
|
>
|
||||||
|
<AddModifyDialogFormSelect
|
||||||
|
v-model="employee_store.employee.supervisor_full_name"
|
||||||
|
:options="supervisor_options"
|
||||||
|
:label="$t('profile.employee.supervisor')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AddModifyDialogFormInput
|
||||||
|
v-model="employee_store.employee.external_payroll_id"
|
||||||
|
:label="$t('profile.employee.bankroll_id')"
|
||||||
|
:placeholder="$t('employee_management.can_be_entered_later')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="q-ma-xs"
|
||||||
|
:class="$q.screen.lt.sm ? 'column' : 'row'"
|
||||||
|
>
|
||||||
|
<AddModifyDialogFormInput
|
||||||
|
v-model="employee_store.employee.daily_expected_hours"
|
||||||
|
:label="$t('employee_list.table.expected_daily_hours')"
|
||||||
|
:rules="[(value: string, rules: QuasarRules) => form_rules.isNotEmpty(value, rules, $t('employee_list.errors.daily_hours_required'))]"
|
||||||
|
type="number"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AddModifyDialogFormInput
|
||||||
|
v-if="employee_store.employee.paid_time_off"
|
||||||
|
v-model="employee_store.employee.paid_time_off.banked_hours"
|
||||||
|
:label="$t('employee_management.banked_hours')"
|
||||||
|
type="number"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="col q-px-sm"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="q-ma-xs"
|
||||||
|
:class="$q.screen.lt.sm ? 'column' : 'row'"
|
||||||
|
>
|
||||||
|
<AddModifyDialogFormInput
|
||||||
|
v-if="employee_store.employee.paid_time_off"
|
||||||
|
v-model="employee_store.employee.paid_time_off.sick_hours"
|
||||||
|
:label="$t('employee_management.sick_hours')"
|
||||||
|
type="number"
|
||||||
|
@update:model-value="employee_store.employee.paid_time_off.last_updated = new Date().toISOString().slice(0, 10)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AddModifyDialogFormInput
|
||||||
|
v-if="employee_store.employee.paid_time_off"
|
||||||
|
v-model="employee_store.employee.paid_time_off.vacation_hours"
|
||||||
|
:label="$t('employee_management.vacation_hours')"
|
||||||
|
type="number"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="q-ma-xs"
|
||||||
|
:class="$q.screen.lt.md ? 'column' : 'row'"
|
||||||
|
>
|
||||||
|
<AddModifyDialogFormInput
|
||||||
|
v-model="employee_store.employee.first_work_day"
|
||||||
|
v-model:is-date-picker-open="is_first_day_picker_open"
|
||||||
|
reqires-date-picker
|
||||||
|
:label="$t('profile.employee.hired_date')"
|
||||||
|
:rules="[(value: string, rules: QuasarRules) => form_rules.isNotEmpty(value, rules, $t('employee_list.errors.hire_date_required'))]"
|
||||||
|
mask="####-##-##"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AddModifyDialogFormInput
|
||||||
|
v-model="last_work_day"
|
||||||
|
v-model:is-date-picker-open="is_last_day_picker_open"
|
||||||
|
reqires-date-picker
|
||||||
|
:label="$t('profile.employee.fired_date')"
|
||||||
|
mask="####-##-##"
|
||||||
|
@update:model-value="setLastWorkDay"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.q-field--error .q-field__bottom) {
|
||||||
|
color: white;
|
||||||
|
font-weight: 500;
|
||||||
|
text-transform: uppercase;
|
||||||
|
border-radius: 0 0 5px 5px;
|
||||||
|
padding-top: 0;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--q-negative);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.row > .col) {
|
||||||
|
height: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.q-field--outlined.q-field--highlighted .q-field__control::after) {
|
||||||
|
border-radius: 5px 5px 0 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
<script
|
||||||
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
|
// import { date } from 'quasar';
|
||||||
|
import { useSchedulePresetsStore } from 'src/stores/schedule-presets.store';
|
||||||
|
|
||||||
|
const schedule_preset_store = useSchedulePresetsStore();
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
currentPresetId: number;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="row flex-center fit">
|
||||||
|
<div
|
||||||
|
v-if="currentPresetId > 0"
|
||||||
|
class="col column fit flex-center q-pa-md"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="weekday in schedule_preset_store.current_schedule_preset.weekdays"
|
||||||
|
:key="weekday.day"
|
||||||
|
class="col row justify-center q-py-xs full-width"
|
||||||
|
>
|
||||||
|
<div class="col-10 row items-center bg-dark q-px-md shadow-10 rounded-10">
|
||||||
|
<span class="col-2 text-weight-bolder text-accent text-uppercase text-overline" style="font-size: 1.3em;">{{
|
||||||
|
$t(`shared.weekday.${weekday.day.toLowerCase()}`) }}</span>
|
||||||
|
<div
|
||||||
|
v-for="shift, index in weekday.shifts"
|
||||||
|
:key="index"
|
||||||
|
class="col q-px-md q-py-xs"
|
||||||
|
>
|
||||||
|
<div class="row flex-center rounded-5" style="border: 1px solid var(--q-accent);">
|
||||||
|
<div class="col bg-accent text-white text-uppercase text-weight-bolder text-center">
|
||||||
|
{{ $t(`shared.shift_type.${shift.type.toLowerCase()}`) }}
|
||||||
|
</div>
|
||||||
|
<div class="col text-center text-bold">{{ shift.start_time }}</div>
|
||||||
|
<q-icon name="las la-chevron-right" color="accent" class="col-auto"></q-icon>
|
||||||
|
<div class="col text-center text-bold">{{ shift.end_time }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="col row justify-center"
|
||||||
|
>
|
||||||
|
<q-icon
|
||||||
|
name="las la-calendar-week"
|
||||||
|
size="20em"
|
||||||
|
color="accent"
|
||||||
|
style="opacity: 0.3;"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
<script
|
||||||
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
|
import HorizontalSlideTransition from 'src/modules/shared/components/horizontal-slide-transition.vue';
|
||||||
|
import SchedulePresetsDialog from 'src/modules/employee-list/components/schedule-presets-dialog.vue';
|
||||||
|
import AddModifyDialogSchedulePreview from './add-modify-dialog-schedule-preview.vue';
|
||||||
|
|
||||||
|
import { onMounted, ref, watch } from 'vue';
|
||||||
|
import { useSchedulePresetsStore } from 'src/stores/schedule-presets.store';
|
||||||
|
import { useEmployeeStore } from 'src/stores/employee-store';
|
||||||
|
import { useEmployeeListApi } from '../composables/use-employee-api';
|
||||||
|
import type { PresetManagerMode } from 'src/modules/employee-list/models/schedule-presets.models';
|
||||||
|
|
||||||
|
const schedule_preset_store = useSchedulePresetsStore();
|
||||||
|
const employee_store = useEmployeeStore();
|
||||||
|
const employee_list_api = useEmployeeListApi();
|
||||||
|
|
||||||
|
const preset_options = ref<{ label: string, value: number }[]>([]);
|
||||||
|
const current_preset = ref<{ label: string | undefined, value: number }>({ label: undefined, value: -1 });
|
||||||
|
const manager_watcher = ref(schedule_preset_store.is_manager_open);
|
||||||
|
|
||||||
|
const getPresetOptions = (): { label: string, value: number }[] => {
|
||||||
|
const options = schedule_preset_store.schedule_presets.map(preset => { return { label: preset.name, value: preset.id } });
|
||||||
|
options.push({ label: 'Aucun', value: -1 });
|
||||||
|
return options;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClickSchedulePresetManager = (mode: PresetManagerMode, preset_id?: number) => {
|
||||||
|
schedule_preset_store.schedule_preset_dialog_mode = mode;
|
||||||
|
schedule_preset_store.openSchedulePresetManager(preset_id ?? current_preset.value.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadSelectedPresetOption = () => {
|
||||||
|
preset_options.value = getPresetOptions();
|
||||||
|
const current_option = preset_options.value.find(option => option.value === employee_store.employee.preset_id);
|
||||||
|
current_preset.value = current_option ?? { label: undefined, value: -1 };
|
||||||
|
schedule_preset_store.setCurrentSchedulePreset(current_preset.value.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadSelectedPresetOption();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(manager_watcher, loadSelectedPresetOption)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:key="schedule_preset_store.is_manager_open === false ? '0' : '1'"
|
||||||
|
class="column full-width flex-center items-start"
|
||||||
|
>
|
||||||
|
<SchedulePresetsDialog />
|
||||||
|
|
||||||
|
<div class="col row justify-center full-width no-wrap">
|
||||||
|
<q-select
|
||||||
|
v-model="current_preset"
|
||||||
|
standout="bg-accent"
|
||||||
|
dense
|
||||||
|
options-dense
|
||||||
|
rounded
|
||||||
|
color="accent"
|
||||||
|
:options="getPresetOptions()"
|
||||||
|
class="col-xs-10 col-md-7"
|
||||||
|
popup-content-class="text-uppercase text-weight-medium rounded-20"
|
||||||
|
popup-content-style="border: 2px solid var(--q-accent)"
|
||||||
|
menu-anchor="bottom middle"
|
||||||
|
menu-self="top middle"
|
||||||
|
:menu-offset="[0, 10]"
|
||||||
|
@update:modelValue="option => employee_list_api.setSchedulePreset(option.value)"
|
||||||
|
>
|
||||||
|
<template #selected>
|
||||||
|
<span
|
||||||
|
class="text-uppercase text-center text-weight-bold full-width"
|
||||||
|
:style="current_preset.label === undefined ? 'opacity: 0.5;' : ''"
|
||||||
|
>
|
||||||
|
{{ current_preset.label === undefined ?
|
||||||
|
$t('employee_management.schedule_presets.preset_list_placeholder') :
|
||||||
|
current_preset.label }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
icon="add"
|
||||||
|
color="accent"
|
||||||
|
class="col-auto q-px-sm q-ml-sm rounded-50"
|
||||||
|
@click="onClickSchedulePresetManager('create', -1)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<HorizontalSlideTransition :show="current_preset !== undefined && current_preset?.value !== -1">
|
||||||
|
<div class="col-auto row no-wrap full-height">
|
||||||
|
<q-btn
|
||||||
|
icon="edit"
|
||||||
|
color="accent"
|
||||||
|
class="col-auto q-px-sm q-ml-sm rounded-50"
|
||||||
|
@click="onClickSchedulePresetManager('update')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
icon="content_copy"
|
||||||
|
color="accent"
|
||||||
|
class="col-auto q-px-sm q-mx-sm rounded-50"
|
||||||
|
@click="onClickSchedulePresetManager('copy')"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
rounded
|
||||||
|
icon="clear"
|
||||||
|
color="negative"
|
||||||
|
class="col-auto q-px-sm full-height"
|
||||||
|
@click="onClickSchedulePresetManager('delete')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</HorizontalSlideTransition>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<AddModifyDialogSchedulePreview :current-preset-id="current_preset.value" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
119
src/modules/employee-list/components/add-modify-dialog.vue
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
<script
|
||||||
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
|
import AddModifyDialogForm from 'src/modules/employee-list/components/add-modify-dialog-form.vue';
|
||||||
|
import AddModifyDialogAccess from 'src/modules/employee-list/components/add-modify-dialog-access.vue';
|
||||||
|
import AddModifyDialogSchedule from 'src/modules/employee-list/components/add-modify-dialog-schedule.vue';
|
||||||
|
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useEmployeeStore } from 'src/stores/employee-store';
|
||||||
|
import { EmployeeProfile } from 'src/modules/employee-list/models/employee-profile.models';
|
||||||
|
|
||||||
|
const employee_store = useEmployeeStore();
|
||||||
|
const current_step = ref<'form' | 'access' | 'schedule'>('form');
|
||||||
|
const initial_employee_profile = ref(new EmployeeProfile)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-dialog
|
||||||
|
v-model="employee_store.is_add_modify_dialog_open"
|
||||||
|
full-width
|
||||||
|
full-height
|
||||||
|
@beforeShow="current_step = 'form'"
|
||||||
|
@show="Object.assign(initial_employee_profile, employee_store.employee)"
|
||||||
|
class="shadow-24"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="column bg-secondary rounded-10 no-wrap"
|
||||||
|
:class="$q.dark.isActive ? 'shadow-24' : 'shadow-10'"
|
||||||
|
:style="($q.screen.lt.md ? ' ' : 'max-width: 60vw !important; max-height: 80vh !important;') +
|
||||||
|
($q.dark.isActive ? 'border: 2px solid var(--q-accent)' : '')"
|
||||||
|
>
|
||||||
|
<div class="row col-auto text-white bg-primary flex-center shadow-5">
|
||||||
|
<div class="q-py-sm text-uppercase text-weight-bolder text-h5 ">
|
||||||
|
{{ $t('employee_management.' + employee_store.management_mode) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="employee_store.employee.first_name.length > 0"
|
||||||
|
class="text-uppercase text-weight-light text-h6 q-ml-sm"
|
||||||
|
>
|
||||||
|
{{ `${employee_store.employee.first_name} ${employee_store.employee.last_name}` }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col column q-pa-md no-wrap scroll">
|
||||||
|
<q-tabs
|
||||||
|
v-model="current_step"
|
||||||
|
dense
|
||||||
|
inline-label
|
||||||
|
align="justify"
|
||||||
|
indicator-color="transparent"
|
||||||
|
active-class="text-white bg-accent"
|
||||||
|
class="q-mb-sm"
|
||||||
|
>
|
||||||
|
<q-tab
|
||||||
|
name="form"
|
||||||
|
icon="las la-id-card"
|
||||||
|
:label="$q.screen.lt.sm ? '' : $t('employee_management.details_label')"
|
||||||
|
class="rounded-25 q-ma-xs bg-dark"
|
||||||
|
style="border: 2px solid var(--q-accent);"
|
||||||
|
/>
|
||||||
|
<q-tab
|
||||||
|
name="access"
|
||||||
|
icon="las la-key"
|
||||||
|
:label="$q.screen.lt.sm ? '' : $t('employee_management.access_label')"
|
||||||
|
class="rounded-25 q-ma-xs bg-dark"
|
||||||
|
style="border: 2px solid var(--q-accent);"
|
||||||
|
/>
|
||||||
|
<q-tab
|
||||||
|
name="schedule"
|
||||||
|
icon="calendar_month"
|
||||||
|
:label="$q.screen.lt.sm ? '' : $t('employee_management.schedule_label')"
|
||||||
|
class="rounded-25 q-ma-xs bg-dark"
|
||||||
|
style="border: 2px solid var(--q-accent);"
|
||||||
|
/>
|
||||||
|
</q-tabs>
|
||||||
|
|
||||||
|
<q-tab-panels
|
||||||
|
v-model="current_step"
|
||||||
|
animated
|
||||||
|
:transition-prev="$q.screen.lt.sm ? 'jump-down' : 'jump-right'"
|
||||||
|
:transition-next="$q.screen.lt.sm ? 'jump-up' : 'jump-left'"
|
||||||
|
class="bg-transparent full-height"
|
||||||
|
>
|
||||||
|
<q-tab-panel
|
||||||
|
name="form"
|
||||||
|
class="q-pa-xs"
|
||||||
|
>
|
||||||
|
<AddModifyDialogForm />
|
||||||
|
</q-tab-panel>
|
||||||
|
|
||||||
|
<q-tab-panel
|
||||||
|
name="access"
|
||||||
|
class="q-pa-xs"
|
||||||
|
>
|
||||||
|
<AddModifyDialogAccess />
|
||||||
|
</q-tab-panel>
|
||||||
|
|
||||||
|
<q-tab-panel
|
||||||
|
name="schedule"
|
||||||
|
class="q-pa-xs"
|
||||||
|
>
|
||||||
|
<AddModifyDialogSchedule />
|
||||||
|
</q-tab-panel>
|
||||||
|
</q-tab-panels>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
square
|
||||||
|
color="accent"
|
||||||
|
:label="employee_store.management_mode === 'add_employee' ? $t('shared.label.save') : $t('shared.label.update')"
|
||||||
|
class="col-auto q-py-sm shadow-up-5"
|
||||||
|
@click="employee_store.createOrUpdateEmployee(employee_store.employee)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-dialog>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
<script
|
||||||
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
|
import type { EmployeeProfile } from 'src/modules/employee-list/models/employee-profile.models';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const q = useQuasar();
|
||||||
|
const is_mouseover = ref(false);
|
||||||
|
|
||||||
|
const { row, index = -1, isManagement = false } = defineProps<{
|
||||||
|
row: EmployeeProfile
|
||||||
|
index?: number
|
||||||
|
isManagement?: boolean;
|
||||||
|
}>()
|
||||||
|
|
||||||
|
defineEmits<{
|
||||||
|
onProfileClick: [email: string]
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const getItemStyle = (): string => {
|
||||||
|
const active_style = row.last_work_day === null ? '' : 'opacity: 0.6;';
|
||||||
|
const dark_style = q.dark.isActive ? 'border: 2px solid var(--q-accent);' : '';
|
||||||
|
const hover_style = isManagement ? (is_mouseover.value ? `transform: scale(1.1); z-index: 2;` : 'transform: scale(1) skew(0)') : '';
|
||||||
|
|
||||||
|
return `${active_style} ${dark_style} ${hover_style}`;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="col-xs-6 col-sm-4 col-md-3 col-lg-3 col-xl-2 q-pa-sm row flex-center"
|
||||||
|
:style="`animation-delay: ${index / 25}s;`"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="column col no-wrap bg-dark rounded-15 shadow-12"
|
||||||
|
:class="isManagement ? 'cursor-pointer item-mouse-hover' : ''"
|
||||||
|
style="height: 275px;"
|
||||||
|
:style="getItemStyle()"
|
||||||
|
@click="$emit('onProfileClick', row.email)"
|
||||||
|
@mouseenter="is_mouseover = true"
|
||||||
|
@mouseleave="is_mouseover = false"
|
||||||
|
>
|
||||||
|
<div class="col-auto column flex-center q-pt-md">
|
||||||
|
<q-avatar
|
||||||
|
:color="row.last_work_day === null ? 'accent' : 'negative'"
|
||||||
|
size="8em"
|
||||||
|
class="col-auto shadow-3"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="src/assets/targo-default-avatar.png"
|
||||||
|
alt="employee avatar"
|
||||||
|
class="q-pa-xs"
|
||||||
|
>
|
||||||
|
</q-avatar>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="col column items-center justify-start text-center text-weight-medium text-uppercase q-px-sm q-pt-sm no-wrap"
|
||||||
|
style="line-height: 1.2em; font-size: 1.3em;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ellipsis-2-lines"
|
||||||
|
:class="row.last_work_day === null ? 'text-accent' : 'text-negative'"
|
||||||
|
>
|
||||||
|
{{ row.first_name }} {{ row.last_name }}
|
||||||
|
|
||||||
|
<q-separator class="q-mb-xs q-mx-md" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-auto ellipsis-2-lines text-caption no-wrap">{{ row.job_title }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="col-auto column items-center bg-primary text-white text-caption text-center q-py-xs"
|
||||||
|
style="border-radius: 0 0 15px 15px;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="col-auto row flex-center text-weight-light text-italic"
|
||||||
|
style="font-size: 1em;"
|
||||||
|
>
|
||||||
|
<q-icon
|
||||||
|
name="las la-phone"
|
||||||
|
size="xs"
|
||||||
|
color="accent"
|
||||||
|
class="col-auto"
|
||||||
|
/>
|
||||||
|
<span class="col-auto">{{ row.phone_number }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="col-auto text-italic">extension: </span>
|
||||||
|
|
||||||
|
<span class="col-auto text-weight-medium">{{ row.email }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style
|
||||||
|
lang="css"
|
||||||
|
scoped
|
||||||
|
>
|
||||||
|
.item-mouse-hover {
|
||||||
|
transition: all 0.2s ease-out;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
300
src/modules/employee-list/components/employee-list-table.vue
Normal file
|
|
@ -0,0 +1,300 @@
|
||||||
|
<script
|
||||||
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
|
import EmployeeListTableItem from 'src/modules/employee-list/components/employee-list-table-item.vue';
|
||||||
|
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
import { date, type QTableColumn } from 'quasar';
|
||||||
|
import { useUiStore } from 'src/stores/ui-store';
|
||||||
|
import { useAuthStore } from 'src/stores/auth-store';
|
||||||
|
import { useEmployeeStore } from 'src/stores/employee-store';
|
||||||
|
import { useTimesheetStore } from 'src/stores/timesheet-store';
|
||||||
|
import { employee_list_columns, type EmployeeProfile, type EmployeeListFilters } from 'src/modules/employee-list/models/employee-profile.models';
|
||||||
|
|
||||||
|
const ui_store = useUiStore();
|
||||||
|
const auth_store = useAuthStore();
|
||||||
|
const employee_store = useEmployeeStore();
|
||||||
|
const timesheet_store = useTimesheetStore();
|
||||||
|
|
||||||
|
const is_management = auth_store.user?.user_module_access.includes('employee_management') ?? false;
|
||||||
|
|
||||||
|
const visible_columns = ref<(keyof EmployeeProfile)[]>(['first_name', 'email', 'job_title', 'phone_number', 'last_work_day']);
|
||||||
|
|
||||||
|
const table_grid_container = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
const filters = ref<EmployeeListFilters>({
|
||||||
|
search_bar_string: '',
|
||||||
|
hide_inactive_users: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { maxHeight } = defineProps<{
|
||||||
|
maxHeight: number;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const filterEmployeeRows = (rows: readonly EmployeeProfile[], terms: EmployeeListFilters, _cols: readonly QTableColumn<EmployeeProfile>[]): EmployeeProfile[] => {
|
||||||
|
let result = [...rows];
|
||||||
|
|
||||||
|
if (terms.hide_inactive_users) {
|
||||||
|
const now = new Date();
|
||||||
|
result = result.filter(row => {
|
||||||
|
if (!row.last_work_day) return true;
|
||||||
|
const inactiveDate = date.extractDate(row.last_work_day, 'YYYY-MM-DD');
|
||||||
|
const limit = new Date(inactiveDate);
|
||||||
|
limit.setDate(limit.getDate() + 14);
|
||||||
|
return limit >= now;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (terms.search_bar_string.trim().length > 0) {
|
||||||
|
const searchTerms = terms.search_bar_string.split(' ').map(s => s.trim().toLowerCase());
|
||||||
|
|
||||||
|
result = result.filter(row => {
|
||||||
|
const row_values = Object.values(row).map(v => String(v ?? '').toLowerCase());
|
||||||
|
const row_values_without_emails = row_values.filter(value => !value.includes('@'));
|
||||||
|
return searchTerms.every(term =>
|
||||||
|
row_values_without_emails.some(value => value.includes(term))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
table_grid_container.value = document.querySelector(".q-table__grid-content") as HTMLElement;
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="full-width">
|
||||||
|
<q-table
|
||||||
|
:key="filters.hide_inactive_users ? '1' : '0'"
|
||||||
|
dense
|
||||||
|
hide-pagination
|
||||||
|
title=" "
|
||||||
|
card-style="max-height: 70vh;"
|
||||||
|
:rows="employee_store.employee_list"
|
||||||
|
:columns="employee_list_columns"
|
||||||
|
row-key="email"
|
||||||
|
:rows-per-page-options="[0]"
|
||||||
|
:pagination="{ sortBy: 'first_name' }"
|
||||||
|
:filter="filters"
|
||||||
|
:filter-method="filterEmployeeRows"
|
||||||
|
class="bg-transparent no-shadow sticky-header-table full-width q-pt-lg"
|
||||||
|
:style="employee_store.employee_list.length > 0 ? `max-height: ${maxHeight - (ui_store.user_preferences.is_employee_list_grid ? 0 : 20)}px;` : ''"
|
||||||
|
:table-class="$q.dark.isActive ? 'q-py-none q-mx-md rounded-10 bg-dark shadow-10 hide-scrollbar' : 'q-py-none q-mx-md rounded-10 bg-white shadow-10 hide-scrollbar'"
|
||||||
|
color="accent"
|
||||||
|
separator="none"
|
||||||
|
table-header-class="text-accent text-uppercase"
|
||||||
|
card-container-class="justify-center"
|
||||||
|
:grid="ui_store.user_preferences.is_employee_list_grid"
|
||||||
|
:loading="employee_store.is_loading"
|
||||||
|
:no-data-label="$t('shared.error.no_data_found')"
|
||||||
|
:no-results-label="$t('shared.error.no_search_results')"
|
||||||
|
:loading-label="$t('shared.label.loading')"
|
||||||
|
:visible-columns="visible_columns"
|
||||||
|
>
|
||||||
|
<template #top>
|
||||||
|
<div class="row flex-center full-width q-mb-sm">
|
||||||
|
<q-btn
|
||||||
|
v-if="is_management"
|
||||||
|
rounded
|
||||||
|
color="accent"
|
||||||
|
icon="las la-user-edit"
|
||||||
|
:label="$t('shared.label.add')"
|
||||||
|
class="text-uppercase q-py-sm"
|
||||||
|
@click.stop="_evt => employee_store.openAddModifyDialog()"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-checkbox
|
||||||
|
v-if="is_management"
|
||||||
|
v-model="filters.hide_inactive_users"
|
||||||
|
color="accent"
|
||||||
|
:label="$t('employee_management.filter.hide_terminated')"
|
||||||
|
class="text-uppercase q-ml-md text-weight-medium q-px-sm"
|
||||||
|
>
|
||||||
|
<q-icon
|
||||||
|
name="las la-user-times"
|
||||||
|
color="negative"
|
||||||
|
size="sm"
|
||||||
|
class="q-px-sm"
|
||||||
|
/>
|
||||||
|
</q-checkbox>
|
||||||
|
|
||||||
|
|
||||||
|
<q-space />
|
||||||
|
|
||||||
|
<q-btn-toggle
|
||||||
|
v-model="ui_store.user_preferences.is_employee_list_grid"
|
||||||
|
push
|
||||||
|
rounded
|
||||||
|
color="white"
|
||||||
|
text-color="accent"
|
||||||
|
toggle-color="accent"
|
||||||
|
class="q-mr-md"
|
||||||
|
:options="[
|
||||||
|
{ icon: 'grid_view', value: true },
|
||||||
|
{ icon: 'view_list', value: false },
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-input
|
||||||
|
v-model="filters.search_bar_string"
|
||||||
|
outlined
|
||||||
|
dense
|
||||||
|
rounded
|
||||||
|
color="accent"
|
||||||
|
bg-color="white"
|
||||||
|
label-color="accent"
|
||||||
|
debounce="300"
|
||||||
|
:label="$t('shared.label.search')"
|
||||||
|
>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon
|
||||||
|
name="search"
|
||||||
|
color="accent"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #header="props">
|
||||||
|
<q-tr :props="props">
|
||||||
|
<q-th
|
||||||
|
v-for="col in props.cols"
|
||||||
|
:key="col.name"
|
||||||
|
:props="props"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="text-uppercase text-weight-bolder text-white"
|
||||||
|
style="font-size: 1.2em;"
|
||||||
|
>
|
||||||
|
{{ $t(col.label) }}
|
||||||
|
</span>
|
||||||
|
</q-th>
|
||||||
|
</q-tr>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #item="props">
|
||||||
|
<transition
|
||||||
|
appear
|
||||||
|
enter-active-class="animated zoomIn fast"
|
||||||
|
leave-active-class="animated zoomOut fast"
|
||||||
|
mode="out-in"
|
||||||
|
>
|
||||||
|
<EmployeeListTableItem
|
||||||
|
:key="props.rowIndex"
|
||||||
|
:row="props.row"
|
||||||
|
:index="props.rowIndex"
|
||||||
|
:is-management="is_management"
|
||||||
|
@on-profile-click="email => is_management ? employee_store.openAddModifyDialog(email) : ''"
|
||||||
|
/>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #body-cell="scope">
|
||||||
|
<q-td
|
||||||
|
:props="scope"
|
||||||
|
@click="is_management ? employee_store.openAddModifyDialog(scope.row.email) : ''"
|
||||||
|
:class="scope.rowIndex % 2 === 0 ? ($q.dark.isActive ? 'bg-primary' : 'bg-secondary') : ''"
|
||||||
|
>
|
||||||
|
<transition
|
||||||
|
appear
|
||||||
|
enter-active-class="animated fadeInUp slow"
|
||||||
|
leave-active-class="animated fadeOutDown faster"
|
||||||
|
mode="out-in"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:key="scope.rowIndex + (timesheet_store.pay_period?.pay_period_no ?? 0)"
|
||||||
|
class="rounded-5 cursor-pointer"
|
||||||
|
style="font-size: 1.2em;"
|
||||||
|
:style="scope.row.last_work_day === null ? '' : 'opacity: 0.5;'"
|
||||||
|
>
|
||||||
|
<div v-if="scope.col.name === 'first_name'">
|
||||||
|
<span
|
||||||
|
class="text-h5 text-uppercase q-mr-xs"
|
||||||
|
:class="scope.row.last_work_day === null ? 'text-accent' : 'text-negative'"
|
||||||
|
>{{ scope.value }}</span>
|
||||||
|
<span class="text-uppercase text-weight-light">{{ scope.row.last_name }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="scope.col.name === 'last_work_day'">
|
||||||
|
<q-badge
|
||||||
|
:color="scope.row.last_work_day === null ? 'accent' : 'negative'"
|
||||||
|
class="row rounded-50 q-px-sm self-center"
|
||||||
|
>
|
||||||
|
<span class="text-bold text-uppercase q-mr-sm">
|
||||||
|
{{ scope.row.last_work_day === null ? $t('employee_list.table.active') :
|
||||||
|
$t('employee_list.table.inactive') }}
|
||||||
|
</span>
|
||||||
|
<q-icon
|
||||||
|
:name="scope.row.last_work_day === null ? 'check' : 'clear'"
|
||||||
|
size="xs"
|
||||||
|
/>
|
||||||
|
</q-badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span v-else>{{ scope.value }}</span>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Template for custome failed-to-load state -->
|
||||||
|
<template #no-data="{ message, filter }">
|
||||||
|
<div class="full-width column items-center text-accent q-gutter-sm">
|
||||||
|
<span class="text-h6 q-mt-xl">
|
||||||
|
{{ message }}
|
||||||
|
</span>
|
||||||
|
<q-icon
|
||||||
|
size="4em"
|
||||||
|
:name="filter ? 'filter_alt_off' : 'error_outline'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.q-table__card .q-table__sort-icon) {
|
||||||
|
fill: white !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.q-table--dense .q-table__sort-icon) {
|
||||||
|
font-size: 150%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky-header-table thead tr:first-child th {
|
||||||
|
background-color: var(--q-primary);
|
||||||
|
margin-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead tr th {
|
||||||
|
position: sticky;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead tr:first-child th {
|
||||||
|
top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.q-table--loading thead tr:last-child th {
|
||||||
|
top: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody {
|
||||||
|
scroll-margin-top: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.q-table) {
|
||||||
|
transition: height 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.q-table__grid-content) {
|
||||||
|
overflow: auto
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { useEmployeeStore } from 'src/stores/employee-store';
|
|
||||||
|
|
||||||
const employeeStore = useEmployeeStore();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<q-dialog v-model="employeeStore.isShowingEmployeeAddModifyWindow">
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
LOL
|
|
||||||
</q-card-section>
|
|
||||||
<q-inner-loading :showing="employeeStore.isLoadingEmployeeProfile"/>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
<script
|
||||||
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
|
import { computed, onMounted, ref } from 'vue';
|
||||||
|
import { useEmployeeStore } from 'src/stores/employee-store';
|
||||||
|
import { useEmployeeListApi } from 'src/modules/employee-list/composables/use-employee-api';
|
||||||
|
|
||||||
|
const employee_store = useEmployeeStore();
|
||||||
|
const employee_list_api = useEmployeeListApi();
|
||||||
|
|
||||||
|
const { presetId } = defineProps<{
|
||||||
|
presetId: number;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const employee_amount_using_preset = ref(0);
|
||||||
|
const delete_input_string = ref('');
|
||||||
|
const is_approve_deletion = computed(() => ['SUPPRIMER', 'DELETE'].includes(delete_input_string.value));
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const employees_with_preset = employee_store.employee_list.filter(employee => employee.preset_id === presetId);
|
||||||
|
employee_amount_using_preset.value = employees_with_preset.length;
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="column flex-center bg-secondary q-pa-md rounded-10 shadow-24"
|
||||||
|
style="border: 2px solid var(--q-negative); width: 40vw !important;"
|
||||||
|
>
|
||||||
|
<span class="col-auto text-weight-bold text-uppercase text-center text-negative text-h5 q-pb-lg">
|
||||||
|
{{ $t('shared.label.remove') }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="employee_amount_using_preset > 0"
|
||||||
|
class="col row flex-center text-weight-medium text-center q-mb-lg"
|
||||||
|
>
|
||||||
|
<span class="col-auto">{{ $t('employee_management.schedule_presets.delete_warning_employee_1') }}</span>
|
||||||
|
<span class="col-auto q-px-sm text-h6 text-weight-bolder text-negative">{{ employee_amount_using_preset
|
||||||
|
}}</span>
|
||||||
|
<span class="col-auto">{{ $t('employee_management.module_access.preset_employee') +
|
||||||
|
(employee_amount_using_preset > 1 ? 's' : '') }}</span>
|
||||||
|
<span>{{ $t('employee_management.schedule_presets.delete_warning_employee_2') }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<span class="text-weight-bold text-uppercase">{{ $t('employee_management.schedule_presets.delete_warning')
|
||||||
|
}}</span>
|
||||||
|
<q-input
|
||||||
|
v-model="delete_input_string"
|
||||||
|
standout
|
||||||
|
dense
|
||||||
|
rounded
|
||||||
|
:placeholder="$t('employee_management.enter_delete_input')"
|
||||||
|
input-class="text-center"
|
||||||
|
:input-style="delete_input_string.length > 0 ? '' : 'opacity: 0.6;'"
|
||||||
|
class="q-my-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-auto row">
|
||||||
|
<q-space />
|
||||||
|
<q-btn
|
||||||
|
push
|
||||||
|
dense
|
||||||
|
:disable="!is_approve_deletion"
|
||||||
|
:color="is_approve_deletion ? 'negative' : 'grey-6'"
|
||||||
|
:label="$t('shared.label.remove')"
|
||||||
|
class="q-px-md"
|
||||||
|
@click="employee_list_api.deleteSchedulePreset(presetId)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
<script
|
||||||
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import type { ShiftOption } from 'src/modules/timesheets/models/shift.models';
|
||||||
|
import type { SchedulePresetShift } from '../models/schedule-presets.models';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const SHIFT_OPTIONS: ShiftOption[] = [
|
||||||
|
{ label: t('timesheet.shift.types.REGULAR'), value: 'REGULAR', icon: 'wb_sunny', icon_color: 'blue-grey-3' },
|
||||||
|
{ label: t('timesheet.shift.types.EVENING'), value: 'EVENING', icon: 'bedtime', icon_color: 'indigo-8' },
|
||||||
|
{ label: t('timesheet.shift.types.EMERGENCY'), value: 'EMERGENCY', icon: 'ring_volume', icon_color: 'red-8' },
|
||||||
|
{ label: t('timesheet.shift.types.VACATION'), value: 'VACATION', icon: 'beach_access', icon_color: 'yellow-8' },
|
||||||
|
{ label: t('timesheet.shift.types.HOLIDAY'), value: 'HOLIDAY', icon: 'forest', icon_color: 'green-8' },
|
||||||
|
{ label: t('timesheet.shift.types.SICK'), value: 'SICK', icon: 'medication_liquid', icon_color: 'light-blue-6' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const shift = defineModel<SchedulePresetShift>('shift', { required: true });
|
||||||
|
const shift_type_selected = ref(SHIFT_OPTIONS[0]);
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
error: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
defineEmits<{
|
||||||
|
'clickDelete': [void];
|
||||||
|
'blurTimeField': [void];
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="row col-auto flex-center">
|
||||||
|
<div class="col q-pa-xs">
|
||||||
|
<q-select
|
||||||
|
ref="select"
|
||||||
|
v-model="shift_type_selected"
|
||||||
|
:standout="$q.dark.isActive ? 'bg-blue-grey-3' : 'bg-blue-grey-9'"
|
||||||
|
dense
|
||||||
|
options-dense
|
||||||
|
hide-dropdown-icon
|
||||||
|
:menu-offset="[0, 10]"
|
||||||
|
menu-anchor="bottom middle"
|
||||||
|
menu-self="top middle"
|
||||||
|
:options="SHIFT_OPTIONS"
|
||||||
|
class="col rounded-5 bg-dark weekday-field"
|
||||||
|
popup-content-class="text-uppercase text-weight-bold text-center rounded-5"
|
||||||
|
popup-content-style="border: 2px solid var(--q-accent)"
|
||||||
|
@update:model-value="option => shift.type = option.value"
|
||||||
|
>
|
||||||
|
<template #selected-item="scope">
|
||||||
|
<div
|
||||||
|
class="row flex-center text-uppercase q-ma-none q-pa-none no-wrap ellipsis full-width"
|
||||||
|
:tabindex="scope.tabindex"
|
||||||
|
>
|
||||||
|
<q-icon
|
||||||
|
:name="scope.opt.icon"
|
||||||
|
:color="scope.opt.icon_color"
|
||||||
|
class="col-auto q-mx-xs"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
style="line-height: 0.9em;"
|
||||||
|
class="col-auto ellipsis"
|
||||||
|
>{{ scope.opt.label }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #after>
|
||||||
|
<q-toggle
|
||||||
|
v-model="shift.is_remote"
|
||||||
|
dense
|
||||||
|
keep-color
|
||||||
|
size="2.5em"
|
||||||
|
color="accent"
|
||||||
|
icon="las la-building"
|
||||||
|
checked-icon="las la-laptop"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
anchor="top middle"
|
||||||
|
self="bottom middle"
|
||||||
|
:offset="[0, 10]"
|
||||||
|
class="text-uppercase text-weight-medium text-white bg-accent"
|
||||||
|
>
|
||||||
|
{{ shift.is_remote ? $t('timesheet.shift.types.REMOTE') :
|
||||||
|
$t('timesheet.shift.types.OFFICE') }}
|
||||||
|
</q-tooltip>
|
||||||
|
</q-toggle>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col q-px-xs">
|
||||||
|
<q-input
|
||||||
|
v-model="shift.start_time"
|
||||||
|
standout
|
||||||
|
dense
|
||||||
|
hide-bottom-space
|
||||||
|
type="time"
|
||||||
|
class="text-uppercase weekday-field"
|
||||||
|
:error="error"
|
||||||
|
@blur="$emit('blurTimeField')"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<div
|
||||||
|
class="column text-uppercase text-accent text-weight-bold full-height"
|
||||||
|
style="font-size: 0.5em; transform: translateY(4px);"
|
||||||
|
>
|
||||||
|
{{ $t('shared.misc.in') }} :
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col q-px-xs">
|
||||||
|
<q-input
|
||||||
|
v-model="shift.end_time"
|
||||||
|
standout
|
||||||
|
dense
|
||||||
|
hide-bottom-space
|
||||||
|
type="time"
|
||||||
|
class="text-uppercase weekday-field"
|
||||||
|
:error="error"
|
||||||
|
@blur="$emit('blurTimeField')"
|
||||||
|
>
|
||||||
|
<template #prepend>
|
||||||
|
<div
|
||||||
|
class="column text-uppercase text-accent text-weight-bold full-height"
|
||||||
|
style="font-size: 0.5em; transform: translateY(4px);"
|
||||||
|
>
|
||||||
|
{{ $t('shared.misc.out') }} :
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-auto q-px-xs">
|
||||||
|
<q-btn
|
||||||
|
dense
|
||||||
|
push
|
||||||
|
color="negative"
|
||||||
|
icon="clear"
|
||||||
|
size="sm"
|
||||||
|
tabindex="-1"
|
||||||
|
@click="$emit('clickDelete')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.q-field__native) {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekday-field :deep(.q-field__control) {
|
||||||
|
height: 25px;
|
||||||
|
min-height: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekday-field :deep(.q-field__marginal) {
|
||||||
|
height: 25px;
|
||||||
|
min-height: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.q-field--auto-height.q-field--dense .q-field__native) {
|
||||||
|
min-height: 25px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
121
src/modules/employee-list/components/schedule-presets-dialog.vue
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
<script
|
||||||
|
setup
|
||||||
|
lang="ts"
|
||||||
|
>
|
||||||
|
import SchedulePresetsDialogRow from './schedule-presets-dialog-row.vue';
|
||||||
|
import SchedulePresetsDialogDelete from 'src/modules/employee-list/components/schedule-presets-dialog-delete.vue';
|
||||||
|
|
||||||
|
import { useEmployeeListApi } from '../composables/use-employee-api';
|
||||||
|
import { SchedulePresetShift } from '../models/schedule-presets.models';
|
||||||
|
import { useSchedulePresetsStore } from 'src/stores/schedule-presets.store';
|
||||||
|
import { isShiftOverlap } from 'src/modules/timesheets/utils/shift.util';
|
||||||
|
|
||||||
|
const schedule_preset_store = useSchedulePresetsStore();
|
||||||
|
const employee_list_api = useEmployeeListApi();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<q-dialog
|
||||||
|
v-model="schedule_preset_store.is_manager_open"
|
||||||
|
full-width
|
||||||
|
>
|
||||||
|
<SchedulePresetsDialogDelete
|
||||||
|
v-if="schedule_preset_store.schedule_preset_dialog_mode === 'delete'"
|
||||||
|
:preset-id="schedule_preset_store.current_schedule_preset.id"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="column flex-center bg-secondary rounded-10 shadow-24 no-wrap"
|
||||||
|
style="border: 2px solid var(--q-accent); width: 50vw !important;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="row col-auto flex-center bg-primary full-width"
|
||||||
|
style="border-radius: 8px 8px 0 0;"
|
||||||
|
>
|
||||||
|
<span class="row col-auto text-uppercase text-weight-bold text-white q-py-sm">{{
|
||||||
|
schedule_preset_store.current_schedule_preset.id === -1 ?
|
||||||
|
$t('shared.label.add') :
|
||||||
|
$t('shared.label.modify') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row col-auto q-px-sm flex-center full-width q-py-sm">
|
||||||
|
<div class="col-8 bg-dark rounded-10 ellipsis">
|
||||||
|
<q-input
|
||||||
|
v-model="schedule_preset_store.current_schedule_preset.name"
|
||||||
|
standout
|
||||||
|
dense
|
||||||
|
hide-bottom-space
|
||||||
|
:placeholder="$t('employee_management.schedule_presets.preset_name_placeholder')"
|
||||||
|
class="text-uppercase"
|
||||||
|
input-class="text-weight-bold text-center"
|
||||||
|
>
|
||||||
|
<template #before>
|
||||||
|
<q-icon
|
||||||
|
name="edit"
|
||||||
|
color="accent"
|
||||||
|
class="q-ml-sm"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="schedule_preset_store.schedule_preset_dialog_mode !== 'copy'"
|
||||||
|
class="column col full-width q-py-sm q-px-lg no-wrap scroll"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="weekday of schedule_preset_store.current_schedule_preset.weekdays"
|
||||||
|
:key="weekday.day"
|
||||||
|
class="row col-auto items-center q-my-xs shadow-2 bg-dark rounded-10 ellipsis"
|
||||||
|
style="min-height: 50px;"
|
||||||
|
>
|
||||||
|
<span class="col-2 text-uppercase text-weight-bold q-ml-sm ellipsis">{{
|
||||||
|
$t(`shared.weekday.${weekday.day.toLowerCase()}`) }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="col column">
|
||||||
|
<div
|
||||||
|
v-for="_shift, index in weekday.shifts"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<SchedulePresetsDialogRow
|
||||||
|
v-model:shift="weekday.shifts[index]!"
|
||||||
|
:error="weekday.is_error"
|
||||||
|
@click-delete="weekday.shifts.splice(index, 1)"
|
||||||
|
@blur-time-field="weekday.is_error = isShiftOverlap(weekday.shifts)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-auto self-stretch">
|
||||||
|
<q-btn
|
||||||
|
square
|
||||||
|
icon="more_time"
|
||||||
|
color="accent"
|
||||||
|
class="full-height q-ma-none q-px-sm"
|
||||||
|
tabindex="-1"
|
||||||
|
@click="weekday.shifts.push(new SchedulePresetShift(weekday.day))"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-auto row self-end q-px-lg q-mt-sm full-width">
|
||||||
|
<q-space />
|
||||||
|
<q-btn
|
||||||
|
:disable="schedule_preset_store.current_schedule_preset.name === ''"
|
||||||
|
push
|
||||||
|
dense
|
||||||
|
:color="schedule_preset_store.current_schedule_preset.name === '' ? 'grey-7' : 'accent'"
|
||||||
|
icon="download"
|
||||||
|
:label="$t('shared.label.save')"
|
||||||
|
class="col-auto q-px-md q-mb-sm"
|
||||||
|
@click="employee_list_api.saveSchedulePreset"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-dialog>
|
||||||
|
</template>
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import type { EmployeeListTableItem } from 'src/modules/employee-list/types/employee-list-table-interface';
|
|
||||||
|
|
||||||
// const getEmployeeAvatar = (first_name: string, last_name: string) => {
|
|
||||||
// // add logic here to see if user has an avatar image and return that instead of initials
|
|
||||||
// return first_name.charAt(0) + last_name.charAt(0);
|
|
||||||
// };
|
|
||||||
|
|
||||||
const { row } = defineProps<{
|
|
||||||
row: EmployeeListTableItem
|
|
||||||
}>()
|
|
||||||
const emit = defineEmits<{
|
|
||||||
onProfileClick: [email: string]
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<q-card
|
|
||||||
v-ripple
|
|
||||||
class="column col-xs-6 col-sm-4 col-md-3 col-lg-2 no-wrap rounded-15 cursor-pointer q-ma-sm"
|
|
||||||
style="max-width: 230px;"
|
|
||||||
@click="emit('onProfileClick', row.email)"
|
|
||||||
>
|
|
||||||
<q-card-section class="col-6 text-center">
|
|
||||||
<q-avatar
|
|
||||||
color="primary"
|
|
||||||
size="8em"
|
|
||||||
class="shadow-3"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="src/assets/targo-default-avatar.png"
|
|
||||||
alt="employee avatar"
|
|
||||||
class="q-pa-xs"
|
|
||||||
>
|
|
||||||
</q-avatar>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<q-card-section
|
|
||||||
class="col-grow text-center text-h6 text-weight-medium text-uppercase q-pb-none"
|
|
||||||
style="line-height: 0.8em;"
|
|
||||||
>
|
|
||||||
<div class="ellipsis text-primary"> {{ row.first_name }} {{ row.last_name }} </div>
|
|
||||||
<q-separator color="primary" class="q-mx-sm q-mt-xs" />
|
|
||||||
<div class=" ellipsis-2-lines text-caption"> {{ row.job_title }} </div>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<q-card-section class="bg-primary text-white text-caption text-center q-py-none col-2 content-center ">
|
|
||||||
<div> {{ row.email }} </div>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</template>
|
|
||||||
|
|
@ -1,145 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed, onMounted, ref } from 'vue';
|
|
||||||
import { useEmployeeListApi } from 'src/modules/employee-list/composables/use-employee-api';
|
|
||||||
import { useEmployeeStore } from 'src/stores/employee-store';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import SupervisorCrewTableItem from './supervisor-crew-table-item.vue';
|
|
||||||
|
|
||||||
import type { EmployeeListTableItem } from '../../types/employee-list-table-interface';
|
|
||||||
import type { QTableColumn } from 'quasar';
|
|
||||||
|
|
||||||
const employee_list_api = useEmployeeListApi();
|
|
||||||
const employee_store = useEmployeeStore();
|
|
||||||
const is_loading_list = ref<boolean>(true);
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
|
||||||
const filter = ref("");
|
|
||||||
const is_grid_mode = ref(true);
|
|
||||||
const pagination = ref({ rowsPerPage: 0 });
|
|
||||||
|
|
||||||
const employee_list_columns = computed((): QTableColumn<EmployeeListTableItem>[] => [
|
|
||||||
{name: 'first_name', label: t('employee_list.table.first_name'), field: 'first_name', align: 'left'},
|
|
||||||
{name: 'last_name', label: t('employee_list.table.last_name'), field: 'last_name', align: 'left'},
|
|
||||||
{name: 'email', label: t('employee_list.table.email'), field: 'email', align: 'left'},
|
|
||||||
{name: 'supervisor_full_name', label: t('employee_list.table.supervisor'), field: 'supervisor_full_name', align: 'left'},
|
|
||||||
{name: 'company_name', label: t('employee_list.table.company'), field: 'company_name', align: 'left'},
|
|
||||||
{name: 'job_title', label: t('employee_list.table.role'), field: 'job_title', align: 'left'},
|
|
||||||
]);
|
|
||||||
|
|
||||||
onMounted( async () => {
|
|
||||||
is_loading_list.value = true;
|
|
||||||
await employee_list_api.getEmployeeList();
|
|
||||||
is_loading_list.value = false;
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="q-pa-lg col">
|
|
||||||
<q-table
|
|
||||||
dense
|
|
||||||
flat
|
|
||||||
hide-pagination
|
|
||||||
virtual-scroll
|
|
||||||
title=" "
|
|
||||||
card-style="max-height: 70vh;"
|
|
||||||
:rows="employee_store.employeeList"
|
|
||||||
:columns="employee_list_columns"
|
|
||||||
row-key="name"
|
|
||||||
v-model:pagination="pagination"
|
|
||||||
:rows-per-page-options="[0]"
|
|
||||||
:filter="filter"
|
|
||||||
class="q-pa-md bg-transparent"
|
|
||||||
:class="is_grid_mode ? '': 'my-sticky-header-table'"
|
|
||||||
: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'"
|
|
||||||
color="primary"
|
|
||||||
table-header-class="text-primary text-uppercase"
|
|
||||||
card-container-class="justify-center"
|
|
||||||
:grid="is_grid_mode"
|
|
||||||
:loading="is_loading_list"
|
|
||||||
:no-data-label="$t('shared.error.no_data_found')"
|
|
||||||
:no-results-label="$t('shared.error.no_search_results')"
|
|
||||||
:loading-label="$t('shared.label.loading')"
|
|
||||||
@row-click="() => console.log('click!')"
|
|
||||||
>
|
|
||||||
<template v-slot:item="props">
|
|
||||||
<SupervisorCrewTableItem :row="props.row"/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-slot:top>
|
|
||||||
<div class="row full-width q-mb-sm">
|
|
||||||
<q-btn
|
|
||||||
push
|
|
||||||
color="primary"
|
|
||||||
icon="person_add"
|
|
||||||
:label="$t('shared.label.add')"
|
|
||||||
class="text-uppercase"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<q-space />
|
|
||||||
|
|
||||||
<q-btn-toggle
|
|
||||||
v-model="is_grid_mode"
|
|
||||||
push
|
|
||||||
color="white"
|
|
||||||
text-color="primary"
|
|
||||||
toggle-color="primary"
|
|
||||||
class="q-mr-md"
|
|
||||||
:options="[
|
|
||||||
{icon: 'grid_view', value: true},
|
|
||||||
{icon: 'view_list', value: false},
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
<q-input
|
|
||||||
v-model="filter"
|
|
||||||
outlined
|
|
||||||
dense
|
|
||||||
rounded
|
|
||||||
color="primary"
|
|
||||||
bg-color="white"
|
|
||||||
label-color="primary"
|
|
||||||
:label="$t('shared.label.search')"
|
|
||||||
>
|
|
||||||
<template v-slot:append>
|
|
||||||
<q-icon
|
|
||||||
name="search"
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Template for custome failed-to-load state -->
|
|
||||||
<template v-slot:no-data="{ message, filter }">
|
|
||||||
<div class="full-width column items-center text-primary q-gutter-sm">
|
|
||||||
<span class="text-h6 q-mt-xl">
|
|
||||||
{{ message }}
|
|
||||||
</span>
|
|
||||||
<q-icon
|
|
||||||
size="4em"
|
|
||||||
:name="filter ? 'filter_alt_off' : 'error_outline'"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</q-table>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="sass">
|
|
||||||
.my-sticky-header-table
|
|
||||||
thead tr:first-child th
|
|
||||||
background-color: var(--q-dark)
|
|
||||||
margin-top: none
|
|
||||||
|
|
||||||
thead tr th
|
|
||||||
position: sticky
|
|
||||||
z-index: 1
|
|
||||||
thead tr:first-child th
|
|
||||||
top: 0
|
|
||||||
|
|
||||||
&.q-table--loading thead tr:last-child th
|
|
||||||
top: 48px
|
|
||||||
|
|
||||||
tbody
|
|
||||||
scroll-margin-top: 48px
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,18 +1,86 @@
|
||||||
import { useEmployeeStore } from "src/stores/employee-store";
|
import { useEmployeeStore } from "src/stores/employee-store";
|
||||||
|
import { useSchedulePresetsStore } from "src/stores/schedule-presets.store";
|
||||||
|
import { SchedulePreset } from "../models/schedule-presets.models";
|
||||||
|
import { isShiftOverlap } from "src/modules/timesheets/utils/shift.util";
|
||||||
|
|
||||||
export const useEmployeeListApi = () => {
|
export const useEmployeeListApi = () => {
|
||||||
const employeeListStore = useEmployeeStore();
|
const employee_store = useEmployeeStore();
|
||||||
|
const schedule_preset_store = useSchedulePresetsStore();
|
||||||
|
|
||||||
const getEmployeeList = (): Promise<void> => {
|
const getEmployeeList = async (): Promise<void> => {
|
||||||
return employeeListStore.getEmployeeList();
|
employee_store.is_loading = true;
|
||||||
|
|
||||||
|
const success = await employee_store.getEmployeeList();
|
||||||
|
if (success) await schedule_preset_store.findSchedulePresetList();
|
||||||
|
|
||||||
|
employee_store.is_loading = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getEmployeeDetails = (email: string): Promise<void> => {
|
const getEmployeeDetails = async (email: string): Promise<void> => {
|
||||||
return employeeListStore.getEmployeeDetails(email);
|
const success = await employee_store.getEmployeeDetails(email);
|
||||||
|
if (success && employee_store.employee.preset_id !== null) {
|
||||||
|
schedule_preset_store.setCurrentSchedulePreset(employee_store.employee.preset_id ?? -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setSchedulePreset = (preset_id: number) => {
|
||||||
|
schedule_preset_store.setCurrentSchedulePreset(preset_id);
|
||||||
|
employee_store.employee.preset_id = preset_id < 0 ? null : preset_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveSchedulePreset = async () => {
|
||||||
|
// Get the currently edited schedule preset from the store (frontend model)
|
||||||
|
const preset = schedule_preset_store.current_schedule_preset;
|
||||||
|
|
||||||
|
// Check if there's any overlap between shifts. If there is, is_error property
|
||||||
|
// will be toggled to true and save process will stop
|
||||||
|
for (const weekday of preset.weekdays) {
|
||||||
|
weekday.is_error = isShiftOverlap(weekday.shifts);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preset.weekdays.some(weekday => weekday.is_error)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flatten all weekday shifts into a single array
|
||||||
|
const preset_shifts = preset.weekdays.flatMap(weekday => weekday.shifts);
|
||||||
|
|
||||||
|
// Build a backend-compatible SchedulePreset instance
|
||||||
|
const backend_preset = new SchedulePreset(
|
||||||
|
preset.id,
|
||||||
|
preset.name,
|
||||||
|
preset_shifts
|
||||||
|
);
|
||||||
|
|
||||||
|
// Track whether the create/update operation succeeds
|
||||||
|
let success = false;
|
||||||
|
|
||||||
|
// Create a new preset if it has no backend ID, otherwise update the existing one
|
||||||
|
if (preset.id === -1)
|
||||||
|
success = await schedule_preset_store.createSchedulePreset(backend_preset);
|
||||||
|
else
|
||||||
|
success = await schedule_preset_store.updateSchedulePreset(backend_preset);
|
||||||
|
|
||||||
|
// On success, refresh the preset list and close the preset manager UI
|
||||||
|
if (success) {
|
||||||
|
await schedule_preset_store.findSchedulePresetList();
|
||||||
|
schedule_preset_store.is_manager_open = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteSchedulePreset = async (preset_id: number) => {
|
||||||
|
const success = await schedule_preset_store.deleteSchedulePreset(preset_id);
|
||||||
|
if (success) {
|
||||||
|
await schedule_preset_store.findSchedulePresetList();
|
||||||
|
schedule_preset_store.is_manager_open = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getEmployeeList,
|
getEmployeeList,
|
||||||
getEmployeeDetails,
|
getEmployeeDetails,
|
||||||
|
setSchedulePreset,
|
||||||
|
saveSchedulePreset,
|
||||||
|
deleteSchedulePreset,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
17
src/modules/employee-list/employee-list-utils.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import type { EmbeddedValidationRule, EmbeddedValidationRuleFn } from "quasar";
|
||||||
|
|
||||||
|
export type QuasarRules = Record<EmbeddedValidationRule, EmbeddedValidationRuleFn>;
|
||||||
|
type EmployeeProfileValidationRule<T> = EmbeddedValidationRule | ((value: T, rules: QuasarRules, error_message: string) => boolean | string | Promise<boolean | string>);
|
||||||
|
|
||||||
|
export const useEmployeeProfileRules = () => {
|
||||||
|
const isNotEmpty: EmployeeProfileValidationRule<unknown> = (value, _rules, error_message) => (value !== undefined && value !== null && value !== '') || error_message;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isNotEmpty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const company_options = [
|
||||||
|
{ label: 'Targo', value: 'Targo' },
|
||||||
|
{ label: 'Solucom', value: 'Solucom' },
|
||||||
|
]
|
||||||
149
src/modules/employee-list/models/employee-profile.models.ts
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
import type { QSelectOption, QTableColumn } from "quasar";
|
||||||
|
import type { UserModuleAccess } from "src/modules/shared/models/user.models";
|
||||||
|
|
||||||
|
export type ModuleAccessPreset = 'admin' | 'supervisor' | 'employee' | 'none';
|
||||||
|
export type CompanyNames = 'Targo' | 'Solucom';
|
||||||
|
|
||||||
|
export interface PaidTimeOff {
|
||||||
|
sick_hours: number;
|
||||||
|
vacation_hours: number;
|
||||||
|
banked_hours: number;
|
||||||
|
last_updated?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EmployeeProfile {
|
||||||
|
first_name: string;
|
||||||
|
last_name: string;
|
||||||
|
supervisor_full_name: string;
|
||||||
|
company_name: CompanyNames;
|
||||||
|
job_title: string;
|
||||||
|
email: string;
|
||||||
|
phone_number: string;
|
||||||
|
first_work_day: string;
|
||||||
|
last_work_day?: string | null;
|
||||||
|
external_payroll_id?: number;
|
||||||
|
daily_expected_hours?: number;
|
||||||
|
paid_time_off?: PaidTimeOff;
|
||||||
|
residence: string;
|
||||||
|
birth_date: string;
|
||||||
|
is_supervisor: boolean;
|
||||||
|
user_module_access: UserModuleAccess[];
|
||||||
|
preset_id?: number | null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.first_name = '';
|
||||||
|
this.last_name = '';
|
||||||
|
this.supervisor_full_name = '';
|
||||||
|
this.company_name = 'Targo';
|
||||||
|
this.job_title = '';
|
||||||
|
this.email = '';
|
||||||
|
this.phone_number = '';
|
||||||
|
this.first_work_day = '';
|
||||||
|
this.last_work_day = null;
|
||||||
|
this.residence = '';
|
||||||
|
this.birth_date = '';
|
||||||
|
this.is_supervisor = false;
|
||||||
|
this.user_module_access = ['dashboard',];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EmployeeListFilters {
|
||||||
|
search_bar_string: string;
|
||||||
|
hide_inactive_users: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const employee_list_columns: QTableColumn<EmployeeProfile>[] = [
|
||||||
|
{
|
||||||
|
name: 'first_name',
|
||||||
|
label: 'timesheet_approvals.table.full_name',
|
||||||
|
field: 'first_name',
|
||||||
|
align: 'left',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'last_name',
|
||||||
|
label: 'employee_list.table.last_name',
|
||||||
|
field: 'last_name',
|
||||||
|
align: 'left',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
label: 'employee_list.table.email',
|
||||||
|
field: 'email',
|
||||||
|
align: 'left',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'supervisor_full_name',
|
||||||
|
label: 'employee_list.table.supervisor',
|
||||||
|
field: 'supervisor_full_name',
|
||||||
|
align: 'left',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'company_name',
|
||||||
|
label: 'employee_list.table.company',
|
||||||
|
field: 'company_name',
|
||||||
|
align: 'left',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'phone_number',
|
||||||
|
label: 'employee_list.table.phone_number',
|
||||||
|
field: 'phone_number',
|
||||||
|
align: 'left',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'job_title',
|
||||||
|
label: 'employee_list.table.role',
|
||||||
|
field: 'job_title',
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expected_daily_hours',
|
||||||
|
label: 'employee_list.table.expected_daily_hours',
|
||||||
|
field: 'daily_expected_hours',
|
||||||
|
align: 'left',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'last_work_day',
|
||||||
|
label: 'status',
|
||||||
|
field: 'last_work_day',
|
||||||
|
align: 'center',
|
||||||
|
sortable: true,
|
||||||
|
sort: (a: string | null, b: string | null) => {
|
||||||
|
if (a === null && b === null) return 0;
|
||||||
|
else if (a === null && b !== null) return 1;
|
||||||
|
else return -1;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const employee_access_options: QSelectOption<UserModuleAccess>[] = [
|
||||||
|
{ label: 'dashboard', value: 'dashboard' },
|
||||||
|
{ label: 'employee_list', value: 'employee_list' },
|
||||||
|
{ label: 'personal_profile', value: 'personal_profile' },
|
||||||
|
{ label: 'timesheets', value: 'timesheets' },
|
||||||
|
{ label: 'employee_management', value: 'employee_management' },
|
||||||
|
{ label: 'timesheets_approval', value: 'timesheets_approval' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export const employee_access_presets: Record<ModuleAccessPreset, UserModuleAccess[]> = {
|
||||||
|
'admin' : ['dashboard', 'employee_list', 'employee_management', 'personal_profile', 'timesheets', 'timesheets_approval'],
|
||||||
|
'supervisor' : ['dashboard', 'employee_list', 'personal_profile', 'timesheets', 'timesheets_approval'],
|
||||||
|
'employee' : ['dashboard', 'timesheets', 'personal_profile', 'employee_list'],
|
||||||
|
'none' : [],
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getEmployeeAccessOptionIcon = (module: UserModuleAccess): string => {
|
||||||
|
switch (module) {
|
||||||
|
case 'dashboard': return 'home';
|
||||||
|
case 'employee_list' : return 'groups';
|
||||||
|
case 'employee_management': return 'las la-user-edit';
|
||||||
|
case 'personal_profile': return 'las la-id-card';
|
||||||
|
case 'timesheets': return 'punch_clock';
|
||||||
|
case 'timesheets_approval': return 'event_available';
|
||||||
|
}
|
||||||
|
}
|
||||||
59
src/modules/employee-list/models/schedule-presets.models.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
import type { ShiftType } from "src/modules/timesheets/models/shift.models";
|
||||||
|
|
||||||
|
export type Weekday = 'SUN' | 'MON' | 'TUE' | 'WED' | 'THU' | 'FRI' | 'SAT';
|
||||||
|
|
||||||
|
export const WEEKDAYS: Weekday[] = ['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT'];
|
||||||
|
|
||||||
|
export type PresetManagerMode = 'create' | 'update' | 'copy' | 'delete';
|
||||||
|
|
||||||
|
export class SchedulePreset {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
shifts: SchedulePresetShift[];
|
||||||
|
|
||||||
|
constructor(id?: number, name?: string, shifts?: SchedulePresetShift[]) {
|
||||||
|
this.id = id ?? -1;
|
||||||
|
this.name = name ?? 'default';
|
||||||
|
this.shifts = shifts ?? [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SchedulePresetShift {
|
||||||
|
preset_id: number;
|
||||||
|
week_day: Weekday;
|
||||||
|
type: ShiftType;
|
||||||
|
start_time: string;
|
||||||
|
end_time: string;
|
||||||
|
is_remote: boolean;
|
||||||
|
|
||||||
|
constructor(weekday: Weekday) {
|
||||||
|
this.preset_id = -1;
|
||||||
|
this.week_day = weekday;
|
||||||
|
this.type = 'REGULAR';
|
||||||
|
this.start_time = '00:00';
|
||||||
|
this.end_time = '00:00';
|
||||||
|
this.is_remote = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SchedulePresetFrontend {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
weekdays: WeekdayPresetShifts[];
|
||||||
|
|
||||||
|
constructor(schedule_preset?: SchedulePreset) {
|
||||||
|
this.id = schedule_preset?.id ?? -1;
|
||||||
|
this.name = schedule_preset?.name ?? '';
|
||||||
|
this.weekdays = WEEKDAYS.map(day => ({
|
||||||
|
day,
|
||||||
|
is_error: false,
|
||||||
|
shifts: schedule_preset !== undefined ? schedule_preset?.shifts.filter(shift => shift.week_day === day) : [],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WeekdayPresetShifts {
|
||||||
|
day: Weekday;
|
||||||
|
is_error: boolean;
|
||||||
|
shifts: SchedulePresetShift[];
|
||||||
|
}
|
||||||
31
src/modules/employee-list/services/employee-list-service.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { api } from 'src/boot/axios';
|
||||||
|
import type { EmployeeProfile } from 'src/modules/employee-list/models/employee-profile.models';
|
||||||
|
import type { BackendResponse } from 'src/modules/shared/models/backend-response.models';
|
||||||
|
|
||||||
|
export const EmployeeListService = {
|
||||||
|
getEmployeeList: async (): Promise<BackendResponse<EmployeeProfile[]>> => {
|
||||||
|
const response = await api.get<BackendResponse<EmployeeProfile[]>>('/employees/employee-list')
|
||||||
|
return response.data;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
getEmployeeDetails: async (): Promise<BackendResponse<EmployeeProfile>> => {
|
||||||
|
const response = await api.get('employees/profile');
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
getEmployeeDetailsWithEmployeeEmail: async (employee_email: string): Promise<BackendResponse<EmployeeProfile>> => {
|
||||||
|
const response = await api.get(`employees/profile?employee_email=${employee_email}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
createNewEmployee: async (profile: Omit<EmployeeProfile, 'last_work_day' | 'birth_date'>): Promise<BackendResponse<EmployeeProfile>> => {
|
||||||
|
const response = await api.post('employees/create', profile);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateEmployee: async (profile: EmployeeProfile): Promise<BackendResponse<EmployeeProfile>> => {
|
||||||
|
const response = await api.patch('employees/update', profile);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { api } from "src/boot/axios";
|
||||||
|
import type { SchedulePreset } from "src/modules/employee-list/models/schedule-presets.models";
|
||||||
|
import type { BackendResponse } from "src/modules/shared/models/backend-response.models";
|
||||||
|
|
||||||
|
export const SchedulePresetsService = {
|
||||||
|
createSchedulePresets: async (preset: SchedulePreset) => {
|
||||||
|
const response = await api.post(`/schedule-presets/create/`, preset);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateSchedulePresets: async (preset: SchedulePreset): Promise<BackendResponse<boolean>> => {
|
||||||
|
const response = await api.patch(`/schedule-presets/update`, preset);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteSchedulePresets: async (preset_id: number): Promise<BackendResponse<boolean>> => {
|
||||||
|
const response = await api.delete(`/schedule-presets/delete/${preset_id}`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
getSchedulePresetsList: async (): Promise<BackendResponse<SchedulePreset[]>> => {
|
||||||
|
const response = await api.get(`/schedule-presets/find-list`);
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
|
||||||
|
applyPresets: async (preset_name: string, start_date: string) => {
|
||||||
|
const response = await api.post(`/schedule-presets/apply-presets/`, { preset: preset_name, start: start_date });
|
||||||
|
return response.data;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
// /* eslint-disable */
|
|
||||||
import { api } from 'src/boot/axios';
|
|
||||||
import type { EmployeeListTableItem } from '../types/employee-list-table-interface';
|
|
||||||
import type { EmployeeProfile } from '../types/employee-profile-interface';
|
|
||||||
|
|
||||||
|
|
||||||
export const EmployeeListService = {
|
|
||||||
getEmployeeList: async (): Promise<EmployeeListTableItem[]> => {
|
|
||||||
const response = await api.get<EmployeeListTableItem[]>('/employees/employee-list')
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
|
|
||||||
getEmployeeDetails: async (email: string): Promise<EmployeeProfile> => {
|
|
||||||
const response = await api.get<EmployeeProfile>('employees/profile/' + email);
|
|
||||||
return response.data;
|
|
||||||
},
|
|
||||||
};
|
|
||||||