ultisuite-client/lib/stores/scheduled-store.ts
2026-05-16 20:30:50 +02:00

244 lines
7.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { create } from "zustand"
import { persist } from "zustand/middleware"
import { debouncedPersistJSONStorage } from "@/lib/stores/debounced-json-storage"
import type { Email } from "@/lib/email-data"
export type ScheduleSendPayload = {
sendAtIso: string
to: { name: string; email: string }[]
subject: string
previewText: string
bodyHtml: string
}
type ScheduledStoreState = {
scheduledEmails: Email[]
snoozedEmails: Email[]
sentPlaceholderEmails: Email[]
}
function rowToSchedulePayload(row: Email): ScheduleSendPayload {
const email = row.senderEmail?.trim() ?? ""
const name = row.scheduledToName ?? row.sender
return {
sendAtIso: row.scheduledSendAt ?? new Date().toISOString(),
to: email ? [{ name, email }] : [],
subject: row.subject,
previewText: row.preview,
bodyHtml: row.body ?? `<p></p>`,
}
}
type ScheduledStoreActions = {
createScheduledSend: (payload: ScheduleSendPayload) => { id: string }
deleteScheduledSend: (id: string) => void
archiveScheduledSend: (id: string) => void
snoozeScheduledSend: (id: string) => void
rescheduleScheduledSend: (id: string, sendAtIso: string) => void
markScheduledReadState: (id: string, read: boolean) => void
getScheduledEditPayload: (id: string) => ScheduleSendPayload | null
updateScheduledSend: (id: string, payload: ScheduleSendPayload) => void
sendScheduledNow: (id: string) => void
removeScheduledLocal: (id: string) => void
/** Mettre en attente depuis la boîte (clone id `snz-…` dans En attente ; lappelant masque lid source). */
snoozeMailboxEmail: (row: Email) => void
/** Quitter « En attente » : réaffiche dans la Boîte (snz-) ou parmi Planifiés (ex-envoi différé snoozé). */
restoreSnoozedToInbox: (row: Email) => void
}
export const useScheduledStore = create<ScheduledStoreState & ScheduledStoreActions>()(
persist(
(set, get) => ({
scheduledEmails: [],
snoozedEmails: [],
sentPlaceholderEmails: [],
createScheduledSend: (payload) => {
const id = `sched-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`
const first = payload.to[0]
const toName = first?.name?.trim() || first?.email || "Destinataire"
const row: Email = {
id,
sender: toName,
senderEmail: first?.email,
subject: payload.subject.trim() || "(Sans objet)",
preview: payload.previewText.slice(0, 200),
body: payload.bodyHtml,
date: payload.sendAtIso,
read: true,
starred: false,
important: false,
labels: ["scheduled"],
scheduledSendAt: payload.sendAtIso,
scheduledToName: toName,
}
set((s) => ({
scheduledEmails: [row, ...s.scheduledEmails.filter((e) => e.id !== id)],
}))
return { id }
},
deleteScheduledSend: (id) =>
set((s) => ({
scheduledEmails: s.scheduledEmails.filter((e) => e.id !== id),
})),
archiveScheduledSend: (id) =>
set((s) => ({
scheduledEmails: s.scheduledEmails.filter((e) => e.id !== id),
})),
snoozeScheduledSend: (id) =>
set((s) => {
const row = s.scheduledEmails.find((e) => e.id === id)
if (!row) return s
const wake = new Date(Date.now() + 24 * 60 * 60 * 1000)
return {
scheduledEmails: s.scheduledEmails.filter((e) => e.id !== id),
snoozedEmails: [
{
...row,
labels: ["snoozed"],
scheduledSendAt: undefined,
scheduledToName: undefined,
snoozeWakeAt: wake.toISOString(),
sender: row.scheduledToName ?? row.sender,
read: true,
},
...s.snoozedEmails,
],
}
}),
rescheduleScheduledSend: (id, sendAtIso) =>
set((s) => ({
scheduledEmails: s.scheduledEmails.map((e) =>
e.id === id ? { ...e, scheduledSendAt: sendAtIso } : e
),
})),
markScheduledReadState: (id, read) =>
set((s) => ({
scheduledEmails: s.scheduledEmails.map((e) =>
e.id === id ? { ...e, read } : e
),
})),
getScheduledEditPayload: (id) => {
const row = get().scheduledEmails.find((e) => e.id === id)
if (!row) return null
return rowToSchedulePayload(row)
},
updateScheduledSend: (id, payload) =>
set((s) => {
const first = payload.to[0]
const toName = first?.name?.trim() || first?.email || "Destinataire"
return {
scheduledEmails: s.scheduledEmails.map((e) =>
e.id === id
? {
...e,
sender: toName,
senderEmail: first?.email,
subject: payload.subject.trim() || "(Sans objet)",
preview: payload.previewText.slice(0, 200),
body: payload.bodyHtml,
scheduledSendAt: payload.sendAtIso,
scheduledToName: toName,
}
: e
),
}
}),
sendScheduledNow: (id) =>
set((s) => {
const row = s.scheduledEmails.find((e) => e.id === id)
if (!row) return s
const now = new Date()
return {
scheduledEmails: s.scheduledEmails.filter((e) => e.id !== id),
sentPlaceholderEmails: [
{
id: `sent-now-${Date.now()}-${Math.random().toString(36).slice(2, 5)}`,
sender: row.scheduledToName ?? row.sender,
senderEmail: row.senderEmail,
subject: row.subject,
preview: row.preview,
body: row.body,
date: now.toISOString(),
read: true,
starred: false,
important: false,
labels: ["sent"],
},
...s.sentPlaceholderEmails,
],
}
}),
removeScheduledLocal: (id) =>
set((s) => ({
scheduledEmails: s.scheduledEmails.filter((e) => e.id !== id),
})),
snoozeMailboxEmail: (row) =>
set((s) => {
const persistedId = row.id.startsWith("snz-") ? row.id : `snz-${row.id}`
const wake = new Date(Date.now() + 24 * 60 * 60 * 1000)
const wakeIso = wake.toISOString()
const copy: Email = {
...row,
id: persistedId,
labels: ["snoozed"],
snoozeWakeAt: wakeIso,
scheduledSendAt: undefined,
scheduledToName: undefined,
read: true,
}
return {
snoozedEmails: [
copy,
...s.snoozedEmails.filter((e) => e.id !== persistedId),
],
}
}),
restoreSnoozedToInbox: (row) =>
set((s) => {
const nextSnoozed = s.snoozedEmails.filter((e) => e.id !== row.id)
if (row.id.startsWith("snz-")) {
return { snoozedEmails: nextSnoozed }
}
const resumeAt =
row.snoozeWakeAt ??
new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString()
const back: Email = {
...row,
labels: ["scheduled"],
scheduledSendAt: resumeAt,
scheduledToName: row.sender,
snoozeWakeAt: undefined,
date: "",
read: true,
}
return {
snoozedEmails: nextSnoozed,
scheduledEmails: [
back,
...s.scheduledEmails.filter((e) => e.id !== row.id),
],
}
}),
}),
{
name: "ultimail-scheduled-state",
storage: debouncedPersistJSONStorage,
version: 1,
}
)
)