Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93e0c4052c | ||
|
|
22373a0b87 | ||
|
|
d7968af525 | ||
| df2a48c20d | |||
| 7f1c02256b | |||
| fdc9b8b60d | |||
| 1025fed0d1 | |||
| 0331d94ca5 | |||
| 755c39a0f6 | |||
| 8f8eeddd91 | |||
| 548b101d82 |
@@ -1,2 +1,2 @@
|
||||
parameters:
|
||||
app.version: '0.3.10'
|
||||
app.version: '0.3.12'
|
||||
|
||||
@@ -32,21 +32,19 @@
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<button
|
||||
type="submit"
|
||||
class="rounded-md bg-primary-500 px-4 py-2 text-sm font-semibold text-white hover:bg-secondary-500 disabled:opacity-50"
|
||||
<MalioButton
|
||||
:label="$t('bookstack.settings.save')"
|
||||
button-class="w-auto px-4"
|
||||
:disabled="isSaving"
|
||||
>
|
||||
{{ $t('bookstack.settings.save') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md border border-neutral-300 px-4 py-2 text-sm font-semibold text-neutral-700 hover:bg-neutral-50 disabled:opacity-50"
|
||||
@click="handleSave"
|
||||
/>
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
:label="$t('bookstack.settings.testConnection')"
|
||||
button-class="w-auto px-4"
|
||||
:disabled="isTesting"
|
||||
@click="handleTest"
|
||||
>
|
||||
{{ $t('bookstack.settings.testConnection') }}
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p v-if="testResult !== null" class="text-sm font-medium" :class="testResult ? 'text-green-600' : 'text-red-600'">
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
<div>
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-lg font-bold text-neutral-900">Clients</h2>
|
||||
<button
|
||||
class="rounded-md bg-primary-500 px-4 py-2 text-sm font-semibold text-white hover:bg-secondary-500"
|
||||
<MalioButton
|
||||
icon-name="mdi:plus"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-4"
|
||||
label="Ajouter un client"
|
||||
@click="openCreate"
|
||||
>
|
||||
+ Ajouter un client
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
|
||||
@@ -92,19 +92,21 @@
|
||||
<td class="px-3 py-3 text-neutral-400">{{ formatDate(ticket.createdAt) }}</td>
|
||||
<td class="px-3 py-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
class="rounded p-1 text-neutral-400 transition-colors hover:bg-neutral-100 hover:text-primary-500"
|
||||
:title="$t('clientTicket.changeStatus')"
|
||||
<MalioButtonIcon
|
||||
icon="mdi:swap-horizontal"
|
||||
:aria-label="$t('clientTicket.changeStatus')"
|
||||
variant="ghost"
|
||||
icon-size="18"
|
||||
@click.stop="openStatusChange(ticket)"
|
||||
>
|
||||
<Icon name="mdi:swap-horizontal" size="18" />
|
||||
</button>
|
||||
<button
|
||||
class="rounded p-1 text-neutral-400 transition-colors hover:bg-red-50 hover:text-red-500"
|
||||
/>
|
||||
<MalioButtonIcon
|
||||
icon="mdi:delete-outline"
|
||||
aria-label="Supprimer"
|
||||
variant="ghost"
|
||||
icon-size="18"
|
||||
button-class="text-neutral-400 hover:bg-red-50 hover:text-red-500"
|
||||
@click.stop="openDeleteConfirm(ticket)"
|
||||
>
|
||||
<Icon name="mdi:delete-outline" size="18" />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -155,19 +157,18 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end gap-3">
|
||||
<button
|
||||
class="rounded-lg border border-neutral-300 px-4 py-2 text-sm font-semibold text-neutral-700 transition-colors hover:bg-neutral-50"
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
:label="$t('common.cancel')"
|
||||
button-class="w-auto px-4"
|
||||
@click="statusModalOpen = false"
|
||||
>
|
||||
{{ $t('common.cancel') }}
|
||||
</button>
|
||||
<button
|
||||
class="rounded-lg bg-primary-500 px-6 py-2 text-sm font-semibold text-white transition-colors hover:bg-secondary-500 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
/>
|
||||
<MalioButton
|
||||
label="Confirmer"
|
||||
button-class="w-auto px-6"
|
||||
:disabled="isUpdatingStatus"
|
||||
@click="confirmStatusChange"
|
||||
>
|
||||
Confirmer
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -186,19 +187,19 @@
|
||||
<h3 class="text-lg font-bold text-neutral-900">{{ $t('clientTicket.confirmDelete') }}</h3>
|
||||
<p class="mt-2 text-sm text-neutral-600">{{ $t('clientTicket.confirmDeleteMessage') }}</p>
|
||||
<div class="mt-6 flex justify-end gap-3">
|
||||
<button
|
||||
class="rounded-lg border border-neutral-300 px-4 py-2 text-sm font-semibold text-neutral-700 transition-colors hover:bg-neutral-50"
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
:label="$t('common.cancel')"
|
||||
button-class="w-auto px-4"
|
||||
@click="deleteModalOpen = false"
|
||||
>
|
||||
{{ $t('common.cancel') }}
|
||||
</button>
|
||||
<button
|
||||
class="rounded-lg bg-red-500 px-6 py-2 text-sm font-semibold text-white transition-colors hover:bg-red-600 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
/>
|
||||
<MalioButton
|
||||
variant="danger"
|
||||
label="Supprimer"
|
||||
button-class="w-auto px-6"
|
||||
:disabled="isDeleting"
|
||||
@click="confirmDelete"
|
||||
>
|
||||
Supprimer
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
<div>
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-lg font-bold text-neutral-900">Efforts</h2>
|
||||
<button
|
||||
class="rounded-md bg-primary-500 px-4 py-2 text-sm font-semibold text-white hover:bg-secondary-500"
|
||||
<MalioButton
|
||||
icon-name="mdi:plus"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-4"
|
||||
label="Ajouter un effort"
|
||||
@click="openCreate"
|
||||
>
|
||||
+ Ajouter un effort
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
|
||||
@@ -24,21 +24,19 @@
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<button
|
||||
type="submit"
|
||||
class="rounded-md bg-primary-500 px-4 py-2 text-sm font-semibold text-white hover:bg-secondary-500 disabled:opacity-50"
|
||||
<MalioButton
|
||||
:label="$t('gitea.settings.save')"
|
||||
button-class="w-auto px-4"
|
||||
:disabled="isSaving"
|
||||
>
|
||||
{{ $t('gitea.settings.save') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md border border-neutral-300 px-4 py-2 text-sm font-semibold text-neutral-700 hover:bg-neutral-50 disabled:opacity-50"
|
||||
@click="handleSave"
|
||||
/>
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
:label="$t('gitea.settings.testConnection')"
|
||||
button-class="w-auto px-4"
|
||||
:disabled="isTesting"
|
||||
@click="handleTest"
|
||||
>
|
||||
{{ $t('gitea.settings.testConnection') }}
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p v-if="testResult !== null" class="text-sm font-medium" :class="testResult ? 'text-green-600' : 'text-red-600'">
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
<div>
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-lg font-bold text-neutral-900">Priorités</h2>
|
||||
<button
|
||||
class="rounded-md bg-primary-500 px-4 py-2 text-sm font-semibold text-white hover:bg-secondary-500"
|
||||
<MalioButton
|
||||
icon-name="mdi:plus"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-4"
|
||||
label="Ajouter une priorité"
|
||||
@click="openCreate"
|
||||
>
|
||||
+ Ajouter une priorité
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
<div>
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-lg font-bold text-neutral-900">Statuts</h2>
|
||||
<button
|
||||
class="rounded-md bg-primary-500 px-4 py-2 text-sm font-semibold text-white hover:bg-secondary-500"
|
||||
<MalioButton
|
||||
icon-name="mdi:plus"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-4"
|
||||
label="Ajouter un statut"
|
||||
@click="openCreate"
|
||||
>
|
||||
+ Ajouter un statut
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
<div>
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-lg font-bold text-neutral-900">Tags</h2>
|
||||
<button
|
||||
class="rounded-md bg-primary-500 px-4 py-2 text-sm font-semibold text-white hover:bg-secondary-500"
|
||||
<MalioButton
|
||||
icon-name="mdi:plus"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-4"
|
||||
label="Ajouter un tag"
|
||||
@click="openCreate"
|
||||
>
|
||||
+ Ajouter un tag
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
<div>
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-lg font-bold text-neutral-900">Utilisateurs</h2>
|
||||
<button
|
||||
class="rounded-md bg-primary-500 px-4 py-2 text-sm font-semibold text-white hover:bg-secondary-500"
|
||||
<MalioButton
|
||||
icon-name="mdi:plus"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-4"
|
||||
label="Ajouter un utilisateur"
|
||||
@click="openCreate"
|
||||
>
|
||||
+ Ajouter un utilisateur
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
|
||||
@@ -37,21 +37,19 @@
|
||||
<span class="text-sm">{{ $t('zimbra.settings.enabled') }}</span>
|
||||
</label>
|
||||
<div class="flex gap-3">
|
||||
<button
|
||||
type="submit"
|
||||
class="rounded-md bg-primary-500 px-4 py-2 text-sm font-semibold text-white hover:bg-secondary-500 disabled:opacity-50"
|
||||
<MalioButton
|
||||
:label="$t('zimbra.settings.save')"
|
||||
button-class="w-auto px-4"
|
||||
:disabled="isSaving"
|
||||
>
|
||||
{{ $t('zimbra.settings.save') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md border border-neutral-300 px-4 py-2 text-sm font-semibold text-neutral-700 hover:bg-neutral-50 disabled:opacity-50"
|
||||
@click="handleSave"
|
||||
/>
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
:label="$t('zimbra.settings.testConnection')"
|
||||
button-class="w-auto px-4"
|
||||
:disabled="isTesting"
|
||||
@click="handleTest"
|
||||
>
|
||||
{{ $t('zimbra.settings.testConnection') }}
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
<p v-if="testResult !== null" class="text-sm font-medium" :class="testResult ? 'text-green-600' : 'text-red-600'">
|
||||
{{ testResult ? $t('zimbra.settings.testSuccess') : $t('zimbra.settings.testFailed') }}
|
||||
|
||||
@@ -29,22 +29,22 @@
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Edit button (only for open tickets submitted by current user) -->
|
||||
<button
|
||||
<MalioButton
|
||||
v-if="canEdit && !isEditing"
|
||||
type="button"
|
||||
class="flex h-8 items-center gap-1.5 rounded-lg px-3 text-sm font-medium text-primary-500 transition-colors hover:bg-primary-50"
|
||||
variant="tertiary"
|
||||
icon-name="mdi:pencil-outline"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-3"
|
||||
:label="$t('common.edit')"
|
||||
@click="startEdit"
|
||||
>
|
||||
<Icon name="mdi:pencil-outline" size="16" />
|
||||
{{ $t('common.edit') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="flex h-8 w-8 items-center justify-center rounded-lg text-neutral-400 transition-colors hover:bg-neutral-200/60 hover:text-neutral-600"
|
||||
/>
|
||||
<MalioButtonIcon
|
||||
icon="mdi:close"
|
||||
aria-label="Fermer"
|
||||
variant="ghost"
|
||||
icon-size="20"
|
||||
@click="close"
|
||||
>
|
||||
<Icon name="mdi:close" size="20" />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -90,21 +90,18 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end gap-3">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg border border-neutral-300 px-4 py-2 text-sm font-semibold text-neutral-700 transition-colors hover:bg-neutral-50"
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
:label="$t('common.cancel')"
|
||||
button-class="w-auto px-4"
|
||||
@click="cancelEdit"
|
||||
>
|
||||
{{ $t('common.cancel') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg bg-primary-500 px-6 py-2 text-sm font-semibold text-white transition-colors hover:bg-secondary-500 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
/>
|
||||
<MalioButton
|
||||
:label="$t('common.save')"
|
||||
button-class="w-auto px-6"
|
||||
:disabled="isSaving"
|
||||
@click="saveEdit"
|
||||
>
|
||||
{{ $t('common.save') }}
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- Trigger button -->
|
||||
<button
|
||||
class="relative flex shrink-0 items-center gap-2 rounded-md bg-neutral-100 px-3 py-2 text-sm font-semibold text-neutral-700 transition-colors hover:bg-neutral-200 sm:px-4"
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
icon-name="mdi:ticket-outline"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-3 sm:px-4 shrink-0"
|
||||
@click="open"
|
||||
>
|
||||
<Icon name="mdi:ticket-outline" class="size-4 sm:size-5" />
|
||||
<span class="hidden sm:inline">{{ $t('clientTicket.adminTab') }}</span>
|
||||
<span
|
||||
v-if="totalCount > 0"
|
||||
@@ -13,7 +15,7 @@
|
||||
>
|
||||
{{ totalCount }}
|
||||
</span>
|
||||
</button>
|
||||
</MalioButton>
|
||||
|
||||
<!-- Panel -->
|
||||
<Teleport v-if="isOpen" to="body">
|
||||
@@ -33,13 +35,13 @@
|
||||
<h2 class="text-base font-bold text-neutral-900">{{ $t('clientTicket.adminTab') }}</h2>
|
||||
<p class="mt-0.5 text-xs text-neutral-400">{{ projectName }}</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="flex h-8 w-8 items-center justify-center rounded-lg text-neutral-400 transition-colors hover:bg-neutral-100 hover:text-neutral-600"
|
||||
<MalioButtonIcon
|
||||
icon="mdi:close"
|
||||
aria-label="Fermer"
|
||||
variant="ghost"
|
||||
icon-size="20"
|
||||
@click="close"
|
||||
>
|
||||
<Icon name="mdi:close" size="20" />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
@@ -97,13 +99,13 @@
|
||||
<p class="mt-0.5 text-xs text-neutral-400">{{ formatDate(ticket.createdAt) }}</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<button
|
||||
class="rounded p-1 text-neutral-400 transition-colors hover:bg-neutral-100 hover:text-primary-500"
|
||||
:title="$t('clientTicket.changeStatus')"
|
||||
<MalioButtonIcon
|
||||
icon="mdi:swap-horizontal"
|
||||
:aria-label="$t('clientTicket.changeStatus')"
|
||||
variant="ghost"
|
||||
icon-size="16"
|
||||
@click.stop="openStatusChange(ticket)"
|
||||
>
|
||||
<Icon name="mdi:swap-horizontal" size="16" />
|
||||
</button>
|
||||
/>
|
||||
<Icon
|
||||
:name="expandedId === ticket.id ? 'mdi:chevron-up' : 'mdi:chevron-down'"
|
||||
size="18"
|
||||
@@ -179,19 +181,18 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end gap-3">
|
||||
<button
|
||||
class="rounded-lg border border-neutral-300 px-4 py-2 text-sm font-semibold text-neutral-700 transition-colors hover:bg-neutral-50"
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
:label="$t('common.cancel')"
|
||||
button-class="w-auto px-4"
|
||||
@click="statusModalOpen = false"
|
||||
>
|
||||
{{ $t('common.cancel') }}
|
||||
</button>
|
||||
<button
|
||||
class="rounded-lg bg-primary-500 px-6 py-2 text-sm font-semibold text-white transition-colors hover:bg-secondary-500 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
/>
|
||||
<MalioButton
|
||||
label="Confirmer"
|
||||
button-class="w-auto px-6"
|
||||
:disabled="isUpdatingStatus"
|
||||
@click="confirmStatusChange"
|
||||
>
|
||||
Confirmer
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<AppDrawer v-model="isOpen" :title="isEditing ? $t('clients.editClient') : $t('clients.addClient')">
|
||||
<MalioDrawer v-model="isOpen" :title="isEditing ? $t('clients.editClient') : $t('clients.addClient')">
|
||||
<form @submit.prevent="handleSubmit" class="flex flex-col gap-2">
|
||||
<MalioInputText
|
||||
v-model="form.name"
|
||||
@@ -35,16 +35,15 @@
|
||||
/>
|
||||
|
||||
<div class="mt-6 flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
class="rounded-md bg-primary-500 px-6 py-2 text-sm font-semibold text-white hover:bg-secondary-500 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
<MalioButton
|
||||
label="Enregistrer"
|
||||
button-class="w-auto px-6"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
Enregistrer
|
||||
</button>
|
||||
@click="handleSubmit"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</AppDrawer>
|
||||
</MalioDrawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
<template>
|
||||
<div ref="bellRef" class="relative">
|
||||
<button
|
||||
type="button"
|
||||
class="relative rounded-md p-2 text-white hover:bg-primary-600 transition-colors"
|
||||
@click="toggleDropdown"
|
||||
>
|
||||
<Icon name="mdi:bell-outline" size="24" />
|
||||
<div class="relative">
|
||||
<MalioButtonIcon
|
||||
icon="mdi:bell-outline"
|
||||
aria-label="Notifications"
|
||||
variant="ghost"
|
||||
icon-size="24"
|
||||
button-class="text-white hover:bg-primary-600"
|
||||
@click="toggleDropdown"
|
||||
/>
|
||||
<span
|
||||
v-if="unreadCount > 0"
|
||||
class="absolute -right-0.5 -top-0.5 flex h-5 min-w-5 items-center justify-center rounded-full bg-red-500 px-1 text-xs font-bold text-white"
|
||||
class="absolute -right-0.5 -top-0.5 flex h-5 min-w-5 items-center justify-center rounded-full bg-red-500 px-1 text-xs font-bold text-white pointer-events-none"
|
||||
>
|
||||
{{ unreadCount > 99 ? '99+' : unreadCount }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Transition name="dropdown">
|
||||
<div
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<AppDrawer v-model="isOpen" :title="isEditing ? $t('projects.editProject') : $t('projects.addProject')">
|
||||
<MalioDrawer v-model="isOpen" :title="isEditing ? $t('projects.editProject') : $t('projects.addProject')">
|
||||
<form @submit.prevent="handleSubmit" class="flex flex-col gap-2">
|
||||
<MalioInputText
|
||||
v-model="form.code"
|
||||
@@ -54,41 +54,44 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
class="rounded-md bg-primary-500 px-6 py-2 text-sm font-semibold text-white hover:bg-secondary-500 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
<MalioButton
|
||||
label="Enregistrer"
|
||||
button-class="w-auto px-6"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
Enregistrer
|
||||
</button>
|
||||
@click="handleSubmit"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div v-if="isEditing && project" class="mt-6 border-t border-neutral-200 pt-4 flex items-center justify-between">
|
||||
<button
|
||||
class="flex items-center gap-2 text-sm text-neutral-500 hover:text-amber-600"
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
:icon-name="project.archived ? 'mdi:archive-arrow-up-outline' : 'mdi:archive-arrow-down-outline'"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-4"
|
||||
:disabled="isSubmitting"
|
||||
@click="handleArchiveToggle"
|
||||
>
|
||||
<Icon :name="project.archived ? 'mdi:archive-arrow-up-outline' : 'mdi:archive-arrow-down-outline'" size="18" />
|
||||
{{ project.archived ? 'Désarchiver' : 'Archiver' }}
|
||||
</button>
|
||||
<button
|
||||
</MalioButton>
|
||||
<MalioButton
|
||||
v-if="project.taskCount === 0"
|
||||
class="flex items-center gap-2 text-sm text-neutral-500 hover:text-red-600"
|
||||
variant="danger"
|
||||
icon-name="mdi:delete-outline"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-4"
|
||||
:disabled="isSubmitting"
|
||||
@click="confirmDeleteOpen = true"
|
||||
>
|
||||
<Icon name="mdi:delete-outline" size="18" />
|
||||
{{ $t('common.delete') }}
|
||||
</button>
|
||||
</MalioButton>
|
||||
</div>
|
||||
|
||||
<ConfirmDeleteProjectModal
|
||||
v-model="confirmDeleteOpen"
|
||||
@confirm="handleDelete"
|
||||
/>
|
||||
</AppDrawer>
|
||||
</MalioDrawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -3,20 +3,20 @@
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-lg font-bold text-neutral-900">Groupes</h2>
|
||||
<div class="flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
class="text-sm font-medium text-neutral-500 hover:text-neutral-700"
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
button-class="w-auto px-3"
|
||||
:label="showArchived ? $t('archive.hideArchived') : $t('archive.showArchived')"
|
||||
@click="showArchived = !showArchived"
|
||||
>
|
||||
{{ showArchived ? $t('archive.hideArchived') : $t('archive.showArchived') }}
|
||||
</button>
|
||||
<button
|
||||
/>
|
||||
<MalioButton
|
||||
v-if="!showArchived"
|
||||
class="rounded-md bg-primary-500 px-4 py-2 text-sm font-semibold text-white hover:bg-secondary-500"
|
||||
icon-name="mdi:plus"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-4"
|
||||
label="Ajouter un groupe"
|
||||
@click="openCreate"
|
||||
>
|
||||
+ Ajouter un groupe
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -39,22 +39,20 @@
|
||||
{{ item.description ?? '—' }}
|
||||
</template>
|
||||
<template #actions="{ item }">
|
||||
<button
|
||||
<MalioButton
|
||||
v-if="!showArchived && canArchiveGroup(item)"
|
||||
type="button"
|
||||
class="rounded-md bg-neutral-500 px-3 py-1 text-xs font-semibold text-white hover:bg-neutral-600"
|
||||
variant="secondary"
|
||||
:label="$t('archive.archiveButton')"
|
||||
button-class="w-auto px-3"
|
||||
@click.stop="handleArchive(item)"
|
||||
>
|
||||
{{ $t('archive.archiveButton') }}
|
||||
</button>
|
||||
<button
|
||||
/>
|
||||
<MalioButton
|
||||
v-if="showArchived"
|
||||
type="button"
|
||||
class="rounded-md bg-neutral-500 px-3 py-1 text-xs font-semibold text-white hover:bg-neutral-600"
|
||||
variant="secondary"
|
||||
:label="$t('archive.unarchiveButton')"
|
||||
button-class="w-auto px-3"
|
||||
@click.stop="handleUnarchive(item)"
|
||||
>
|
||||
{{ $t('archive.unarchiveButton') }}
|
||||
</button>
|
||||
/>
|
||||
</template>
|
||||
</DataTable>
|
||||
|
||||
|
||||
@@ -57,13 +57,14 @@
|
||||
>
|
||||
{{ link.title }}
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-auto shrink-0 text-neutral-300 hover:text-red-500"
|
||||
<MalioButtonIcon
|
||||
icon="mdi:close"
|
||||
aria-label="Supprimer le lien"
|
||||
variant="ghost"
|
||||
icon-size="16"
|
||||
button-class="ml-auto shrink-0 text-neutral-300 hover:text-red-500"
|
||||
@click="handleRemove(link.id)"
|
||||
>
|
||||
<Icon name="mdi:close" size="16" />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -72,13 +72,14 @@
|
||||
/>
|
||||
|
||||
<!-- Delete -->
|
||||
<button
|
||||
class="flex h-9 w-9 shrink-0 items-center justify-center self-end rounded-md text-neutral-500 transition-colors hover:bg-red-50 hover:text-red-500"
|
||||
title="Supprimer"
|
||||
<MalioButtonIcon
|
||||
icon="mdi:delete-outline"
|
||||
aria-label="Supprimer"
|
||||
variant="ghost"
|
||||
icon-size="22"
|
||||
button-class="self-end text-neutral-500 hover:bg-red-50 hover:text-red-500"
|
||||
@click="emit('bulk-delete')"
|
||||
>
|
||||
<Icon name="mdi:delete-outline" size="22" />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -29,13 +29,14 @@
|
||||
</div>
|
||||
<h4 class="text-sm font-semibold text-neutral-900">{{ task.title }}</h4>
|
||||
</div>
|
||||
<button
|
||||
class="shrink-0 transition-colors"
|
||||
:class="isTimerOnTask ? 'text-[#F18619] hover:text-[#d97314]' : 'text-neutral-400 hover:text-primary-500'"
|
||||
<MalioButtonIcon
|
||||
:icon="isTimerOnTask ? 'mdi:stop-circle-outline' : 'mdi:play-circle-outline'"
|
||||
:aria-label="isTimerOnTask ? 'Arrêter le timer' : 'Démarrer le timer'"
|
||||
variant="ghost"
|
||||
icon-size="20"
|
||||
:button-class="isTimerOnTask ? 'shrink-0 text-[#F18619] hover:text-[#d97314]' : 'shrink-0 text-neutral-400 hover:text-primary-500'"
|
||||
@click.stop="isTimerOnTask ? timerStore.stop() : onPlay()"
|
||||
>
|
||||
<Icon :name="isTimerOnTask ? 'mdi:stop-circle-outline' : 'mdi:play-circle-outline'" size="20" />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 flex items-center gap-1.5">
|
||||
|
||||
@@ -32,14 +32,15 @@
|
||||
</div>
|
||||
|
||||
<!-- Delete button -->
|
||||
<button
|
||||
<MalioButtonIcon
|
||||
v-if="isAdmin"
|
||||
type="button"
|
||||
class="absolute right-1 top-1 hidden rounded p-0.5 text-neutral-400 transition-colors hover:bg-red-50 hover:text-red-500 group-hover:block"
|
||||
icon="heroicons:x-mark"
|
||||
aria-label="Supprimer"
|
||||
variant="ghost"
|
||||
icon-size="16"
|
||||
button-class="absolute right-1 top-1 hidden text-neutral-400 hover:bg-red-50 hover:text-red-500 group-hover:block"
|
||||
@click.stop="$emit('delete', doc)"
|
||||
>
|
||||
<Icon name="heroicons:x-mark" class="h-4 w-4" />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,28 +12,34 @@
|
||||
ref="overlayRef"
|
||||
>
|
||||
<!-- Close button -->
|
||||
<button
|
||||
class="absolute right-4 top-4 rounded-full bg-black/50 p-2 text-white transition-colors hover:bg-black/70"
|
||||
<MalioButtonIcon
|
||||
icon="heroicons:x-mark"
|
||||
aria-label="Fermer"
|
||||
variant="ghost"
|
||||
icon-size="24"
|
||||
button-class="absolute right-4 top-4 rounded-full bg-black/50 text-white hover:bg-black/70"
|
||||
@click="$emit('close')"
|
||||
>
|
||||
<Icon name="heroicons:x-mark" class="h-6 w-6" />
|
||||
</button>
|
||||
/>
|
||||
|
||||
<!-- Navigation arrows -->
|
||||
<button
|
||||
<MalioButtonIcon
|
||||
v-if="hasPrev"
|
||||
class="absolute left-4 top-1/2 -translate-y-1/2 rounded-full bg-black/50 p-2 text-white transition-colors hover:bg-black/70"
|
||||
icon="heroicons:chevron-left"
|
||||
aria-label="Précédent"
|
||||
variant="ghost"
|
||||
icon-size="24"
|
||||
button-class="absolute left-4 top-1/2 -translate-y-1/2 rounded-full bg-black/50 text-white hover:bg-black/70"
|
||||
@click="$emit('prev')"
|
||||
>
|
||||
<Icon name="heroicons:chevron-left" class="h-6 w-6" />
|
||||
</button>
|
||||
<button
|
||||
/>
|
||||
<MalioButtonIcon
|
||||
v-if="hasNext"
|
||||
class="absolute right-4 top-1/2 -translate-y-1/2 rounded-full bg-black/50 p-2 text-white transition-colors hover:bg-black/70"
|
||||
icon="heroicons:chevron-right"
|
||||
aria-label="Suivant"
|
||||
variant="ghost"
|
||||
icon-size="24"
|
||||
button-class="absolute right-4 top-1/2 -translate-y-1/2 rounded-full bg-black/50 text-white hover:bg-black/70"
|
||||
@click="$emit('next')"
|
||||
>
|
||||
<Icon name="heroicons:chevron-right" class="h-6 w-6" />
|
||||
</button>
|
||||
/>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="flex max-h-[90vh] max-w-[90vw] flex-col items-center">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<AppDrawer v-model="isOpen" :title="isEditing ? $t('taskEfforts.editEffort') : $t('taskEfforts.addEffort')">
|
||||
<MalioDrawer v-model="isOpen" :title="isEditing ? $t('taskEfforts.editEffort') : $t('taskEfforts.addEffort')">
|
||||
<form @submit.prevent="handleSubmit" class="flex flex-col gap-2">
|
||||
<MalioInputText
|
||||
v-model="form.label"
|
||||
@@ -10,16 +10,15 @@
|
||||
/>
|
||||
|
||||
<div class="mt-6 flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
class="rounded-md bg-primary-500 px-6 py-2 text-sm font-semibold text-white hover:bg-secondary-500 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
<MalioButton
|
||||
label="Enregistrer"
|
||||
button-class="w-auto px-6"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
Enregistrer
|
||||
</button>
|
||||
@click="handleSubmit"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</AppDrawer>
|
||||
</MalioDrawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -38,24 +38,22 @@
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex gap-1">
|
||||
<button
|
||||
<MalioButtonIcon
|
||||
v-if="activeTab === 'branches'"
|
||||
type="button"
|
||||
class="rounded-md px-2.5 py-1.5 text-xs font-medium text-neutral-500 transition-colors hover:bg-neutral-200/60 hover:text-neutral-700"
|
||||
:title="$t('gitea.branch.copy')"
|
||||
icon="mdi:content-copy"
|
||||
:aria-label="$t('gitea.branch.copy')"
|
||||
variant="ghost"
|
||||
icon-size="14"
|
||||
@click="handleCopy"
|
||||
>
|
||||
<Icon name="mdi:content-copy" size="14" />
|
||||
</button>
|
||||
<button
|
||||
/>
|
||||
<MalioButton
|
||||
v-if="activeTab === 'branches'"
|
||||
type="button"
|
||||
class="rounded-md bg-primary-500 px-2.5 py-1.5 text-xs font-semibold text-white transition-colors hover:bg-secondary-500"
|
||||
icon-name="mdi:plus"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-2.5 py-1.5 text-xs"
|
||||
:label="$t('gitea.branch.create')"
|
||||
@click="showCreateForm = !showCreateForm"
|
||||
>
|
||||
<Icon name="mdi:plus" size="14" class="mr-0.5 inline-block align-[-2px]" />
|
||||
{{ $t('gitea.branch.create') }}
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -79,14 +77,12 @@
|
||||
:label="$t('gitea.branch.baseBranch')"
|
||||
input-class="w-full"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="mb-[2px] rounded-md bg-primary-500 px-4 py-2 text-xs font-semibold text-white transition-colors hover:bg-secondary-500 disabled:opacity-50"
|
||||
<MalioButton
|
||||
:label="isCreating ? '...' : $t('gitea.branch.create')"
|
||||
button-class="w-auto px-4 mb-[2px] text-xs"
|
||||
:disabled="isCreating"
|
||||
@click="handleCreate"
|
||||
>
|
||||
{{ isCreating ? '...' : $t('gitea.branch.create') }}
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
<code class="mt-2 block rounded bg-neutral-50 px-2 py-1 text-[11px] text-neutral-500">
|
||||
{{ branchPreview }}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<AppDrawer v-model="isOpen" :title="isEditing ? $t('taskGroups.editGroup') : $t('taskGroups.addGroup')">
|
||||
<MalioDrawer v-model="isOpen" :title="isEditing ? $t('taskGroups.editGroup') : $t('taskGroups.addGroup')">
|
||||
<form @submit.prevent="handleSubmit" class="flex flex-col gap-2">
|
||||
<MalioInputText
|
||||
v-model="form.title"
|
||||
@@ -25,34 +25,31 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex items-center" :class="isEditing ? 'justify-between' : 'justify-end'">
|
||||
<button
|
||||
<MalioButton
|
||||
v-if="canArchive"
|
||||
type="button"
|
||||
class="rounded-md bg-neutral-500 px-4 py-2 text-sm font-semibold text-white hover:bg-neutral-600 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
variant="secondary"
|
||||
:label="$t('archive.archiveButton')"
|
||||
button-class="w-auto px-4"
|
||||
:disabled="isSubmitting"
|
||||
@click="handleArchive"
|
||||
>
|
||||
{{ $t('archive.archiveButton') }}
|
||||
</button>
|
||||
<button
|
||||
/>
|
||||
<MalioButton
|
||||
v-if="canUnarchive"
|
||||
type="button"
|
||||
class="rounded-md bg-neutral-500 px-4 py-2 text-sm font-semibold text-white hover:bg-neutral-600 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
variant="secondary"
|
||||
:label="$t('archive.unarchiveButton')"
|
||||
button-class="w-auto px-4"
|
||||
:disabled="isSubmitting"
|
||||
@click="handleUnarchive"
|
||||
>
|
||||
{{ $t('archive.unarchiveButton') }}
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="rounded-md bg-primary-500 px-6 py-2 text-sm font-semibold text-white hover:bg-secondary-500 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
/>
|
||||
<MalioButton
|
||||
label="Enregistrer"
|
||||
button-class="w-auto px-6"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
Enregistrer
|
||||
</button>
|
||||
@click="handleSubmit"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</AppDrawer>
|
||||
</MalioDrawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -78,13 +78,14 @@
|
||||
|
||||
<!-- Right: timer top, avatar bottom -->
|
||||
<div class="flex shrink-0 flex-col items-end justify-between self-stretch gap-1">
|
||||
<button
|
||||
class="shrink-0 transition-colors"
|
||||
:class="isTimerOnTask ? 'text-[#F18619] hover:text-[#d97314]' : 'text-neutral-400 hover:text-primary-500'"
|
||||
<MalioButtonIcon
|
||||
:icon="isTimerOnTask ? 'mdi:stop-circle-outline' : 'mdi:play-circle-outline'"
|
||||
:aria-label="isTimerOnTask ? 'Arrêter le timer' : 'Démarrer le timer'"
|
||||
variant="ghost"
|
||||
icon-size="20"
|
||||
:button-class="isTimerOnTask ? 'shrink-0 text-[#F18619] hover:text-[#d97314]' : 'shrink-0 text-neutral-400 hover:text-primary-500'"
|
||||
@click.stop="isTimerOnTask ? timerStore.stop() : timerStore.startFromTask(task)"
|
||||
>
|
||||
<Icon :name="isTimerOnTask ? 'mdi:stop-circle-outline' : 'mdi:play-circle-outline'" size="20" />
|
||||
</button>
|
||||
/>
|
||||
<UserAvatar
|
||||
v-if="task.assignee"
|
||||
:user="task.assignee"
|
||||
|
||||
@@ -27,13 +27,13 @@
|
||||
{{ isEditing ? $t('tasks.editTask') : $t('tasks.addTask') }}
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="flex h-8 w-8 items-center justify-center rounded-lg text-neutral-400 transition-colors hover:bg-neutral-200/60 hover:text-neutral-600"
|
||||
<MalioButtonIcon
|
||||
icon="mdi:close"
|
||||
aria-label="Fermer"
|
||||
variant="ghost"
|
||||
icon-size="20"
|
||||
@click="close"
|
||||
>
|
||||
<Icon name="mdi:close" size="20" />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Client ticket link -->
|
||||
@@ -417,48 +417,43 @@
|
||||
class="mt-6 flex items-center border-t border-neutral-100 pt-5"
|
||||
:class="isEditing ? 'justify-between' : 'justify-end'"
|
||||
>
|
||||
<button
|
||||
<MalioButton
|
||||
v-if="isEditing"
|
||||
type="button"
|
||||
class="rounded-lg bg-red-50 px-4 py-2 text-sm font-semibold text-red-600 transition-colors hover:bg-red-100 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
variant="danger"
|
||||
label="Supprimer"
|
||||
button-class="w-auto px-4"
|
||||
:disabled="isSubmitting"
|
||||
@click="confirmDeleteOpen = true"
|
||||
>
|
||||
Supprimer
|
||||
</button>
|
||||
/>
|
||||
<div class="flex gap-3">
|
||||
<button
|
||||
<MalioButton
|
||||
v-if="canArchive"
|
||||
type="button"
|
||||
class="rounded-lg border border-neutral-300 px-4 py-2 text-sm font-semibold text-neutral-700 transition-colors hover:bg-neutral-50 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
variant="tertiary"
|
||||
:label="$t('archive.archiveButton')"
|
||||
button-class="w-auto px-4"
|
||||
:disabled="isSubmitting"
|
||||
@click="handleArchive"
|
||||
>
|
||||
{{ $t('archive.archiveButton') }}
|
||||
</button>
|
||||
<button
|
||||
/>
|
||||
<MalioButton
|
||||
v-if="canUnarchive"
|
||||
type="button"
|
||||
class="rounded-lg border border-neutral-300 px-4 py-2 text-sm font-semibold text-neutral-700 transition-colors hover:bg-neutral-50 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
variant="tertiary"
|
||||
:label="$t('archive.unarchiveButton')"
|
||||
button-class="w-auto px-4"
|
||||
:disabled="isSubmitting"
|
||||
@click="handleUnarchive"
|
||||
>
|
||||
{{ $t('archive.unarchiveButton') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg border border-neutral-300 px-4 py-2 text-sm font-semibold text-neutral-700 transition-colors hover:bg-neutral-50"
|
||||
/>
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
label="Annuler"
|
||||
button-class="w-auto px-4"
|
||||
@click="close"
|
||||
>
|
||||
Annuler
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="rounded-lg bg-primary-500 px-6 py-2 text-sm font-semibold text-white transition-colors hover:bg-secondary-500 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
/>
|
||||
<MalioButton
|
||||
label="Enregistrer"
|
||||
button-class="w-auto px-6"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
Enregistrer
|
||||
</button>
|
||||
@click="handleSubmit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<AppDrawer v-model="isOpen" :title="isEditing ? $t('taskPriorities.editPriority') : $t('taskPriorities.addPriority')">
|
||||
<MalioDrawer v-model="isOpen" :title="isEditing ? $t('taskPriorities.editPriority') : $t('taskPriorities.addPriority')">
|
||||
<form @submit.prevent="handleSubmit" class="flex flex-col gap-2">
|
||||
<MalioInputText
|
||||
v-model="form.label"
|
||||
@@ -13,16 +13,15 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
class="rounded-md bg-primary-500 px-6 py-2 text-sm font-semibold text-white hover:bg-secondary-500 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
<MalioButton
|
||||
label="Enregistrer"
|
||||
button-class="w-auto px-6"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
Enregistrer
|
||||
</button>
|
||||
@click="handleSubmit"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</AppDrawer>
|
||||
</MalioDrawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<AppDrawer v-model="isOpen" :title="isEditing ? $t('taskStatuses.editStatus') : $t('taskStatuses.addStatus')">
|
||||
<MalioDrawer v-model="isOpen" :title="isEditing ? $t('taskStatuses.editStatus') : $t('taskStatuses.addStatus')">
|
||||
<form @submit.prevent="handleSubmit" class="flex flex-col gap-2">
|
||||
<MalioInputText
|
||||
v-model="form.label"
|
||||
@@ -31,16 +31,15 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
class="rounded-md bg-primary-500 px-6 py-2 text-sm font-semibold text-white hover:bg-secondary-500 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
<MalioButton
|
||||
label="Enregistrer"
|
||||
button-class="w-auto px-6"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
Enregistrer
|
||||
</button>
|
||||
@click="handleSubmit"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</AppDrawer>
|
||||
</MalioDrawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<AppDrawer v-model="isOpen" :title="isEditing ? $t('taskTags.editTag') : $t('taskTags.addTag')">
|
||||
<MalioDrawer v-model="isOpen" :title="isEditing ? $t('taskTags.editTag') : $t('taskTags.addTag')">
|
||||
<form @submit.prevent="handleSubmit" class="flex flex-col gap-2">
|
||||
<MalioInputText
|
||||
v-model="form.label"
|
||||
@@ -13,16 +13,15 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
class="rounded-md bg-primary-500 px-6 py-2 text-sm font-semibold text-white hover:bg-secondary-500 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
<MalioButton
|
||||
label="Enregistrer"
|
||||
button-class="w-auto px-6"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
Enregistrer
|
||||
</button>
|
||||
@click="handleSubmit"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</AppDrawer>
|
||||
</MalioDrawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<AppDrawer v-model="isOpen" :title="isEditing ? $t('timeEntries.editEntry') : $t('timeEntries.addEntry')">
|
||||
<MalioDrawer v-model="isOpen" :title="isEditing ? $t('timeEntries.editEntry') : $t('timeEntries.addEntry')">
|
||||
<form class="space-y-4" @submit.prevent="onSubmit">
|
||||
<div>
|
||||
<label class="mb-1 block text-sm font-semibold text-neutral-700">Titre</label>
|
||||
@@ -97,33 +97,30 @@
|
||||
</div>
|
||||
|
||||
<div class="flex items-center" :class="isEditing ? 'justify-between' : 'justify-end'">
|
||||
<button
|
||||
<MalioButton
|
||||
v-if="isEditing"
|
||||
type="button"
|
||||
class="rounded-md bg-red-500 px-4 py-2 text-sm font-semibold text-white hover:bg-red-600 transition"
|
||||
variant="danger"
|
||||
label="Supprimer"
|
||||
button-class="w-auto px-4"
|
||||
@click="onDelete"
|
||||
>
|
||||
Supprimer
|
||||
</button>
|
||||
/>
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
<MalioButton
|
||||
v-if="isEditing"
|
||||
type="button"
|
||||
class="rounded-md bg-blue-500 px-4 py-2 text-sm font-semibold text-white hover:bg-blue-600 transition"
|
||||
variant="secondary"
|
||||
label="Dupliquer"
|
||||
button-class="w-auto px-4"
|
||||
@click="onDuplicate"
|
||||
>
|
||||
Dupliquer
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="rounded-md bg-primary-500 px-4 py-2 text-sm font-semibold text-white hover:bg-primary-600 transition"
|
||||
>
|
||||
Enregistrer
|
||||
</button>
|
||||
/>
|
||||
<MalioButton
|
||||
label="Enregistrer"
|
||||
button-class="w-auto px-4"
|
||||
@click="onSubmit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</AppDrawer>
|
||||
</MalioDrawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -54,13 +54,14 @@
|
||||
</div>
|
||||
|
||||
<!-- Delete action -->
|
||||
<button
|
||||
class="shrink-0 rounded-md p-1.5 text-neutral-300 opacity-0 transition hover:bg-red-50 hover:text-red-500 group-hover:opacity-100"
|
||||
:title="$t('common.delete')"
|
||||
<MalioButtonIcon
|
||||
icon="mdi:delete-outline"
|
||||
:aria-label="$t('common.delete')"
|
||||
variant="ghost"
|
||||
icon-size="18"
|
||||
button-class="shrink-0 text-neutral-300 opacity-0 hover:bg-red-50 hover:text-red-500 group-hover:opacity-100"
|
||||
@click.stop="emit('deleteEntry', entry)"
|
||||
>
|
||||
<Icon name="mdi:delete-outline" size="18" />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
205
frontend/components/time-tracking/TimeTrackingExportDrawer.vue
Normal file
205
frontend/components/time-tracking/TimeTrackingExportDrawer.vue
Normal file
@@ -0,0 +1,205 @@
|
||||
<template>
|
||||
<MalioDrawer v-model="isOpen" :title="$t('timeEntries.exportTitle')" drawer-class="max-w-lg">
|
||||
<div class="flex flex-col gap-6 p-4">
|
||||
<!-- Period presets -->
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-semibold text-neutral-700">Période</p>
|
||||
<div class="flex flex-col gap-2">
|
||||
<MalioRadioButton
|
||||
v-model="periodMode"
|
||||
name="exportPeriod"
|
||||
value="currentMonth"
|
||||
:label="$t('timeEntries.exportCurrentMonth')"
|
||||
/>
|
||||
<MalioRadioButton
|
||||
v-model="periodMode"
|
||||
name="exportPeriod"
|
||||
value="lastMonth"
|
||||
:label="$t('timeEntries.exportLastMonth')"
|
||||
/>
|
||||
<MalioRadioButton
|
||||
v-model="periodMode"
|
||||
name="exportPeriod"
|
||||
value="custom"
|
||||
:label="$t('timeEntries.exportCustomPeriod')"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="periodMode === 'custom'" class="mt-3 flex items-center gap-3">
|
||||
<div class="flex-1">
|
||||
<label class="mb-1 block text-xs text-neutral-500">{{ $t('timeEntries.exportFrom') }}</label>
|
||||
<input
|
||||
v-model="customFrom"
|
||||
type="date"
|
||||
class="w-full rounded-md border border-neutral-300 px-3 py-2 text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<label class="mb-1 block text-xs text-neutral-500">{{ $t('timeEntries.exportTo') }}</label>
|
||||
<input
|
||||
v-model="customTo"
|
||||
type="date"
|
||||
class="w-full rounded-md border border-neutral-300 px-3 py-2 text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User filter (admin only) -->
|
||||
<div v-if="isAdmin" class="[&>div]:!mt-0">
|
||||
<MalioSelectCheckbox
|
||||
v-model="selectedUserIds"
|
||||
:options="userOptions"
|
||||
:label="$t('timeEntries.exportUsers')"
|
||||
:display-tag="true"
|
||||
:display-select-all="true"
|
||||
min-width="!w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Client filter -->
|
||||
<div class="[&>div]:!mt-0">
|
||||
<MalioSelect
|
||||
v-model="selectedClientId"
|
||||
:options="clientOptions"
|
||||
:label="$t('timeEntries.exportClient')"
|
||||
:empty-option-label="$t('timeEntries.exportAllClients')"
|
||||
min-width="!w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Project filter -->
|
||||
<div class="[&>div]:!mt-0">
|
||||
<MalioSelectCheckbox
|
||||
v-model="selectedProjectIds"
|
||||
:options="filteredProjectOptions"
|
||||
:label="$t('timeEntries.exportProjects')"
|
||||
:display-tag="true"
|
||||
:display-select-all="true"
|
||||
min-width="!w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Tag filter -->
|
||||
<div class="[&>div]:!mt-0">
|
||||
<MalioSelectCheckbox
|
||||
v-model="selectedTagIds"
|
||||
:options="tagOptions"
|
||||
:label="$t('timeEntries.exportTags')"
|
||||
:display-tag="true"
|
||||
:display-select-all="true"
|
||||
min-width="!w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Export button -->
|
||||
<MalioButton
|
||||
:label="$t('timeEntries.export')"
|
||||
icon-name="mdi:download"
|
||||
icon-position="left"
|
||||
button-class="w-full"
|
||||
@click="doExport"
|
||||
/>
|
||||
</div>
|
||||
</MalioDrawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { UserData } from '~/services/dto/user-data'
|
||||
import type { Project } from '~/services/dto/project'
|
||||
import type { TaskTag } from '~/services/dto/task-tag'
|
||||
import type { Client } from '~/services/dto/client'
|
||||
|
||||
const props = defineProps<{
|
||||
users: UserData[]
|
||||
projects: Project[]
|
||||
tags: TaskTag[]
|
||||
clients: Client[]
|
||||
}>()
|
||||
|
||||
const isOpen = defineModel<boolean>({ default: false })
|
||||
const emit = defineEmits<{
|
||||
(e: 'export', params: {
|
||||
after: string
|
||||
before: string
|
||||
users?: number[]
|
||||
projects?: number[]
|
||||
client?: number
|
||||
tags?: number[]
|
||||
}): void
|
||||
}>()
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const isAdmin = computed(() => authStore.user?.roles?.includes('ROLE_ADMIN') ?? false)
|
||||
|
||||
const periodMode = ref<'currentMonth' | 'lastMonth' | 'custom'>('currentMonth')
|
||||
const customFrom = ref('')
|
||||
const customTo = ref('')
|
||||
const selectedUserIds = ref<number[]>([])
|
||||
const selectedClientId = ref<number | null>(null)
|
||||
const selectedProjectIds = ref<number[]>([])
|
||||
const selectedTagIds = ref<number[]>([])
|
||||
|
||||
const userOptions = computed(() =>
|
||||
props.users.map(u => ({ text: u.username, value: u.id }))
|
||||
)
|
||||
|
||||
const clientOptions = computed(() =>
|
||||
props.clients.map(c => ({ text: c.name, value: c.id }))
|
||||
)
|
||||
|
||||
const filteredProjectOptions = computed(() => {
|
||||
let list = props.projects
|
||||
if (selectedClientId.value) {
|
||||
list = list.filter(p => p.client?.id === selectedClientId.value)
|
||||
}
|
||||
return list.map(p => ({ text: p.name, value: p.id }))
|
||||
})
|
||||
|
||||
const tagOptions = computed(() =>
|
||||
props.tags.map(t => ({ text: t.label, value: t.id }))
|
||||
)
|
||||
|
||||
// Reset project selection when client changes
|
||||
watch(selectedClientId, () => {
|
||||
selectedProjectIds.value = []
|
||||
})
|
||||
|
||||
function getDateRange(): { after: string; before: string } {
|
||||
const now = new Date()
|
||||
if (periodMode.value === 'currentMonth') {
|
||||
const first = new Date(now.getFullYear(), now.getMonth(), 1)
|
||||
const last = new Date(now.getFullYear(), now.getMonth() + 1, 1)
|
||||
return {
|
||||
after: first.toISOString().slice(0, 10),
|
||||
before: last.toISOString().slice(0, 10),
|
||||
}
|
||||
}
|
||||
if (periodMode.value === 'lastMonth') {
|
||||
const first = new Date(now.getFullYear(), now.getMonth() - 1, 1)
|
||||
const last = new Date(now.getFullYear(), now.getMonth(), 1)
|
||||
return {
|
||||
after: first.toISOString().slice(0, 10),
|
||||
before: last.toISOString().slice(0, 10),
|
||||
}
|
||||
}
|
||||
return {
|
||||
after: customFrom.value,
|
||||
before: customTo.value,
|
||||
}
|
||||
}
|
||||
|
||||
function doExport() {
|
||||
const { after, before } = getDateRange()
|
||||
if (!after || !before) return
|
||||
|
||||
emit('export', {
|
||||
after,
|
||||
before,
|
||||
users: selectedUserIds.value.length ? selectedUserIds.value : undefined,
|
||||
projects: selectedProjectIds.value.length ? selectedProjectIds.value : undefined,
|
||||
client: selectedClientId.value ?? undefined,
|
||||
tags: selectedTagIds.value.length ? selectedTagIds.value : undefined,
|
||||
})
|
||||
isOpen.value = false
|
||||
}
|
||||
</script>
|
||||
@@ -13,13 +13,13 @@
|
||||
>
|
||||
<div class="flex items-center justify-between border-b border-neutral-200 px-6 py-4">
|
||||
<h2 class="text-lg font-bold text-neutral-900">{{ title }}</h2>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded p-1 text-neutral-400 hover:text-neutral-600"
|
||||
<MalioButtonIcon
|
||||
icon="mdi:close"
|
||||
aria-label="Fermer"
|
||||
variant="ghost"
|
||||
icon-size="24"
|
||||
@click="close"
|
||||
>
|
||||
<Icon name="mdi:close" size="24" />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1 overflow-y-auto px-6 py-4">
|
||||
<slot />
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
<template>
|
||||
<header class="border-b border-neutral-200 bg-primary-500 px-3 py-2 text-white sm:px-5 sm:py-2 max-h-[60px]">
|
||||
<div class="flex h-full items-center justify-between">
|
||||
<button
|
||||
class="rounded-md p-2 text-white hover:bg-primary-600 transition-colors lg:hidden"
|
||||
<MalioButtonIcon
|
||||
icon="mdi:menu"
|
||||
aria-label="Menu"
|
||||
variant="ghost"
|
||||
icon-size="24"
|
||||
button-class="lg:hidden text-white hover:bg-primary-600"
|
||||
@click="ui.openMobileSidebar()"
|
||||
>
|
||||
<Icon name="mdi:menu" size="24" />
|
||||
</button>
|
||||
/>
|
||||
<div class="hidden items-center gap-2 lg:flex">
|
||||
<h1 class="text-lg font-bold tracking-tight">{{ appTitle }}</h1>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md p-1 text-white/60 transition-colors hover:bg-primary-600 hover:text-white"
|
||||
:title="appTitle === 'NeauTime' ? 'Switch to Lesstime' : 'Switch to NeauTime'"
|
||||
<MalioButtonIcon
|
||||
icon="mdi:swap-horizontal"
|
||||
:aria-label="appTitle === 'NeauTime' ? 'Switch to Lesstime' : 'Switch to NeauTime'"
|
||||
variant="ghost"
|
||||
icon-size="18"
|
||||
button-class="text-white/60 hover:bg-primary-600 hover:text-white"
|
||||
@click="toggleTitle"
|
||||
>
|
||||
<Icon name="mdi:swap-horizontal" size="18" />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-auto flex items-center gap-4 text-xl text-white sm:gap-8">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md p-1.5 text-white/70 transition-colors hover:bg-primary-600 hover:text-white"
|
||||
:title="ui.darkMode ? 'Mode clair' : 'Mode sombre'"
|
||||
<MalioButtonIcon
|
||||
:icon="ui.darkMode ? 'mdi:weather-sunny' : 'mdi:weather-night'"
|
||||
:aria-label="ui.darkMode ? 'Mode clair' : 'Mode sombre'"
|
||||
variant="ghost"
|
||||
icon-size="22"
|
||||
button-class="text-white/70 hover:bg-primary-600 hover:text-white"
|
||||
@click="ui.toggleDarkMode()"
|
||||
>
|
||||
<Icon :name="ui.darkMode ? 'mdi:weather-sunny' : 'mdi:weather-night'" size="22" />
|
||||
</button>
|
||||
/>
|
||||
<NotificationBell />
|
||||
<div class="group relative flex gap-2 sm:gap-4">
|
||||
<UserAvatar v-if="user" :user="user" size="md" class="cursor-pointer" />
|
||||
|
||||
@@ -9,20 +9,18 @@
|
||||
{{ $t('taskDocuments.confirmDeleteMessage') }}
|
||||
</p>
|
||||
<div class="mt-6 flex justify-end gap-3">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md border border-neutral-300 px-4 py-2 text-sm font-semibold text-neutral-700 hover:bg-neutral-50"
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
:label="$t('common.cancel')"
|
||||
button-class="w-auto px-4"
|
||||
@click="cancel"
|
||||
>
|
||||
{{ $t('common.cancel') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md bg-red-600 px-4 py-2 text-sm font-semibold text-white hover:bg-red-700"
|
||||
/>
|
||||
<MalioButton
|
||||
variant="danger"
|
||||
label="Supprimer"
|
||||
button-class="w-auto px-4"
|
||||
@click="$emit('confirm')"
|
||||
>
|
||||
Supprimer
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,20 +9,18 @@
|
||||
{{ $t('projects.deleteConfirmMessage') }}
|
||||
</p>
|
||||
<div class="mt-6 flex justify-end gap-3">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md border border-neutral-300 px-4 py-2 text-sm font-semibold text-neutral-700 hover:bg-neutral-50"
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
:label="$t('common.cancel')"
|
||||
button-class="w-auto px-4"
|
||||
@click="cancel"
|
||||
>
|
||||
{{ $t('common.cancel') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md bg-red-600 px-4 py-2 text-sm font-semibold text-white hover:bg-red-700"
|
||||
/>
|
||||
<MalioButton
|
||||
variant="danger"
|
||||
:label="$t('common.delete')"
|
||||
button-class="w-auto px-4"
|
||||
@click="$emit('confirm')"
|
||||
>
|
||||
{{ $t('common.delete') }}
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,21 +21,19 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end gap-3">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md border border-neutral-300 px-4 py-2 text-sm font-semibold text-neutral-700 hover:bg-neutral-50"
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
:label="$t('common.cancel')"
|
||||
button-class="w-auto px-4"
|
||||
@click="cancel"
|
||||
>
|
||||
{{ $t('common.cancel') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md bg-[red-600] px-4 py-2 text-sm font-semibold text-white hover:bg-[red-700] disabled:opacity-50"
|
||||
/>
|
||||
<MalioButton
|
||||
variant="danger"
|
||||
:label="$t('common.delete')"
|
||||
button-class="w-auto px-4"
|
||||
:disabled="isProcessing"
|
||||
@click="confirm"
|
||||
>
|
||||
{{ $t('common.delete') }}
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,20 +9,18 @@
|
||||
{{ $t('tasks.deleteConfirmMessage') }}
|
||||
</p>
|
||||
<div class="mt-6 flex justify-end gap-3">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md border border-neutral-300 px-4 py-2 text-sm font-semibold text-neutral-700 hover:bg-neutral-50"
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
label="Annuler"
|
||||
button-class="w-auto px-4"
|
||||
@click="cancel"
|
||||
>
|
||||
Annuler
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-md bg-red-600 px-4 py-2 text-sm font-semibold text-white hover:bg-red-700"
|
||||
/>
|
||||
<MalioButton
|
||||
variant="danger"
|
||||
label="Supprimer"
|
||||
button-class="w-auto px-4"
|
||||
@click="$emit('confirm')"
|
||||
>
|
||||
Supprimer
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -35,13 +35,15 @@
|
||||
<td v-if="deletable || $slots.actions" class="px-4 py-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<slot name="actions" :item="item" />
|
||||
<button
|
||||
<MalioButtonIcon
|
||||
v-if="deletable"
|
||||
class="text-neutral-400 transition-colors hover:text-red-500"
|
||||
icon="mdi:delete-outline"
|
||||
aria-label="Supprimer"
|
||||
variant="ghost"
|
||||
icon-size="20"
|
||||
button-class="text-neutral-400 hover:text-red-500"
|
||||
@click.stop="$emit('delete', item)"
|
||||
>
|
||||
<Icon name="mdi:delete-outline" size="20" />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -18,21 +18,18 @@
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-3">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg border border-neutral-300 px-4 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-50"
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
:label="$t('common.cancel')"
|
||||
button-class="w-auto px-4"
|
||||
@click="emit('cancel')"
|
||||
>
|
||||
{{ $t('common.cancel') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg bg-primary-500 px-4 py-2 text-sm font-semibold text-white hover:bg-secondary-500"
|
||||
/>
|
||||
<MalioButton
|
||||
:label="$t('common.confirm')"
|
||||
button-class="w-auto px-4"
|
||||
:disabled="cropping"
|
||||
@click="onConfirm"
|
||||
>
|
||||
{{ $t('common.confirm') }}
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<AppDrawer v-model="isOpen" :title="isEditing ? $t('users.editUser') : $t('users.addUser')">
|
||||
<MalioDrawer v-model="isOpen" :title="isEditing ? $t('users.editUser') : $t('users.addUser')">
|
||||
<form class="flex flex-col gap-2" @submit.prevent="handleSubmit">
|
||||
<MalioInputText
|
||||
v-model="form.username"
|
||||
@@ -70,16 +70,15 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
class="rounded-md bg-primary-500 px-6 py-2 text-sm font-semibold text-white hover:bg-secondary-500 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
<MalioButton
|
||||
label="Enregistrer"
|
||||
button-class="w-auto px-6"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
Enregistrer
|
||||
</button>
|
||||
@click="handleSubmit"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</AppDrawer>
|
||||
</MalioDrawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -162,7 +162,21 @@
|
||||
"noEntries": "Aucune activité pour cette période",
|
||||
"addEntry": "Ajouter une Activité",
|
||||
"editEntry": "Modifier un temps",
|
||||
"export": "Exporter"
|
||||
"export": "Exporter",
|
||||
"exportTitle": "Exporter les temps",
|
||||
"exportCurrentMonth": "Mois en cours",
|
||||
"exportLastMonth": "Mois dernier",
|
||||
"exportCustomPeriod": "Période personnalisée",
|
||||
"exportFrom": "Du",
|
||||
"exportTo": "Au",
|
||||
"exportUsers": "Utilisateurs",
|
||||
"exportClient": "Client",
|
||||
"exportProjects": "Projets",
|
||||
"exportTags": "Tags",
|
||||
"exportAllClients": "Tous les clients",
|
||||
"exportLoading": "Export en cours...",
|
||||
"exportSuccess": "Export terminé !",
|
||||
"exportError": "Erreur lors de l'export."
|
||||
},
|
||||
"archive": {
|
||||
"title": "Archives",
|
||||
|
||||
101
frontend/package-lock.json
generated
101
frontend/package-lock.json
generated
@@ -7,7 +7,7 @@
|
||||
"name": "nuxt-app",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@malio/layer-ui": "^1.1.0",
|
||||
"@malio/layer-ui": "^1.2.0",
|
||||
"@nuxt/icon": "^2.2.1",
|
||||
"@nuxtjs/i18n": "^10.2.3",
|
||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||
@@ -76,7 +76,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
|
||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.29.0",
|
||||
"@babel/generator": "^7.29.0",
|
||||
@@ -1038,6 +1037,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
|
||||
"integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
|
||||
}
|
||||
@@ -1047,6 +1047,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.3.tgz",
|
||||
"integrity": "sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint/object-schema": "^3.0.3",
|
||||
"debug": "^4.3.1",
|
||||
@@ -1061,6 +1062,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.3.tgz",
|
||||
"integrity": "sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint/core": "^1.1.1"
|
||||
},
|
||||
@@ -1073,6 +1075,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.1.tgz",
|
||||
"integrity": "sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.15"
|
||||
},
|
||||
@@ -1085,6 +1088,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.3.tgz",
|
||||
"integrity": "sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
}
|
||||
@@ -1094,6 +1098,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.1.tgz",
|
||||
"integrity": "sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint/core": "^1.1.1",
|
||||
"levn": "^0.4.1"
|
||||
@@ -1169,6 +1174,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||
"integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.18.0"
|
||||
}
|
||||
@@ -1178,6 +1184,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
|
||||
"integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@humanfs/core": "^0.19.1",
|
||||
"@humanwhocodes/retry": "^0.4.0"
|
||||
@@ -1191,6 +1198,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
|
||||
"integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12.22"
|
||||
},
|
||||
@@ -1204,6 +1212,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
|
||||
"integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.18"
|
||||
},
|
||||
@@ -2203,9 +2212,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@malio/layer-ui": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://gitea.malio.fr/api/packages/MALIO-DEV/npm/%40malio%2Flayer-ui/-/1.1.0/layer-ui-1.1.0.tgz",
|
||||
"integrity": "sha512-mc+kOK+EDfo6ZZcE0/FaVnvDyIDJrigkgOzvL8rxnpljXEiRlKj5673e5e6ZIoOyKFqktzbJXzFr4V6UBD0wPg==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://gitea.malio.fr/api/packages/MALIO-DEV/npm/%40malio%2Flayer-ui/-/1.2.0/layer-ui-1.2.0.tgz",
|
||||
"integrity": "sha512-/D/p7Tz5t8xsZ+qL4kwBs2XXA/yNJpwF5C8pbSrz06Z8Je/Yut2J4KT1YpPHcfyFFE3TB8TpV0Okg/29aN6Ggg==",
|
||||
"dependencies": {
|
||||
"@nuxt/icon": "^2.2.1",
|
||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||
@@ -2483,7 +2492,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-4.3.1.tgz",
|
||||
"integrity": "sha512-UjBFt72dnpc+83BV3OIbCT0YHLevJtgJCHpxMX0YRKWLDhhbcDdUse87GtsQBrjvOzK7WUNUYLDS/hQLYev5rA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"c12": "^3.3.3",
|
||||
"consola": "^3.4.2",
|
||||
@@ -2556,7 +2564,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@nuxt/schema/-/schema-4.3.1.tgz",
|
||||
"integrity": "sha512-S+wHJdYDuyk9I43Ej27y5BeWMZgi7R/UVql3b3qtT35d0fbpXW7fUenzhLRCCDC6O10sjguc6fcMcR9sMKvV8g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/shared": "^3.5.27",
|
||||
"defu": "^6.1.4",
|
||||
@@ -3203,7 +3210,6 @@
|
||||
"resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.95.0.tgz",
|
||||
"integrity": "sha512-Te8fE/SmiiKWIrwBwxz5Dod87uYvsbcZ9JAL5ylPg1DevyKgTkxCXnPEaewk1Su2qpfNmry5RHoN+NywWFCG+A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@oxc-project/types": "^0.95.0"
|
||||
},
|
||||
@@ -5309,7 +5315,8 @@
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
|
||||
"integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
@@ -5321,7 +5328,8 @@
|
||||
"version": "7.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/resolve": {
|
||||
"version": "1.20.2",
|
||||
@@ -5662,7 +5670,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.29.tgz",
|
||||
"integrity": "sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@vue/compiler-core": "3.5.29",
|
||||
@@ -5912,7 +5919,6 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
|
||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -5952,6 +5958,7 @@
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
|
||||
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
@@ -6283,7 +6290,6 @@
|
||||
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
|
||||
"integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"bare-abort-controller": "*"
|
||||
},
|
||||
@@ -6477,7 +6483,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
@@ -6606,7 +6611,6 @@
|
||||
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
|
||||
"integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -6748,7 +6752,6 @@
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
|
||||
"integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@kurkle/color": "^0.3.0"
|
||||
},
|
||||
@@ -6785,7 +6788,6 @@
|
||||
"resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz",
|
||||
"integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"consola": "^3.2.3"
|
||||
}
|
||||
@@ -7341,7 +7343,8 @@
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/deepmerge": {
|
||||
"version": "4.3.1",
|
||||
@@ -7847,6 +7850,7 @@
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz",
|
||||
"integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==",
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/esrecurse": "^4.3.1",
|
||||
"@types/estree": "^1.0.8",
|
||||
@@ -7877,6 +7881,7 @@
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@@ -7889,6 +7894,7 @@
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
|
||||
"integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
},
|
||||
@@ -7901,6 +7907,7 @@
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.3"
|
||||
},
|
||||
@@ -7913,6 +7920,7 @@
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
@@ -7922,6 +7930,7 @@
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz",
|
||||
"integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"acorn": "^8.16.0",
|
||||
"acorn-jsx": "^5.3.2",
|
||||
@@ -7939,6 +7948,7 @@
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
|
||||
"integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
},
|
||||
@@ -7964,6 +7974,7 @@
|
||||
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
|
||||
"integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"estraverse": "^5.1.0"
|
||||
},
|
||||
@@ -7976,6 +7987,7 @@
|
||||
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
|
||||
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"estraverse": "^5.2.0"
|
||||
},
|
||||
@@ -8076,7 +8088,8 @@
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/fast-fifo": {
|
||||
"version": "1.3.2",
|
||||
@@ -8104,13 +8117,15 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
|
||||
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/fast-levenshtein": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
|
||||
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/fast-npm-meta": {
|
||||
"version": "1.4.0",
|
||||
@@ -8155,6 +8170,7 @@
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
|
||||
"integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"flat-cache": "^4.0.0"
|
||||
},
|
||||
@@ -8185,6 +8201,7 @@
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
|
||||
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"locate-path": "^6.0.0",
|
||||
"path-exists": "^4.0.0"
|
||||
@@ -8201,6 +8218,7 @@
|
||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
|
||||
"integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"flatted": "^3.2.9",
|
||||
"keyv": "^4.5.4"
|
||||
@@ -8213,7 +8231,8 @@
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.0.tgz",
|
||||
"integrity": "sha512-kC6Bb+ooptOIvWj5B63EQWkF0FEnNjV2ZNkLMLZRDDduIiWeFF4iKnslwhiWxjAdbg4NzTNo6h0qLuvFrcx+Sw==",
|
||||
"license": "ISC"
|
||||
"license": "ISC",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/foreground-child": {
|
||||
"version": "3.3.1",
|
||||
@@ -8746,6 +8765,7 @@
|
||||
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
|
||||
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.8.19"
|
||||
}
|
||||
@@ -9122,19 +9142,22 @@
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
||||
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/json-stable-stringify-without-jsonify": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
|
||||
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.3",
|
||||
@@ -9212,6 +9235,7 @@
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"json-buffer": "3.0.1"
|
||||
}
|
||||
@@ -9472,6 +9496,7 @@
|
||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
||||
"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"prelude-ls": "^1.2.1",
|
||||
"type-check": "~0.4.0"
|
||||
@@ -9556,6 +9581,7 @@
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"p-locate": "^5.0.0"
|
||||
},
|
||||
@@ -9947,7 +9973,8 @@
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
|
||||
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
@@ -10183,7 +10210,6 @@
|
||||
"resolved": "https://registry.npmjs.org/nuxt/-/nuxt-4.3.1.tgz",
|
||||
"integrity": "sha512-bl+0rFcT5Ax16aiWFBFPyWcsTob19NTZaDL5P6t0MQdK63AtgS6fN6fwvwdbXtnTk6/YdCzlmuLzXhSM22h0OA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@dxup/nuxt": "^0.3.2",
|
||||
"@nuxt/cli": "^3.33.0",
|
||||
@@ -10454,6 +10480,7 @@
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
"integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"deep-is": "^0.1.3",
|
||||
"fast-levenshtein": "^2.0.6",
|
||||
@@ -10505,7 +10532,6 @@
|
||||
"resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.112.0.tgz",
|
||||
"integrity": "sha512-7rQ3QdJwobMQLMZwQaPuPYMEF2fDRZwf51lZ//V+bA37nejjKW5ifMHbbCwvA889Y4RLhT+/wLJpPRhAoBaZYw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@oxc-project/types": "^0.112.0"
|
||||
},
|
||||
@@ -10589,6 +10615,7 @@
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"yocto-queue": "^0.1.0"
|
||||
},
|
||||
@@ -10604,6 +10631,7 @@
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
|
||||
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"p-limit": "^3.0.2"
|
||||
},
|
||||
@@ -10646,6 +10674,7 @@
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -10749,7 +10778,6 @@
|
||||
"resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz",
|
||||
"integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "^7.7.7"
|
||||
},
|
||||
@@ -10866,7 +10894,6 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.11",
|
||||
"picocolors": "^1.1.1",
|
||||
@@ -11410,7 +11437,6 @@
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz",
|
||||
"integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
"util-deprecate": "^1.0.2"
|
||||
@@ -11461,6 +11487,7 @@
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
@@ -11497,6 +11524,7 @@
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
@@ -11858,7 +11886,6 @@
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
|
||||
"integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.8"
|
||||
},
|
||||
@@ -12641,7 +12668,6 @@
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
|
||||
"integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
"arg": "^5.0.2",
|
||||
@@ -12982,6 +13008,7 @@
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"prelude-ls": "^1.2.1"
|
||||
},
|
||||
@@ -13049,7 +13076,6 @@
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -13485,6 +13511,7 @@
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
@@ -13509,7 +13536,6 @@
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
|
||||
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.27.0",
|
||||
"fdir": "^6.5.0",
|
||||
@@ -13871,7 +13897,6 @@
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.29.tgz",
|
||||
"integrity": "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.29",
|
||||
"@vue/compiler-sfc": "3.5.29",
|
||||
@@ -13936,7 +13961,6 @@
|
||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.3.0.tgz",
|
||||
"integrity": "sha512-1J+xDfDJTLhDxElkd3+XUhT7FYSZd2b8pa7IRKGxhWH/8yt6PTvi3xmWhGwhYT5EaXdatui11pF2R6tL73/zPA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@intlify/core-base": "11.3.0",
|
||||
"@intlify/devtools-types": "11.3.0",
|
||||
@@ -13958,7 +13982,6 @@
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz",
|
||||
"integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/devtools-api": "^6.6.4"
|
||||
},
|
||||
@@ -14011,6 +14034,7 @@
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -14179,6 +14203,7 @@
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"build:dist": "nuxt generate && rm -rf dist && cp -R .output/public dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@malio/layer-ui": "^1.1.0",
|
||||
"@malio/layer-ui": "^1.2.0",
|
||||
"@nuxt/icon": "^2.2.1",
|
||||
"@nuxtjs/i18n": "^10.2.3",
|
||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||
|
||||
@@ -30,13 +30,12 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full rounded-md bg-primary-500 px-4 py-2 text-base font-semibold text-white transition hover:bg-secondary-500 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
<MalioButton
|
||||
label="Se connecter"
|
||||
button-class="w-full"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
Se connecter
|
||||
</button>
|
||||
@click="handleSubmit"
|
||||
/>
|
||||
<p class="font-bold">v{{ version }}</p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -324,15 +324,16 @@ onMounted(async () => {
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<h1 class="text-xl font-bold text-primary-500 sm:text-2xl">{{ $t('myTasks.title') }}</h1>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
class="flex items-center gap-1.5 rounded-lg bg-primary-500 px-3 py-1.5 text-sm font-semibold text-white transition-colors hover:bg-secondary-500"
|
||||
<MalioButton
|
||||
icon-name="mdi:plus"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-3"
|
||||
@click="openTaskCreate"
|
||||
>
|
||||
<Icon name="mdi:plus" size="18" />
|
||||
{{ $t('myTasks.createTask') }}
|
||||
</button>
|
||||
</MalioButton>
|
||||
<button
|
||||
class="flex items-center justify-center rounded-md border p-1.5 transition-colors"
|
||||
class="flex h-[40px] w-[40px] items-center justify-center rounded-md border transition-colors"
|
||||
:class="viewMode === 'list'
|
||||
? 'border-primary-500 bg-primary-500 text-white'
|
||||
: 'border-primary-500 text-primary-500 hover:bg-primary-50'"
|
||||
|
||||
@@ -104,21 +104,19 @@
|
||||
:placeholder="$t('clientTicket.rejectComment')"
|
||||
/>
|
||||
<div class="mt-4 flex justify-end gap-3">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg border border-neutral-300 px-4 py-2 text-sm font-semibold text-neutral-700 hover:bg-neutral-50"
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
:label="$t('common.cancel')"
|
||||
button-class="w-auto px-4"
|
||||
@click="cancelReject"
|
||||
>
|
||||
{{ $t('common.cancel') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg bg-red-600 px-4 py-2 text-sm font-semibold text-white hover:bg-red-700 disabled:opacity-50"
|
||||
/>
|
||||
<MalioButton
|
||||
variant="danger"
|
||||
:label="$t('clientTicket.status.rejected')"
|
||||
button-class="w-auto px-4"
|
||||
:disabled="!rejectComment.trim()"
|
||||
@click="confirmReject"
|
||||
>
|
||||
{{ $t('clientTicket.status.rejected') }}
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -74,13 +74,12 @@
|
||||
>
|
||||
{{ $t('common.cancel') }}
|
||||
</NuxtLink>
|
||||
<button
|
||||
type="submit"
|
||||
class="rounded-lg bg-primary-500 px-6 py-2 text-sm font-semibold text-white transition-colors hover:bg-secondary-500 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
<MalioButton
|
||||
:label="$t('portal.submitTicket')"
|
||||
button-class="w-auto px-6"
|
||||
:disabled="isSubmitting"
|
||||
>
|
||||
{{ $t('portal.submitTicket') }}
|
||||
</button>
|
||||
@click="handleSubmit"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -26,15 +26,14 @@
|
||||
/>
|
||||
</label>
|
||||
|
||||
<button
|
||||
<MalioButton
|
||||
v-if="auth.user?.avatarUrl"
|
||||
type="button"
|
||||
class="rounded-lg border border-red-300 px-4 py-2 text-sm font-medium text-red-600 hover:bg-red-50"
|
||||
variant="danger"
|
||||
button-class="w-auto px-4"
|
||||
:disabled="removing"
|
||||
:label="$t('profile.removeAvatar')"
|
||||
@click="onRemove"
|
||||
>
|
||||
{{ $t('profile.removeAvatar') }}
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -60,20 +60,20 @@
|
||||
<p class="mt-0.5 text-xs text-neutral-400">{{ formatDate(ticket.createdAt) }}</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<button
|
||||
class="rounded p-1.5 text-neutral-400 transition-colors hover:bg-neutral-100 hover:text-primary-500"
|
||||
:title="$t('clientTicket.changeStatus')"
|
||||
<MalioButtonIcon
|
||||
icon="mdi:swap-horizontal"
|
||||
:aria-label="$t('clientTicket.changeStatus')"
|
||||
variant="ghost"
|
||||
icon-size="18"
|
||||
@click.stop="openStatusChange(ticket)"
|
||||
>
|
||||
<Icon name="mdi:swap-horizontal" size="18" />
|
||||
</button>
|
||||
<button
|
||||
class="rounded p-1.5 text-neutral-400 transition-colors hover:bg-red-50 hover:text-red-500"
|
||||
title="Supprimer"
|
||||
/>
|
||||
<MalioButtonIcon
|
||||
icon="mdi:delete-outline"
|
||||
aria-label="Supprimer"
|
||||
variant="ghost"
|
||||
icon-size="18"
|
||||
@click.stop="onDelete(ticket)"
|
||||
>
|
||||
<Icon name="mdi:delete-outline" size="18" />
|
||||
</button>
|
||||
/>
|
||||
<Icon
|
||||
:name="expandedId === ticket.id ? 'mdi:chevron-up' : 'mdi:chevron-down'"
|
||||
size="20"
|
||||
@@ -143,19 +143,18 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end gap-3">
|
||||
<button
|
||||
class="rounded-lg border border-neutral-300 px-4 py-2 text-sm font-semibold text-neutral-700 transition-colors hover:bg-neutral-50"
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
:label="$t('common.cancel')"
|
||||
button-class="w-auto px-4"
|
||||
@click="statusModalOpen = false"
|
||||
>
|
||||
{{ $t('common.cancel') }}
|
||||
</button>
|
||||
<button
|
||||
class="rounded-lg bg-primary-500 px-6 py-2 text-sm font-semibold text-white transition-colors hover:bg-secondary-500 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
/>
|
||||
<MalioButton
|
||||
label="Confirmer"
|
||||
button-class="w-auto px-6"
|
||||
:disabled="isUpdatingStatus"
|
||||
@click="confirmStatusChange"
|
||||
>
|
||||
Confirmer
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,15 +4,17 @@
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<h1 class="text-xl font-bold text-primary-500 sm:text-2xl">{{ project?.name ?? '' }}</h1>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
class="shrink-0 rounded-md bg-primary-500 px-3 py-2 text-xs font-semibold text-white hover:bg-secondary-500 sm:px-4 sm:text-sm"
|
||||
<MalioButton
|
||||
icon-name="mdi:plus"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-3 shrink-0"
|
||||
@click="openTaskCreate"
|
||||
>
|
||||
<span class="hidden sm:inline">+ Ajouter un ticket</span>
|
||||
<span class="sm:hidden">+ Ticket</span>
|
||||
</button>
|
||||
<span class="hidden sm:inline">Ajouter un ticket</span>
|
||||
<span class="sm:hidden">Ticket</span>
|
||||
</MalioButton>
|
||||
<button
|
||||
class="flex items-center justify-center rounded-md border p-1.5 transition-colors"
|
||||
class="flex h-[40px] w-[40px] items-center justify-center rounded-md border transition-colors"
|
||||
:class="viewMode === 'list'
|
||||
? 'border-primary-500 bg-primary-500 text-white'
|
||||
: 'border-primary-500 text-primary-500 hover:bg-primary-50'"
|
||||
@@ -21,13 +23,12 @@
|
||||
>
|
||||
<Icon name="mdi:format-list-bulleted" size="20" />
|
||||
</button>
|
||||
<button
|
||||
class="flex shrink-0 items-center rounded-md bg-neutral-200 px-3 py-2 text-neutral-600 hover:bg-neutral-300 sm:px-4"
|
||||
title="Paramètres du projet"
|
||||
<MalioButtonIcon
|
||||
icon="heroicons:cog-6-tooth"
|
||||
aria-label="Paramètres du projet"
|
||||
variant="ghost"
|
||||
@click="projectDrawerOpen = true"
|
||||
>
|
||||
<Icon name="heroicons:cog-6-tooth" class="size-4 sm:size-5" />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4,23 +4,24 @@
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<h1 class="text-xl font-bold text-primary-500 sm:text-2xl">{{ $t('projects.title') }}</h1>
|
||||
<div class="flex items-center gap-2 sm:gap-3">
|
||||
<button
|
||||
class="flex items-center gap-1.5 rounded-md px-2 py-2 text-sm font-medium transition sm:px-3"
|
||||
:class="showArchived
|
||||
? 'bg-amber-100 text-amber-700 hover:bg-amber-200'
|
||||
: 'text-neutral-500 hover:bg-neutral-100 hover:text-neutral-700'"
|
||||
<MalioButton
|
||||
variant="tertiary"
|
||||
:icon-name="showArchived ? 'mdi:archive-arrow-up-outline' : 'mdi:archive-outline'"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-3"
|
||||
@click="toggleArchived"
|
||||
>
|
||||
<Icon :name="showArchived ? 'mdi:archive-arrow-up-outline' : 'mdi:archive-outline'" size="18" />
|
||||
<span class="hidden sm:inline">{{ showArchived ? $t('projects.hideArchived') : $t('projects.showArchived') }}</span>
|
||||
</button>
|
||||
<button
|
||||
class="shrink-0 rounded-md bg-primary-500 px-3 py-2 text-xs font-semibold text-white hover:bg-secondary-500 sm:px-4 sm:text-sm"
|
||||
</MalioButton>
|
||||
<MalioButton
|
||||
icon-name="mdi:plus"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-3 shrink-0"
|
||||
@click="openCreate"
|
||||
>
|
||||
<span class="hidden sm:inline">+ {{ $t('projects.addProject') }}</span>
|
||||
<span class="sm:hidden">+ {{ $t('projects.addProjectShort') }}</span>
|
||||
</button>
|
||||
<span class="hidden sm:inline">{{ $t('projects.addProject') }}</span>
|
||||
<span class="sm:hidden">{{ $t('projects.addProjectShort') }}</span>
|
||||
</MalioButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -44,12 +45,13 @@
|
||||
{{ $t('common.archived') }}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
class="p-1 text-neutral-400 hover:text-primary-500"
|
||||
<MalioButtonIcon
|
||||
icon="mdi:pencil-outline"
|
||||
aria-label="Modifier le projet"
|
||||
variant="ghost"
|
||||
icon-size="16"
|
||||
@click.stop="openEdit(project)"
|
||||
>
|
||||
<Icon name="mdi:pencil-outline" size="16" />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-neutral-600 line-clamp-4">
|
||||
{{ project.description ?? '' }}
|
||||
|
||||
@@ -3,27 +3,35 @@
|
||||
<div ref="pageHeaderEl" class="sticky top-8 z-20 flex-shrink-0 bg-white pb-4 sm:top-12">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<h1 class="text-xl font-bold text-primary-500 sm:text-2xl">Suivi des temps</h1>
|
||||
<button
|
||||
class="shrink-0 rounded-md bg-primary-500 px-3 py-2 text-xs font-semibold text-white hover:bg-primary-600 transition sm:px-4 sm:text-sm"
|
||||
<MalioButton
|
||||
icon-name="mdi:plus"
|
||||
icon-position="left"
|
||||
button-class="shrink-0"
|
||||
@click="openCreateDrawer()"
|
||||
>
|
||||
<span class="hidden sm:inline">+ Ajouter une Activité</span>
|
||||
<span class="sm:hidden">+ Activité</span>
|
||||
</button>
|
||||
<span class="hidden sm:inline">Ajouter une Activité</span>
|
||||
<span class="sm:hidden">Activité</span>
|
||||
</MalioButton>
|
||||
</div>
|
||||
|
||||
<div class="relative z-30 mt-4 flex flex-wrap items-center gap-3 sm:gap-4">
|
||||
<div class="flex shrink-0 items-center gap-1 h-8">
|
||||
<button class="flex h-8 w-8 items-center justify-center rounded-full text-neutral-400 hover:text-neutral-700 transition" @click="navigatePrev">
|
||||
<Icon name="mdi:chevron-left" size="20" />
|
||||
</button>
|
||||
<MalioButtonIcon
|
||||
icon="mdi:chevron-left"
|
||||
aria-label="Précédent"
|
||||
variant="ghost"
|
||||
@click="navigatePrev"
|
||||
/>
|
||||
<DateFilter v-model="selectedDateFilter" :picker-mode="viewMode === 'day' ? 'day' : 'week'" />
|
||||
<h2 class="shrink-0 whitespace-nowrap text-lg font-bold leading-8 text-orange-500">
|
||||
{{ currentMonthLabel }}
|
||||
</h2>
|
||||
<button class="flex h-8 w-8 items-center justify-center rounded-full text-neutral-400 hover:text-neutral-700 transition" @click="navigateNext">
|
||||
<Icon name="mdi:chevron-right" size="20" />
|
||||
</button>
|
||||
<MalioButtonIcon
|
||||
icon="mdi:chevron-right"
|
||||
aria-label="Suivant"
|
||||
variant="ghost"
|
||||
@click="navigateNext"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center rounded-full bg-neutral-100 p-1">
|
||||
@@ -76,13 +84,14 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="flex shrink-0 items-center gap-2 rounded-md border border-neutral-200 bg-white px-3 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-50 transition"
|
||||
@click="exportTimeEntries"
|
||||
>
|
||||
<Icon name="mdi:download" size="18" />
|
||||
{{ $t('timeEntries.export') }}
|
||||
</button>
|
||||
<MalioButton
|
||||
:label="$t('timeEntries.export')"
|
||||
variant="secondary"
|
||||
icon-name="mdi:download"
|
||||
icon-position="left"
|
||||
button-class="w-auto px-4"
|
||||
@click="exportDrawerOpen = true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -128,6 +137,15 @@
|
||||
@paste="onPaste"
|
||||
@delete="onDelete"
|
||||
/>
|
||||
|
||||
<TimeTrackingExportDrawer
|
||||
v-model="exportDrawerOpen"
|
||||
:users="users"
|
||||
:projects="projects"
|
||||
:tags="tags"
|
||||
:clients="clients"
|
||||
@export="onExport"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -136,6 +154,7 @@ import type { TimeEntry } from '~/services/dto/time-entry'
|
||||
import type { UserData } from '~/services/dto/user-data'
|
||||
import type { Project } from '~/services/dto/project'
|
||||
import type { TaskTag } from '~/services/dto/task-tag'
|
||||
import type { Client } from '~/services/dto/client'
|
||||
import { useTimeEntryService } from '~/services/time-entries'
|
||||
import type { HydraCollection } from '~/utils/api'
|
||||
import { extractHydraMembers } from '~/utils/api'
|
||||
@@ -156,6 +175,8 @@ const entries = ref<TimeEntry[]>([])
|
||||
const users = ref<UserData[]>([])
|
||||
const projects = ref<Project[]>([])
|
||||
const tags = ref<TaskTag[]>([])
|
||||
const clients = ref<Client[]>([])
|
||||
const exportDrawerOpen = ref(false)
|
||||
|
||||
const drawerOpen = ref(false)
|
||||
const editingEntry = ref<TimeEntry | null>(null)
|
||||
@@ -305,38 +326,35 @@ async function onDelete(entry: TimeEntry) {
|
||||
await loadEntries()
|
||||
}
|
||||
|
||||
function getExportDateRange(): { after: string, before: string } {
|
||||
if (Array.isArray(selectedDateFilter.value) && selectedDateFilter.value.length === 2) {
|
||||
return {
|
||||
after: selectedDateFilter.value[0].toISOString().slice(0, 10),
|
||||
before: selectedDateFilter.value[1].toISOString().slice(0, 10),
|
||||
}
|
||||
async function onExport(params: {
|
||||
after: string
|
||||
before: string
|
||||
users?: number[]
|
||||
projects?: number[]
|
||||
client?: number
|
||||
tags?: number[]
|
||||
}) {
|
||||
const toast = useToast()
|
||||
const { t } = useNuxtApp().$i18n as { t: (key: string) => string }
|
||||
|
||||
toast.info({ message: t('timeEntries.exportLoading') })
|
||||
|
||||
try {
|
||||
const result = await timeEntryService.downloadExport(params)
|
||||
|
||||
const url = URL.createObjectURL(result.blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = result.filename
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
|
||||
toast.success({ message: t('timeEntries.exportSuccess') })
|
||||
} catch {
|
||||
toast.error({ message: t('timeEntries.exportError') })
|
||||
}
|
||||
const end = new Date(startDate.value)
|
||||
end.setDate(end.getDate() + (viewMode.value === 'day' ? 1 : 7))
|
||||
return {
|
||||
after: startDate.value.toISOString().slice(0, 10),
|
||||
before: end.toISOString().slice(0, 10),
|
||||
}
|
||||
}
|
||||
|
||||
function exportTimeEntries() {
|
||||
const { after, before } = getExportDateRange()
|
||||
|
||||
const url = timeEntryService.getExportUrl({
|
||||
after,
|
||||
before,
|
||||
user: selectedUserId.value ?? undefined,
|
||||
project: selectedProjectId.value ?? undefined,
|
||||
tags: selectedTagId.value ? [selectedTagId.value] : undefined,
|
||||
})
|
||||
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = ''
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
}
|
||||
|
||||
async function loadEntries() {
|
||||
@@ -353,15 +371,17 @@ async function loadEntries() {
|
||||
async function loadReferenceData() {
|
||||
const api = useApi()
|
||||
|
||||
const [usersData, projectsData, typesData] = await Promise.all([
|
||||
const [usersData, projectsData, typesData, clientsData] = await Promise.all([
|
||||
api.get<HydraCollection<UserData>>('/users'),
|
||||
api.get<HydraCollection<Project>>('/projects'),
|
||||
api.get<HydraCollection<TaskTag>>('/task_tags'),
|
||||
api.get<HydraCollection<Client>>('/clients'),
|
||||
])
|
||||
|
||||
users.value = extractHydraMembers(usersData)
|
||||
projects.value = extractHydraMembers(projectsData)
|
||||
tags.value = extractHydraMembers(typesData)
|
||||
clients.value = extractHydraMembers(clientsData)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
@@ -53,20 +53,42 @@ export function useTimeEntryService() {
|
||||
function getExportUrl(params: {
|
||||
after: string
|
||||
before: string
|
||||
user?: number
|
||||
project?: number
|
||||
users?: number[]
|
||||
projects?: number[]
|
||||
client?: number
|
||||
tags?: number[]
|
||||
}): string {
|
||||
const query = new URLSearchParams()
|
||||
query.set('after', params.after)
|
||||
query.set('before', params.before)
|
||||
if (params.user) query.set('user', String(params.user))
|
||||
if (params.project) query.set('project', String(params.project))
|
||||
if (params.users?.length) {
|
||||
params.users.forEach(id => query.append('users[]', String(id)))
|
||||
}
|
||||
if (params.client) query.set('client', String(params.client))
|
||||
if (params.projects?.length) {
|
||||
params.projects.forEach(id => query.append('projects[]', String(id)))
|
||||
}
|
||||
if (params.tags?.length) {
|
||||
params.tags.forEach(id => query.append('tags[]', String(id)))
|
||||
}
|
||||
return `/api/time_entries/export?${query.toString()}`
|
||||
return `/time_entries/export?${query.toString()}`
|
||||
}
|
||||
|
||||
return { getByDateRange, getActive, create, update, remove, getExportUrl }
|
||||
async function downloadExport(params: {
|
||||
after: string
|
||||
before: string
|
||||
users?: number[]
|
||||
projects?: number[]
|
||||
client?: number
|
||||
tags?: number[]
|
||||
}): Promise<{ blob: Blob; filename: string }> {
|
||||
const url = getExportUrl(params)
|
||||
const response = await api.getBlob(url)
|
||||
const disposition = response.headers.get('content-disposition') ?? ''
|
||||
const filenameMatch = disposition.match(/filename="?([^";\n]+)"?/)
|
||||
const filename = filenameMatch?.[1] ?? `export-temps-${params.after}_${params.before}.xlsx`
|
||||
return { blob: response.data, filename }
|
||||
}
|
||||
|
||||
return { getByDateRange, getActive, create, update, remove, getExportUrl, downloadExport }
|
||||
}
|
||||
|
||||
@@ -19,6 +19,28 @@ export default <Partial<Config>>{
|
||||
},
|
||||
blue: {
|
||||
500: '#056CF2'
|
||||
},
|
||||
m: {
|
||||
primary: 'rgb(var(--m-primary) / <alpha-value>)',
|
||||
secondary: 'rgb(var(--m-secondary, 75 77 237) / <alpha-value>)',
|
||||
tertiary: 'rgb(var(--m-tertiary, 243 244 248) / <alpha-value>)',
|
||||
border: 'rgb(var(--m-border) / <alpha-value>)',
|
||||
text: 'rgb(var(--m-text) / <alpha-value>)',
|
||||
muted: 'rgb(var(--m-muted) / <alpha-value>)',
|
||||
bg: 'rgb(var(--m-bg) / <alpha-value>)',
|
||||
surface: 'rgb(var(--m-surface) / <alpha-value>)',
|
||||
disabled: 'rgb(var(--m-disabled) / <alpha-value>)',
|
||||
danger: 'rgb(var(--m-danger) / <alpha-value>)',
|
||||
success: 'rgb(var(--m-success) / <alpha-value>)',
|
||||
'btn-primary': 'rgb(var(--m-btn-primary) / <alpha-value>)',
|
||||
'btn-primary-hover': 'rgb(var(--m-btn-primary-hover) / <alpha-value>)',
|
||||
'btn-primary-active': 'rgb(var(--m-btn-primary-active) / <alpha-value>)',
|
||||
'btn-secondary': 'rgb(var(--m-btn-secondary) / <alpha-value>)',
|
||||
'btn-secondary-hover': 'rgb(var(--m-btn-secondary-hover) / <alpha-value>)',
|
||||
'btn-secondary-active': 'rgb(var(--m-btn-secondary-active) / <alpha-value>)',
|
||||
'btn-danger': 'rgb(var(--m-btn-danger) / <alpha-value>)',
|
||||
'btn-danger-hover': 'rgb(var(--m-btn-danger-hover) / <alpha-value>)',
|
||||
'btn-danger-active': 'rgb(var(--m-btn-danger-active) / <alpha-value>)',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,27 +47,65 @@ class TimeEntryExportController extends AbstractController
|
||||
throw new BadRequestHttpException('Format de date invalide. Utilisez YYYY-MM-DD.');
|
||||
}
|
||||
|
||||
// Max range: 12 months
|
||||
if ($after->modify('+12 months') < $before) {
|
||||
throw new BadRequestHttpException('La plage de dates ne peut pas dépasser 12 mois.');
|
||||
}
|
||||
|
||||
// Authorization: non-admin users can only export their own data
|
||||
$user = null;
|
||||
// --- Users ---
|
||||
$users = null;
|
||||
if (!$this->security->isGranted('ROLE_ADMIN')) {
|
||||
/** @var User $user */
|
||||
$user = $this->security->getUser();
|
||||
/** @var User $currentUser */
|
||||
$currentUser = $this->security->getUser();
|
||||
$users = [$currentUser];
|
||||
} else {
|
||||
$userId = $request->query->getInt('user');
|
||||
if ($userId > 0) {
|
||||
$user = $this->entityManager->getRepository(User::class)->find($userId);
|
||||
/** @var int[] $userIds */
|
||||
$userIds = array_filter(
|
||||
array_map('intval', (array) $request->query->all('users')),
|
||||
fn (int $id) => $id > 0,
|
||||
);
|
||||
if ([] !== $userIds) {
|
||||
$users = $this->entityManager->getRepository(User::class)->findBy(['id' => $userIds]);
|
||||
}
|
||||
}
|
||||
|
||||
$project = null;
|
||||
$projectId = $request->query->getInt('project');
|
||||
if ($projectId > 0) {
|
||||
$project = $this->entityManager->getRepository(Project::class)->find($projectId);
|
||||
// --- Client (filter projects by client) ---
|
||||
$clientId = $request->query->getInt('client');
|
||||
$clientProjects = null;
|
||||
if ($clientId > 0) {
|
||||
$clientProjects = $this->entityManager->getRepository(Project::class)->findBy(['client' => $clientId]);
|
||||
}
|
||||
|
||||
// --- Projects ---
|
||||
$projects = null;
|
||||
|
||||
/** @var int[] $projectIds */
|
||||
$projectIds = array_filter(
|
||||
array_map('intval', (array) $request->query->all('projects')),
|
||||
fn (int $id) => $id > 0,
|
||||
);
|
||||
if ([] !== $projectIds) {
|
||||
$projects = $this->entityManager->getRepository(Project::class)->findBy(['id' => $projectIds]);
|
||||
}
|
||||
|
||||
// Merge: if both client and projects are set, intersect; if only client, use client projects
|
||||
if (null !== $clientProjects && null !== $projects) {
|
||||
$clientProjectIds = array_map(fn (Project $p) => $p->getId(), $clientProjects);
|
||||
$projects = array_values(array_filter($projects, fn (Project $p) => in_array($p->getId(), $clientProjectIds, true)));
|
||||
if ([] === $projects) {
|
||||
$projects = null;
|
||||
// No matching projects — force empty result by using a dummy condition
|
||||
$entries = [];
|
||||
$tempFile = $this->exportService->generate($entries, $after, $before);
|
||||
$filename = sprintf('export-temps-%s_%s.xlsx', $after->format('Y-m-d'), $before->format('Y-m-d'));
|
||||
$response = new BinaryFileResponse($tempFile);
|
||||
$response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $filename);
|
||||
$response->headers->set('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
||||
$response->deleteFileAfterSend(true);
|
||||
|
||||
return $response;
|
||||
}
|
||||
} elseif (null !== $clientProjects) {
|
||||
$projects = $clientProjects;
|
||||
}
|
||||
|
||||
/** @var int[] $tagIds */
|
||||
@@ -79,8 +117,8 @@ class TimeEntryExportController extends AbstractController
|
||||
$entries = $this->timeEntryRepository->findForExport(
|
||||
$after,
|
||||
$before,
|
||||
$user,
|
||||
$project,
|
||||
$users ?: null,
|
||||
$projects ?: null,
|
||||
$tagIds ?: null,
|
||||
);
|
||||
|
||||
|
||||
@@ -30,15 +30,17 @@ class TimeEntryRepository extends ServiceEntityRepository
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|int[] $tagIds
|
||||
* @param null|User[] $users
|
||||
* @param null|Project[] $projects
|
||||
* @param null|int[] $tagIds
|
||||
*
|
||||
* @return TimeEntry[]
|
||||
*/
|
||||
public function findForExport(
|
||||
DateTimeImmutable $after,
|
||||
DateTimeImmutable $before,
|
||||
?User $user = null,
|
||||
?Project $project = null,
|
||||
?array $users = null,
|
||||
?array $projects = null,
|
||||
?array $tagIds = null,
|
||||
): array {
|
||||
$qb = $this->createQueryBuilder('te')
|
||||
@@ -49,15 +51,15 @@ class TimeEntryRepository extends ServiceEntityRepository
|
||||
->orderBy('te.startedAt', 'ASC')
|
||||
;
|
||||
|
||||
if (null !== $user) {
|
||||
$qb->andWhere('te.user = :user')
|
||||
->setParameter('user', $user)
|
||||
if (null !== $users && [] !== $users) {
|
||||
$qb->andWhere('te.user IN (:users)')
|
||||
->setParameter('users', $users)
|
||||
;
|
||||
}
|
||||
|
||||
if (null !== $project) {
|
||||
$qb->andWhere('te.project = :project')
|
||||
->setParameter('project', $project)
|
||||
if (null !== $projects && [] !== $projects) {
|
||||
$qb->andWhere('te.project IN (:projects)')
|
||||
->setParameter('projects', $projects)
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user