/** * Staff Email API Client * * Provides API functions for the platform staff email client. * This is for platform users (superuser, platform_manager, platform_support) * who have been assigned email addresses in staff routing mode. */ import apiClient from './client'; import { StaffEmailFolder, StaffEmail, StaffEmailListItem, StaffEmailLabel, StaffEmailAttachment, StaffEmailFilters, StaffEmailCreateDraft, StaffEmailMove, StaffEmailBulkAction, StaffEmailReply, StaffEmailForward, EmailContactSuggestion, StaffEmailStats, } from '../types'; const BASE_URL = '/staff-email'; // ============================================================================ // Folders // ============================================================================ export const getFolders = async (): Promise => { const response = await apiClient.get(`${BASE_URL}/folders/`); return response.data.map(transformFolder); }; export const createFolder = async (name: string): Promise => { const response = await apiClient.post(`${BASE_URL}/folders/`, { name }); return transformFolder(response.data); }; export const updateFolder = async (id: number, name: string): Promise => { const response = await apiClient.patch(`${BASE_URL}/folders/${id}/`, { name }); return transformFolder(response.data); }; export const deleteFolder = async (id: number): Promise => { await apiClient.delete(`${BASE_URL}/folders/${id}/`); }; // ============================================================================ // Emails (Messages) // ============================================================================ export interface PaginatedEmailResponse { count: number; next: string | null; previous: string | null; results: StaffEmailListItem[]; } export const getEmails = async ( filters?: StaffEmailFilters, page: number = 1, pageSize: number = 50 ): Promise => { const params = new URLSearchParams(); params.append('page', String(page)); params.append('page_size', String(pageSize)); if (filters?.folderId) params.append('folder', String(filters.folderId)); if (filters?.emailAddressId) params.append('email_address', String(filters.emailAddressId)); if (filters?.isRead !== undefined) params.append('is_read', String(filters.isRead)); if (filters?.isStarred !== undefined) params.append('is_starred', String(filters.isStarred)); if (filters?.isImportant !== undefined) params.append('is_important', String(filters.isImportant)); if (filters?.labelId) params.append('label', String(filters.labelId)); if (filters?.search) params.append('search', filters.search); if (filters?.fromDate) params.append('from_date', filters.fromDate); if (filters?.toDate) params.append('to_date', filters.toDate); // Debug logging - remove after fixing folder filter issue console.log('[StaffEmail API] getEmails called with:', { filters, params: params.toString() }); const response = await apiClient.get(`${BASE_URL}/messages/?${params.toString()}`); // Handle both paginated response {count, results, ...} and legacy array response const data = response.data; console.log('[StaffEmail API] Raw response data:', data); if (Array.isArray(data)) { // Legacy format (array of emails) console.log('[StaffEmail API] Response (legacy array):', { count: data.length }); return { count: data.length, next: null, previous: null, results: data.map(transformEmailListItem), }; } // New paginated format const result = { count: data.count ?? 0, next: data.next ?? null, previous: data.previous ?? null, results: (data.results ?? []).map(transformEmailListItem), }; // Debug logging - remove after fixing folder filter issue console.log('[StaffEmail API] Response:', { count: result.count, resultCount: result.results.length }); return result; }; export const getEmail = async (id: number): Promise => { const response = await apiClient.get(`${BASE_URL}/messages/${id}/`); return transformEmail(response.data); }; export const getEmailThread = async (threadId: string): Promise => { const response = await apiClient.get(`${BASE_URL}/messages/`, { params: { thread_id: threadId }, }); return response.data.results.map(transformEmail); }; /** * Convert string email addresses to the format expected by the backend. * Backend expects: [{ email: "test@example.com", name: "" }] * Frontend sends: ["test@example.com"] */ function formatEmailAddresses(addresses: string[]): Array<{ email: string; name: string }> { return addresses.map((addr) => { // Check if it's already in "Name " format const match = addr.match(/^(.+?)\s*<(.+?)>$/); if (match) { return { name: match[1].trim(), email: match[2].trim() }; } return { email: addr.trim(), name: '' }; }); } export const createDraft = async (data: StaffEmailCreateDraft): Promise => { const payload = { email_address: data.emailAddressId, to_addresses: formatEmailAddresses(data.toAddresses), cc_addresses: formatEmailAddresses(data.ccAddresses || []), bcc_addresses: formatEmailAddresses(data.bccAddresses || []), subject: data.subject, body_text: data.bodyText || '', body_html: data.bodyHtml || '', in_reply_to: data.inReplyTo, thread_id: data.threadId, }; const response = await apiClient.post(`${BASE_URL}/messages/`, payload); return transformEmail(response.data); }; export const updateDraft = async (id: number, data: Partial): Promise => { const payload: Record = {}; if (data.toAddresses !== undefined) payload.to_addresses = formatEmailAddresses(data.toAddresses); if (data.ccAddresses !== undefined) payload.cc_addresses = formatEmailAddresses(data.ccAddresses); if (data.bccAddresses !== undefined) payload.bcc_addresses = formatEmailAddresses(data.bccAddresses); if (data.subject !== undefined) payload.subject = data.subject; if (data.bodyText !== undefined) payload.body_text = data.bodyText; if (data.bodyHtml !== undefined) payload.body_html = data.bodyHtml; const response = await apiClient.patch(`${BASE_URL}/messages/${id}/`, payload); return transformEmail(response.data); }; export const deleteDraft = async (id: number): Promise => { await apiClient.delete(`${BASE_URL}/messages/${id}/`); }; export const sendEmail = async (id: number): Promise => { const response = await apiClient.post(`${BASE_URL}/messages/${id}/send/`); return transformEmail(response.data); }; export const replyToEmail = async (id: number, data: StaffEmailReply): Promise => { const payload = { body_text: data.bodyText || '', body_html: data.bodyHtml || '', reply_all: data.replyAll || false, }; const response = await apiClient.post(`${BASE_URL}/messages/${id}/reply/`); return transformEmail(response.data); }; export const forwardEmail = async (id: number, data: StaffEmailForward): Promise => { const payload = { to_addresses: formatEmailAddresses(data.toAddresses), cc_addresses: formatEmailAddresses(data.ccAddresses || []), body_text: data.bodyText || '', body_html: data.bodyHtml || '', }; const response = await apiClient.post(`${BASE_URL}/messages/${id}/forward/`, payload); return transformEmail(response.data); }; export const moveEmails = async (data: StaffEmailMove): Promise => { await apiClient.post(`${BASE_URL}/messages/move/`, { email_ids: data.emailIds, folder_id: data.folderId, }); }; export const markAsRead = async (id: number): Promise => { await apiClient.post(`${BASE_URL}/messages/${id}/mark_read/`); }; export const markAsUnread = async (id: number): Promise => { await apiClient.post(`${BASE_URL}/messages/${id}/mark_unread/`); }; export const starEmail = async (id: number): Promise => { await apiClient.post(`${BASE_URL}/messages/${id}/star/`); }; export const unstarEmail = async (id: number): Promise => { await apiClient.post(`${BASE_URL}/messages/${id}/unstar/`); }; export const archiveEmail = async (id: number): Promise => { await apiClient.post(`${BASE_URL}/messages/${id}/archive/`); }; export const trashEmail = async (id: number): Promise => { await apiClient.post(`${BASE_URL}/messages/${id}/trash/`); }; export const restoreEmail = async (id: number): Promise => { await apiClient.post(`${BASE_URL}/messages/${id}/restore/`); }; export const permanentlyDeleteEmail = async (id: number): Promise => { await apiClient.delete(`${BASE_URL}/messages/${id}/`); }; export const bulkAction = async (data: StaffEmailBulkAction): Promise => { await apiClient.post(`${BASE_URL}/messages/bulk_action/`, { email_ids: data.emailIds, action: data.action, }); }; // ============================================================================ // Labels // ============================================================================ export const getLabels = async (): Promise => { const response = await apiClient.get(`${BASE_URL}/labels/`); return response.data.map(transformLabel); }; export const createLabel = async (name: string, color: string): Promise => { const response = await apiClient.post(`${BASE_URL}/labels/`, { name, color }); return transformLabel(response.data); }; export const updateLabel = async (id: number, data: { name?: string; color?: string }): Promise => { const response = await apiClient.patch(`${BASE_URL}/labels/${id}/`, data); return transformLabel(response.data); }; export const deleteLabel = async (id: number): Promise => { await apiClient.delete(`${BASE_URL}/labels/${id}/`); }; export const addLabelToEmail = async (emailId: number, labelId: number): Promise => { await apiClient.post(`${BASE_URL}/messages/${emailId}/add_label/`, { label_id: labelId }); }; export const removeLabelFromEmail = async (emailId: number, labelId: number): Promise => { await apiClient.post(`${BASE_URL}/messages/${emailId}/remove_label/`, { label_id: labelId }); }; // ============================================================================ // Contacts // ============================================================================ export const searchContacts = async (query: string): Promise => { const response = await apiClient.get(`${BASE_URL}/contacts/`, { params: { search: query }, }); return response.data.map(transformContact); }; // ============================================================================ // Attachments // ============================================================================ export const uploadAttachment = async (file: File, emailId?: number): Promise => { const formData = new FormData(); formData.append('file', file); if (emailId) { formData.append('email_id', String(emailId)); } const response = await apiClient.post(`${BASE_URL}/attachments/`, formData, { headers: { 'Content-Type': 'multipart/form-data' }, }); return transformAttachment(response.data); }; export const deleteAttachment = async (id: number): Promise => { await apiClient.delete(`${BASE_URL}/attachments/${id}/`); }; // ============================================================================ // Sync // ============================================================================ export const syncEmails = async (): Promise<{ success: boolean; message: string }> => { const response = await apiClient.post(`${BASE_URL}/messages/sync/`); return response.data; }; export interface FullSyncTask { email_address: string; task_id: string; } export interface FullSyncResponse { status: string; tasks: FullSyncTask[]; } export const fullSyncEmails = async (): Promise => { const response = await apiClient.post(`${BASE_URL}/messages/full_sync/`); return response.data; }; // ============================================================================ // User's Email Addresses // ============================================================================ export interface UserEmailAddress { id: number; email_address: string; display_name: string; color: string; is_default: boolean; last_check_at: string | null; emails_processed_count: number; } export const getUserEmailAddresses = async (): Promise => { const response = await apiClient.get(`${BASE_URL}/messages/email_addresses/`); return response.data; }; // ============================================================================ // Transform Functions (snake_case -> camelCase) // ============================================================================ function transformFolder(data: any): StaffEmailFolder { return { id: data.id, owner: data.owner, name: data.name, folderType: data.folder_type, emailCount: data.email_count || 0, unreadCount: data.unread_count || 0, createdAt: data.created_at, updatedAt: data.updated_at, }; } function transformEmailListItem(data: any): StaffEmailListItem { return { id: data.id, folder: data.folder, fromAddress: data.from_address, fromName: data.from_name || '', toAddresses: data.to_addresses || [], subject: data.subject || '(No Subject)', snippet: data.snippet || '', status: data.status, isRead: data.is_read, isStarred: data.is_starred, isImportant: data.is_important, hasAttachments: data.has_attachments || false, attachmentCount: data.attachment_count || 0, threadId: data.thread_id, emailDate: data.email_date, createdAt: data.created_at, labels: (data.labels || []).map(transformLabel), }; } function transformEmail(data: any): StaffEmail { return { ...transformEmailListItem(data), owner: data.owner, emailAddress: data.email_address, messageId: data.message_id || '', inReplyTo: data.in_reply_to, references: data.references || '', ccAddresses: data.cc_addresses || [], bccAddresses: data.bcc_addresses || [], bodyText: data.body_text || '', bodyHtml: data.body_html || '', isAnswered: data.is_answered || false, isPermanentlyDeleted: data.is_permanently_deleted || false, deletedAt: data.deleted_at, attachments: (data.attachments || []).map(transformAttachment), updatedAt: data.updated_at, }; } function transformLabel(data: any): StaffEmailLabel { return { id: data.id, owner: data.owner, name: data.name, color: data.color || '#3b82f6', createdAt: data.created_at, }; } function transformAttachment(data: any): StaffEmailAttachment { return { id: data.id, filename: data.filename, contentType: data.content_type, size: data.size, url: data.url || data.file_url || '', createdAt: data.created_at, }; } function transformContact(data: any): EmailContactSuggestion { return { id: data.id, owner: data.owner, email: data.email, name: data.name || '', useCount: data.use_count || 0, lastUsedAt: data.last_used_at, }; }