feat : modification écran RTT + modification écran frais
All checks were successful
Auto Tag Develop / tag (push) Successful in 8s

This commit is contained in:
2026-03-19 17:10:11 +01:00
parent 3ec1e1f10d
commit 17f871e82d
15 changed files with 468 additions and 67 deletions

View File

@@ -2,12 +2,13 @@
<section class="mt-8">
<div class="overflow-hidden bg-white">
<div
class="grid grid-cols-5 border border-black bg-tertiary-500 px-6 py-3 text-[20px] font-semibold text-black rounded-t-md">
class="grid grid-cols-6 border border-black bg-tertiary-500 px-6 py-3 text-[20px] font-semibold text-black rounded-t-md">
<p>Mois</p>
<p>Nombre de Km</p>
<p>Montant </p>
<p>Commentaire</p>
<p>Justificatif</p>
<p>Justif. Km</p>
<p>Justif. Montant</p>
</div>
<div v-if="allowances.length === 0" class="px-6 py-4 text-[20px] font-bold text-primary-500 border-x border-b border-primary-500 rounded-b-md">
Aucun frais kilométrique.
@@ -16,23 +17,36 @@
<div
v-for="item in allowances"
:key="item.id"
class="grid grid-cols-5 border-b border-primary-500 px-6 py-3 text-md font-bold text-primary-500 last:border-b-0 cursor-pointer hover:bg-tertiary-500"
class="grid grid-cols-6 border-b border-primary-500 px-6 py-3 text-md font-bold text-primary-500 last:border-b-0 cursor-pointer hover:bg-tertiary-500"
@click="onOpenEditDrawer(item)"
>
<p>{{ formatMonth(item.month) }}</p>
<p>{{ item.kilometers }}</p>
<p>{{ item.amount ? item.amount + ' €' : '-' }}</p>
<p>{{ item.comment ?? '-' }}</p>
<p>
<p class="min-w-0">
<a
v-if="item.receiptPath"
:href="getReceiptUrl(props.apiBase, item.id)"
:href="getKmReceiptUrl(props.apiBase, item.id)"
target="_blank"
class="text-primary-500 hover:text-secondary-500 flex gap-2 items-center"
@click.stop
>
<Icon name="mdi:file-download-outline" size="20"/>
<span>{{ item.receiptName ?? 'Télécharger' }}</span>
<Icon name="mdi:file-download-outline" size="20" class="shrink-0"/>
<span class="truncate">{{ item.receiptName ?? 'Télécharger' }}</span>
</a>
<span v-else>-</span>
</p>
<p class="min-w-0">
<a
v-if="item.amountReceiptPath"
:href="getAmountReceiptUrl(props.apiBase, item.id)"
target="_blank"
class="text-primary-500 hover:text-secondary-500 flex gap-2 items-center"
@click.stop
>
<Icon name="mdi:file-download-outline" size="20" class="shrink-0"/>
<span class="truncate">{{ item.amountReceiptName ?? 'Télécharger' }}</span>
</a>
<span v-else>-</span>
</p>
@@ -94,20 +108,38 @@
</div>
<div>
<label class="text-md font-semibold text-neutral-700" for="mileage-receipt">
Justificatif
<label class="text-md font-semibold text-neutral-700" for="mileage-km-receipt">
Justificatif Km
</label>
<div v-if="isEditing && editingItem?.receiptName" class="mt-1 text-sm text-neutral-500">
Fichier actuel : {{ editingItem.receiptName }}
</div>
<input
id="mileage-receipt"
ref="fileInput"
id="mileage-km-receipt"
ref="kmFileInput"
type="file"
class="mt-2 w-full rounded-md border border-neutral-300 px-3 py-2 text-base text-neutral-900 file:mr-3 file:rounded file:border-0 file:bg-primary-500 file:px-3 file:py-1 file:text-sm file:text-white"
@change="onFileChange"
@change="onKmFileChange"
/>
<p v-if="fileError" class="mt-1 text-sm text-red-600">{{ fileError }}</p>
<p v-if="kmFileError" class="mt-1 text-sm text-red-600">{{ kmFileError }}</p>
<p v-else class="mt-1 text-sm text-neutral-500">Fichier au format pdf</p>
</div>
<div>
<label class="text-md font-semibold text-neutral-700" for="mileage-amount-receipt">
Justificatif Montant
</label>
<div v-if="isEditing && editingItem?.amountReceiptName" class="mt-1 text-sm text-neutral-500">
Fichier actuel : {{ editingItem.amountReceiptName }}
</div>
<input
id="mileage-amount-receipt"
ref="amountFileInput"
type="file"
class="mt-2 w-full rounded-md border border-neutral-300 px-3 py-2 text-base text-neutral-900 file:mr-3 file:rounded file:border-0 file:bg-primary-500 file:px-3 file:py-1 file:text-sm file:text-white"
@change="onAmountFileChange"
/>
<p v-if="amountFileError" class="mt-1 text-sm text-red-600">{{ amountFileError }}</p>
<p v-else class="mt-1 text-sm text-neutral-500">Fichier au format pdf</p>
</div>
@@ -156,7 +188,7 @@
<script setup lang="ts">
import type {MileageAllowance} from '~/services/dto/mileage-allowance'
import {getReceiptUrl} from '~/services/mileage-allowances'
import {getKmReceiptUrl, getAmountReceiptUrl} from '~/services/mileage-allowances'
import AppDrawer from '~/components/AppDrawer.vue'
const props = defineProps<{
@@ -165,17 +197,20 @@ const props = defineProps<{
}>()
const emit = defineEmits<{
(event: 'create', data: { month: string; kilometers: number; amount: number; comment?: string }, file?: File): void
(event: 'update', id: number, data: { month: string; kilometers: number; amount: number; comment?: string }, file?: File): void
(event: 'create', data: { month: string; kilometers: number; amount: number; comment?: string }, kmFile?: File, amountFile?: File): void
(event: 'update', id: number, data: { month: string; kilometers: number; amount: number; comment?: string }, kmFile?: File, amountFile?: File): void
(event: 'delete', id: number): void
}>()
const isDrawerOpen = ref(false)
const isEditing = ref(false)
const editingItem = ref<MileageAllowance | null>(null)
const selectedFile = ref<File | undefined>(undefined)
const fileInput = ref<HTMLInputElement | null>(null)
const fileError = ref('')
const selectedKmFile = ref<File | undefined>(undefined)
const selectedAmountFile = ref<File | undefined>(undefined)
const kmFileInput = ref<HTMLInputElement | null>(null)
const amountFileInput = ref<HTMLInputElement | null>(null)
const kmFileError = ref('')
const amountFileError = ref('')
const currentYearMonth = () => {
const now = new Date()
@@ -190,7 +225,7 @@ const form = reactive({
})
const isFormValid = computed(() => {
return form.month && (form.kilometers > 0 || form.amount > 0) && !fileError.value
return form.month && (form.kilometers > 0 || form.amount > 0) && !kmFileError.value && !amountFileError.value
})
const monthLabels: Record<number, string> = {
@@ -221,10 +256,15 @@ const resetForm = () => {
form.kilometers = 0
form.amount = 0
form.comment = ''
selectedFile.value = undefined
fileError.value = ''
if (fileInput.value) {
fileInput.value.value = ''
selectedKmFile.value = undefined
selectedAmountFile.value = undefined
kmFileError.value = ''
amountFileError.value = ''
if (kmFileInput.value) {
kmFileInput.value.value = ''
}
if (amountFileInput.value) {
amountFileInput.value.value = ''
}
}
@@ -243,24 +283,41 @@ const onOpenEditDrawer = (item: MileageAllowance) => {
form.kilometers = item.kilometers
form.amount = item.amount
form.comment = item.comment ?? ''
selectedFile.value = undefined
if (fileInput.value) {
fileInput.value.value = ''
selectedKmFile.value = undefined
selectedAmountFile.value = undefined
if (kmFileInput.value) {
kmFileInput.value.value = ''
}
if (amountFileInput.value) {
amountFileInput.value.value = ''
}
isDrawerOpen.value = true
}
const onFileChange = (event: Event) => {
const onKmFileChange = (event: Event) => {
const target = event.target as HTMLInputElement
const file = target.files?.[0]
if (file && file.type !== 'application/pdf') {
fileError.value = 'Seuls les fichiers PDF sont acceptés.'
selectedFile.value = undefined
kmFileError.value = 'Seuls les fichiers PDF sont acceptés.'
selectedKmFile.value = undefined
target.value = ''
return
}
fileError.value = ''
selectedFile.value = file ?? undefined
kmFileError.value = ''
selectedKmFile.value = file ?? undefined
}
const onAmountFileChange = (event: Event) => {
const target = event.target as HTMLInputElement
const file = target.files?.[0]
if (file && file.type !== 'application/pdf') {
amountFileError.value = 'Seuls les fichiers PDF sont acceptés.'
selectedAmountFile.value = undefined
target.value = ''
return
}
amountFileError.value = ''
selectedAmountFile.value = file ?? undefined
}
const onSubmit = () => {
@@ -272,9 +329,9 @@ const onSubmit = () => {
}
if (isEditing.value && editingItem.value) {
emit('update', editingItem.value.id, data, selectedFile.value)
emit('update', editingItem.value.id, data, selectedKmFile.value, selectedAmountFile.value)
} else {
emit('create', data, selectedFile.value)
emit('create', data, selectedKmFile.value, selectedAmountFile.value)
}
isDrawerOpen.value = false
}

View File

@@ -67,26 +67,26 @@
<tr v-if="showCarryRow" class="bg-tertiary-500">
<td class="px-5 py-[10px] font-bold text-primary-500 border border-primary-500">Report</td>
<td class="px-4 py-[10px] text-center text-neutral-500 border border-primary-500 border-r-2">-</td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(summary!.carryBase25Minutes) }}</td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(summary!.carryBonus25Minutes) }}</td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(summary!.carryBase25Minutes + summary!.carryBonus25Minutes) }}</td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(summary!.carryBase50Minutes) }}</td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(summary!.carryBonus50Minutes) }}</td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(summary!.carryBase50Minutes + summary!.carryBonus50Minutes) }}</td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(summary!.carryFromPreviousYearMinutes) }}</td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(summary!.carryBase25Minutes) }} <span class="text-neutral-400">/ {{ formatCentiemes(summary!.carryBase25Minutes) }}</span></td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(summary!.carryBonus25Minutes) }} <span class="text-neutral-400">/ {{ formatCentiemes(summary!.carryBonus25Minutes) }}</span></td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(summary!.carryBase25Minutes + summary!.carryBonus25Minutes) }} <span class="text-neutral-400">/ {{ formatCentiemes(summary!.carryBase25Minutes + summary!.carryBonus25Minutes) }}</span></td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(summary!.carryBase50Minutes) }} <span class="text-neutral-400">/ {{ formatCentiemes(summary!.carryBase50Minutes) }}</span></td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(summary!.carryBonus50Minutes) }} <span class="text-neutral-400">/ {{ formatCentiemes(summary!.carryBonus50Minutes) }}</span></td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(summary!.carryBase50Minutes + summary!.carryBonus50Minutes) }} <span class="text-neutral-400">/ {{ formatCentiemes(summary!.carryBase50Minutes + summary!.carryBonus50Minutes) }}</span></td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(summary!.carryFromPreviousYearMinutes) }} <span class="text-neutral-400">/ {{ formatCentiemes(summary!.carryFromPreviousYearMinutes) }}</span></td>
</tr>
<!-- Report mois précédent (cumulated balance from previous months, July+) -->
<tr v-if="showMonthReportRow" class="bg-tertiary-500">
<td class="px-5 py-[10px] font-bold text-primary-500 border border-primary-500">Report</td>
<td class="px-4 py-[10px] text-center text-neutral-500 border border-primary-500 border-r-2">-</td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(monthReport.base25) }}</td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(monthReport.bonus25) }}</td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(monthReport.total25) }}</td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(monthReport.base50) }}</td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(monthReport.bonus50) }}</td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(monthReport.total50) }}</td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(monthReport.total) }}</td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(monthReport.base25) }} <span class="text-neutral-400">/ {{ formatCentiemes(monthReport.base25) }}</span></td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(monthReport.bonus25) }} <span class="text-neutral-400">/ {{ formatCentiemes(monthReport.bonus25) }}</span></td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(monthReport.total25) }} <span class="text-neutral-400">/ {{ formatCentiemes(monthReport.total25) }}</span></td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(monthReport.base50) }} <span class="text-neutral-400">/ {{ formatCentiemes(monthReport.base50) }}</span></td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(monthReport.bonus50) }} <span class="text-neutral-400">/ {{ formatCentiemes(monthReport.bonus50) }}</span></td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(monthReport.total50) }} <span class="text-neutral-400">/ {{ formatCentiemes(monthReport.total50) }}</span></td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(monthReport.total) }} <span class="text-neutral-400">/ {{ formatCentiemes(monthReport.total) }}</span></td>
</tr>
<!-- Week rows (always 5) -->
@@ -162,13 +162,13 @@
<tr>
<td class="px-5 py-[10px] font-bold text-primary-500 border border-primary-500">Reste</td>
<td class="px-4 py-[10px] text-center text-neutral-500 border border-primary-500 border-r-2">-</td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(reste.base25) }}</td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(reste.bonus25) }}</td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(reste.total25) }}</td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(reste.base50) }}</td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(reste.bonus50) }}</td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(reste.total50) }}</td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(reste.total) }}</td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(reste.base25) }} <span class="text-neutral-400">/ {{ formatCentiemes(reste.base25) }}</span></td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(reste.bonus25) }} <span class="text-neutral-400">/ {{ formatCentiemes(reste.bonus25) }}</span></td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(reste.total25) }} <span class="text-neutral-400">/ {{ formatCentiemes(reste.total25) }}</span></td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(reste.base50) }} <span class="text-neutral-400">/ {{ formatCentiemes(reste.base50) }}</span></td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(reste.bonus50) }} <span class="text-neutral-400">/ {{ formatCentiemes(reste.bonus50) }}</span></td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500 border-r-2">{{ formatMinutes(reste.total50) }} <span class="text-neutral-400">/ {{ formatCentiemes(reste.total50) }}</span></td>
<td class="px-4 py-[10px] text-center font-bold text-primary-500 border border-primary-500">{{ formatMinutes(reste.total) }} <span class="text-neutral-400">/ {{ formatCentiemes(reste.total) }}</span></td>
</tr>
</tbody>
</table>
@@ -481,6 +481,11 @@ const formatMinutes = (minutes: number): string => {
return `${sign}${hours} h ${rest} m`
}
const formatCentiemes = (minutes: number): string => {
const value = minutes / 60
return value.toFixed(2).replace('.', ',')
}
// --- Payment drawer ---
const isPaymentDrawerOpen = ref(false)