fix : malio UI
This commit is contained in:
@@ -6,6 +6,7 @@
|
|||||||
<MalioSelectCheckbox
|
<MalioSelectCheckbox
|
||||||
v-model="selectedSiteIds"
|
v-model="selectedSiteIds"
|
||||||
:options="siteOptions"
|
:options="siteOptions"
|
||||||
|
groupClass="w-80"
|
||||||
label="Sites"
|
label="Sites"
|
||||||
display-select-all
|
display-select-all
|
||||||
/>
|
/>
|
||||||
@@ -46,6 +47,7 @@
|
|||||||
<MalioSelectCheckbox
|
<MalioSelectCheckbox
|
||||||
v-model="selectedSiteIds"
|
v-model="selectedSiteIds"
|
||||||
:options="siteOptions"
|
:options="siteOptions"
|
||||||
|
groupClass="w-80"
|
||||||
label="Sites"
|
label="Sites"
|
||||||
display-select-all
|
display-select-all
|
||||||
/>
|
/>
|
||||||
|
|||||||
8
frontend/package-lock.json
generated
8
frontend/package-lock.json
generated
@@ -7,7 +7,7 @@
|
|||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@malio/layer-ui": "^1.4.3",
|
"@malio/layer-ui": "^1.4.5",
|
||||||
"@nuxt/icon": "^2.2.1",
|
"@nuxt/icon": "^2.2.1",
|
||||||
"@nuxtjs/i18n": "^10.2.1",
|
"@nuxtjs/i18n": "^10.2.1",
|
||||||
"@pinia/nuxt": "^0.11.3",
|
"@pinia/nuxt": "^0.11.3",
|
||||||
@@ -2222,9 +2222,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@malio/layer-ui": {
|
"node_modules/@malio/layer-ui": {
|
||||||
"version": "1.4.3",
|
"version": "1.4.5",
|
||||||
"resolved": "https://gitea.malio.fr/api/packages/MALIO-DEV/npm/%40malio%2Flayer-ui/-/1.4.3/layer-ui-1.4.3.tgz",
|
"resolved": "https://gitea.malio.fr/api/packages/MALIO-DEV/npm/%40malio%2Flayer-ui/-/1.4.5/layer-ui-1.4.5.tgz",
|
||||||
"integrity": "sha512-XGR0VteuRGGizl8ZP2ZRlyWsdSTAwOYR7z5687Gx/SFr5eTg+poOV2NupqOuWCksxEcXA54vzzC0vMG8PbSvxg==",
|
"integrity": "sha512-UfVkLJk3WWGoZE1eyei0pY45IUbZRzabJr2X6GNFabHd/8EmuXqwP+LxCl8wEAO4ODrNKsVLJdi0eL3Zekv4Dg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/icon": "^2.2.1",
|
"@nuxt/icon": "^2.2.1",
|
||||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nuxt/icon": "^2.2.1",
|
"@nuxt/icon": "^2.2.1",
|
||||||
"@nuxtjs/i18n": "^10.2.1",
|
"@nuxtjs/i18n": "^10.2.1",
|
||||||
"@malio/layer-ui": "^1.4.3",
|
"@malio/layer-ui": "^1.4.5",
|
||||||
"@pinia/nuxt": "^0.11.3",
|
"@pinia/nuxt": "^0.11.3",
|
||||||
"nuxt": "^4.3.0",
|
"nuxt": "^4.3.0",
|
||||||
"nuxt-toast": "^1.4.0",
|
"nuxt-toast": "^1.4.0",
|
||||||
|
|||||||
@@ -2,13 +2,12 @@
|
|||||||
<div class="h-full flex flex-col overflow-hidden">
|
<div class="h-full flex flex-col overflow-hidden">
|
||||||
<div class="flex items-center justify-between pb-6">
|
<div class="flex items-center justify-between pb-6">
|
||||||
<h1 class="text-4xl font-bold text-primary-500">Types de statut</h1>
|
<h1 class="text-4xl font-bold text-primary-500">Types de statut</h1>
|
||||||
<button
|
<MalioButton
|
||||||
type="button"
|
label="Ajouter un type"
|
||||||
class="rounded-lg bg-primary-500 px-4 py-2 text-md font-semibold text-white hover:bg-secondary-500"
|
icon-name="mdi:plus"
|
||||||
|
icon-position="left"
|
||||||
@click="openCreate"
|
@click="openCreate"
|
||||||
>
|
/>
|
||||||
+ Ajouter un type
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -56,60 +55,40 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AppDrawer v-model="isDrawerOpen" :title="drawerTitle">
|
<MalioDrawer v-model="isDrawerOpen" :title="drawerTitle">
|
||||||
<form class="space-y-4" @submit.prevent="handleSubmit">
|
<form class="space-y-4" @submit.prevent="handleSubmit">
|
||||||
<div>
|
<MalioInputText
|
||||||
<label class="text-md font-semibold text-neutral-700" for="code">
|
v-model="form.code"
|
||||||
Code <span class="text-red-600">*</span>
|
label="Code *"
|
||||||
</label>
|
group-class="mt-2"
|
||||||
<input
|
:max-length="10"
|
||||||
id="code"
|
:error="showCodeError ? 'Le code est obligatoire.' : ''"
|
||||||
v-model="form.code"
|
/>
|
||||||
type="text"
|
<MalioInputText
|
||||||
maxlength="10"
|
v-model="form.label"
|
||||||
:class="codeFieldClass"
|
label="Libellé *"
|
||||||
/>
|
group-class="mt-2"
|
||||||
<p v-if="showCodeError" class="mt-1 text-sm text-red-600">
|
:error="showLabelError ? 'Le libellé est obligatoire.' : ''"
|
||||||
Le code est obligatoire.
|
/>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="text-md font-semibold text-neutral-700" for="label">
|
|
||||||
Libellé <span class="text-red-600">*</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="label"
|
|
||||||
v-model="form.label"
|
|
||||||
type="text"
|
|
||||||
:class="labelFieldClass"
|
|
||||||
/>
|
|
||||||
<p v-if="showLabelError" class="mt-1 text-sm text-red-600">
|
|
||||||
Le libellé est obligatoire.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<label class="text-md font-semibold text-neutral-700">
|
<label class="text-md font-semibold text-neutral-700">
|
||||||
Compté comme travaillé
|
Compté comme travaillé
|
||||||
</label>
|
</label>
|
||||||
<div class="mt-2 flex items-center gap-6">
|
<div class="mt-2 flex items-center gap-6">
|
||||||
<label class="inline-flex items-center gap-2 text-md text-neutral-800">
|
<MalioRadioButton
|
||||||
<input
|
v-model="form.countAsWorkedHours"
|
||||||
v-model="form.countAsWorkedHours"
|
name="countAsWorkedHours"
|
||||||
type="radio"
|
:value="true"
|
||||||
class="h-4 w-4"
|
label="Oui"
|
||||||
:value="true"
|
group-class="w-auto mt-0"
|
||||||
/>
|
/>
|
||||||
Oui
|
<MalioRadioButton
|
||||||
</label>
|
v-model="form.countAsWorkedHours"
|
||||||
<label class="inline-flex items-center gap-2 text-md text-neutral-800">
|
name="countAsWorkedHours"
|
||||||
<input
|
:value="false"
|
||||||
v-model="form.countAsWorkedHours"
|
label="Non"
|
||||||
type="radio"
|
group-class="w-auto mt-0"
|
||||||
class="h-4 w-4"
|
/>
|
||||||
:value="false"
|
|
||||||
/>
|
|
||||||
Non
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -130,32 +109,29 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="editingType" class="grid grid-cols-2 gap-3 pt-2">
|
<div v-if="editingType" class="grid grid-cols-2 gap-3 pt-2">
|
||||||
<button
|
<MalioButton
|
||||||
type="button"
|
label="Supprimer"
|
||||||
class="flex items-center justify-center rounded-md bg-red-500 px-4 py-2 text-md font-semibold text-white hover:bg-red-600"
|
variant="danger"
|
||||||
|
button-class="w-full"
|
||||||
@click="confirmDelete(editingType)"
|
@click="confirmDelete(editingType)"
|
||||||
>
|
/>
|
||||||
Supprimer
|
<MalioButton
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="submit"
|
type="submit"
|
||||||
class="flex items-center justify-center rounded-md bg-primary-500 px-4 py-2 text-md font-semibold text-white hover:bg-secondary-500"
|
label="Modifier"
|
||||||
:class="submitButtonClass"
|
button-class="w-full"
|
||||||
>
|
:disabled="isSubmitting || !isFormValid"
|
||||||
Modifier
|
/>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex justify-center pt-2">
|
<div v-else class="flex justify-center pt-2">
|
||||||
<button
|
<MalioButton
|
||||||
type="submit"
|
type="submit"
|
||||||
class="flex w-[200px] items-center justify-center gap-2 rounded-md bg-primary-500 px-4 py-2 text-md font-semibold text-white hover:bg-secondary-500"
|
label="Valider"
|
||||||
:class="submitButtonClass"
|
button-class="w-[200px]"
|
||||||
>
|
:disabled="isSubmitting || !isFormValid"
|
||||||
+ Ajouter
|
/>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</AppDrawer>
|
</MalioDrawer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -202,20 +178,6 @@ const showCodeError = computed(() => validationTouched.code && !isCodeValid.valu
|
|||||||
const showLabelError = computed(() => validationTouched.label && !isLabelValid.value)
|
const showLabelError = computed(() => validationTouched.label && !isLabelValid.value)
|
||||||
const showColorError = computed(() => validationTouched.color && !isColorValid.value)
|
const showColorError = computed(() => validationTouched.color && !isColorValid.value)
|
||||||
|
|
||||||
const baseInputClass =
|
|
||||||
'mt-2 w-full rounded-md border px-3 py-2 text-base text-neutral-900 focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-secondary-500/20'
|
|
||||||
const codeFieldClass = computed(() => {
|
|
||||||
if (showCodeError.value) {
|
|
||||||
return `${baseInputClass} border-red-500`
|
|
||||||
}
|
|
||||||
return `${baseInputClass} border-neutral-300`
|
|
||||||
})
|
|
||||||
const labelFieldClass = computed(() => {
|
|
||||||
if (showLabelError.value) {
|
|
||||||
return `${baseInputClass} border-red-500`
|
|
||||||
}
|
|
||||||
return `${baseInputClass} border-neutral-300`
|
|
||||||
})
|
|
||||||
const colorFieldClass = computed(() => {
|
const colorFieldClass = computed(() => {
|
||||||
const baseColorClass = 'h-10 w-16 cursor-pointer rounded-md border bg-white p-1'
|
const baseColorClass = 'h-10 w-16 cursor-pointer rounded-md border bg-white p-1'
|
||||||
if (showColorError.value) {
|
if (showColorError.value) {
|
||||||
@@ -224,13 +186,6 @@ const colorFieldClass = computed(() => {
|
|||||||
return `${baseColorClass} border-neutral-300`
|
return `${baseColorClass} border-neutral-300`
|
||||||
})
|
})
|
||||||
|
|
||||||
const submitButtonClass = computed(() => {
|
|
||||||
if (isSubmitting.value || !isFormValid.value) {
|
|
||||||
return 'opacity-50 cursor-not-allowed'
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
})
|
|
||||||
|
|
||||||
const loadAbsenceTypes = async () => {
|
const loadAbsenceTypes = async () => {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -5,16 +5,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-3 py-6">
|
<div class="flex flex-col gap-3 py-6">
|
||||||
<div class="flex items-center justify-between gap-4">
|
<div class="flex items-center justify-between gap-4">
|
||||||
<div class="flex items-center gap-4">
|
<MalioSelectCheckbox
|
||||||
<div class="relative z-50 w-80">
|
v-model="selectedSiteIds"
|
||||||
<MalioSelectCheckbox
|
:options="siteOptions"
|
||||||
v-model="selectedSiteIds"
|
label="Sites"
|
||||||
:options="siteOptions"
|
groupClass="relative z-50 w-80 h-10"
|
||||||
label="Sites"
|
display-select-all
|
||||||
display-select-all
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<MalioButton
|
<MalioButton
|
||||||
label="Ajouter une absence"
|
label="Ajouter une absence"
|
||||||
|
|||||||
@@ -4,34 +4,19 @@
|
|||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<h1 class="text-4xl font-bold text-primary-500">Employés</h1>
|
<h1 class="text-4xl font-bold text-primary-500">Employés</h1>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<button
|
<MalioButton
|
||||||
type="button"
|
label="Export"
|
||||||
class="flex items-center gap-2 rounded-lg bg-primary-500 px-4 py-2 text-md font-semibold text-white hover:bg-secondary-500"
|
variant="secondary"
|
||||||
@click="handleLeaveRecapPrint"
|
icon-name="mdi:download"
|
||||||
>
|
icon-position="left"
|
||||||
Export récap. congés
|
@click="openExportDrawer"
|
||||||
</button>
|
/>
|
||||||
<button
|
<MalioButton
|
||||||
type="button"
|
label="Ajouter un employé"
|
||||||
class="flex items-center gap-2 rounded-lg bg-primary-500 px-4 py-2 text-md font-semibold text-white hover:bg-secondary-500"
|
icon-name="mdi:plus"
|
||||||
@click="isSalaryRecapOpen = true"
|
icon-position="left"
|
||||||
>
|
|
||||||
Export récap. salaire
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="flex items-center gap-2 rounded-lg bg-primary-500 px-4 py-2 text-md font-semibold text-white hover:bg-secondary-500"
|
|
||||||
@click="isYearlyHoursBulkOpen = true"
|
|
||||||
>
|
|
||||||
Export heures
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="flex items-center gap-2 rounded-lg bg-primary-500 px-4 py-2 text-md font-semibold text-white hover:bg-secondary-500"
|
|
||||||
@click="openCreate"
|
@click="openCreate"
|
||||||
>
|
/>
|
||||||
+ Ajouter un employé
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-3 py-7">
|
<div class="flex items-center gap-3 py-7">
|
||||||
@@ -46,6 +31,7 @@
|
|||||||
<MalioSelectCheckbox
|
<MalioSelectCheckbox
|
||||||
v-model="selectedSiteIds"
|
v-model="selectedSiteIds"
|
||||||
:options="siteOptions"
|
:options="siteOptions"
|
||||||
|
groupClass="w-80"
|
||||||
label="Sites"
|
label="Sites"
|
||||||
display-select-all
|
display-select-all
|
||||||
/>
|
/>
|
||||||
@@ -262,16 +248,58 @@
|
|||||||
</form>
|
</form>
|
||||||
</AppDrawer>
|
</AppDrawer>
|
||||||
|
|
||||||
<SalaryRecapDrawer
|
<MalioDrawer v-model="isExportDrawerOpen" title="Export">
|
||||||
v-model="isSalaryRecapOpen"
|
<div class="space-y-4">
|
||||||
@submit="handleSalaryRecapPrint"
|
<MalioSelect
|
||||||
/>
|
:model-value="exportChoice === '' ? null : exportChoice"
|
||||||
|
:options="exportTypeOptions"
|
||||||
|
label="Type d'export"
|
||||||
|
empty-option-label="Choisir un export"
|
||||||
|
group-class="mt-2"
|
||||||
|
min-width=""
|
||||||
|
@update:model-value="onExportChoiceChange"
|
||||||
|
/>
|
||||||
|
|
||||||
<BulkYearlyHoursDrawer
|
<div v-if="exportChoice === 'salary-recap'">
|
||||||
v-model="isYearlyHoursBulkOpen"
|
<label class="text-md font-semibold text-neutral-700" for="export-salary-month">
|
||||||
:is-loading="isYearlyHoursBulkLoading"
|
Mois <span class="text-red-600">*</span>
|
||||||
@submit="handleBulkYearlyHoursPrint"
|
</label>
|
||||||
/>
|
<input
|
||||||
|
id="export-salary-month"
|
||||||
|
v-model="exportSalaryMonth"
|
||||||
|
type="month"
|
||||||
|
class="mt-2 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-md text-neutral-900"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-else-if="exportChoice === 'yearly-hours'">
|
||||||
|
<MalioSelect
|
||||||
|
:model-value="exportYear"
|
||||||
|
:options="exportYearOptions"
|
||||||
|
label="Année *"
|
||||||
|
min-width=""
|
||||||
|
@update:model-value="(v) => { if (v !== null) exportYear = Number(v) }"
|
||||||
|
/>
|
||||||
|
<MalioSelect
|
||||||
|
:model-value="exportMonth === '' ? null : exportMonth"
|
||||||
|
:options="exportMonthOptions"
|
||||||
|
label="Mois *"
|
||||||
|
empty-option-label="Choisir un mois"
|
||||||
|
min-width=""
|
||||||
|
@update:model-value="(v) => { exportMonth = v === null ? '' : Number(v) }"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="flex justify-center pt-2">
|
||||||
|
<MalioButton
|
||||||
|
label="Valider"
|
||||||
|
button-class="w-[200px]"
|
||||||
|
:disabled="!isExportValid"
|
||||||
|
@click="handleExportValidate"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</MalioDrawer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -285,8 +313,6 @@ import {listContracts} from '~/services/contracts'
|
|||||||
import {createEmployee, deleteEmployee, listEmployees, updateEmployee} from '~/services/employees'
|
import {createEmployee, deleteEmployee, listEmployees, updateEmployee} from '~/services/employees'
|
||||||
import {listSites} from '~/services/sites'
|
import {listSites} from '~/services/sites'
|
||||||
import {listInterimAgencies, type InterimAgency} from '~/services/interim-agencies'
|
import {listInterimAgencies, type InterimAgency} from '~/services/interim-agencies'
|
||||||
import SalaryRecapDrawer from '~/components/SalaryRecapDrawer.vue'
|
|
||||||
import BulkYearlyHoursDrawer from '~/components/BulkYearlyHoursDrawer.vue'
|
|
||||||
import {contractNatureLabel, isContractNature, requiresContractEndDate, showsContractEndDate} from '~/utils/contract'
|
import {contractNatureLabel, isContractNature, requiresContractEndDate, showsContractEndDate} from '~/utils/contract'
|
||||||
import {usePdfPrinter} from '~/composables/usePdfPrinter'
|
import {usePdfPrinter} from '~/composables/usePdfPrinter'
|
||||||
|
|
||||||
@@ -297,9 +323,50 @@ useHead({
|
|||||||
const isDrawerOpen = ref(false)
|
const isDrawerOpen = ref(false)
|
||||||
const isSubmitting = ref(false)
|
const isSubmitting = ref(false)
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
const isSalaryRecapOpen = ref(false)
|
const isExportDrawerOpen = ref(false)
|
||||||
const isYearlyHoursBulkOpen = ref(false)
|
const exportChoice = ref<'leave-recap' | 'salary-recap' | 'yearly-hours' | ''>('')
|
||||||
const isYearlyHoursBulkLoading = ref(false)
|
const exportYear = ref<number>(new Date().getFullYear())
|
||||||
|
const exportMonth = ref<number | ''>(new Date().getMonth() + 1)
|
||||||
|
const exportSalaryMonth = ref<string>(`${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}`)
|
||||||
|
|
||||||
|
const exportTypeOptions = [
|
||||||
|
{ label: 'Récap. congés', value: 'leave-recap' },
|
||||||
|
{ label: 'Récap. salaire', value: 'salary-recap' },
|
||||||
|
{ label: 'Heures annuelles', value: 'yearly-hours' }
|
||||||
|
]
|
||||||
|
const exportYearOptions = computed(() => {
|
||||||
|
const current = new Date().getFullYear()
|
||||||
|
return Array.from({ length: 6 }, (_, i) => ({ label: String(current - i), value: current - i }))
|
||||||
|
})
|
||||||
|
const exportMonthOptions = [
|
||||||
|
{ label: 'Janvier', value: 1 },
|
||||||
|
{ label: 'Février', value: 2 },
|
||||||
|
{ label: 'Mars', value: 3 },
|
||||||
|
{ label: 'Avril', value: 4 },
|
||||||
|
{ label: 'Mai', value: 5 },
|
||||||
|
{ label: 'Juin', value: 6 },
|
||||||
|
{ label: 'Juillet', value: 7 },
|
||||||
|
{ label: 'Août', value: 8 },
|
||||||
|
{ label: 'Septembre', value: 9 },
|
||||||
|
{ label: 'Octobre', value: 10 },
|
||||||
|
{ label: 'Novembre', value: 11 },
|
||||||
|
{ label: 'Décembre', value: 12 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const isExportValid = computed(() => {
|
||||||
|
if (!exportChoice.value) return false
|
||||||
|
if (exportChoice.value === 'salary-recap') {
|
||||||
|
return exportSalaryMonth.value.trim() !== ''
|
||||||
|
}
|
||||||
|
if (exportChoice.value === 'yearly-hours') {
|
||||||
|
return exportYear.value > 0 && exportMonth.value !== ''
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
const onExportChoiceChange = (value: string | number | null) => {
|
||||||
|
exportChoice.value = (value === null ? '' : String(value)) as 'leave-recap' | 'salary-recap' | 'yearly-hours' | ''
|
||||||
|
}
|
||||||
const { printPdf } = usePdfPrinter()
|
const { printPdf } = usePdfPrinter()
|
||||||
const sitesInitialized = ref(false)
|
const sitesInitialized = ref(false)
|
||||||
const editingEmployee = ref<Employee | null>(null)
|
const editingEmployee = ref<Employee | null>(null)
|
||||||
@@ -632,26 +699,29 @@ const openCreate = () => {
|
|||||||
isDrawerOpen.value = true
|
isDrawerOpen.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleLeaveRecapPrint = async () => {
|
const openExportDrawer = () => {
|
||||||
await printPdf('/leave-recap/print')
|
exportChoice.value = ''
|
||||||
|
const now = new Date()
|
||||||
|
exportYear.value = now.getFullYear()
|
||||||
|
exportMonth.value = now.getMonth() + 1
|
||||||
|
exportSalaryMonth.value = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`
|
||||||
|
isExportDrawerOpen.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSalaryRecapPrint = async (month: string) => {
|
const handleExportValidate = async () => {
|
||||||
await printPdf(`/salary-recap/print?month=${month}`)
|
if (!isExportValid.value) return
|
||||||
isSalaryRecapOpen.value = false
|
const choice = exportChoice.value
|
||||||
}
|
isExportDrawerOpen.value = false
|
||||||
|
if (choice === 'leave-recap') {
|
||||||
const handleBulkYearlyHoursPrint = async (payload: { year: number; month: number | null }) => {
|
await printPdf('/leave-recap/print')
|
||||||
isYearlyHoursBulkLoading.value = true
|
} else if (choice === 'salary-recap') {
|
||||||
try {
|
await printPdf(`/salary-recap/print?month=${exportSalaryMonth.value}`)
|
||||||
const monthParam = null !== payload.month ? `&month=${payload.month}` : ''
|
} else if (choice === 'yearly-hours') {
|
||||||
await printPdf(`/yearly-hours/print-all?year=${payload.year}${monthParam}`)
|
await printPdf(`/yearly-hours/print-all?year=${exportYear.value}&month=${exportMonth.value}`)
|
||||||
isYearlyHoursBulkOpen.value = false
|
|
||||||
} finally {
|
|
||||||
isYearlyHoursBulkLoading.value = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const confirmDelete = async (employee: Employee) => {
|
const confirmDelete = async (employee: Employee) => {
|
||||||
const ok = window.confirm(`Supprimer ${employee.firstName} ${employee.lastName} ?`)
|
const ok = window.confirm(`Supprimer ${employee.firstName} ${employee.lastName} ?`)
|
||||||
if (!ok) return
|
if (!ok) return
|
||||||
|
|||||||
@@ -9,31 +9,18 @@
|
|||||||
class="mt-8 space-y-6 rounded-lg border border-neutral-200 bg-white p-6 shadow-sm"
|
class="mt-8 space-y-6 rounded-lg border border-neutral-200 bg-white p-6 shadow-sm"
|
||||||
@submit.prevent="handleSubmit"
|
@submit.prevent="handleSubmit"
|
||||||
>
|
>
|
||||||
<div>
|
<MalioInputText
|
||||||
<label class="text-sm font-semibold text-neutral-700" for="username">
|
v-model="username"
|
||||||
Nom d'utilisateur
|
label="Nom d'utilisateur"
|
||||||
</label>
|
autocomplete="username"
|
||||||
<input
|
group-class="mt-2"
|
||||||
id="username"
|
/>
|
||||||
v-model="username"
|
|
||||||
type="text"
|
|
||||||
autocomplete="username"
|
|
||||||
class="mt-2 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-base text-neutral-900 focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-secondary-500/20"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<MalioInputPassword
|
||||||
<label class="text-sm font-semibold text-neutral-700" for="password">
|
v-model="password"
|
||||||
Mot de passe
|
label="Mot de passe"
|
||||||
</label>
|
autocomplete="current-password"
|
||||||
<input
|
/>
|
||||||
id="password"
|
|
||||||
v-model="password"
|
|
||||||
type="password"
|
|
||||||
autocomplete="current-password"
|
|
||||||
class="mt-2 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-base text-neutral-900 focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-secondary-500/20"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|||||||
@@ -2,13 +2,12 @@
|
|||||||
<div class="h-full flex flex-col overflow-hidden">
|
<div class="h-full flex flex-col overflow-hidden">
|
||||||
<div class="flex items-center justify-between pb-6">
|
<div class="flex items-center justify-between pb-6">
|
||||||
<h1 class="text-4xl font-bold text-primary-500">Sites</h1>
|
<h1 class="text-4xl font-bold text-primary-500">Sites</h1>
|
||||||
<button
|
<MalioButton
|
||||||
type="button"
|
label="Ajouter un site"
|
||||||
class="rounded-lg bg-primary-500 px-4 py-2 text-md font-semibold text-white hover:bg-secondary-500"
|
icon-name="mdi:plus"
|
||||||
|
icon-position="left"
|
||||||
@click="openCreate"
|
@click="openCreate"
|
||||||
>
|
/>
|
||||||
+ Ajouter un site
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -52,22 +51,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AppDrawer v-model="isDrawerOpen" :title="drawerTitle">
|
<MalioDrawer v-model="isDrawerOpen" :title="drawerTitle">
|
||||||
<form class="space-y-4" @submit.prevent="handleSubmit">
|
<form class="space-y-4" @submit.prevent="handleSubmit">
|
||||||
<div>
|
<MalioInputText
|
||||||
<label class="text-md font-semibold text-neutral-700" for="name">
|
v-model="form.name"
|
||||||
Nom <span class="text-red-600">*</span>
|
label="Nom *"
|
||||||
</label>
|
group-class="mt-2"
|
||||||
<input
|
:error="showNameError ? 'Le nom du site est obligatoire.' : ''"
|
||||||
id="name"
|
/>
|
||||||
v-model="form.name"
|
|
||||||
type="text"
|
|
||||||
:class="nameFieldClass"
|
|
||||||
/>
|
|
||||||
<p v-if="showNameError" class="mt-1 text-sm text-red-600">
|
|
||||||
Le nom du site est obligatoire.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<label class="text-md font-semibold text-neutral-700" for="color">
|
<label class="text-md font-semibold text-neutral-700" for="color">
|
||||||
Couleur <span class="text-red-600">*</span>
|
Couleur <span class="text-red-600">*</span>
|
||||||
@@ -83,32 +74,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="editingSite" class="grid grid-cols-2 gap-3 pt-2">
|
<div v-if="editingSite" class="grid grid-cols-2 gap-3 pt-2">
|
||||||
<button
|
<MalioButton
|
||||||
type="button"
|
label="Supprimer"
|
||||||
class="flex items-center justify-center rounded-md bg-red-500 px-4 py-2 text-md font-semibold text-white hover:bg-red-600"
|
variant="danger"
|
||||||
|
button-class="w-full"
|
||||||
@click="confirmDelete(editingSite)"
|
@click="confirmDelete(editingSite)"
|
||||||
>
|
/>
|
||||||
Supprimer
|
<MalioButton
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="submit"
|
type="submit"
|
||||||
class="flex items-center justify-center rounded-md bg-primary-500 px-4 py-2 text-md font-semibold text-white hover:bg-secondary-500"
|
label="Modifier"
|
||||||
:class="submitButtonClass"
|
button-class="w-full"
|
||||||
>
|
:disabled="isSubmitting || !isFormValid"
|
||||||
Modifier
|
/>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex justify-center pt-2">
|
<div v-else class="flex justify-center pt-2">
|
||||||
<button
|
<MalioButton
|
||||||
type="submit"
|
type="submit"
|
||||||
class="flex w-[200px] items-center justify-center gap-2 rounded-md bg-primary-500 px-4 py-2 text-md font-semibold text-white hover:bg-secondary-500"
|
label="Valider"
|
||||||
:class="submitButtonClass"
|
button-class="w-[200px]"
|
||||||
>
|
:disabled="isSubmitting || !isFormValid"
|
||||||
+ Ajouter
|
/>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</AppDrawer>
|
</MalioDrawer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -146,22 +134,6 @@ const isFormValid = computed(() => isNameValid.value)
|
|||||||
|
|
||||||
const showNameError = computed(() => validationTouched.name && !isNameValid.value)
|
const showNameError = computed(() => validationTouched.name && !isNameValid.value)
|
||||||
|
|
||||||
const baseInputClass =
|
|
||||||
'mt-2 w-full rounded-md border px-3 py-2 text-base text-neutral-900 focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-secondary-500/20'
|
|
||||||
const nameFieldClass = computed(() => {
|
|
||||||
if (showNameError.value) {
|
|
||||||
return `${baseInputClass} border-red-500`
|
|
||||||
}
|
|
||||||
return `${baseInputClass} border-neutral-300`
|
|
||||||
})
|
|
||||||
|
|
||||||
const submitButtonClass = computed(() => {
|
|
||||||
if (isSubmitting.value || !isFormValid.value) {
|
|
||||||
return 'opacity-50 cursor-not-allowed'
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
})
|
|
||||||
|
|
||||||
const loadSites = async () => {
|
const loadSites = async () => {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -2,13 +2,12 @@
|
|||||||
<div class="h-full flex flex-col overflow-hidden">
|
<div class="h-full flex flex-col overflow-hidden">
|
||||||
<div class="flex items-center justify-between pb-6">
|
<div class="flex items-center justify-between pb-6">
|
||||||
<h1 class="text-2xl font-bold text-primary-500 lg:text-4xl">Utilisateurs</h1>
|
<h1 class="text-2xl font-bold text-primary-500 lg:text-4xl">Utilisateurs</h1>
|
||||||
<button
|
<MalioButton
|
||||||
type="button"
|
label="Ajouter"
|
||||||
class="rounded-lg bg-primary-500 px-3 py-2 text-sm font-semibold text-white hover:bg-secondary-500 lg:px-4 lg:text-md"
|
icon-name="mdi:plus"
|
||||||
|
icon-position="left"
|
||||||
@click="openCreate"
|
@click="openCreate"
|
||||||
>
|
/>
|
||||||
+ Ajouter
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -93,43 +92,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AppDrawer
|
<MalioDrawer
|
||||||
v-model="isDrawerOpen"
|
v-model="isDrawerOpen"
|
||||||
:title="editingUser ? 'Modifier un utilisateur' : 'Ajouter un utilisateur'"
|
:title="editingUser ? 'Modifier un utilisateur' : 'Ajouter un utilisateur'"
|
||||||
>
|
>
|
||||||
<form class="space-y-4" @submit.prevent="handleSubmit">
|
<form class="space-y-4" @submit.prevent="handleSubmit">
|
||||||
<div>
|
<MalioInputText
|
||||||
<label class="text-md font-semibold text-neutral-700" for="username">
|
v-model="form.username"
|
||||||
Nom d'utilisateur <span class="text-red-600">*</span>
|
:label="editingUser ? `Nom d'utilisateur` : `Nom d'utilisateur *`"
|
||||||
</label>
|
group-class="mt-2"
|
||||||
<input
|
:error="showUsernameError ? `Le nom d'utilisateur est obligatoire.` : ''"
|
||||||
id="username"
|
/>
|
||||||
v-model="form.username"
|
|
||||||
type="text"
|
|
||||||
:class="usernameFieldClass"
|
|
||||||
/>
|
|
||||||
<p v-if="showUsernameError" class="mt-1 text-sm text-red-600">
|
|
||||||
Le nom d'utilisateur est obligatoire.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="text-md font-semibold text-neutral-700" for="password">
|
<MalioInputPassword
|
||||||
Mot de passe
|
|
||||||
<span v-if="!editingUser" class="text-red-600">*</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="password"
|
|
||||||
v-model="form.password"
|
v-model="form.password"
|
||||||
type="password"
|
:label="editingUser ? 'Mot de passe' : 'Mot de passe *'"
|
||||||
:class="passwordFieldClass"
|
:hint="editingUser ? 'Laisse vide pour ne pas changer le mot de passe.' : ''"
|
||||||
|
:error="!editingUser && showPasswordError ? 'Le mot de passe est obligatoire.' : ''"
|
||||||
/>
|
/>
|
||||||
<p v-if="editingUser" class="mt-1 text-sm text-neutral-500">
|
|
||||||
Laisse vide pour ne pas changer le mot de passe.
|
|
||||||
</p>
|
|
||||||
<p v-else-if="showPasswordError" class="mt-1 text-sm text-red-600">
|
|
||||||
Le mot de passe est obligatoire.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -172,40 +153,32 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="form.accessMode === 'self'">
|
<div v-if="form.accessMode === 'self'">
|
||||||
<label class="text-md font-semibold text-neutral-700" for="employee">
|
<MalioSelect
|
||||||
Employé lié
|
:model-value="form.employeeId === '' ? null : form.employeeId"
|
||||||
</label>
|
:options="employeeOptions"
|
||||||
<select
|
label="Employé lié"
|
||||||
id="employee"
|
empty-option-label="Aucun"
|
||||||
v-model="form.employeeId"
|
min-width=""
|
||||||
class="mt-2 w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-md text-neutral-900"
|
:error="showSelfEmployeeError ? 'Sélectionne un employé.' : ''"
|
||||||
>
|
@update:model-value="onEmployeeChange"
|
||||||
<option value="">Aucun</option>
|
/>
|
||||||
<option v-for="employee in employees" :key="employee.id" :value="employee.id">
|
|
||||||
{{ employee.firstName }} {{ employee.lastName }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<p v-if="showSelfEmployeeError" class="mt-1 text-sm text-red-600">
|
|
||||||
Sélectionne un employé.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="form.accessMode === 'sites'">
|
<div v-if="form.accessMode === 'sites'">
|
||||||
<p class="text-md font-semibold text-neutral-700">Sites autorisés</p>
|
<p class="text-md font-semibold text-neutral-700">Sites autorisés</p>
|
||||||
<div class="mt-2 grid gap-2 sm:grid-cols-2">
|
<div class="mt-2 grid gap-2 sm:grid-cols-2">
|
||||||
<label
|
<div
|
||||||
v-for="site in sites"
|
v-for="site in sites"
|
||||||
:key="site.id"
|
:key="site.id"
|
||||||
class="flex items-center gap-2 rounded-md border border-neutral-200 px-3 py-2 text-sm text-neutral-700 cursor-pointer"
|
class="flex h-10 items-center rounded-md border border-neutral-200 px-3"
|
||||||
>
|
>
|
||||||
<input
|
<MalioCheckbox
|
||||||
type="checkbox"
|
:model-value="form.siteIds.includes(site.id)"
|
||||||
class="cursor-pointer"
|
:label="site.name"
|
||||||
:checked="form.siteIds.includes(site.id)"
|
group-class="flex items-center"
|
||||||
@change="toggleSite(site.id)"
|
@update:model-value="toggleSite(site.id)"
|
||||||
/>
|
/>
|
||||||
<span>{{ site.name }}</span>
|
</div>
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
<p v-if="showSitesError" class="mt-1 text-sm text-red-600">
|
<p v-if="showSitesError" class="mt-1 text-sm text-red-600">
|
||||||
Sélectionne au moins un site.
|
Sélectionne au moins un site.
|
||||||
@@ -213,44 +186,31 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="flex items-center gap-2 cursor-pointer">
|
<MalioCheckbox
|
||||||
<input
|
v-model="form.isLocked"
|
||||||
v-model="form.isLocked"
|
label="Verrouiller le compte"
|
||||||
type="checkbox"
|
hint="Un compte verrouillé ne peut plus se connecter."
|
||||||
class="cursor-pointer"
|
/>
|
||||||
/>
|
|
||||||
<span class="text-md font-semibold text-neutral-700">Verrouiller le compte</span>
|
|
||||||
</label>
|
|
||||||
<p class="mt-1 text-sm text-neutral-500">
|
|
||||||
Un compte verrouillé ne peut plus se connecter.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="flex items-center gap-2 cursor-pointer">
|
<MalioCheckbox
|
||||||
<input
|
v-model="form.hasLeaveRecapAccess"
|
||||||
v-model="form.hasLeaveRecapAccess"
|
label="Accès à l'écran Récap. congés"
|
||||||
type="checkbox"
|
hint="Affiche l'onglet dans la sidebar et donne accès au tableau récap."
|
||||||
class="cursor-pointer"
|
/>
|
||||||
/>
|
|
||||||
<span class="text-md font-semibold text-neutral-700">Accès à l'écran Récap. congés</span>
|
|
||||||
</label>
|
|
||||||
<p class="mt-1 text-sm text-neutral-500">
|
|
||||||
Affiche l'onglet dans la sidebar et donne accès au tableau récap.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-center pt-2">
|
<div class="flex justify-center pt-2">
|
||||||
<button
|
<MalioButton
|
||||||
type="submit"
|
type="submit"
|
||||||
class="flex w-[200px] items-center justify-center gap-2 rounded-md bg-primary-500 px-4 py-2 text-md font-semibold text-white hover:bg-secondary-500"
|
:label="editingUser ? 'Modifier' : 'Valider'"
|
||||||
:class="submitButtonClass"
|
button-class="w-[200px]"
|
||||||
>
|
:disabled="isSubmitting || !isFormValid"
|
||||||
{{ editingUser ? 'Modifier' : '+ Ajouter' }}
|
/>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</AppDrawer>
|
</MalioDrawer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -348,27 +308,13 @@ const getSiteLabels = (user: User) => {
|
|||||||
return names.length > 0 ? names.join(', ') : 'Sites sélectionnés'
|
return names.length > 0 ? names.join(', ') : 'Sites sélectionnés'
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseInputClass =
|
const employeeOptions = computed(() =>
|
||||||
'mt-2 w-full rounded-md border px-3 py-2 text-base text-neutral-900 focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-secondary-500/20'
|
employees.value.map((e) => ({ label: `${e.firstName} ${e.lastName}`, value: e.id }))
|
||||||
const usernameFieldClass = computed(() => {
|
)
|
||||||
if (showUsernameError.value) {
|
|
||||||
return `${baseInputClass} border-red-500`
|
|
||||||
}
|
|
||||||
return `${baseInputClass} border-neutral-300`
|
|
||||||
})
|
|
||||||
const passwordFieldClass = computed(() => {
|
|
||||||
if (showPasswordError.value) {
|
|
||||||
return `${baseInputClass} border-red-500`
|
|
||||||
}
|
|
||||||
return `${baseInputClass} border-neutral-300`
|
|
||||||
})
|
|
||||||
|
|
||||||
const submitButtonClass = computed(() => {
|
const onEmployeeChange = (value: string | number | null) => {
|
||||||
if (isSubmitting.value || !isFormValid.value) {
|
form.employeeId = value === null ? '' : Number(value)
|
||||||
return 'opacity-50 cursor-not-allowed'
|
}
|
||||||
}
|
|
||||||
return ''
|
|
||||||
})
|
|
||||||
|
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
|
|||||||
Reference in New Issue
Block a user