@consilioweb/payload-support 2.0.0 → 3.0.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/README.md +656 -355
- package/dist/components/RichTextEditor/index.d.ts +19 -0
- package/dist/components/TicketConversation/RewriteDropdown.d.ts +6 -0
- package/dist/components/TicketConversation/SkeletonText.d.ts +4 -0
- package/dist/components/TicketConversation/components/AISummaryPanel.d.ts +14 -0
- package/dist/components/TicketConversation/components/ActionPanels.d.ts +47 -0
- package/dist/components/TicketConversation/components/ActivityLog.d.ts +5 -0
- package/dist/components/TicketConversation/components/ClientBar.d.ts +5 -0
- package/dist/components/TicketConversation/components/ClientHistory.d.ts +24 -0
- package/dist/components/TicketConversation/components/CodeBlock.d.ts +26 -0
- package/dist/components/TicketConversation/components/CodeBlockInserter.d.ts +12 -0
- package/dist/components/TicketConversation/components/QuickActions.d.ts +32 -0
- package/dist/components/TicketConversation/components/TicketHeader.d.ts +19 -0
- package/dist/components/TicketConversation/components/TimeTrackingPanel.d.ts +24 -0
- package/dist/components/TicketConversation/config.d.ts +10 -0
- package/dist/components/TicketConversation/config.js +1 -40
- package/dist/components/TicketConversation/constants.d.ts +56 -0
- package/dist/components/TicketConversation/context.d.ts +53 -0
- package/dist/components/TicketConversation/hooks/useAI.d.ts +21 -0
- package/dist/components/TicketConversation/hooks/useDocumentIdFromUrl.d.ts +3 -0
- package/dist/components/TicketConversation/hooks/useFeatures.d.ts +23 -0
- package/dist/components/TicketConversation/hooks/useFeatures.js +61 -0
- package/dist/components/TicketConversation/hooks/useMessageActions.d.ts +20 -0
- package/dist/components/TicketConversation/hooks/useReply.d.ts +27 -0
- package/dist/components/TicketConversation/hooks/useTicketActions.d.ts +58 -0
- package/dist/components/TicketConversation/hooks/useTimeTracking.d.ts +18 -0
- package/dist/components/TicketConversation/hooks/useTranslation.d.ts +33 -0
- package/dist/components/TicketConversation/hooks/useTranslation.js +22 -3
- package/dist/components/TicketConversation/index.d.ts +4 -0
- package/dist/components/TicketConversation/index.js +2 -2
- package/dist/components/TicketConversation/types.d.ts +56 -0
- package/dist/components/TicketConversation/utils.d.ts +2 -0
- package/dist/index.cjs +392 -320
- package/dist/index.d.cts +100 -3
- package/dist/index.d.ts +100 -3
- package/dist/index.js +388 -321
- package/dist/utils/features.d.ts +123 -0
- package/dist/utils/features.js +147 -0
- package/dist/views/BillingView/client.d.ts +2 -0
- package/dist/views/BillingView/index.d.ts +4 -0
- package/dist/views/ChatView/client.d.ts +2 -0
- package/dist/views/ChatView/client.js +9 -9
- package/dist/views/ChatView/index.d.ts +4 -0
- package/dist/views/CrmView/client.d.ts +2 -0
- package/dist/views/CrmView/client.js +6 -9
- package/dist/views/CrmView/index.d.ts +4 -0
- package/dist/views/EmailTrackingView/client.d.ts +2 -0
- package/dist/views/EmailTrackingView/index.d.ts +4 -0
- package/dist/views/ImportConversationView/client.d.ts +2 -0
- package/dist/views/ImportConversationView/index.d.ts +4 -0
- package/dist/views/LogsView/client.d.ts +2 -0
- package/dist/views/LogsView/index.d.ts +4 -0
- package/dist/views/NewTicketView/client.d.ts +2 -0
- package/dist/views/NewTicketView/index.d.ts +4 -0
- package/dist/views/PendingEmailsView/client.d.ts +2 -0
- package/dist/views/PendingEmailsView/index.d.ts +4 -0
- package/dist/views/SupportDashboardView/client.d.ts +2 -0
- package/dist/views/SupportDashboardView/client.js +1 -1
- package/dist/views/SupportDashboardView/index.d.ts +4 -0
- package/dist/views/TicketDetailView/NextActionItem.d.ts +6 -0
- package/dist/views/TicketDetailView/RewriteDropdown.d.ts +7 -0
- package/dist/views/TicketDetailView/client.d.ts +2 -0
- package/dist/views/TicketDetailView/client.js +2 -2
- package/dist/views/TicketDetailView/constants.d.ts +13 -0
- package/dist/views/TicketDetailView/helpers.d.ts +2 -0
- package/dist/views/TicketDetailView/index.d.ts +4 -0
- package/dist/views/TicketDetailView/types.d.ts +40 -0
- package/dist/views/TicketInboxView/client.d.ts +2 -0
- package/dist/views/TicketInboxView/client.js +5 -5
- package/dist/views/TicketInboxView/index.d.ts +4 -0
- package/dist/views/TicketingSettingsView/client.d.ts +2 -0
- package/dist/views/TicketingSettingsView/client.js +6 -5
- package/dist/views/TicketingSettingsView/index.d.ts +4 -0
- package/dist/views/TimeDashboardView/client.d.ts +2 -0
- package/dist/views/TimeDashboardView/index.d.ts +4 -0
- package/dist/views/shared/AdminViewHeader.d.ts +13 -0
- package/dist/views/shared/ErrorBoundary.d.ts +17 -0
- package/dist/views/shared/Skeleton.d.ts +21 -0
- package/dist/views/shared/StatusPill.d.ts +8 -0
- package/dist/views/shared/adminTokens.d.ts +19 -0
- package/dist/views/shared/config.d.ts +8 -0
- package/dist/views/shared/config.js +1 -40
- package/dist/views/shared/dateLocale.d.ts +5 -0
- package/dist/views/shared/index.d.ts +12 -0
- package/dist/views/shared/index.js +2 -1
- package/dist/views/shared/locales/en.json +708 -0
- package/dist/views/shared/locales/fr.json +708 -0
- package/dist/views/shared/sla.d.ts +14 -0
- package/dist/views.d.ts +13 -13
- package/package.json +26 -41
- package/src/collections/SupportClients.ts +29 -0
- package/src/collections/TicketCollaborators.ts +6 -1
- package/src/collections/TicketMessages.ts +46 -24
- package/src/collections/Tickets.ts +117 -79
- package/src/collections/TimeEntries.ts +57 -32
- package/src/components/TicketConversation/config.ts +16 -80
- package/src/components/TicketConversation/hooks/useFeatures.ts +109 -0
- package/src/components/TicketConversation/hooks/useTranslation.ts +38 -4
- package/src/components/TicketConversation/index.tsx +2 -2
- package/src/endpoints/ai.ts +2 -1
- package/src/endpoints/client-intelligence.ts +2 -1
- package/src/endpoints/process-scheduled.ts +13 -8
- package/src/endpoints/round-robin-config.ts +30 -20
- package/src/endpoints/settings.ts +28 -77
- package/src/index.ts +6 -2
- package/src/portal/LiveChat.tsx +33 -22
- package/src/portal/auth/tickets/detail/TicketPolling.tsx +5 -1
- package/src/types/lucide-react.d.ts +10 -2
- package/src/utils/aiAgent.ts +2 -1
- package/src/utils/aiProvider.ts +21 -0
- package/src/utils/features.ts +297 -0
- package/src/utils/fireWebhooks.ts +6 -0
- package/src/utils/generateTicketSynthesis.ts +2 -1
- package/src/utils/index.ts +4 -2
- package/src/utils/readSettings.ts +82 -9
- package/src/utils/webhookDispatcher.ts +19 -7
- package/src/views/ChatView/client.tsx +13 -9
- package/src/views/CrmView/client.tsx +10 -11
- package/src/views/SupportDashboardView/client.tsx +1 -1
- package/src/views/TicketDetailView/client.tsx +2 -2
- package/src/views/TicketInboxView/client.tsx +10 -5
- package/src/views/TicketingSettingsView/client.tsx +9 -7
- package/src/views/shared/config.ts +14 -80
- package/src/views/shared/index.ts +3 -1
- package/src/views/shared/locales/en.json +2 -2
- package/src/views/shared/locales/fr.json +2 -2
- package/dist/components/RichTextEditor/index.cjs +0 -279
- package/dist/components/TicketConversation/RewriteDropdown.cjs +0 -98
- package/dist/components/TicketConversation/SkeletonText.cjs +0 -21
- package/dist/components/TicketConversation/components/AISummaryPanel.cjs +0 -85
- package/dist/components/TicketConversation/components/ActionPanels.cjs +0 -193
- package/dist/components/TicketConversation/components/ActivityLog.cjs +0 -24
- package/dist/components/TicketConversation/components/ClientBar.cjs +0 -43
- package/dist/components/TicketConversation/components/ClientHistory.cjs +0 -138
- package/dist/components/TicketConversation/components/CodeBlock.cjs +0 -171
- package/dist/components/TicketConversation/components/CodeBlockInserter.cjs +0 -156
- package/dist/components/TicketConversation/components/QuickActions.cjs +0 -87
- package/dist/components/TicketConversation/components/TicketHeader.cjs +0 -93
- package/dist/components/TicketConversation/components/TimeTrackingPanel.cjs +0 -134
- package/dist/components/TicketConversation/config.cjs +0 -44
- package/dist/components/TicketConversation/constants.cjs +0 -127
- package/dist/components/TicketConversation/context.cjs +0 -13
- package/dist/components/TicketConversation/hooks/useAI.cjs +0 -196
- package/dist/components/TicketConversation/hooks/useDocumentIdFromUrl.cjs +0 -20
- package/dist/components/TicketConversation/hooks/useMessageActions.cjs +0 -136
- package/dist/components/TicketConversation/hooks/useReply.cjs +0 -188
- package/dist/components/TicketConversation/hooks/useTicketActions.cjs +0 -290
- package/dist/components/TicketConversation/hooks/useTimeTracking.cjs +0 -123
- package/dist/components/TicketConversation/hooks/useTranslation.cjs +0 -78
- package/dist/components/TicketConversation/index.cjs +0 -1114
- package/dist/components/TicketConversation/types.cjs +0 -2
- package/dist/components/TicketConversation/utils.cjs +0 -27
- package/dist/views/BillingView/client.cjs +0 -469
- package/dist/views/BillingView/index.cjs +0 -34
- package/dist/views/ChatView/client.cjs +0 -301
- package/dist/views/ChatView/index.cjs +0 -34
- package/dist/views/CrmView/client.cjs +0 -458
- package/dist/views/CrmView/index.cjs +0 -34
- package/dist/views/EmailTrackingView/client.cjs +0 -171
- package/dist/views/EmailTrackingView/index.cjs +0 -34
- package/dist/views/ImportConversationView/client.cjs +0 -238
- package/dist/views/ImportConversationView/index.cjs +0 -34
- package/dist/views/LogsView/client.cjs +0 -148
- package/dist/views/LogsView/index.cjs +0 -32
- package/dist/views/NewTicketView/client.cjs +0 -213
- package/dist/views/NewTicketView/index.cjs +0 -32
- package/dist/views/PendingEmailsView/client.cjs +0 -471
- package/dist/views/PendingEmailsView/index.cjs +0 -34
- package/dist/views/SupportDashboardView/client.cjs +0 -437
- package/dist/views/SupportDashboardView/index.cjs +0 -34
- package/dist/views/TicketDetailView/NextActionItem.cjs +0 -40
- package/dist/views/TicketDetailView/RewriteDropdown.cjs +0 -80
- package/dist/views/TicketDetailView/client.cjs +0 -1412
- package/dist/views/TicketDetailView/constants.cjs +0 -20
- package/dist/views/TicketDetailView/helpers.cjs +0 -23
- package/dist/views/TicketDetailView/index.cjs +0 -34
- package/dist/views/TicketDetailView/types.cjs +0 -2
- package/dist/views/TicketInboxView/client.cjs +0 -359
- package/dist/views/TicketInboxView/index.cjs +0 -32
- package/dist/views/TicketingSettingsView/client.cjs +0 -740
- package/dist/views/TicketingSettingsView/index.cjs +0 -34
- package/dist/views/TimeDashboardView/client.cjs +0 -180
- package/dist/views/TimeDashboardView/index.cjs +0 -34
- package/dist/views/shared/AdminViewHeader.cjs +0 -68
- package/dist/views/shared/ErrorBoundary.cjs +0 -55
- package/dist/views/shared/Skeleton.cjs +0 -77
- package/dist/views/shared/StatusPill.cjs +0 -47
- package/dist/views/shared/adminTokens.cjs +0 -34
- package/dist/views/shared/config.cjs +0 -44
- package/dist/views/shared/dateLocale.cjs +0 -5
- package/dist/views/shared/index.cjs +0 -72
- package/dist/views/shared/sla.cjs +0 -37
- package/dist/views.cjs +0 -86
- package/dist/views.d.cts +0 -13
- package/src/__tests__/aiSummaryRendering.test.ts +0 -21
- package/src/__tests__/authResponses.test.ts +0 -69
- package/src/__tests__/capabilities.test.ts +0 -71
- package/src/__tests__/generateTrackingToken.test.ts +0 -67
- package/src/__tests__/integration/ai-agent.test.ts +0 -46
- package/src/__tests__/integration/auth.test.ts +0 -43
- package/src/__tests__/integration/automation-rules.test.ts +0 -68
- package/src/__tests__/integration/buildTestPayload.ts +0 -52
- package/src/__tests__/integration/channels.test.ts +0 -52
- package/src/__tests__/integration/core-behaviors.test.ts +0 -81
- package/src/__tests__/integration/dashboard-volume.test.ts +0 -31
- package/src/__tests__/integration/digest.test.ts +0 -48
- package/src/__tests__/integration/invoice-pdf.test.ts +0 -30
- package/src/__tests__/integration/isolation.test.ts +0 -81
- package/src/__tests__/integration/mentions-invoice.test.ts +0 -50
- package/src/__tests__/integration/nps.test.ts +0 -37
- package/src/__tests__/integration/per-team-sla.test.ts +0 -44
- package/src/__tests__/integration/push.test.ts +0 -75
- package/src/__tests__/integration/sanitization.test.ts +0 -35
- package/src/__tests__/integration/sla-pause.test.ts +0 -39
- package/src/__tests__/integration/smoke.test.ts +0 -26
- package/src/__tests__/integration/snooze.test.ts +0 -68
- package/src/__tests__/integration/teams.test.ts +0 -38
- package/src/__tests__/rateLimiter.test.ts +0 -83
- package/src/__tests__/sanitizeHtml.test.ts +0 -165
- package/src/__tests__/sla.test.ts +0 -258
- package/src/__tests__/trackOpenEndpoint.test.ts +0 -54
- package/src/__tests__/webhookSecurity.test.ts +0 -58
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ticketing feature flags — single source of truth (client + server).
|
|
3
|
+
*
|
|
4
|
+
* Flags are stored SERVER-side, in the `support-settings` row of
|
|
5
|
+
* `payload-preferences` (see `readSettings.ts` and `endpoints/settings.ts`).
|
|
6
|
+
* A flag toggled on one machine therefore applies to every browser and to
|
|
7
|
+
* server-side code (hooks, cron endpoints) alike.
|
|
8
|
+
*
|
|
9
|
+
* `localStorage` is still used, but demoted to a plain CACHE: it lets admin
|
|
10
|
+
* views paint synchronously on first render and keeps them usable when the
|
|
11
|
+
* settings endpoint is unreachable. It is never authoritative.
|
|
12
|
+
*
|
|
13
|
+
* Two flags are *projections* of settings that already lived server-side long
|
|
14
|
+
* before this module, and are deliberately NOT stored twice:
|
|
15
|
+
* - `autoClose` ← `settings.autoClose.enabled`
|
|
16
|
+
* - `autoCloseDays` ← `settings.autoClose.daysBeforeClose`
|
|
17
|
+
* The `autoClose` block stays authoritative; the flags are recomputed on read
|
|
18
|
+
* and dropped on write. See `projectAutoClose` / `stripProjectedFeatures`.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Feature flags for the ticketing module.
|
|
22
|
+
* Each feature can be enabled/disabled by the admin.
|
|
23
|
+
* When disabled, the corresponding UI section is hidden entirely.
|
|
24
|
+
*/
|
|
25
|
+
export interface TicketingFeatures {
|
|
26
|
+
/** Time tracking: timer, manual entries, billing */
|
|
27
|
+
timeTracking: boolean;
|
|
28
|
+
/** AI features: sentiment, synthesis, suggestion, rewrite */
|
|
29
|
+
ai: boolean;
|
|
30
|
+
/** Satisfaction surveys: CSAT rating after resolution */
|
|
31
|
+
satisfaction: boolean;
|
|
32
|
+
/** Live chat integration: chat → ticket conversion */
|
|
33
|
+
chat: boolean;
|
|
34
|
+
/** Email tracking: pixel tracking, open/sent status per message */
|
|
35
|
+
emailTracking: boolean;
|
|
36
|
+
/** Canned responses: quick reply templates */
|
|
37
|
+
canned: boolean;
|
|
38
|
+
/** Ticket merge: combine two tickets into one */
|
|
39
|
+
merge: boolean;
|
|
40
|
+
/** Snooze: temporarily hide a ticket */
|
|
41
|
+
snooze: boolean;
|
|
42
|
+
/** External messages: add messages received outside the system */
|
|
43
|
+
externalMessages: boolean;
|
|
44
|
+
/** Client history: past tickets, projects, notes sidebar */
|
|
45
|
+
clientHistory: boolean;
|
|
46
|
+
/** Activity log: audit trail of actions on the ticket */
|
|
47
|
+
activityLog: boolean;
|
|
48
|
+
/** Split ticket: extract a message into a new ticket */
|
|
49
|
+
splitTicket: boolean;
|
|
50
|
+
/** Scheduled replies: send a message at a future date */
|
|
51
|
+
scheduledReplies: boolean;
|
|
52
|
+
/** Auto-close: automatically resolve inactive tickets (projection of settings.autoClose.enabled) */
|
|
53
|
+
autoClose: boolean;
|
|
54
|
+
/** Auto-close delay in days (projection of settings.autoClose.daysBeforeClose) */
|
|
55
|
+
autoCloseDays: number;
|
|
56
|
+
/** Round-robin: distribute new tickets evenly among agents */
|
|
57
|
+
roundRobin: boolean;
|
|
58
|
+
}
|
|
59
|
+
/** Default features — all enabled except round-robin */
|
|
60
|
+
export declare const DEFAULT_TICKETING_FEATURES: TicketingFeatures;
|
|
61
|
+
/** Flags derived from another settings block — never persisted inside `features`. */
|
|
62
|
+
declare const PROJECTED_FEATURE_KEYS: readonly ["autoClose", "autoCloseDays"];
|
|
63
|
+
/**
|
|
64
|
+
* Coerce arbitrary input (stale localStorage, hand-edited preference row, older
|
|
65
|
+
* plugin version) into a complete, well-typed feature set. Unknown keys are
|
|
66
|
+
* dropped and wrongly-typed values fall back to their default.
|
|
67
|
+
*/
|
|
68
|
+
export declare function normalizeFeatures(raw: unknown): TicketingFeatures;
|
|
69
|
+
/** Shape of the `autoClose` settings block the two projected flags mirror. */
|
|
70
|
+
export interface AutoCloseProjectionSource {
|
|
71
|
+
enabled?: boolean;
|
|
72
|
+
daysBeforeClose?: number;
|
|
73
|
+
}
|
|
74
|
+
/** Recompute the projected flags from the authoritative `autoClose` block. */
|
|
75
|
+
export declare function projectAutoClose(features: TicketingFeatures, autoClose: AutoCloseProjectionSource | undefined): TicketingFeatures;
|
|
76
|
+
/**
|
|
77
|
+
* Drop the projected flags before persisting, so `features` and the `autoClose`
|
|
78
|
+
* block can never disagree.
|
|
79
|
+
*/
|
|
80
|
+
export declare function stripProjectedFeatures(features: TicketingFeatures): Omit<TicketingFeatures, (typeof PROJECTED_FEATURE_KEYS)[number]>;
|
|
81
|
+
/** localStorage key. Was the source of truth before 2.1 — now only a cache. */
|
|
82
|
+
export declare const FEATURES_CACHE_KEY = "ticketing_features";
|
|
83
|
+
/** Set once the pre-2.1 localStorage values have been pushed to the server. */
|
|
84
|
+
export declare const FEATURES_ADOPTED_KEY = "ticketing_features_adopted";
|
|
85
|
+
/** Admin-only settings endpoint backing the flags. */
|
|
86
|
+
export declare const FEATURES_ENDPOINT = "/api/support/settings";
|
|
87
|
+
/** Read the cached flags, or `null` when nothing usable is cached. */
|
|
88
|
+
export declare function readFeaturesCache(): TicketingFeatures | null;
|
|
89
|
+
/** Refresh the cache with the values the server just confirmed. */
|
|
90
|
+
export declare function writeFeaturesCache(features: TicketingFeatures): void;
|
|
91
|
+
/**
|
|
92
|
+
* Synchronous read used for first paint. Returns the cache when present, the
|
|
93
|
+
* defaults otherwise. Callers must still refresh from the server — see
|
|
94
|
+
* `useFeatures`.
|
|
95
|
+
*/
|
|
96
|
+
export declare function getFeatures(): TicketingFeatures;
|
|
97
|
+
/** True once this browser's legacy flags have been handed over to the server. */
|
|
98
|
+
export declare function hasAdoptedLegacyFeatures(): boolean;
|
|
99
|
+
export declare function markLegacyFeaturesAdopted(): void;
|
|
100
|
+
/**
|
|
101
|
+
* Flags this browser configured before the server became authoritative, or
|
|
102
|
+
* `null` when there is nothing to migrate (no local values, or already done).
|
|
103
|
+
*/
|
|
104
|
+
export declare function readLegacyFeatures(): TicketingFeatures | null;
|
|
105
|
+
export interface FetchedFeatures {
|
|
106
|
+
features: TicketingFeatures;
|
|
107
|
+
/** False when the server has never had feature flags saved (fresh install). */
|
|
108
|
+
configured: boolean;
|
|
109
|
+
/** True when the values come from the cache/defaults because the call failed. */
|
|
110
|
+
offline: boolean;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Read the flags from the server. Falls back to the local cache, then to the
|
|
114
|
+
* defaults: a failed settings call must never leave a ticket screen unusable.
|
|
115
|
+
*/
|
|
116
|
+
export declare function fetchFeatures(signal?: AbortSignal): Promise<FetchedFeatures>;
|
|
117
|
+
/**
|
|
118
|
+
* Persist the flags server-side, then refresh the local cache.
|
|
119
|
+
*
|
|
120
|
+
* @returns true when the server accepted the write.
|
|
121
|
+
*/
|
|
122
|
+
export declare function saveFeatures(features: TicketingFeatures): Promise<boolean>;
|
|
123
|
+
export {};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
const DEFAULT_TICKETING_FEATURES = {
|
|
2
|
+
timeTracking: true,
|
|
3
|
+
ai: true,
|
|
4
|
+
satisfaction: true,
|
|
5
|
+
chat: true,
|
|
6
|
+
emailTracking: true,
|
|
7
|
+
canned: true,
|
|
8
|
+
merge: true,
|
|
9
|
+
snooze: true,
|
|
10
|
+
externalMessages: true,
|
|
11
|
+
clientHistory: true,
|
|
12
|
+
activityLog: true,
|
|
13
|
+
splitTicket: true,
|
|
14
|
+
scheduledReplies: true,
|
|
15
|
+
autoClose: true,
|
|
16
|
+
autoCloseDays: 7,
|
|
17
|
+
roundRobin: false
|
|
18
|
+
};
|
|
19
|
+
const BOOLEAN_FEATURE_KEYS = [
|
|
20
|
+
"timeTracking",
|
|
21
|
+
"ai",
|
|
22
|
+
"satisfaction",
|
|
23
|
+
"chat",
|
|
24
|
+
"emailTracking",
|
|
25
|
+
"canned",
|
|
26
|
+
"merge",
|
|
27
|
+
"snooze",
|
|
28
|
+
"externalMessages",
|
|
29
|
+
"clientHistory",
|
|
30
|
+
"activityLog",
|
|
31
|
+
"splitTicket",
|
|
32
|
+
"scheduledReplies",
|
|
33
|
+
"autoClose",
|
|
34
|
+
"roundRobin"
|
|
35
|
+
];
|
|
36
|
+
const PROJECTED_FEATURE_KEYS = ["autoClose", "autoCloseDays"];
|
|
37
|
+
function normalizeFeatures(raw) {
|
|
38
|
+
const out = { ...DEFAULT_TICKETING_FEATURES };
|
|
39
|
+
if (!raw || typeof raw !== "object") return out;
|
|
40
|
+
const src = raw;
|
|
41
|
+
for (const key of BOOLEAN_FEATURE_KEYS) {
|
|
42
|
+
if (typeof src[key] === "boolean") out[key] = src[key];
|
|
43
|
+
}
|
|
44
|
+
const days = src.autoCloseDays;
|
|
45
|
+
if (typeof days === "number" && Number.isFinite(days) && days >= 1) {
|
|
46
|
+
out.autoCloseDays = Math.floor(days);
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
function projectAutoClose(features, autoClose) {
|
|
51
|
+
if (!autoClose) return features;
|
|
52
|
+
return {
|
|
53
|
+
...features,
|
|
54
|
+
autoClose: typeof autoClose.enabled === "boolean" ? autoClose.enabled : features.autoClose,
|
|
55
|
+
autoCloseDays: typeof autoClose.daysBeforeClose === "number" && autoClose.daysBeforeClose >= 1 ? Math.floor(autoClose.daysBeforeClose) : features.autoCloseDays
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function stripProjectedFeatures(features) {
|
|
59
|
+
const out = { ...features };
|
|
60
|
+
for (const key of PROJECTED_FEATURE_KEYS) delete out[key];
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
const FEATURES_CACHE_KEY = "ticketing_features";
|
|
64
|
+
const FEATURES_ADOPTED_KEY = "ticketing_features_adopted";
|
|
65
|
+
const FEATURES_ENDPOINT = "/api/support/settings";
|
|
66
|
+
function storage() {
|
|
67
|
+
try {
|
|
68
|
+
if (typeof window === "undefined" || !window.localStorage) return null;
|
|
69
|
+
return window.localStorage;
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function readFeaturesCache() {
|
|
75
|
+
const store = storage();
|
|
76
|
+
if (!store) return null;
|
|
77
|
+
try {
|
|
78
|
+
const stored = store.getItem(FEATURES_CACHE_KEY);
|
|
79
|
+
if (!stored) return null;
|
|
80
|
+
return normalizeFeatures(JSON.parse(stored));
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function writeFeaturesCache(features) {
|
|
86
|
+
const store = storage();
|
|
87
|
+
if (!store) return;
|
|
88
|
+
try {
|
|
89
|
+
store.setItem(FEATURES_CACHE_KEY, JSON.stringify(features));
|
|
90
|
+
} catch {
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function getFeatures() {
|
|
94
|
+
return readFeaturesCache() ?? { ...DEFAULT_TICKETING_FEATURES };
|
|
95
|
+
}
|
|
96
|
+
function hasAdoptedLegacyFeatures() {
|
|
97
|
+
const store = storage();
|
|
98
|
+
if (!store) return true;
|
|
99
|
+
try {
|
|
100
|
+
return store.getItem(FEATURES_ADOPTED_KEY) === "1";
|
|
101
|
+
} catch {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function markLegacyFeaturesAdopted() {
|
|
106
|
+
const store = storage();
|
|
107
|
+
if (!store) return;
|
|
108
|
+
try {
|
|
109
|
+
store.setItem(FEATURES_ADOPTED_KEY, "1");
|
|
110
|
+
} catch {
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function readLegacyFeatures() {
|
|
114
|
+
if (hasAdoptedLegacyFeatures()) return null;
|
|
115
|
+
return readFeaturesCache();
|
|
116
|
+
}
|
|
117
|
+
async function fetchFeatures(signal) {
|
|
118
|
+
try {
|
|
119
|
+
const res = await fetch(FEATURES_ENDPOINT, { credentials: "include", signal });
|
|
120
|
+
if (res.ok) {
|
|
121
|
+
const data = await res.json();
|
|
122
|
+
const features = projectAutoClose(normalizeFeatures(data.features), data.autoClose);
|
|
123
|
+
writeFeaturesCache(features);
|
|
124
|
+
return { features, configured: data.featuresConfigured === true, offline: false };
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
}
|
|
128
|
+
return { features: getFeatures(), configured: false, offline: true };
|
|
129
|
+
}
|
|
130
|
+
async function saveFeatures(features) {
|
|
131
|
+
const normalized = normalizeFeatures(features);
|
|
132
|
+
try {
|
|
133
|
+
const res = await fetch(FEATURES_ENDPOINT, {
|
|
134
|
+
method: "POST",
|
|
135
|
+
headers: { "Content-Type": "application/json" },
|
|
136
|
+
credentials: "include",
|
|
137
|
+
body: JSON.stringify({ features: normalized })
|
|
138
|
+
});
|
|
139
|
+
if (!res.ok) return false;
|
|
140
|
+
writeFeaturesCache(normalized);
|
|
141
|
+
return true;
|
|
142
|
+
} catch {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export { DEFAULT_TICKETING_FEATURES, FEATURES_ADOPTED_KEY, FEATURES_CACHE_KEY, FEATURES_ENDPOINT, fetchFeatures, getFeatures, hasAdoptedLegacyFeatures, markLegacyFeaturesAdopted, normalizeFeatures, projectAutoClose, readFeaturesCache, readLegacyFeatures, saveFeatures, stripProjectedFeatures, writeFeaturesCache };
|
|
@@ -21,6 +21,7 @@ const ChatViewClient = () => {
|
|
|
21
21
|
const sessionsPollTimeout = useRef(void 0);
|
|
22
22
|
const messagesPollInterval = useRef(3e3);
|
|
23
23
|
const messagesPollTimeout = useRef(void 0);
|
|
24
|
+
const sessionsSnapshotRef = useRef("");
|
|
24
25
|
const [sessionExpired, setSessionExpired] = useState(false);
|
|
25
26
|
const fetchSessions = useCallback(async () => {
|
|
26
27
|
let hadChanges = false;
|
|
@@ -32,15 +33,14 @@ const ChatViewClient = () => {
|
|
|
32
33
|
}
|
|
33
34
|
if (res.ok) {
|
|
34
35
|
const data = await res.json();
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
});
|
|
36
|
+
const newActive = data.active || [];
|
|
37
|
+
const newClosed = data.closed || [];
|
|
38
|
+
const snapshot = JSON.stringify([newActive, newClosed]);
|
|
39
|
+
if (snapshot !== sessionsSnapshotRef.current) {
|
|
40
|
+
sessionsSnapshotRef.current = snapshot;
|
|
41
|
+
hadChanges = true;
|
|
42
|
+
setSessions({ active: newActive, closed: newClosed });
|
|
43
|
+
}
|
|
44
44
|
}
|
|
45
45
|
} catch {
|
|
46
46
|
}
|
|
@@ -92,21 +92,18 @@ const CrmClient = () => {
|
|
|
92
92
|
const fetchDetail = useCallback(async (clientId) => {
|
|
93
93
|
setDetailLoading(true);
|
|
94
94
|
try {
|
|
95
|
-
const [clientRes, ticketsRes, projectsRes
|
|
95
|
+
const [clientRes, ticketsRes, projectsRes] = await Promise.all([
|
|
96
96
|
fetch(`/api/support-clients/${clientId}?depth=0`),
|
|
97
97
|
fetch(`/api/tickets?where[client][equals]=${clientId}&sort=-createdAt&limit=20&depth=0`),
|
|
98
|
-
fetch(`/api/projects?where[client][equals]=${clientId}&depth=0`)
|
|
99
|
-
fetch(`/api/time-entries?limit=0&depth=0`)
|
|
98
|
+
fetch(`/api/projects?where[client][equals]=${clientId}&depth=0`)
|
|
100
99
|
]);
|
|
101
100
|
const client = clientRes.ok ? await clientRes.json() : null;
|
|
102
101
|
const tickets = ticketsRes.ok ? (await ticketsRes.json()).docs || [] : [];
|
|
103
102
|
const projects = projectsRes.ok ? (await projectsRes.json()).docs || [] : [];
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
totalTimeMinutes = timeData.docs.filter((e) => ticketIds.includes(e.ticket)).reduce((sum, e) => sum + (e.duration || 0), 0);
|
|
109
|
-
}
|
|
103
|
+
const totalTimeMinutes = tickets.reduce(
|
|
104
|
+
(sum, t2) => sum + (t2.totalTimeMinutes || 0),
|
|
105
|
+
0
|
|
106
|
+
);
|
|
110
107
|
const openTickets = tickets.filter(
|
|
111
108
|
(t2) => ["open", "waiting_client"].includes(t2.status)
|
|
112
109
|
).length;
|
|
@@ -400,7 +400,7 @@ const SupportDashboardClient = () => {
|
|
|
400
400
|
/* @__PURE__ */ jsx(VolumeChart, { data: volumeData })
|
|
401
401
|
] }),
|
|
402
402
|
/* @__PURE__ */ jsxs("div", { className: styles.panel, children: [
|
|
403
|
-
/* @__PURE__ */ jsx("h2", { className: styles.panelTitle, children: t("dashboard.
|
|
403
|
+
/* @__PURE__ */ jsx("h2", { className: styles.panelTitle, children: t("dashboard.csatTitle") }),
|
|
404
404
|
/* @__PURE__ */ jsx(CSATRing, { score: stats.satisfactionAvg, count: stats.satisfactionCount })
|
|
405
405
|
] })
|
|
406
406
|
] })
|
|
@@ -6,7 +6,7 @@ import Link from 'next/link';
|
|
|
6
6
|
import { RichTextEditor } from '../../components/RichTextEditor/index.js';
|
|
7
7
|
import { hasCodeBlocks, CodeBlockRendererHtml, MessageWithCodeBlocks } from '../../components/TicketConversation/components/CodeBlock.js';
|
|
8
8
|
import { CodeBlockInserter } from '../../components/TicketConversation/components/CodeBlockInserter.js';
|
|
9
|
-
import {
|
|
9
|
+
import { useFeatures } from '../../components/TicketConversation/hooks/useFeatures.js';
|
|
10
10
|
import { computeSlaState, formatSlaRemaining } from '../shared/sla.js';
|
|
11
11
|
import { useTranslation } from '../../components/TicketConversation/hooks/useTranslation.js';
|
|
12
12
|
import { DATE_LOCALE } from '../shared/dateLocale.js';
|
|
@@ -20,7 +20,7 @@ const TicketDetailClient = () => {
|
|
|
20
20
|
const { t } = useTranslation();
|
|
21
21
|
const searchParams = useSearchParams();
|
|
22
22
|
const ticketId = searchParams.get("id");
|
|
23
|
-
const features =
|
|
23
|
+
const { features } = useFeatures();
|
|
24
24
|
const editorRef = useRef(null);
|
|
25
25
|
const threadEndRef = useRef(null);
|
|
26
26
|
const dropdownRef = useRef(null);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const ALIASES: Array<{
|
|
2
|
+
value: string;
|
|
3
|
+
label: string;
|
|
4
|
+
}>;
|
|
5
|
+
export declare const STATUS_STYLE: Record<string, {
|
|
6
|
+
bg: string;
|
|
7
|
+
color: string;
|
|
8
|
+
}>;
|
|
9
|
+
export declare const REWRITE_STYLES: {
|
|
10
|
+
id: string;
|
|
11
|
+
label: string;
|
|
12
|
+
desc: string;
|
|
13
|
+
}[];
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface Message {
|
|
2
|
+
id: string | number;
|
|
3
|
+
body: string;
|
|
4
|
+
bodyHtml?: string;
|
|
5
|
+
authorType: 'client' | 'admin' | 'email';
|
|
6
|
+
isInternal?: boolean;
|
|
7
|
+
isSolution?: boolean;
|
|
8
|
+
createdAt: string;
|
|
9
|
+
fromChat?: boolean;
|
|
10
|
+
fromAlias?: string;
|
|
11
|
+
attachments?: Array<{
|
|
12
|
+
file: {
|
|
13
|
+
id: number;
|
|
14
|
+
url?: string;
|
|
15
|
+
filename?: string;
|
|
16
|
+
mimeType?: string;
|
|
17
|
+
} | number;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
20
|
+
export interface ClientInfo {
|
|
21
|
+
id: number;
|
|
22
|
+
company: string;
|
|
23
|
+
firstName: string;
|
|
24
|
+
lastName: string;
|
|
25
|
+
email: string;
|
|
26
|
+
phone?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface TimeEntry {
|
|
29
|
+
id: string | number;
|
|
30
|
+
duration: number;
|
|
31
|
+
description?: string;
|
|
32
|
+
date: string;
|
|
33
|
+
}
|
|
34
|
+
export interface ActivityEntry {
|
|
35
|
+
id: string | number;
|
|
36
|
+
action: string;
|
|
37
|
+
detail?: string;
|
|
38
|
+
actorType?: string;
|
|
39
|
+
createdAt: string;
|
|
40
|
+
}
|
|
@@ -141,11 +141,11 @@ const TicketInboxClient = () => {
|
|
|
141
141
|
try {
|
|
142
142
|
const sn = `where[and][0][or][0][snoozeUntil][exists]=false&where[and][0][or][1][snoozeUntil][less_than_equal]=${encodeURIComponent((/* @__PURE__ */ new Date()).toISOString())}`;
|
|
143
143
|
const [all, openRes, waiting, resolved, breach] = await Promise.all([
|
|
144
|
-
fetch(`/api/tickets
|
|
145
|
-
fetch(`/api/tickets?
|
|
146
|
-
fetch(`/api/tickets?
|
|
147
|
-
fetch(`/api/tickets?
|
|
148
|
-
fetch(`/api/tickets?
|
|
144
|
+
fetch(`/api/tickets/count?${sn}`, { credentials: "include" }),
|
|
145
|
+
fetch(`/api/tickets/count?where[status][equals]=open&${sn}`, { credentials: "include" }),
|
|
146
|
+
fetch(`/api/tickets/count?where[status][equals]=waiting_client&${sn}`, { credentials: "include" }),
|
|
147
|
+
fetch(`/api/tickets/count?where[status][equals]=resolved&${sn}`, { credentials: "include" }),
|
|
148
|
+
fetch(`/api/tickets/count?where[or][0][slaFirstResponseBreached][equals]=true&where[or][1][slaResolutionBreached][equals]=true&${sn}`, { credentials: "include" })
|
|
149
149
|
]);
|
|
150
150
|
const [a, o, w, r, b] = await Promise.all([all.json(), openRes.json(), waiting.json(), resolved.json(), breach.json()]);
|
|
151
151
|
setCounts({
|
|
@@ -4,7 +4,8 @@ import React, { useState, useEffect } from 'react';
|
|
|
4
4
|
import { Settings, Mail, Bot, Clock, Timer, Globe, FileSignature } from 'lucide-react';
|
|
5
5
|
import { btnStyle, V } from '../shared/adminTokens.js';
|
|
6
6
|
import { AdminViewHeader } from '../shared/AdminViewHeader.js';
|
|
7
|
-
import {
|
|
7
|
+
import { DEFAULT_FEATURES } from '../../components/TicketConversation/config.js';
|
|
8
|
+
import { useFeatures } from '../../components/TicketConversation/hooks/useFeatures.js';
|
|
8
9
|
import { useTranslation } from '../../components/TicketConversation/hooks/useTranslation.js';
|
|
9
10
|
import ts from '../../styles/TicketingSettings.module.scss';
|
|
10
11
|
|
|
@@ -206,7 +207,7 @@ const TicketingSettingsClient = () => {
|
|
|
206
207
|
const { t } = useTranslation();
|
|
207
208
|
const FEATURE_LIST = React.useMemo(() => getFeatureList(t), [t]);
|
|
208
209
|
const CATEGORIES = React.useMemo(() => getCategories(t), [t]);
|
|
209
|
-
const
|
|
210
|
+
const { features, setFeatures, save: saveFeatures } = useFeatures();
|
|
210
211
|
const [settings, setSettings] = useState(DEFAULT_SETTINGS);
|
|
211
212
|
const [signature, setSignature] = useState("");
|
|
212
213
|
const [saved, setSaved] = useState(false);
|
|
@@ -253,13 +254,13 @@ const TicketingSettingsClient = () => {
|
|
|
253
254
|
};
|
|
254
255
|
const handleSave = async () => {
|
|
255
256
|
setSaving(true);
|
|
256
|
-
saveFeatures(features);
|
|
257
|
+
const featuresOk = await saveFeatures(features);
|
|
257
258
|
const [settingsOk, prefsOk] = await Promise.all([
|
|
258
259
|
saveSettingsToAPI(settings),
|
|
259
260
|
saveUserPrefs({ locale: settings.locale.language, signature })
|
|
260
261
|
]);
|
|
261
262
|
setSaving(false);
|
|
262
|
-
if (settingsOk && prefsOk) {
|
|
263
|
+
if (featuresOk && settingsOk && prefsOk) {
|
|
263
264
|
setSaved(true);
|
|
264
265
|
setTimeout(() => setSaved(false), 3e3);
|
|
265
266
|
}
|
|
@@ -299,7 +300,7 @@ const TicketingSettingsClient = () => {
|
|
|
299
300
|
/* @__PURE__ */ jsxs(
|
|
300
301
|
CollapsibleSection,
|
|
301
302
|
{
|
|
302
|
-
title: t("settingsView.
|
|
303
|
+
title: t("settingsView.featuresTitle"),
|
|
303
304
|
icon: /* @__PURE__ */ jsx(Settings, { size: 16 }),
|
|
304
305
|
color: V.blue,
|
|
305
306
|
badge: /* @__PURE__ */ jsxs("span", { className: ts.badge, style: { backgroundColor: "#dbeafe", color: "#1e40af" }, children: [
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface AdminViewHeaderProps {
|
|
3
|
+
icon: React.ReactNode;
|
|
4
|
+
title: string;
|
|
5
|
+
subtitle?: string;
|
|
6
|
+
breadcrumb?: {
|
|
7
|
+
label: string;
|
|
8
|
+
href: string;
|
|
9
|
+
};
|
|
10
|
+
actions?: React.ReactNode;
|
|
11
|
+
}
|
|
12
|
+
export declare const AdminViewHeader: React.FC<AdminViewHeaderProps>;
|
|
13
|
+
export {};
|