ultisuite-client/lib/agenda/use-agenda-invitation-suggestions.ts
R3D347HR4Y ad1370ea7e
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
feat: enhance configuration and add new demo layouts
- Introduced turbopack alias for canvas in next.config.mjs.
- Updated package.json scripts for development and branding tasks.
- Added new dependencies for Tiptap extensions.
- Implemented new demo layouts for agenda, contacts, drive, and mail applications.
- Enhanced globals.css for improved theming and splash screen animations.
- Added OAuth callback handling for drive mounts.
- Updated layout components to integrate new demo shells and improve structure.
2026-06-12 19:10:24 +02:00

246 lines
8.0 KiB
TypeScript

"use client"
import { useMemo } from "react"
import { useLabels } from "@/lib/api/hooks/use-folder-label-queries"
import { useUnifiedFolders } from "@/lib/api/hooks/use-unified-folder-queries"
import { useImapFolders } from "@/lib/api/hooks/use-imap-folders"
import { useMailAccounts } from "@/lib/api/hooks/use-mail-queries"
import { useContactsList } from "@/lib/contacts/use-contacts-list"
import { fullContactDisplayName } from "@/lib/contacts/types"
import type { AgendaInvitationExclusion } from "@/lib/agenda/agenda-settings-types"
import { exclusionKey } from "@/lib/agenda/agenda-settings-types"
import {
buildDestinationMailboxes,
destinationEmailFromStoredKey,
} from "@/lib/agenda/agenda-destination-identities"
import { identityKey } from "@/lib/agenda/agenda-store"
import { useComposeIdentitiesStore } from "@/lib/stores/compose-identities-store"
export type AgendaImportSuggestion = AgendaInvitationExclusion & {
id: string
group: string
}
const MAILBOX_SUGGESTIONS: AgendaInvitationExclusion[] = [
{ type: "mailbox", value: "spam", label: "Spam" },
{ type: "mailbox", value: "trash", label: "Corbeille" },
{ type: "mailbox", value: "promotions", label: "Promotions" },
{ type: "mailbox", value: "social", label: "Réseaux sociaux" },
{ type: "mailbox", value: "updates", label: "Notifications" },
]
function matchesQuery(text: string, query: string): boolean {
return text.toLowerCase().includes(query.toLowerCase())
}
export function useAgendaDestinationMailboxes() {
const identities = useComposeIdentitiesStore((s) => s.identities)
const { data: accounts = [] } = useMailAccounts()
return useMemo(
() => buildDestinationMailboxes(identities, accounts.map((a) => a.email)),
[identities, accounts],
)
}
export function useAgendaInvitationSuggestions(
query: string,
taken: Set<string>,
opts?: { types?: AgendaInvitationExclusion["type"][] },
) {
const { contacts } = useContactsList()
const identities = useComposeIdentitiesStore((s) => s.identities)
const { data: accounts = [] } = useMailAccounts()
const destinationMailboxes = useMemo(
() => buildDestinationMailboxes(identities, accounts.map((a) => a.email)),
[identities, accounts],
)
const { data: labels = [] } = useLabels()
const { data: unifiedFolders = [] } = useUnifiedFolders("all")
const { folders: imapFolders = [] } = useImapFolders()
return useMemo(() => {
const q = query.trim()
const allowed = opts?.types
const includeType = (type: AgendaInvitationExclusion["type"]) =>
!allowed || allowed.includes(type)
const out: AgendaImportSuggestion[] = []
const seenSuggestionIds = new Set<string>()
const push = (suggestion: AgendaImportSuggestion) => {
if (seenSuggestionIds.has(suggestion.id)) return
seenSuggestionIds.add(suggestion.id)
out.push(suggestion)
}
if (includeType("not_in_contacts")) {
const allRule: AgendaInvitationExclusion = {
type: "not_in_contacts",
value: "",
label: "Toute personne pas dans les contacts",
}
const allId = exclusionKey(allRule)
if (!taken.has(allId) && (!q || matchesQuery(allRule.label, q))) {
push({ ...allRule, id: allId, group: "Règles contacts" })
}
const seenIdentityKeys = new Set<string>()
for (const identity of identities) {
const key = identityKey(identity.email, identity.accountId)
if (seenIdentityKeys.has(key)) continue
seenIdentityKeys.add(key)
const rule: AgendaInvitationExclusion = {
type: "not_in_contacts",
value: key,
label: `Personnes pas dans les contacts de ${identity.name || identity.email}`,
}
const id = exclusionKey(rule)
if (taken.has(id)) continue
if (q && !matchesQuery(rule.label, q) && !matchesQuery(identity.email, q)) continue
push({ ...rule, id, group: "Règles contacts" })
}
}
if (includeType("identity")) {
for (const mailbox of destinationMailboxes) {
const rule: AgendaInvitationExclusion = {
type: "identity",
value: mailbox.key,
label: mailbox.label,
}
const id = exclusionKey(rule)
if (taken.has(id)) continue
if (q && !matchesQuery(mailbox.label, q) && !matchesQuery(mailbox.key, q)) continue
push({ ...rule, id, group: "Mails de destination" })
}
}
if (includeType("contact")) {
for (const contact of contacts) {
const name = fullContactDisplayName(contact)
for (const { value: email } of contact.emails) {
if (!email) continue
const rule: AgendaInvitationExclusion = {
type: "contact",
value: email,
label: name ? `${name} <${email}>` : email,
}
const id = exclusionKey(rule)
if (taken.has(id)) continue
if (q && !matchesQuery(rule.label, q) && !matchesQuery(email, q)) continue
push({ ...rule, id, group: "Contacts" })
if (out.length > 40) break
}
}
}
if (includeType("email") && q.includes("@")) {
const rule: AgendaInvitationExclusion = {
type: "email",
value: q,
label: q,
}
const id = exclusionKey(rule)
if (!taken.has(id)) push({ ...rule, id, group: "Adresses" })
}
if (includeType("label")) {
for (const label of labels) {
const rule: AgendaInvitationExclusion = {
type: "label",
value: label.name,
label: `Libellé · ${label.name}`,
}
const id = exclusionKey(rule)
if (taken.has(id)) continue
if (q && !matchesQuery(label.name, q)) continue
push({ ...rule, id, group: "Libellés" })
}
}
if (includeType("folder")) {
const folders = [
...unifiedFolders.map((f) => ({
value: f.id,
label: `Dossier · ${f.name}`,
})),
...imapFolders.map((f) => ({
value: f.name,
label: `Dossier · ${f.name}`,
})),
]
for (const folder of folders) {
const rule: AgendaInvitationExclusion = {
type: "folder",
value: folder.value,
label: folder.label,
}
const id = exclusionKey(rule)
if (taken.has(id)) continue
if (q && !matchesQuery(folder.label, q)) continue
push({ ...rule, id, group: "Dossiers" })
}
}
if (includeType("mailbox")) {
for (const mailbox of MAILBOX_SUGGESTIONS) {
const id = exclusionKey(mailbox)
if (taken.has(id)) continue
if (q && !matchesQuery(mailbox.label, q)) continue
push({ ...mailbox, id, group: "Boîtes mail" })
}
}
return out.slice(0, 12)
}, [
contacts,
identities,
destinationMailboxes,
labels,
unifiedFolders,
imapFolders,
query,
taken,
opts?.types,
])
}
export function identityLabelFromKey(key: string): string {
const identities = useComposeIdentitiesStore.getState().identities
const found = identities.find((i) => identityKey(i.email, i.accountId) === key)
if (!found) return key
return `${found.name} <${found.email}>`
}
export function useAgendaSettingsIdentityOptions() {
const identities = useComposeIdentitiesStore((s) => s.identities)
return useMemo(() => {
const seen = new Set<string>()
const out: { key: string; label: string }[] = []
for (const identity of identities) {
const key = identityKey(identity.email, identity.accountId)
if (seen.has(key)) continue
seen.add(key)
out.push({
key,
label: `${identity.name} <${identity.email}>`,
})
}
return out
}, [identities])
}
export function useAgendaSettingsDestinationOptions() {
return useAgendaDestinationMailboxes()
}
/** Libellé d'une clé stockée (legacy identité ou email de destination). */
export function destinationLabelFromStoredKey(
key: string,
mailboxes: ReturnType<typeof buildDestinationMailboxes>,
): string {
return (
mailboxes.find((m) => m.key === destinationEmailFromStoredKey(key))?.label ??
destinationEmailFromStoredKey(key)
)
}