@consilioweb/payload-support 0.10.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/TicketConversation/locales/en.json +25 -1
- package/dist/components/TicketConversation/locales/fr.json +25 -1
- package/dist/index.cjs +799 -4
- package/dist/index.d.cts +15 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.js +800 -6
- package/dist/styles/TicketDetail.module.scss +899 -353
- package/dist/views/TicketDetailView/client.cjs +504 -282
- package/dist/views/TicketDetailView/client.js +505 -283
- package/package.json +1 -1
- package/src/collections/ClientSummaries.ts +16 -0
- package/src/collections/TicketCollaborators.ts +93 -0
- package/src/collections/TicketFeedback.ts +116 -0
- package/src/collections/TicketMessages.ts +9 -0
- package/src/collections/Tickets.ts +31 -1
- package/src/collections/index.ts +2 -0
- package/src/components/TicketConversation/locales/en.json +25 -1
- package/src/components/TicketConversation/locales/fr.json +25 -1
- package/src/endpoints/escalate.ts +44 -0
- package/src/endpoints/index.ts +15 -0
- package/src/endpoints/invite-collaborator.ts +215 -0
- package/src/endpoints/kb-search.ts +156 -0
- package/src/endpoints/ticket-feedback.ts +104 -0
- package/src/endpoints/transfer-ticket.ts +248 -0
- package/src/index.ts +1 -0
- package/src/plugin.ts +4 -0
- package/src/portal/auth/ChatWidget.tsx +11 -0
- package/src/portal/auth/dashboard/DashboardClient.tsx +85 -99
- package/src/portal/auth/dashboard/page.tsx +11 -2
- package/src/portal/auth/tickets/detail/TransferAndInviteActions.tsx +402 -0
- package/src/portal/auth/tickets/detail/page.tsx +122 -23
- package/src/portal/auth/tickets/new/KbDeflection.tsx +128 -0
- package/src/portal/auth/tickets/new/page.tsx +203 -100
- package/src/portal/locales/en.json +5 -0
- package/src/portal/locales/fr.json +5 -0
- package/src/styles/TicketDetail.module.scss +899 -353
- package/src/types.ts +19 -0
- package/src/utils/slugs.ts +2 -0
- package/src/views/TicketDetailView/client.tsx +404 -121
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@consilioweb/payload-support",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Payload CMS plugin — professional support & ticketing system with AI, SLA, time tracking, live chat, and more",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -59,6 +59,22 @@ export function createClientSummariesCollection(slugs: CollectionSlugs): Collect
|
|
|
59
59
|
admin: { readOnly: true },
|
|
60
60
|
// Array of strings: "Hébergé chez OVH", "Site WordPress", etc.
|
|
61
61
|
},
|
|
62
|
+
{
|
|
63
|
+
name: 'nextActions',
|
|
64
|
+
type: 'array',
|
|
65
|
+
label: 'Prochaines actions suggérées',
|
|
66
|
+
admin: { description: 'Liste des actions suggérées par l\'IA' },
|
|
67
|
+
fields: [
|
|
68
|
+
{ name: 'label', type: 'text', required: true },
|
|
69
|
+
{
|
|
70
|
+
name: 'priority',
|
|
71
|
+
type: 'select',
|
|
72
|
+
options: ['now', 'today', 'this-week'],
|
|
73
|
+
defaultValue: 'today',
|
|
74
|
+
},
|
|
75
|
+
{ name: 'done', type: 'checkbox', defaultValue: false },
|
|
76
|
+
],
|
|
77
|
+
},
|
|
62
78
|
// ── Stats ──
|
|
63
79
|
{
|
|
64
80
|
name: 'ticketCount',
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { CollectionConfig } from 'payload'
|
|
2
|
+
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Collaborators (viewers/contributors) added to a ticket by the primary client owner.
|
|
6
|
+
*
|
|
7
|
+
* Created via the `/api/support/tickets/:id/invite` endpoint. Each row links a
|
|
8
|
+
* ticket to a `support-clients` (the invitee) with a role: `viewer` (read-only)
|
|
9
|
+
* or `collaborator` (can post messages, when wired up later).
|
|
10
|
+
*
|
|
11
|
+
* Access logic on Tickets/TicketMessages should OR this table to grant access
|
|
12
|
+
* to invited clients without losing existing ownership-based rules.
|
|
13
|
+
*/
|
|
14
|
+
export function createTicketCollaboratorsCollection(slugs: CollectionSlugs): CollectionConfig {
|
|
15
|
+
return {
|
|
16
|
+
slug: 'ticket-collaborators',
|
|
17
|
+
labels: {
|
|
18
|
+
singular: 'Collaborateur ticket',
|
|
19
|
+
plural: 'Collaborateurs tickets',
|
|
20
|
+
},
|
|
21
|
+
admin: {
|
|
22
|
+
useAsTitle: 'email',
|
|
23
|
+
group: 'Support',
|
|
24
|
+
defaultColumns: ['email', 'ticket', 'client', 'role', 'invitedBy', 'createdAt'],
|
|
25
|
+
},
|
|
26
|
+
access: {
|
|
27
|
+
// Admins always; clients only see rows that reference them.
|
|
28
|
+
read: ({ req }) => {
|
|
29
|
+
if (req.user?.collection === slugs.users) return true
|
|
30
|
+
if (req.user?.collection === slugs.supportClients) {
|
|
31
|
+
return { client: { equals: req.user.id } }
|
|
32
|
+
}
|
|
33
|
+
return false
|
|
34
|
+
},
|
|
35
|
+
create: ({ req }) => !!req.user,
|
|
36
|
+
update: () => false,
|
|
37
|
+
delete: ({ req }) => req.user?.collection === slugs.users,
|
|
38
|
+
},
|
|
39
|
+
fields: [
|
|
40
|
+
{
|
|
41
|
+
name: 'ticket',
|
|
42
|
+
type: 'relationship',
|
|
43
|
+
relationTo: slugs.tickets,
|
|
44
|
+
required: true,
|
|
45
|
+
hasMany: false,
|
|
46
|
+
label: 'Ticket',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'client',
|
|
50
|
+
type: 'relationship',
|
|
51
|
+
relationTo: slugs.supportClients,
|
|
52
|
+
required: true,
|
|
53
|
+
label: 'Client invité',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'email',
|
|
57
|
+
type: 'email',
|
|
58
|
+
index: true,
|
|
59
|
+
label: 'Email invité',
|
|
60
|
+
admin: { description: 'Cache de l\'email du client invité au moment de l\'invitation' },
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'role',
|
|
64
|
+
type: 'select',
|
|
65
|
+
defaultValue: 'viewer',
|
|
66
|
+
options: [
|
|
67
|
+
{ label: 'Lecteur', value: 'viewer' },
|
|
68
|
+
{ label: 'Collaborateur', value: 'collaborator' },
|
|
69
|
+
],
|
|
70
|
+
label: 'Rôle',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'invitedBy',
|
|
74
|
+
type: 'relationship',
|
|
75
|
+
relationTo: [slugs.users, slugs.supportClients],
|
|
76
|
+
required: true,
|
|
77
|
+
label: 'Invité par',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'acceptedAt',
|
|
81
|
+
type: 'date',
|
|
82
|
+
label: 'Accepté le',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'invitationToken',
|
|
86
|
+
type: 'text',
|
|
87
|
+
index: true,
|
|
88
|
+
admin: { hidden: true },
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
timestamps: true,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { CollectionConfig, CollectionAfterChangeHook } from 'payload'
|
|
2
|
+
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
|
+
import { createAdminNotification } from '../utils/adminNotification'
|
|
4
|
+
|
|
5
|
+
// ─── Hooks ───────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
function createAlertOnLowRating(slugs: CollectionSlugs, notificationSlug: string): CollectionAfterChangeHook {
|
|
8
|
+
return async ({ doc, operation, req }) => {
|
|
9
|
+
if (operation !== 'create') return doc
|
|
10
|
+
const rating = typeof doc.rating === 'number' ? doc.rating : Number(doc.rating)
|
|
11
|
+
if (!Number.isFinite(rating) || rating > 2) return doc
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const ticketId = typeof doc.ticket === 'object' ? doc.ticket?.id : doc.ticket
|
|
15
|
+
const ticketNumber = typeof doc.ticket === 'object' ? doc.ticket?.ticketNumber : undefined
|
|
16
|
+
const ticketLabel = ticketNumber || (ticketId ? `#${ticketId}` : '?')
|
|
17
|
+
|
|
18
|
+
await createAdminNotification(req.payload, {
|
|
19
|
+
title: `Client mecontent : ${ticketLabel}`,
|
|
20
|
+
message: `Note ${rating}/5${doc.comment ? ` — ${String(doc.comment).slice(0, 200)}` : ''}`,
|
|
21
|
+
type: 'satisfaction',
|
|
22
|
+
link: ticketId ? `/admin/collections/${slugs.tickets}/${ticketId}` : undefined,
|
|
23
|
+
}, notificationSlug)
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error('[support] Failed to alert on low rating:', err)
|
|
26
|
+
}
|
|
27
|
+
return doc
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ─── Collection factory ──────────────────────────────────
|
|
32
|
+
|
|
33
|
+
export function createTicketFeedbackCollection(slugs: CollectionSlugs, options?: {
|
|
34
|
+
notificationSlug?: string
|
|
35
|
+
}): CollectionConfig {
|
|
36
|
+
const notificationSlug = options?.notificationSlug || 'admin-notifications'
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
slug: 'ticket-feedback',
|
|
40
|
+
labels: {
|
|
41
|
+
singular: 'Feedback ticket',
|
|
42
|
+
plural: 'Feedbacks tickets',
|
|
43
|
+
},
|
|
44
|
+
admin: {
|
|
45
|
+
useAsTitle: 'rating',
|
|
46
|
+
group: 'Support',
|
|
47
|
+
defaultColumns: ['ticket', 'rating', 'client', 'submittedFrom', 'createdAt'],
|
|
48
|
+
},
|
|
49
|
+
access: {
|
|
50
|
+
// Creation is gated by the dedicated endpoint (signed token / portal session).
|
|
51
|
+
// We accept admins and authenticated support clients here; anonymous creation
|
|
52
|
+
// must go through the endpoint which uses overrideAccess.
|
|
53
|
+
create: ({ req }) => {
|
|
54
|
+
if (req.user?.collection === slugs.users) return true
|
|
55
|
+
if (req.user?.collection === slugs.supportClients) return true
|
|
56
|
+
return false
|
|
57
|
+
},
|
|
58
|
+
read: ({ req }) => {
|
|
59
|
+
// Admins only — keeps feedback private from clients
|
|
60
|
+
return req.user?.collection === slugs.users
|
|
61
|
+
},
|
|
62
|
+
update: () => false,
|
|
63
|
+
delete: ({ req }) => req.user?.collection === slugs.users,
|
|
64
|
+
},
|
|
65
|
+
fields: [
|
|
66
|
+
{
|
|
67
|
+
name: 'ticket',
|
|
68
|
+
type: 'relationship',
|
|
69
|
+
relationTo: slugs.tickets,
|
|
70
|
+
required: true,
|
|
71
|
+
hasMany: false,
|
|
72
|
+
unique: true,
|
|
73
|
+
label: 'Ticket',
|
|
74
|
+
admin: { readOnly: true },
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'rating',
|
|
78
|
+
type: 'number',
|
|
79
|
+
required: true,
|
|
80
|
+
min: 1,
|
|
81
|
+
max: 5,
|
|
82
|
+
label: 'Note (1-5)',
|
|
83
|
+
admin: { description: '1 = tres insatisfait, 5 = tres satisfait' },
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: 'comment',
|
|
87
|
+
type: 'textarea',
|
|
88
|
+
label: 'Commentaire',
|
|
89
|
+
admin: { description: 'Commentaire optionnel du client' },
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'client',
|
|
93
|
+
type: 'relationship',
|
|
94
|
+
relationTo: slugs.supportClients,
|
|
95
|
+
label: 'Client',
|
|
96
|
+
admin: { readOnly: true },
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'submittedFrom',
|
|
100
|
+
type: 'select',
|
|
101
|
+
defaultValue: 'portal',
|
|
102
|
+
label: 'Source',
|
|
103
|
+
options: [
|
|
104
|
+
{ label: 'Portail', value: 'portal' },
|
|
105
|
+
{ label: 'Email', value: 'email' },
|
|
106
|
+
{ label: 'Admin', value: 'admin' },
|
|
107
|
+
],
|
|
108
|
+
admin: { readOnly: true },
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
hooks: {
|
|
112
|
+
afterChange: [createAlertOnLowRating(slugs, notificationSlug)],
|
|
113
|
+
},
|
|
114
|
+
timestamps: true,
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -311,6 +311,15 @@ export function createTicketMessagesCollection(slugs: CollectionSlugs, options?:
|
|
|
311
311
|
name: 'attachments', type: 'array', label: 'Pieces jointes',
|
|
312
312
|
fields: [{ name: 'file', type: 'upload', relationTo: slugs.media, required: true, label: 'Fichier' }],
|
|
313
313
|
},
|
|
314
|
+
{
|
|
315
|
+
name: 'fromAlias',
|
|
316
|
+
type: 'text',
|
|
317
|
+
label: 'Alias d\'expéditeur',
|
|
318
|
+
admin: {
|
|
319
|
+
description: 'Si défini, le message est envoyé au nom de cet alias plutôt que de l\'agent authentifié',
|
|
320
|
+
position: 'sidebar',
|
|
321
|
+
},
|
|
322
|
+
},
|
|
314
323
|
{ name: 'isInternal', type: 'checkbox', defaultValue: false, label: 'Note interne', admin: { position: 'sidebar' } },
|
|
315
324
|
{ name: 'isSolution', type: 'checkbox', defaultValue: false, label: 'Reponse solution', admin: { position: 'sidebar' } },
|
|
316
325
|
{ name: 'skipNotification', type: 'checkbox', defaultValue: false, label: 'Sans notification', admin: { position: 'sidebar', condition: (data) => data?.skipNotification === true } },
|
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
CollectionAfterChangeHook,
|
|
5
5
|
CollectionBeforeDeleteHook,
|
|
6
6
|
Field,
|
|
7
|
+
Where,
|
|
7
8
|
} from 'payload'
|
|
8
9
|
import type { CollectionSlugs } from '../utils/slugs'
|
|
9
10
|
import { escapeHtml } from '../utils/emailTemplate'
|
|
@@ -604,6 +605,8 @@ export function createTicketsCollection(slugs: CollectionSlugs, options?: {
|
|
|
604
605
|
{ label: 'Ouvert', value: 'open' },
|
|
605
606
|
{ label: 'En attente client', value: 'waiting_client' },
|
|
606
607
|
{ label: 'Resolu', value: 'resolved' },
|
|
608
|
+
{ label: 'Ferme', value: 'closed' },
|
|
609
|
+
{ label: 'Escalade', value: 'escalated' },
|
|
607
610
|
],
|
|
608
611
|
admin: { width: '50%' },
|
|
609
612
|
},
|
|
@@ -807,9 +810,36 @@ export function createTicketsCollection(slugs: CollectionSlugs, options?: {
|
|
|
807
810
|
if (req.user?.collection === slugs.supportClients) return true
|
|
808
811
|
return false
|
|
809
812
|
},
|
|
810
|
-
read: ({ req }) => {
|
|
813
|
+
read: async ({ req }) => {
|
|
811
814
|
if (req.user?.collection === slugs.users) return true
|
|
812
815
|
if (req.user?.collection === slugs.supportClients) {
|
|
816
|
+
// Owner OR collaborator (via ticket-collaborators table).
|
|
817
|
+
// We resolve the collaborator ticket-ids upfront so we keep a single Where
|
|
818
|
+
// expression — Payload's access fns support async returns.
|
|
819
|
+
let collabTicketIds: Array<number | string> = []
|
|
820
|
+
try {
|
|
821
|
+
const rows = await req.payload.find({
|
|
822
|
+
collection: 'ticket-collaborators' as any,
|
|
823
|
+
where: { client: { equals: req.user.id } },
|
|
824
|
+
limit: 1000,
|
|
825
|
+
depth: 0,
|
|
826
|
+
overrideAccess: true,
|
|
827
|
+
})
|
|
828
|
+
collabTicketIds = rows.docs
|
|
829
|
+
.map((r: any) => (typeof r.ticket === 'object' ? r.ticket?.id : r.ticket))
|
|
830
|
+
.filter((v: unknown): v is number | string => v !== undefined && v !== null)
|
|
831
|
+
} catch {
|
|
832
|
+
// ticket-collaborators collection may not yet exist on older deployments — fall back to owner-only.
|
|
833
|
+
}
|
|
834
|
+
if (collabTicketIds.length > 0) {
|
|
835
|
+
const where: Where = {
|
|
836
|
+
or: [
|
|
837
|
+
{ client: { equals: req.user.id } },
|
|
838
|
+
{ id: { in: collabTicketIds } },
|
|
839
|
+
],
|
|
840
|
+
}
|
|
841
|
+
return where
|
|
842
|
+
}
|
|
813
843
|
return { client: { equals: req.user.id } }
|
|
814
844
|
}
|
|
815
845
|
return false
|
package/src/collections/index.ts
CHANGED
|
@@ -15,3 +15,5 @@ export { createSlaPoliciesCollection } from './SlaPolicies'
|
|
|
15
15
|
export { createMacrosCollection } from './Macros'
|
|
16
16
|
export { createTicketStatusesCollection } from './TicketStatuses'
|
|
17
17
|
export { createClientSummariesCollection } from './ClientSummaries'
|
|
18
|
+
export { createTicketFeedbackCollection } from './TicketFeedback'
|
|
19
|
+
export { createTicketCollaboratorsCollection } from './TicketCollaborators'
|
|
@@ -176,6 +176,12 @@
|
|
|
176
176
|
}
|
|
177
177
|
},
|
|
178
178
|
"composer": {
|
|
179
|
+
"aiSuggestion": {
|
|
180
|
+
"label": "Contextual suggestion",
|
|
181
|
+
"ignore": "Dismiss",
|
|
182
|
+
"insert": "Insert",
|
|
183
|
+
"loading": "Generating…"
|
|
184
|
+
},
|
|
179
185
|
"placeholder": "Write a reply to the client...",
|
|
180
186
|
"placeholderInternal": "Internal note...",
|
|
181
187
|
"placeholderReplyTo": "Reply to {{name}}...",
|
|
@@ -209,6 +215,11 @@
|
|
|
209
215
|
"shortcuts": {
|
|
210
216
|
"sendHint": "\u2318Enter to send",
|
|
211
217
|
"noteHint": "\u2318\u21e7N internal note"
|
|
218
|
+
},
|
|
219
|
+
"aliases": {
|
|
220
|
+
"label": "Send as",
|
|
221
|
+
"tooltip": "Choose a sender alias for this message",
|
|
222
|
+
"self": "(default)"
|
|
212
223
|
}
|
|
213
224
|
},
|
|
214
225
|
"settings": {
|
|
@@ -788,7 +799,20 @@
|
|
|
788
799
|
"statusWaiting": "Waiting",
|
|
789
800
|
"statusResolved": "Resolved",
|
|
790
801
|
"selectTicket": "Select a ticket from the list",
|
|
791
|
-
"notFound": "Ticket not found"
|
|
802
|
+
"notFound": "Ticket not found",
|
|
803
|
+
"sidebar": {
|
|
804
|
+
"tabs": {
|
|
805
|
+
"overview": "Overview",
|
|
806
|
+
"client": "Client",
|
|
807
|
+
"activity": "Activity"
|
|
808
|
+
},
|
|
809
|
+
"previousTickets": "Previous tickets"
|
|
810
|
+
},
|
|
811
|
+
"aiCard": {
|
|
812
|
+
"nextActions": {
|
|
813
|
+
"title": "Suggested next actions"
|
|
814
|
+
}
|
|
815
|
+
}
|
|
792
816
|
},
|
|
793
817
|
"settingsView": {
|
|
794
818
|
"configTitle": "Support Module Configuration",
|
|
@@ -176,6 +176,12 @@
|
|
|
176
176
|
}
|
|
177
177
|
},
|
|
178
178
|
"composer": {
|
|
179
|
+
"aiSuggestion": {
|
|
180
|
+
"label": "Suggestion contextuelle",
|
|
181
|
+
"ignore": "Ignorer",
|
|
182
|
+
"insert": "Insérer",
|
|
183
|
+
"loading": "Génération…"
|
|
184
|
+
},
|
|
179
185
|
"placeholder": "Écrire une réponse au client...",
|
|
180
186
|
"placeholderInternal": "Note interne...",
|
|
181
187
|
"placeholderReplyTo": "Répondre à {{name}}...",
|
|
@@ -209,6 +215,11 @@
|
|
|
209
215
|
"shortcuts": {
|
|
210
216
|
"sendHint": "\u2318Enter pour envoyer",
|
|
211
217
|
"noteHint": "\u2318\u21e7N note interne"
|
|
218
|
+
},
|
|
219
|
+
"aliases": {
|
|
220
|
+
"label": "Envoyer en tant que",
|
|
221
|
+
"tooltip": "Choisir un alias d'exp\u00e9diteur pour ce message",
|
|
222
|
+
"self": "(par d\u00e9faut)"
|
|
212
223
|
}
|
|
213
224
|
},
|
|
214
225
|
"settings": {
|
|
@@ -788,7 +799,20 @@
|
|
|
788
799
|
"statusWaiting": "En attente",
|
|
789
800
|
"statusResolved": "Resolu",
|
|
790
801
|
"selectTicket": "Selectionnez un ticket depuis la liste",
|
|
791
|
-
"notFound": "Ticket introuvable"
|
|
802
|
+
"notFound": "Ticket introuvable",
|
|
803
|
+
"sidebar": {
|
|
804
|
+
"tabs": {
|
|
805
|
+
"overview": "Vue d'ensemble",
|
|
806
|
+
"client": "Client",
|
|
807
|
+
"activity": "Activite"
|
|
808
|
+
},
|
|
809
|
+
"previousTickets": "Tickets precedents"
|
|
810
|
+
},
|
|
811
|
+
"aiCard": {
|
|
812
|
+
"nextActions": {
|
|
813
|
+
"title": "Prochaines actions suggérées"
|
|
814
|
+
}
|
|
815
|
+
}
|
|
792
816
|
},
|
|
793
817
|
"settingsView": {
|
|
794
818
|
"configTitle": "Configuration du module Support",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Endpoint } from 'payload'
|
|
2
|
+
import type { CollectionSlugs } from '../utils/slugs'
|
|
3
|
+
import { requireAdmin, handleAuthError } from '../utils/auth'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* POST /api/support/tickets/:id/escalate
|
|
7
|
+
*
|
|
8
|
+
* Admin-only. Transitions a ticket to the "escalated" status.
|
|
9
|
+
* Used when a ticket needs senior attention or is breaching SLA.
|
|
10
|
+
*/
|
|
11
|
+
export function createEscalateEndpoint(slugs: CollectionSlugs): Endpoint {
|
|
12
|
+
return {
|
|
13
|
+
path: '/support/tickets/:id/escalate',
|
|
14
|
+
method: 'post',
|
|
15
|
+
handler: async (req) => {
|
|
16
|
+
try {
|
|
17
|
+
requireAdmin(req, slugs)
|
|
18
|
+
|
|
19
|
+
const idRaw = req.routeParams?.id as string | undefined
|
|
20
|
+
if (!idRaw) {
|
|
21
|
+
return Response.json({ error: 'missing-id' }, { status: 400 })
|
|
22
|
+
}
|
|
23
|
+
const ticketId = Number(idRaw)
|
|
24
|
+
if (Number.isNaN(ticketId)) {
|
|
25
|
+
return Response.json({ error: 'invalid-id' }, { status: 400 })
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const ticket = await req.payload.update({
|
|
29
|
+
collection: slugs.tickets as any,
|
|
30
|
+
id: ticketId,
|
|
31
|
+
data: { status: 'escalated' },
|
|
32
|
+
overrideAccess: true,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
return Response.json({ ok: true, ticket })
|
|
36
|
+
} catch (err) {
|
|
37
|
+
const authResponse = handleAuthError(err)
|
|
38
|
+
if (authResponse) return authResponse
|
|
39
|
+
console.error('[support/escalate] Error:', err)
|
|
40
|
+
return Response.json({ error: 'Internal server error' }, { status: 500 })
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/endpoints/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { SupportFeatures } from '../types'
|
|
|
5
5
|
import { createAiEndpoint } from './ai'
|
|
6
6
|
import { createClientIntelligenceEndpoint } from './client-intelligence'
|
|
7
7
|
import { createSearchEndpoint } from './search'
|
|
8
|
+
import { createKbSearchEndpoint } from './kb-search'
|
|
8
9
|
import { createBulkActionEndpoint } from './bulk-action'
|
|
9
10
|
import { createMergeTicketsEndpoint } from './merge-tickets'
|
|
10
11
|
import { createSplitTicketEndpoint } from './split-ticket'
|
|
@@ -42,10 +43,15 @@ import { createMergeClientsEndpoint } from './merge-clients'
|
|
|
42
43
|
import { createImportConversationEndpoint } from './import-conversation'
|
|
43
44
|
import { createProcessScheduledEndpoint } from './process-scheduled'
|
|
44
45
|
import { createUserPrefsGetEndpoint, createUserPrefsPostEndpoint } from './user-prefs'
|
|
46
|
+
import { createEscalateEndpoint } from './escalate'
|
|
47
|
+
import { createTicketFeedbackEndpoint } from './ticket-feedback'
|
|
48
|
+
import { createTransferTicketEndpoint } from './transfer-ticket'
|
|
49
|
+
import { createInviteCollaboratorEndpoint } from './invite-collaborator'
|
|
45
50
|
|
|
46
51
|
// Re-export all individual endpoint creators
|
|
47
52
|
export { createAiEndpoint } from './ai'
|
|
48
53
|
export { createSearchEndpoint } from './search'
|
|
54
|
+
export { createKbSearchEndpoint } from './kb-search'
|
|
49
55
|
export { createBulkActionEndpoint } from './bulk-action'
|
|
50
56
|
export { createMergeTicketsEndpoint } from './merge-tickets'
|
|
51
57
|
export { createSplitTicketEndpoint } from './split-ticket'
|
|
@@ -84,6 +90,10 @@ export { createImportConversationEndpoint } from './import-conversation'
|
|
|
84
90
|
export { createProcessScheduledEndpoint } from './process-scheduled'
|
|
85
91
|
export { createUserPrefsGetEndpoint, createUserPrefsPostEndpoint } from './user-prefs'
|
|
86
92
|
export type { UserPrefs } from './user-prefs'
|
|
93
|
+
export { createEscalateEndpoint } from './escalate'
|
|
94
|
+
export { createTicketFeedbackEndpoint } from './ticket-feedback'
|
|
95
|
+
export { createTransferTicketEndpoint } from './transfer-ticket'
|
|
96
|
+
export { createInviteCollaboratorEndpoint } from './invite-collaborator'
|
|
87
97
|
|
|
88
98
|
export interface SupportEndpointOptions {
|
|
89
99
|
oauth?: OAuthGoogleOptions
|
|
@@ -101,6 +111,7 @@ export function createSupportEndpoints(slugs: CollectionSlugs, options?: Support
|
|
|
101
111
|
// Core endpoints (always present)
|
|
102
112
|
const endpoints: Endpoint[] = [
|
|
103
113
|
createSearchEndpoint(slugs),
|
|
114
|
+
createKbSearchEndpoint(slugs),
|
|
104
115
|
createSettingsGetEndpoint(slugs),
|
|
105
116
|
createSettingsPostEndpoint(slugs),
|
|
106
117
|
createAdminStatsEndpoint(slugs),
|
|
@@ -117,6 +128,10 @@ export function createSupportEndpoints(slugs: CollectionSlugs, options?: Support
|
|
|
117
128
|
createResendNotificationEndpoint(slugs),
|
|
118
129
|
createUserPrefsGetEndpoint(slugs),
|
|
119
130
|
createUserPrefsPostEndpoint(slugs),
|
|
131
|
+
createEscalateEndpoint(slugs),
|
|
132
|
+
createTicketFeedbackEndpoint(slugs),
|
|
133
|
+
createTransferTicketEndpoint(slugs),
|
|
134
|
+
createInviteCollaboratorEndpoint(slugs),
|
|
120
135
|
]
|
|
121
136
|
|
|
122
137
|
// Conditional endpoints based on feature flags
|