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:
Gjermund Høsøien Wiggen
2026-06-15 21:40:18 +02:00
parent 9679734e3f
commit 653139ad0d
18 changed files with 1025 additions and 26 deletions

View File

@@ -1094,6 +1094,23 @@ function TicketWorkbenchContent() {
style={{ backgroundColor: STATUS_META[ticket.status]?.color ?? "#71717a" }}
title={statusLabel(ticket.status)}
/>
{(ticket as any).sla_breached && (
<span
className="h-2 w-2 rounded-full shrink-0 bg-red-500"
title={`SLA breached: ${(ticket as any).sla_breached}`}
/>
)}
{!(ticket as any).sla_breached && (ticket as any).sla_resolution_deadline && (
<span
className={cn(
"h-2 w-2 rounded-full shrink-0",
new Date((ticket as any).sla_resolution_deadline) < new Date(Date.now() + 60 * 60 * 1000)
? "bg-amber-500"
: "bg-emerald-500"
)}
title={`SLA due ${formatDistanceToNow(new Date((ticket as any).sla_resolution_deadline), { addSuffix: true })}`}
/>
)}
</div>
{row1Fields.map((col) => {
const cellStyle = {