feat: watcher/CC system, SLA engine, and rich text comments
- Watcher system: ticket_watchers table, watch/unwatch endpoints, notifications to watchers on comments and updates, watcher/cc recipient sources in SendEmail scrip action, watch toggle and watcher avatars in ticket detail UI - SLA engine: sla_policies table, SLA deadline columns on tickets, CRUD routes, OnSlaBreach scrip condition, scheduler SLA calculation, deadlines set on create/reply, cleared on resolve, SLA indicators on ticket list and detail, SLA Policies tab in admin - Rich text: marked-based markdown rendering with XSS safety, Write/Preview toggle in comment composer, styled prose output
This commit is contained in:
@@ -21,6 +21,8 @@ import type {
|
||||
AttachmentUploadResult,
|
||||
TicketLink,
|
||||
LoginResult,
|
||||
Watcher,
|
||||
SlaPolicy,
|
||||
} from "./types";
|
||||
|
||||
const BASE_URL = "/api";
|
||||
@@ -181,6 +183,52 @@ export async function revokeApiToken(id: string): Promise<{ data: { ok: boolean
|
||||
return request<{ ok: boolean }>(`/auth/tokens/${id}`, { method: "DELETE" });
|
||||
}
|
||||
|
||||
// Watchers
|
||||
|
||||
export async function getWatchers(ticketId: number): Promise<{ data: Watcher[] | null; error: string | null }> {
|
||||
return request<Watcher[]>(`/tickets/${ticketId}/watchers`);
|
||||
}
|
||||
|
||||
export async function addWatcher(ticketId: number, userId?: string): Promise<{ data: Watcher | null; error: string | null }> {
|
||||
return request<Watcher>(`/tickets/${ticketId}/watchers`, { method: "POST", body: JSON.stringify(userId ? { user_id: userId } : {}) });
|
||||
}
|
||||
|
||||
export async function removeWatcher(ticketId: number, userId: string): Promise<{ data: { ok: boolean } | null; error: string | null }> {
|
||||
return request<{ ok: boolean }>(`/tickets/${ticketId}/watchers/${userId}`, { method: "DELETE" });
|
||||
}
|
||||
|
||||
// SLA Policies
|
||||
|
||||
export async function getSlaPolicies(): Promise<{ data: SlaPolicy[] | null; error: string | null }> {
|
||||
return request<SlaPolicy[]>("/sla-policies");
|
||||
}
|
||||
|
||||
export async function createSlaPolicy(data: {
|
||||
name: string;
|
||||
queue_id?: string;
|
||||
description?: string;
|
||||
response_time_minutes?: number;
|
||||
resolution_time_minutes?: number;
|
||||
disabled?: boolean;
|
||||
}): Promise<{ data: SlaPolicy | null; error: string | null }> {
|
||||
return request<SlaPolicy>("/sla-policies", { method: "POST", body: JSON.stringify(data) });
|
||||
}
|
||||
|
||||
export async function updateSlaPolicy(id: string, data: {
|
||||
name?: string;
|
||||
queue_id?: string | null;
|
||||
description?: string;
|
||||
response_time_minutes?: number | null;
|
||||
resolution_time_minutes?: number | null;
|
||||
disabled?: boolean;
|
||||
}): Promise<{ data: SlaPolicy | null; error: string | null }> {
|
||||
return request<SlaPolicy>(`/sla-policies/${id}`, { method: "PATCH", body: JSON.stringify(data) });
|
||||
}
|
||||
|
||||
export async function deleteSlaPolicy(id: string): Promise<{ data: { ok: boolean } | null; error: string | null }> {
|
||||
return request<{ ok: boolean }>(`/sla-policies/${id}`, { method: "DELETE" });
|
||||
}
|
||||
|
||||
export async function getQueues(): Promise<{ data: Queue[] | null; error: string | null }> {
|
||||
return request<Queue[]>("/queues");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user