feat(mail) : extract Mail front into Nuxt module layer

LST-67 (2.5) front. Completes the Mail module.

- New frontend/modules/mail/ layer (auto-detected): /mail page (3 columns),
  7 components, mail service + DTO, mail store (folders/messages/unread polling).
- sanitizeMailHtml util and useSystemFolderLabel composable stay global;
  AdminMailTab stays in /admin (service import repointed).
- Consumers repointed: AdminMailTab and PM TaskModal -> ~/modules/mail/...;
  the store is auto-imported (Pinia storesDirs) so the layout badge/polling is
  unchanged.
- /mail gated by the mail module: sidebar.php item with module=mail (so
  SidebarFilter disables /mail when the module is off); the layout filters /mail
  from the API sections to avoid a visual duplicate. ROLE_CLIENT exclusion kept.
- i18n key sidebar.general.mail added.

nuxt build passes; /mail and all other routes preserved.
This commit is contained in:
Matthieu
2026-06-20 19:52:13 +02:00
parent 25d3a693f9
commit bb7d7e7953
17 changed files with 34 additions and 22 deletions
+6 -1
View File
@@ -10,8 +10,11 @@ declare(strict_types=1);
* - `permission` : section ou item masqué si la permission effective absente (RBAC fin —
* `User::getEffectivePermissions()` ; ROLE_ADMIN bypasse via le voter, mais la
* sidebar évalue les permissions effectives réelles — combiner avec `roles` au besoin).
* Les items contextuels (Kanban/Groupes/Archives), feature-flag (Documents, Mail) et user-flag
* Les items contextuels (Kanban/Groupes/Archives), feature-flag (Documents) et user-flag
* (Mes absences) restent rendus côté layout, hors de cet endpoint.
* Mail est déclaré ici UNIQUEMENT pour le gating module (disabledRoutes si module inactif) ;
* son rendu visuel + badge non-lus reste géré côté layout, qui filtre `/mail` de translatedSections
* pour éviter le doublon.
* Les labels sont des clés i18n (sidebar.<domaine>.<item>).
*/
return [
@@ -23,6 +26,8 @@ return [
['label' => 'sidebar.general.myTasks', 'to' => '/my-tasks', 'icon' => 'mdi:clipboard-check-outline', 'module' => 'project-management'],
['label' => 'sidebar.general.projects', 'to' => '/projects', 'icon' => 'mdi:folder-outline', 'module' => 'project-management'],
['label' => 'sidebar.general.timeTracking', 'to' => '/time-tracking', 'icon' => 'mdi:calendar-edit-outline', 'module' => 'time-tracking'],
// Gating module uniquement (cf. en-tête) : rendu visuel + badge gérés côté layout.
['label' => 'sidebar.general.mail', 'to' => '/mail', 'icon' => 'mdi:email-outline', 'module' => 'mail'],
],
],
[
+10 -5
View File
@@ -139,15 +139,20 @@ const route = useRoute()
const { t } = useI18n()
const { sections } = useSidebar()
// `/mail` est déclaré dans config/sidebar.php pour le gating module (disabledRoutes),
// mais son rendu visuel + badge non-lus est géré manuellement ci-dessous (feature-flag Mail).
// On le filtre des sections dynamiques pour éviter un doublon dans la nav.
const translatedSections = computed(() =>
sections.value.map((section) => ({
label: t(section.label),
icon: section.icon,
items: section.items.map((item) => ({
label: t(item.label),
to: item.to,
icon: item.icon,
})),
items: section.items
.filter((item) => item.to !== '/mail')
.map((item) => ({
label: t(item.label),
to: item.to,
icon: item.icon,
})),
})),
)
+1 -1
View File
@@ -140,7 +140,7 @@
</template>
<script setup lang="ts">
import { useMailService } from '~/services/mail'
import { useMailService } from '~/modules/mail/services/mail'
const { getConfiguration, updateConfiguration, testConfiguration } = useMailService()
+2 -1
View File
@@ -353,7 +353,8 @@
"dashboard": "Tableau de bord",
"myTasks": "Mes tâches",
"projects": "Projets",
"timeTracking": "Suivi de temps"
"timeTracking": "Suivi de temps",
"mail": "Messagerie"
},
"admin": {
"section": "Administration",
@@ -1,10 +1,10 @@
<script setup lang="ts">
import type { MailMessageDetailDto } from '~/services/dto/mail'
import type { MailMessageDetailDto } from '~/modules/mail/services/dto/mail'
import type { Task } from '~/modules/project-management/services/dto/task'
import type { Project } from '~/modules/project-management/services/dto/project'
import type { TaskGroup } from '~/modules/project-management/services/dto/task-group'
import type { UserData } from '~/services/dto/user-data'
import { useMailService } from '~/services/mail'
import { useMailService } from '~/modules/mail/services/mail'
import { useProjectService } from '~/modules/project-management/services/projects'
import { useTaskGroupService } from '~/modules/project-management/services/task-groups'
import { useUserService } from '~/services/users'
@@ -1,5 +1,5 @@
<script setup lang="ts">
import type { MailFolderDto } from '~/services/dto/mail'
import type { MailFolderDto } from '~/modules/mail/services/dto/mail'
const props = defineProps<{
/** Arbre de dossiers (getter folderTree du store) */
@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { Task } from '~/modules/project-management/services/dto/task'
import type { Project } from '~/modules/project-management/services/dto/project'
import { useMailService } from '~/services/mail'
import { useMailService } from '~/modules/mail/services/mail'
import { useTaskService } from '~/modules/project-management/services/tasks'
import { useProjectService } from '~/modules/project-management/services/projects'
@@ -1,5 +1,5 @@
<script setup lang="ts">
import type { MailMessageHeaderDto } from '~/services/dto/mail'
import type { MailMessageHeaderDto } from '~/modules/mail/services/dto/mail'
const props = defineProps<{
messages: readonly MailMessageHeaderDto[]
@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { MailMessageDetailDto, MailAddressDto, MailAttachmentDto } from '~/services/dto/mail'
import type { MailMessageDetailDto, MailAddressDto, MailAttachmentDto } from '~/modules/mail/services/dto/mail'
import { sanitizeMailHtml } from '~/utils/sanitizeMailHtml'
import { useMailService } from '~/services/mail'
import { useMailService } from '~/modules/mail/services/mail'
const props = defineProps<{
/** Détail complet du message. null = aucun message sélectionné. */
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { useMailStore } from '~/stores/mail'
import { useMailStore } from '~/modules/mail/stores/mail'
const store = useMailStore()
const { syncing } = storeToRefs(store)
+1
View File
@@ -0,0 +1 @@
export default defineNuxtConfig({})
@@ -1,6 +1,6 @@
<script setup lang="ts">
import type { Task } from '~/modules/project-management/services/dto/task'
import { useMailStore } from '~/stores/mail'
import { useMailStore } from '~/modules/mail/stores/mail'
const { t } = useI18n()
const router = useRouter()
@@ -11,7 +11,7 @@ import type {
MailCreateTaskInput,
MailLinkTaskInput,
MailSyncResultDto,
} from './dto/mail'
} from '~/modules/mail/services/dto/mail'
import type { Task } from '~/modules/project-management/services/dto/task'
type BackendMailMessage = {
@@ -3,8 +3,8 @@ import type {
MailFolderDto,
MailMessageHeaderDto,
MailMessageDetailDto,
} from '~/services/dto/mail'
import { useMailService } from '~/services/mail'
} from '~/modules/mail/services/dto/mail'
import { useMailService } from '~/modules/mail/services/mail'
const POLL_INTERVAL_MS = 30 * 1000 // 30 secondes
@@ -551,8 +551,8 @@ import { useTaskService } from '~/modules/project-management/services/tasks'
import { useTaskRecurrenceService } from '~/modules/project-management/services/task-recurrences'
import type { Project } from '~/modules/project-management/services/dto/project'
import { useMailService } from '~/services/mail'
import type { MailMessageHeaderDto } from '~/services/dto/mail'
import { useMailService } from '~/modules/mail/services/mail'
import type { MailMessageHeaderDto } from '~/modules/mail/services/dto/mail'
const props = defineProps<{
modelValue: boolean