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
+122
View File
@@ -0,0 +1,122 @@
// Lecture de la configuration mail (singleton admin)
export type MailConfigurationDto = {
protocol: string | null
imapHost: string | null
imapPort: number | null
imapEncryption: string | null
smtpHost: string | null
smtpPort: number | null
smtpEncryption: string | null
username: string | null
sentFolderPath: string | null
enabled: boolean
hasPassword: boolean
// password JAMAIS présent dans les réponses GET
}
// Input PATCH configuration (password optionnel, write-only)
export type MailConfigurationUpdateDto = {
protocol?: string | null
imapHost?: string | null
imapPort?: number | null
imapEncryption?: string | null
smtpHost?: string | null
smtpPort?: number | null
smtpEncryption?: string | null
username?: string | null
sentFolderPath?: string | null
enabled?: boolean
password?: string // write-only, jamais retourné
}
// Résultat du test de connexion
export type MailTestConnectionResultDto = {
ok: boolean
foldersCount?: number
error?: string
}
// Dossier mail (peut être imbriqué)
export type MailFolderDto = {
path: string
displayName: string
parentPath: string | null
unreadCount: number
totalCount: number
children?: MailFolderDto[]
}
// Adresse mail (nom + email)
export type MailAddressDto = {
name: string | null
email: string
}
// En-tête d'un message (liste)
export type MailMessageHeaderDto = {
id: number
messageId: string // identifiant IMAP unique
folderPath: string
subject: string | null
fromName: string | null
fromEmail: string | null
toRecipients: MailAddressDto[]
ccRecipients: MailAddressDto[]
sentAt: string | null // ISO 8601
receivedAt: string // ISO 8601
isRead: boolean
isFlagged: boolean
hasAttachments: boolean
linkedTaskIds: number[]
}
// Pièce jointe (métadonnées uniquement, téléchargement via downloadId)
export type MailAttachmentDto = {
downloadId: string
filename: string
mimeType: string
size: number // octets
}
// Détail complet d'un message (enrichi avec body + PJ)
export type MailMessageDetailDto = {
header: MailMessageHeaderDto
bodyHtml: string | null // HTML brut — TOUJOURS passer par sanitizeMailHtml() avant affichage
bodyText: string | null // Fallback texte plain
attachments: MailAttachmentDto[]
}
// Page de messages paginée (cursor-based)
export type MailMessagesPageDto = {
items: MailMessageHeaderDto[]
nextCursor: string | null // null = plus de page suivante
total: number
}
// Input : marquer lu/non-lu
export type MailMessageReadInput = {
read: boolean
}
// Input : marquer étoilé/non-étoilé
export type MailMessageFlagInput = {
flagged: boolean
}
// Input : créer une tâche depuis un mail
export type MailCreateTaskInput = {
projectId: number
taskGroupId?: number | null
assigneeId?: number
statusId?: number
}
// Input : lier une tâche existante à un mail
export type MailLinkTaskInput = {
taskId: number
}
// Résultat de la sync manuelle
export type MailSyncResultDto = {
dispatched: boolean
}