PageSourceSearch

https://smrtbld.ai/js/api/messages.js

js smrtbld.ai collected 2026-10-05 15:08:16 UTC 20,780 bytes, 467 lines download raw bytes

1import { ApiClient } from './base.js';
2
3const api = new ApiClient();
4
5export const messagesApi = {
6    /**
7     * Get paginated list of messages
8     * @param {Object} params - All filter params matching GetMessagesRequest DTO
9     * @returns {Promise<Object>} { messages, hasMore, nextCursor }
10     */
11    // Trailing optional `options` forwarded to the shared ApiClient (see getMessage).
12    // Backward compatible: the four project-messages-panel callers pass params only.
13    getMessages: (params = {}, options = {}) => {
14        const query = new URLSearchParams();
15        if (params.beforeDate) query.append('beforeDate', params.beforeDate);
16        if (params.pageSize) query.append('pageSize', params.pageSize);
17        if (params.projectId) query.append('projectId', params.projectId);
18        if (params.filterType != null) query.append('filterType', params.filterType);
19        if (params.unreadOnly) query.append('unreadOnly', 'true');
20        if (params.followupOnly) query.append('followupOnly', 'true');
21        if (params.attachmentsOnly) query.append('attachmentsOnly', 'true');
22        if (params.deliveryIssuesOnly) query.append('deliveryIssuesOnly', 'true');
23        if (params.categoryIds) query.append('categoryIds', params.categoryIds);
24        if (params.searchTerm) query.append('searchTerm', params.searchTerm);
25
26        const queryString = query.toString();
27        const endpoint = `smrtbuild/messages${queryString ? '?' + queryString : ''}`;
28        return api.get(endpoint, options);
29    },
30
31    /**
32     * Get unread count
33     * @returns {Promise<Object>} { count: 0 }
34     */
35    getUnreadCount: (options = {}) => api.get('smrtbuild/messages/unread-count', options),
36
37    /**
38     * Get the module-dashboard catch-up summary (last N days, default 7).
39     * @param {number} days
40     * @returns {Promise<Object>} { windowDays, windowStart, inboundCount, sentCount,
41     *   handledThreadCount, awaitingThreadCount, repliesToMyThreads, deliveryFailureCount,
42     *   projects: [...], awaiting: [...], correspondents: [...] }
43     */
44    getDashboardSummary: (days = 7, options = {}) => api.get(`smrtbuild/messages/dashboard-summary?days=${days}`, options),
45
46    /**
47     * Get message detail
48     * @param {string|number} id 
49     * @returns {Promise<Object>}
50     */
51    // Trailing optional `options` is forwarded to the shared ApiClient so callers can
52    // supply { signal } for transport cancellation. Backward compatible — every existing
53    // caller that passes only `id` is unaffected.
54    getMessage: (id, options = {}) => api.get(`smrtbuild/messages/${id}`, options),
55
56    /**
57     * Mark as read
58     * @param {string|number} id 
59     * @returns {Promise<void>}
60     */
61    markAsRead: (id) => api.post(`smrtbuild/messages/${id}/mark-read`),
62
63    /**
64     * Toggle followup
65     * @param {string|number} id 
66     * @returns {Promise<void>}
67     */
68    toggleFollowup: (id) => api.post(`smrtbuild/messages/${id}/toggle-followup`),
69
70    /**
71     * Toggle attention (action needed)
72     * @param {string|number} id
73     * @returns {Promise<void>}
74     */
75    toggleAttention: (id) => api.post(`smrtbuild/messages/${id}/toggle-attention`),
76
77    /**
78     * Get attachments for a message
79     * GET /api/smrtbuild/messages/{id}/attachments
80     * @param {string|number} id
81     * @returns {Promise<Array>}
82     */
83    getAttachments: (id) => api.get(`smrtbuild/messages/${id}/attachments`),
84
85    /**
86     * Get replies for a message
87     * GET /api/smrtbuild/messages/{id}/replies
88     * @param {string|number} id
89     * @returns {Promise<Array>}
90     */
91    getReplies: (id) => api.get(`smrtbuild/messages/${id}/replies`),
92
93    /**
94     * Get message with full conversation thread (convenience method)
95     * Fetches message detail, attachments, and replies in parallel
96     * @param {string|number} id
97     * @returns {Promise<Object>} Message with attachments and replies arrays
98     */
99    getMessageWithReplies: async (id) => {
100        const [message, attachments, replies] = await Promise.all([
101            api.get(`smrtbuild/messages/${id}`),
102            api.get(`smrtbuild/messages/${id}/attachments`),
103            api.get(`smrtbuild/messages/${id}/replies`)
104        ]);
105        return {
106            ...message,
107            attachments: attachments || [],
108            replies: replies || []
109        };
110    },
111
112    /**
113     * Get options for new message form (projects, recipients)
114     * GET /api/smrtbuild/messages/add-options
115     * @returns {Promise<Object>}
116     */
117    getAddOptions: (options = {}) => api.get('smrtbuild/messages/add-options', options),
118
119    /**
120     * Get message categories for filtering
121     * GET /api/smrtbuild/messages/categories
122     * @returns {Promise<Array>}
123     */
124    getCategories: () => api.get('smrtbuild/messages/categories'),
125
126    /**
127     * Get contacts for specified projects
128     * GET /api/smrtbuild/messages/add-options/contacts/{projectIds}
129     * @param {string} projectIds - Comma-separated project IDs
130     * @param {Object} [options] - Request options; `{ signal }` cancels the read (CHARTER law 20).
131     * @returns {Promise<Array>}
132     */
133    getContactsForProjects: (projectIds, options) => api.get(`smrtbuild/messages/add-options/contacts/${projectIds}`, options),
134
135    /**
136     * Send a reply to a message
137     * POST /api/smrtbuild/messages/{id}/reply
138     * @param {string|number} id - Original message ID
139     * @param {Object} data - { sendTo, sendCc, subject, bodyText, bodyHtml, tempFileIds, replyAll }
140     * @returns {Promise<Object>}
141     */
142    sendReply: (id, data) => api.post(`smrtbuild/messages/${id}/reply`, data),
143
144    /**
145     * Send a new message
146     * POST /api/smrtbuild/messages
147     * @param {Object} data - { projectId, sendTo, sendCc, subject, bodyText, bodyHtml, tempFileIds, followup, actionNeeded, isDraft }
148     * @returns {Promise<Object>}
149     */
150    sendMessage: (data) => api.post('smrtbuild/messages', data),
151
152    /**
153     * Upload a temp file for message attachments
154     * POST /api/smrtbuild/messages/upload-attachment
155     * @param {File} file - File to upload
156     * @returns {Promise<Object>} { id, fileName }
157     */
158    uploadAttachment: (file) => {
159        const formData = new FormData();
160        formData.append('file', file);
161        return api.postFormData('smrtbuild/messages/upload-attachment', formData);
162    },
163
164    /**
165     * Get task types → tasks (with doc counts) for a project (compose "Add From Tasks" picker)
166     * GET /api/smrtbuild/messages/project-task-documents/{projectId}
167     * @param {number} projectId
168     * @returns {Promise<{ taskTypes: Array }>}
169     */
170    getProjectTaskDocuments: (projectId) =>
171        api.get(`smrtbuild/messages/project-task-documents/${projectId}`),
172
173    /**
174     * Get documents for a single task (lazy-loaded when user drills into a task)
175     * GET /api/smrtbuild/messages/task-documents/{taskId}
176     * @param {number} taskId
177     * @param {{ signal?: AbortSignal }} [options]
178     * @returns {Promise<Array>}
179     */
180    getTaskDocuments: (taskId, options = {}) =>
181        api.get(`smrtbuild/messages/task-documents/${taskId}`, options),
182
183    /**
184     * Copy existing project files (task docs, photos/videos, or project documents) to temp
185     * storage for message attachment — by reference, no re-upload.
186     * POST /api/smrtbuild/messages/copy-to-temp
187     * @param {{ sourceType: 'task-doc'|'photo'|'document', fileIds: number[], projectId: number }} data
188     * @returns {Promise<{ files: Array }>}
189     */
190    copyFilesToTemp: (data) => api.post('smrtbuild/messages/copy-to-temp', data),
191
192    /**
193     * Search messages
194     * POST /api/smrtbuild/messages/search
195     * @param {Object} params - { query, projectId, includeAttachments, fromDate, toDate, page, pageSize }
196     * @param {Object} [options] - Forwarded to the shared ApiClient; carries { signal }.
197     * @returns {Promise<Object>}
198     */
199    // A search is a READ that happens to travel by POST, so it takes the same trailing optional
200    // `options` bag `getMessages` and `getMessage` already take (charter law 20 — a wrapper
201    // discharges cancellation by accepting and forwarding one). `ApiClient.post` spreads it into
202    // the request, so `{ signal }` reaches fetch unchanged. Backward compatible: no existing
203    // caller passes it.
204    searchMessages: (params, options = {}) => api.post('smrtbuild/messages/search', params, options),
205
206    /**
207     * Forward a message thread as PDF
208     * POST /api/smrtbuild/messages/{id}/forward
209     * @param {string|number} id
210     * @returns {Promise<Object>} { uploadCode, filesAdded, success }
211     */
212    forwardMessage: (id) => api.post(`smrtbuild/messages/${id}/forward`, {}),
213
214    /**
215     * Forward selected attachments only (no conversation PDF). Mirrors legacy
216     * `createattachmentforward`. Returns an upload code the compose flow can
217     * attach to a new message.
218     * POST /api/smrtbuild/messages/forward-attachments
219     * @param {number[]} attachmentIds - Selected attachment IDs from the message + replies
220     * @returns {Promise<{ uploadCode: string, filesAdded: string[], success: boolean }>}
221     */
222    forwardAttachments: (attachmentIds) =>
223        api.post('smrtbuild/messages/forward-attachments', { attachmentIds }),
224
225    /**
226     * Export a message thread as a downloadable PDF (no blob upload, no upload code).
227     * Used by the "Print / Export" kebab action.
228     * GET /api/smrtbuild/messages/{id}/export-pdf
229     * @param {string|number} id
230     * @returns {Promise<{ blob: Blob, fileName: string }>}
231     */
232    exportPdf: (id) => api.getBlob(`smrtbuild/messages/${id}/export-pdf`),
233
234    /**
235     * Link (import) an external Outlook or Gmail message into a project's SmartBuild
236     * message center. Used by the unified messages module — when a user browses their
237     * Outlook/Gmail inbox and clicks "Link to Project," this stores the message + its
238     * attachment bytes as a real becc_pm_project_emails row.
239     *
240     * Dedup: if the same externalId already exists for that project, the backend
241     * returns the existing MessageId with alreadyLinked=true — no duplicate insert.
242     *
243     * POST /api/smrtbuild/messages/link-from-external
244     * @param {Object} payload - LinkExternalMessageRequest shape
245     * @returns {Promise<{ messageId, alreadyLinked, attachmentsUploaded, attachmentsAttempted, linkedItemUrl? }>}
246     */
247    linkFromExternal: (payload) => api.post('smrtbuild/messages/link-from-external', payload),
248
249    /**
250     * Respond to an .ics meeting invitation attached to a message (Accept / Decline /
251     * Tentative). Server builds an ICS REPLY and sends it to the organizer via MailGun.
252     * POST /api/smrtbuild/messages/attachments/{attachmentId}/ics-respond
253     *
254     * @param {string|number} attachmentId
255     * @param {{ responseValue: 'ACCEPTED'|'DECLINED'|'TENTATIVE', responseText?: string, projectId?: number }} payload
256     * @returns {Promise<{ replySent: boolean, eventUid?: string, organizer?: string, responseValue: string }>}
257     */
258    icsRespond: (attachmentId, payload) =>
259        api.post(`smrtbuild/messages/attachments/${attachmentId}/ics-respond`, payload),
260
261    /**
262     * Enrichment for an .ics attachment — server-side facts the client parse can't know:
263     * whether it's already on the user's calendar, their current RSVP, whether it's a newer
264     * revision (canUpdate), and whether they can respond. The basic event fields stay client-parsed.
265     * GET /api/smrtbuild/messages/attachments/{attachmentId}/ics-info
266     *
267     * @param {string|number} attachmentId
268     * @returns {Promise<{ uid: string, organizer?: string, eventStatus?: string, isCancelled: boolean,
269     *   sequence: number, calendarEventId?: number, storedSequence?: number, userStatus: string,
270     *   canUpdate: boolean, canRespond: boolean }>}
271     */
272    getIcsInfo: (attachmentId) =>
273        api.get(`smrtbuild/messages/attachments/${attachmentId}/ics-info`),
274
275    /**
276     * Accept an .ics invite AND add it to the user's (or a project's) SmartBuild calendar:
277     * creates the event, invites attendees, persists the RSVP, links it back to the message
278     * (CALENDARTASK), and sends the ACCEPTED reply. Mirrors legacy #icsAddToCalendar.
279     * POST /api/smrtbuild/messages/attachments/{attachmentId}/ics-add-to-calendar
280     *
281     * @param {string|number} attachmentId
282     * @param {{ messageId: number, projectId?: number, title: string, description?: string,
283     *   location?: string, priority?: 'L'|'M'|'H', eventDateStart: string, eventStartTime?: string,
284     *   eventDateEnd?: string, eventEndTime?: string, durationMinutes: number,
285     *   attendeeUserIds?: number[], attendeeContactIds?: number[] }} payload
286     * @returns {Promise<{ calendarEventId: number, calendarEventUrl?: string, linked: boolean, replySent: boolean }>}
287     */
288    addIcsToCalendar: (attachmentId, payload) =>
289        api.post(`smrtbuild/messages/attachments/${attachmentId}/ics-add-to-calendar`, payload),
290
291    /**
292     * Reschedule an already-on-calendar event from an updated invite (newer SEQUENCE).
293     * POST /api/smrtbuild/messages/attachments/{attachmentId}/ics-update
294     *
295     * @param {string|number} attachmentId
296     * @param {{ title: string, eventDateStart: string, eventStartTime?: string,
297     *   eventDateEnd?: string, durationMinutes: number }} payload
298     */
299    updateIcs: (attachmentId, payload) =>
300        api.post(`smrtbuild/messages/attachments/${attachmentId}/ics-update`, payload),
301
302    /**
303     * Cancel (soft-delete) the calendar event for an .ics invite and drop its CALENDARTASK links.
304     * POST /api/smrtbuild/messages/attachments/{attachmentId}/ics-cancel
305     *
306     * @param {string|number} attachmentId
307     */
308    cancelIcs: (attachmentId) =>
309        api.post(`smrtbuild/messages/attachments/${attachmentId}/ics-cancel`, {}),
310
311    /**
312     * Copy a message attachment as a file on a project item — PROGBILL (Progress
313     * Application), SUBMITTALITEM (Submittal Item), or SUPPLIERITEM (Supplier Item).
314     * Mirrors legacy `savefile/{save_type}/{save_id}/{file_id}` for the targets the
315     * new app's per-domain uploadDocument endpoints don't cover. The blob is shared
316     * (no copy) — only a reference row is inserted in the target's _doc table.
317     * POST /api/smrtbuild/messages/attachments/{attachmentId}/copy-to-item
318     *
319     * @param {string|number} attachmentId
320     * @param {{ targetType: 'PROGBILL'|'SUBMITTALITEM'|'SUPPLIERITEM', targetId: number }} payload
321     * @returns {Promise<{ docId: number, targetType: string, title: string }>}
322     */
323    copyAttachmentToItem: (attachmentId, payload) =>
324        api.post(`smrtbuild/messages/attachments/${attachmentId}/copy-to-item`, payload),
325
326    /**
327     * Block an email address
328     * POST /api/smrtbuild/messages/block-email
329     * @param {string} emailAddress
330     * @returns {Promise<void>}
331     */
332    blockEmail: (emailAddress) => api.post('smrtbuild/messages/block-email', { emailAddress }),
333
334    /**
335     * Categorize a message (assign categories)
336     * POST /api/smrtbuild/messages/{id}/categorize
337     * @param {string|number} id
338     * @param {number[]} categoryIds
339     * @returns {Promise<void>}
340     */
341    categorizeMessage: (id, categoryIds) => api.post(`smrtbuild/messages/${id}/categorize`, { categoryIds }),
342
343    /**
344     * Create a new category
345     * POST /api/smrtbuild/messages/categories
346     * @param {string} name
347     * @returns {Promise<Object>}
348     */
349    createCategory: (name) => api.post('smrtbuild/messages/categories', { name }),
350
351    /**
352     * Delete a message category by id (soft-delete server-side).
353     * DELETE /api/smrtbuild/messages/categories/{id}
354     */
355    deleteCategory: (id) => api.delete(`smrtbuild/messages/categories/${id}`),
356
357    /**
358     * Get linked items for a message
359     * GET /api/smrtbuild/messages/{id}/links
360     * @param {string|number} id
361     * @returns {Promise<Array>}
362     */
363    getLinks: (id) => api.get(`smrtbuild/messages/${id}/links`),
364
365    /**
366     * Link a message to a project item
367     * POST /api/smrtbuild/messages/{id}/links
368     * @param {string|number} id
369     * @param {string} linkType - SI, RFI, CHG, SUBMITTAL, TASK, PROGBILL
370     * @param {number} itemId
371     * @returns {Promise<Object>}
372     */
373    createLink: (id, linkType, itemId) => api.post(`smrtbuild/messages/${id}/links`, { linkType, itemId }),
374
375    /**
376     * Remove a linked item
377     * DELETE /api/smrtbuild/messages/{id}/links/{linkId}
378     * @param {string|number} id
379     * @param {string|number} linkId
380     * @returns {Promise<void>}
381     */
382    removeLink: (id, linkId) => api.delete(`smrtbuild/messages/${id}/links/${linkId}`),
383
384    // ── External mailbox (Gmail/Outlook) links — per-user, no message-center import ──
385
386    /** Get the current user's links for an external mailbox message (by provider id). */
387    getExternalLinks: (source, externalId, options = {}) =>
388        api.get(`smrtbuild/messages/external-links?source=${encodeURIComponent(source)}&externalId=${encodeURIComponent(externalId)}`, options),
389
390    /** Link an external mailbox message to a record. payload: {source, externalId, projectId, linkType, itemId}. */
391    createExternalLink: (payload) => api.post('smrtbuild/messages/external-links', payload),
392
393    /** Remove one of the current user's external-message links. */
394    removeExternalLink: (linkId) => api.delete(`smrtbuild/messages/external-links/${linkId}`),
395
396    /**
397     * Render an external (Gmail/Outlook) email to a PDF and file it into the linked record's
398     * Documents (RFI/CHG/SI/Submittal). payload: {source, externalMessageId, subject, from, to,
399     * date, htmlContent, projectId, itemType, itemId}.
400     * POST /api/smrtbuild/messages/external/save-as-pdf
401     */
402    saveExternalEmailAsPdf: (payload) => api.post('smrtbuild/messages/external/save-as-pdf', payload),
403
404    /**
405     * Remove a category from a message
406     * Uses the categorize endpoint to replace all categories (minus the removed one)
407     * @param {string|number} id - Message ID
408     * @param {number} categoryIdToRemove - Category ID to remove
409     * @param {number[]} currentCategoryIds - All current category IDs on the message
410     * @returns {Promise<void>}
411     */
412    removeCategory: (id, categoryIdToRemove, currentCategoryIds) => {
413        const remaining = currentCategoryIds.filter(cid => cid !== categoryIdToRemove);
414        return api.post(`smrtbuild/messages/${id}/categorize`, { categoryIds: remaining });
415    },
416
417    /**
418     * Get download URL for a message attachment
419     * GET /api/smrtbuild/messages/{messageId}/attachments/{attachmentId}/download
420     * @param {string|number} messageId
421     * @param {string|number} attachmentId
422     * @returns {Promise<Object>} { url } or direct blob download
423     */
424    downloadAttachment: (messageId, attachmentId) =>
425        api.get(`smrtbuild/messages/${messageId}/attachments/${attachmentId}/download`),
426
427    /**
428     * Get view URL for a message attachment (blob URL + content type).
429     * Ported from legacy jQueryInlineDoc("ATTACHMENT", attachId).
430     * GET /api/smrtbuild/messages/attachments/{attachmentId}/view
431     * @param {string|number} attachmentId
432     * @returns {Promise<Object>} { url, contentType, fileName }
433     */
434    viewAttachment: (attachmentId) =>
435        api.get(`smrtbuild/messages/attachments/${attachmentId}/view`),
436
437    /**
438     * Get invoice creation options for a message
439     * GET /api/smrtbuild/messages/{id}/invoice-options
440     * @param {string|number} id
441     * @returns {Promise<Object>} { suppliers, teamMembers, costCodes }
442     */
443    getInvoiceOptions: (id) => api.get(`smrtbuild/messages/${id}/invoice-options`),
444
445    /**
446     * Create an invoice from a message
447     * POST /api/smrtbuild/messages/{id}/create-invoice
448     * @param {string|number} id
449     * @param {Object} data - { projectId, supplierId, amount, daysDue, personResponsibleId }
450     * @returns {Promise<Object>}
451     */
452    createInvoice: (id, data) => api.post(`smrtbuild/messages/${id}/create-invoice`, data),
453
454    /**
455     * Get linkable items for the "Link Message To" picker.
456     * GET /api/smrtbuild/messages/linkable-items?projectId={id}&linkType=RFI&search=abc
457     * @param {number} projectId
458     * @param {string} linkType - TASK, RFI, CHG, SI, SUBMITTAL, SUPPLIER, PROGBILL
459     * @param {string} [search]
460     * @returns {Promise<Array>} Array of { id, itemNumber, title, status }
461     */
462    getLinkableItems: (projectId, messageId, linkType, search = '') => {
463        const query = new URLSearchParams({ projectId, messageId, linkType });
464        if (search) query.append('search', search);
465        return api.get(`smrtbuild/messages/linkable-items?${query}`);
466    }
467};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.