@dragonmastery/dragoncore-vue 0.0.8 → 0.0.9

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.
Files changed (108) hide show
  1. package/package.json +4 -13
  2. package/dist/AppLink-CHMMrSFI.js +0 -54
  3. package/dist/AppLink-CHMMrSFI.js.map +0 -1
  4. package/dist/Appearance-BfPdKMXw.js +0 -70
  5. package/dist/Appearance-BfPdKMXw.js.map +0 -1
  6. package/dist/Appearance-C3WguxT-.js +0 -3
  7. package/dist/ChangePasswordPage-DCews8GU.js +0 -86
  8. package/dist/ChangePasswordPage-DCews8GU.js.map +0 -1
  9. package/dist/ChangePasswordPage-Dm5vW0nl.js +0 -6
  10. package/dist/CreateTeamForm-Cg4sD65k.js +0 -27
  11. package/dist/CreateTeamMemberForm-CiG-fCJD.js +0 -27
  12. package/dist/CreateUserPage-B8qeBZij.js +0 -76
  13. package/dist/CreateUserPage-B8qeBZij.js.map +0 -1
  14. package/dist/CreateUserPage-WjYDkwpb.js +0 -6
  15. package/dist/CreditBalanceDashboard-BUdKWieE.js +0 -27
  16. package/dist/CreditManagement-BcyUY_J0.js +0 -27
  17. package/dist/CustomerCreateSupportTicketForm-BplS0xSi.js +0 -27
  18. package/dist/CustomerSupportTicketDetailPage-DZQCplSM.js +0 -717
  19. package/dist/CustomerSupportTicketDetailPage-DZQCplSM.js.map +0 -1
  20. package/dist/CustomerSupportTicketList-DGwy4Wje.js +0 -27
  21. package/dist/CustomerSupportTicketParent-BnmTFigo.js +0 -7
  22. package/dist/CustomerSupportTicketParent-BzY4pmBk.js +0 -66
  23. package/dist/CustomerSupportTicketParent-BzY4pmBk.js.map +0 -1
  24. package/dist/CustomerSupportTicketSuccess-DC1jJG1E.js +0 -27
  25. package/dist/EditTeamForm-BnPwhv5B.js +0 -27
  26. package/dist/EditTeamMemberForm-B8-pI6Xm.js +0 -6
  27. package/dist/EditTeamMemberForm-CKbKomrL.js +0 -191
  28. package/dist/EditTeamMemberForm-CKbKomrL.js.map +0 -1
  29. package/dist/EditUserPage-BG-Fkx_c.js +0 -7
  30. package/dist/EditUserPage-XqF25iwz.js +0 -112
  31. package/dist/EditUserPage-XqF25iwz.js.map +0 -1
  32. package/dist/ForgotPassword-CjWv2V7p.js +0 -7
  33. package/dist/ForgotPassword-D3bjL48L.js +0 -73
  34. package/dist/ForgotPassword-D3bjL48L.js.map +0 -1
  35. package/dist/LoginForm--br4Il85.js +0 -7
  36. package/dist/LoginForm-C85U2E2r.js +0 -116
  37. package/dist/LoginForm-C85U2E2r.js.map +0 -1
  38. package/dist/Logout-DHT-5Qz3.js +0 -6
  39. package/dist/Logout-DZuWLh0O.js +0 -38
  40. package/dist/Logout-DZuWLh0O.js.map +0 -1
  41. package/dist/ResetPassword-M6mvTS24.js +0 -27
  42. package/dist/SavedFiltersPage-D3vJrfzt.js +0 -419
  43. package/dist/SavedFiltersPage-D3vJrfzt.js.map +0 -1
  44. package/dist/Signup-VZa7U-Ur.js +0 -7
  45. package/dist/Signup-hpV8J5cM.js +0 -106
  46. package/dist/Signup-hpV8J5cM.js.map +0 -1
  47. package/dist/StaffCreateSupportTicketForm-Bc7UnK0Q.js +0 -27
  48. package/dist/StaffSupportTicketDetailPage-DY07Ez0R.js +0 -1928
  49. package/dist/StaffSupportTicketDetailPage-DY07Ez0R.js.map +0 -1
  50. package/dist/StaffSupportTicketList-ChJP_67k.js +0 -27
  51. package/dist/StaffSupportTicketParent-CWWhaM37.js +0 -66
  52. package/dist/StaffSupportTicketParent-CWWhaM37.js.map +0 -1
  53. package/dist/StaffSupportTicketParent-Dp1G85wc.js +0 -7
  54. package/dist/StaffSupportTicketSuccess-B6X_dP4f.js +0 -27
  55. package/dist/SupportStaffPage-nd0HowtH.js +0 -156
  56. package/dist/SupportStaffPage-nd0HowtH.js.map +0 -1
  57. package/dist/SupportTicketDevLifecycleBadge-Ba-Rm6QW.js +0 -116
  58. package/dist/SupportTicketDevLifecycleBadge-Ba-Rm6QW.js.map +0 -1
  59. package/dist/SupportTicketMaintenancePage-rcJ7EfDj.js +0 -56
  60. package/dist/SupportTicketMaintenancePage-rcJ7EfDj.js.map +0 -1
  61. package/dist/TeamAttachmentsTab-Dk_Bnk-1.js +0 -27
  62. package/dist/TeamHistoryTab-CNelXR3Q.js +0 -232
  63. package/dist/TeamHistoryTab-CNelXR3Q.js.map +0 -1
  64. package/dist/TeamHistoryTab-siesF93u.js +0 -4
  65. package/dist/TeamList-BtLzbjls.js +0 -27
  66. package/dist/TeamMemberList-EoDXIr0w.js +0 -27
  67. package/dist/TeamMemberParent-DZ5YVyi6.js +0 -27
  68. package/dist/TeamMembersTab-4gmnP9sD.js +0 -21
  69. package/dist/TeamMembersTab-4gmnP9sD.js.map +0 -1
  70. package/dist/TeamMembersTab-DTJxmb-M.js +0 -3
  71. package/dist/TeamNotesTab-BhVRLG8h.js +0 -458
  72. package/dist/TeamNotesTab-BhVRLG8h.js.map +0 -1
  73. package/dist/TeamNotesTab-Crp-afAe.js +0 -7
  74. package/dist/TeamParent-CbrXXzAr.js +0 -27
  75. package/dist/TimelineNoteInput-BVqF4MtZ.js +0 -513
  76. package/dist/TimelineNoteInput-BVqF4MtZ.js.map +0 -1
  77. package/dist/TimelineSystemEvent-D58zN850.js +0 -1897
  78. package/dist/TimelineSystemEvent-D58zN850.js.map +0 -1
  79. package/dist/UserListPage-D68AjrjM.js +0 -4
  80. package/dist/UserListPage-OGYOLwlw.js +0 -153
  81. package/dist/UserListPage-OGYOLwlw.js.map +0 -1
  82. package/dist/UserProfilePage-Q68NAGQQ.js +0 -7
  83. package/dist/UserProfilePage-uAIfC_NW.js +0 -125
  84. package/dist/UserProfilePage-uAIfC_NW.js.map +0 -1
  85. package/dist/ViewTeam-DpE_NfRq.js +0 -27
  86. package/dist/ViewTeamMember-BdBwkuXC.js +0 -27
  87. package/dist/convertToLocalDateTime-DOSGtMn8.js +0 -121
  88. package/dist/convertToLocalDateTime-DOSGtMn8.js.map +0 -1
  89. package/dist/displayIdFormatter-B1ZKgofu.js +0 -13
  90. package/dist/displayIdFormatter-B1ZKgofu.js.map +0 -1
  91. package/dist/extractRpcErrorMessage-C_UbKgHL.js +0 -20
  92. package/dist/extractRpcErrorMessage-C_UbKgHL.js.map +0 -1
  93. package/dist/index.d.ts +0 -6366
  94. package/dist/index.js +0 -30
  95. package/dist/src-CEBiyg_f.css +0 -13
  96. package/dist/src-CEBiyg_f.css.map +0 -1
  97. package/dist/src-CHw8DdkR.js +0 -9200
  98. package/dist/src-CHw8DdkR.js.map +0 -1
  99. package/dist/useBreadcrumbs-DmgSucoe.js +0 -41
  100. package/dist/useBreadcrumbs-DmgSucoe.js.map +0 -1
  101. package/dist/useMutation-B4_S4Xoa.js +0 -50
  102. package/dist/useMutation-B4_S4Xoa.js.map +0 -1
  103. package/dist/useQuery-B7ndu5_P.js +0 -107
  104. package/dist/useQuery-B7ndu5_P.js.map +0 -1
  105. package/dist/useQueryCache-DqcDMsxb.js +0 -254
  106. package/dist/useQueryCache-DqcDMsxb.js.map +0 -1
  107. package/dist/useRpcAuth-Dp2sec-X.js +0 -731
  108. package/dist/useRpcAuth-Dp2sec-X.js.map +0 -1
@@ -1,1897 +0,0 @@
1
- import { l as useUserSessionStore, m as useEnv } from "./useRpcAuth-Dp2sec-X.js";
2
- import { t as useMutation } from "./useMutation-B4_S4Xoa.js";
3
- import { t as useQuery } from "./useQuery-B7ndu5_P.js";
4
- import { Fragment, computed, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, nextTick, normalizeClass, normalizeStyle, onMounted, onUnmounted, openBlock, ref, renderList, renderSlot, toDisplayString, unref, watch, withModifiers } from "vue";
5
-
6
- //#region src/components/ConfirmDialog.vue
7
- const _hoisted_1$7 = { class: "font-bold text-lg mb-4" };
8
- const _hoisted_2$4 = { class: "py-4" };
9
- const _hoisted_3$4 = { class: "modal-action" };
10
- const _hoisted_4$4 = ["disabled"];
11
- const _hoisted_5$4 = ["disabled"];
12
- const _sfc_main$7 = /* @__PURE__ */ defineComponent({
13
- __name: "ConfirmDialog",
14
- props: {
15
- modelValue: { type: Boolean },
16
- title: { default: "Confirm" },
17
- message: { default: "Are you sure?" },
18
- confirmText: { default: "Confirm" },
19
- cancelText: { default: "Cancel" },
20
- processingText: { default: "Processing..." },
21
- confirmButtonClass: { default: "btn-primary" },
22
- isProcessing: {
23
- type: Boolean,
24
- default: false
25
- }
26
- },
27
- emits: [
28
- "update:modelValue",
29
- "confirm",
30
- "cancel"
31
- ],
32
- setup(__props, { emit: __emit }) {
33
- const props = __props;
34
- const emit = __emit;
35
- const dialogRef = ref(null);
36
- const isOpen = ref(props.modelValue);
37
- watch(() => props.modelValue, (newValue) => {
38
- isOpen.value = newValue;
39
- if (newValue && dialogRef.value) dialogRef.value.showModal();
40
- else if (dialogRef.value) dialogRef.value.close();
41
- }, { immediate: true });
42
- watch(isOpen, (newValue) => {
43
- if (newValue && dialogRef.value) dialogRef.value.showModal();
44
- else if (dialogRef.value) dialogRef.value.close();
45
- });
46
- const handleConfirm = () => {
47
- emit("confirm");
48
- };
49
- const handleCancel = () => {
50
- isOpen.value = false;
51
- emit("update:modelValue", false);
52
- emit("cancel");
53
- };
54
- const handleBackdropClick = (event) => {
55
- if (event.target === dialogRef.value) handleCancel();
56
- };
57
- return (_ctx, _cache) => {
58
- return openBlock(), createElementBlock("dialog", {
59
- ref_key: "dialogRef",
60
- ref: dialogRef,
61
- class: normalizeClass(["modal", { "modal-open": isOpen.value }]),
62
- onClick: handleBackdropClick
63
- }, [createElementVNode("div", {
64
- class: "modal-box",
65
- onClick: _cache[0] || (_cache[0] = withModifiers(() => {}, ["stop"]))
66
- }, [
67
- createElementVNode("h3", _hoisted_1$7, toDisplayString(__props.title), 1),
68
- createElementVNode("div", _hoisted_2$4, [renderSlot(_ctx.$slots, "message", {}, () => [createElementVNode("p", null, toDisplayString(__props.message), 1)])]),
69
- createElementVNode("div", _hoisted_3$4, [createElementVNode("button", {
70
- class: "btn btn-outline",
71
- onClick: withModifiers(handleCancel, ["prevent"]),
72
- disabled: __props.isProcessing,
73
- type: "button"
74
- }, toDisplayString(__props.cancelText), 9, _hoisted_4$4), createElementVNode("button", {
75
- class: normalizeClass(["btn", __props.confirmButtonClass]),
76
- onClick: withModifiers(handleConfirm, ["prevent"]),
77
- disabled: __props.isProcessing,
78
- type: "button"
79
- }, toDisplayString(__props.isProcessing ? __props.processingText : __props.confirmText), 11, _hoisted_5$4)])
80
- ])], 2);
81
- };
82
- }
83
- });
84
- var ConfirmDialog_default = _sfc_main$7;
85
-
86
- //#endregion
87
- //#region src/components/ImageModal.vue
88
- const _hoisted_1$6 = { class: "modal-box w-full max-w-full h-full max-h-full p-0 flex flex-col bg-base-100" };
89
- const _hoisted_2$3 = { class: "flex items-center justify-between p-3 sm:p-4 border-b border-base-300 flex-shrink-0 bg-base-100/95 backdrop-blur" };
90
- const _hoisted_3$3 = { class: "flex-1 min-w-0 mr-2" };
91
- const _hoisted_4$3 = { class: "font-semibold text-sm sm:text-base truncate" };
92
- const _hoisted_5$3 = {
93
- key: 0,
94
- class: "text-xs text-base-content/60 mt-0.5"
95
- };
96
- const _hoisted_6$3 = { class: "flex items-center gap-1 sm:gap-2 flex-shrink-0" };
97
- const _hoisted_7$2 = {
98
- key: 0,
99
- class: "absolute inset-0 flex items-center justify-center"
100
- };
101
- const _hoisted_8$2 = {
102
- key: 1,
103
- class: "absolute inset-0 flex flex-col items-center justify-center p-4 text-center"
104
- };
105
- const _hoisted_9$2 = ["src", "alt"];
106
- const _hoisted_10$2 = ["disabled"];
107
- const _hoisted_11$2 = ["disabled"];
108
- const _hoisted_12$2 = {
109
- method: "dialog",
110
- class: "modal-backdrop"
111
- };
112
- const minScale = .5;
113
- const maxScale = 5;
114
- const zoomStep = .05;
115
- const _sfc_main$6 = /* @__PURE__ */ defineComponent({
116
- __name: "ImageModal",
117
- props: {
118
- isOpen: { type: Boolean },
119
- imageSrc: { default: "" },
120
- imageName: { default: "" },
121
- imageIndex: { default: null },
122
- totalImages: { default: null },
123
- onDownload: {
124
- type: Function,
125
- default: void 0
126
- },
127
- onPrevious: {
128
- type: Function,
129
- default: void 0
130
- },
131
- onNext: {
132
- type: Function,
133
- default: void 0
134
- }
135
- },
136
- emits: ["close"],
137
- setup(__props, { emit: __emit }) {
138
- const props = __props;
139
- const emit = __emit;
140
- const dialogRef = ref(null);
141
- const imageRef = ref(null);
142
- const imageContainerRef = ref(null);
143
- const isOpen = ref(props.isOpen);
144
- const isLoading = ref(false);
145
- const hasError = ref(false);
146
- const scale = ref(1);
147
- const translateX = ref(0);
148
- const translateY = ref(0);
149
- const rotation = ref(0);
150
- const isDragging = ref(false);
151
- const isPinching = ref(false);
152
- const isResetting = ref(false);
153
- const touchState = ref(null);
154
- const mouseDragState = ref(null);
155
- const isMobile = ref(false);
156
- const imageStyle = computed(() => ({
157
- transform: `scale(${scale.value}) translate(${translateX.value}px, ${translateY.value}px) rotate(${rotation.value}deg)`,
158
- cursor: mouseDragState.value?.isDragging ? "grabbing" : scale.value > 1 ? "grab" : "default",
159
- touchAction: "none",
160
- userSelect: "none",
161
- willChange: isDragging.value || isPinching.value ? "transform" : "auto"
162
- }));
163
- const showNavigation = computed(() => {
164
- return props.totalImages !== null && props.totalImages > 1 && (props.onPrevious || props.onNext);
165
- });
166
- watch(() => props.isOpen, (newValue) => {
167
- isOpen.value = newValue;
168
- if (newValue && dialogRef.value) {
169
- dialogRef.value.showModal();
170
- isResetting.value = true;
171
- scale.value = 1;
172
- translateX.value = 0;
173
- translateY.value = 0;
174
- rotation.value = 0;
175
- touchState.value = null;
176
- mouseDragState.value = null;
177
- isDragging.value = false;
178
- isPinching.value = false;
179
- isLoading.value = true;
180
- hasError.value = false;
181
- nextTick(() => {
182
- setTimeout(() => {
183
- isResetting.value = false;
184
- }, 50);
185
- });
186
- nextTick(() => {
187
- if (imageContainerRef.value) {
188
- imageContainerRef.value.addEventListener("wheel", handleWheel, { passive: false });
189
- imageContainerRef.value.addEventListener("mousedown", handleMouseDown);
190
- imageContainerRef.value.addEventListener("touchstart", handleTouchStart, { passive: false });
191
- imageContainerRef.value.addEventListener("touchmove", handleTouchMove, { passive: false });
192
- imageContainerRef.value.addEventListener("touchend", handleTouchEnd, { passive: true });
193
- imageContainerRef.value.addEventListener("touchcancel", handleTouchEnd, { passive: true });
194
- }
195
- document.addEventListener("mousemove", handleMouseMove);
196
- document.addEventListener("mouseup", handleMouseUp);
197
- });
198
- } else if (dialogRef.value) {
199
- dialogRef.value.close();
200
- if (imageContainerRef.value) {
201
- imageContainerRef.value.removeEventListener("wheel", handleWheel);
202
- imageContainerRef.value.removeEventListener("mousedown", handleMouseDown);
203
- imageContainerRef.value.removeEventListener("touchstart", handleTouchStart);
204
- imageContainerRef.value.removeEventListener("touchmove", handleTouchMove);
205
- imageContainerRef.value.removeEventListener("touchend", handleTouchEnd);
206
- imageContainerRef.value.removeEventListener("touchcancel", handleTouchEnd);
207
- }
208
- document.removeEventListener("mousemove", handleMouseMove);
209
- document.removeEventListener("mouseup", handleMouseUp);
210
- touchState.value = null;
211
- mouseDragState.value = null;
212
- }
213
- }, { immediate: true });
214
- watch(() => props.imageSrc, () => {
215
- if (props.isOpen && props.imageSrc) {
216
- isLoading.value = true;
217
- hasError.value = false;
218
- scale.value = 1;
219
- translateX.value = 0;
220
- translateY.value = 0;
221
- rotation.value = 0;
222
- }
223
- });
224
- watch(() => props.imageIndex, () => {
225
- if (props.isOpen) {
226
- resetZoom();
227
- isLoading.value = true;
228
- hasError.value = false;
229
- }
230
- });
231
- onMounted(() => {
232
- isMobile.value = window.innerWidth < 768;
233
- window.addEventListener("resize", () => {
234
- isMobile.value = window.innerWidth < 768;
235
- });
236
- });
237
- const handleKeydown = (event) => {
238
- if (!props.isOpen) return;
239
- switch (event.key) {
240
- case "Escape":
241
- handleClose();
242
- break;
243
- case "ArrowLeft":
244
- if (showNavigation.value && props.imageIndex !== null && props.imageIndex > 0) {
245
- event.preventDefault();
246
- goToPrevious();
247
- }
248
- break;
249
- case "ArrowRight":
250
- if (showNavigation.value && props.imageIndex !== null && props.imageIndex < (props.totalImages || 0) - 1) {
251
- event.preventDefault();
252
- goToNext();
253
- }
254
- break;
255
- case "+":
256
- case "=":
257
- event.preventDefault();
258
- zoomIn();
259
- break;
260
- case "-":
261
- event.preventDefault();
262
- zoomOut();
263
- break;
264
- case "0":
265
- event.preventDefault();
266
- resetZoom();
267
- break;
268
- }
269
- };
270
- const zoomIn = () => {
271
- if (scale.value < maxScale) {
272
- const newScale = scale.value + zoomStep;
273
- scale.value = Math.min(maxScale, newScale);
274
- }
275
- };
276
- const zoomOut = () => {
277
- if (scale.value > minScale) {
278
- const newScale = scale.value - zoomStep;
279
- scale.value = Math.max(minScale, newScale);
280
- if (scale.value <= 1) {
281
- translateX.value = 0;
282
- translateY.value = 0;
283
- }
284
- }
285
- };
286
- const resetZoom = () => {
287
- isResetting.value = true;
288
- scale.value = 1;
289
- translateX.value = 0;
290
- translateY.value = 0;
291
- rotation.value = 0;
292
- touchState.value = null;
293
- mouseDragState.value = null;
294
- isDragging.value = false;
295
- isPinching.value = false;
296
- nextTick(() => {
297
- setTimeout(() => {
298
- isResetting.value = false;
299
- }, 50);
300
- });
301
- };
302
- const rotateImage = () => {
303
- rotation.value = rotation.value + 90;
304
- };
305
- const handleWheel = (event) => {
306
- if (!imageRef.value || !props.isOpen) return;
307
- event.preventDefault();
308
- const scrollDirection = (event.deltaY !== 0 ? event.deltaY : event.deltaX) > 0 ? -1 : 1;
309
- const oldScale = scale.value;
310
- const newScale = scrollDirection > 0 ? oldScale + zoomStep : oldScale - zoomStep;
311
- const clampedScale = Math.max(minScale, Math.min(maxScale, newScale));
312
- if (Math.abs(clampedScale - oldScale) > .001) {
313
- const containerRect = imageContainerRef.value?.getBoundingClientRect();
314
- if (!containerRect) return;
315
- const mouseX = event.clientX - containerRect.left - containerRect.width / 2;
316
- const mouseY = event.clientY - containerRect.top - containerRect.height / 2;
317
- const imagePointX = (mouseX - translateX.value) / oldScale;
318
- const imagePointY = (mouseY - translateY.value) / oldScale;
319
- scale.value = clampedScale;
320
- translateX.value = mouseX - imagePointX * clampedScale;
321
- translateY.value = mouseY - imagePointY * clampedScale;
322
- if (scale.value <= 1) {
323
- translateX.value = 0;
324
- translateY.value = 0;
325
- }
326
- }
327
- };
328
- const handleMouseDown = (event) => {
329
- if (!imageRef.value || !props.isOpen || scale.value <= 1) return;
330
- if (event.button !== 0) return;
331
- event.preventDefault();
332
- isDragging.value = true;
333
- mouseDragState.value = {
334
- isDragging: true,
335
- startX: event.clientX,
336
- startY: event.clientY,
337
- startTranslateX: translateX.value,
338
- startTranslateY: translateY.value
339
- };
340
- if (imageRef.value) imageRef.value.style.cursor = "grabbing";
341
- };
342
- const handleMouseMove = (event) => {
343
- if (!mouseDragState.value || !mouseDragState.value.isDragging || !props.isOpen) return;
344
- event.preventDefault();
345
- requestAnimationFrame(() => {
346
- if (!mouseDragState.value) return;
347
- const deltaX = event.clientX - mouseDragState.value.startX;
348
- const deltaY = event.clientY - mouseDragState.value.startY;
349
- translateX.value = mouseDragState.value.startTranslateX + deltaX / scale.value;
350
- translateY.value = mouseDragState.value.startTranslateY + deltaY / scale.value;
351
- const maxTranslate = 300;
352
- translateX.value = Math.max(-maxTranslate, Math.min(maxTranslate, translateX.value));
353
- translateY.value = Math.max(-maxTranslate, Math.min(maxTranslate, translateY.value));
354
- });
355
- };
356
- const handleMouseUp = () => {
357
- if (mouseDragState.value) mouseDragState.value.isDragging = false;
358
- mouseDragState.value = null;
359
- isDragging.value = false;
360
- if (imageRef.value) imageRef.value.style.cursor = scale.value > 1 ? "grab" : "default";
361
- };
362
- const getTouchDistance = (touches) => {
363
- if (touches.length < 2) return 0;
364
- const touch1 = touches[0];
365
- const touch2 = touches[1];
366
- if (!touch1 || !touch2) return 0;
367
- const dx = touch1.clientX - touch2.clientX;
368
- const dy = touch1.clientY - touch2.clientY;
369
- return Math.sqrt(dx * dx + dy * dy);
370
- };
371
- const getTouchCenter = (touches) => {
372
- if (touches.length === 0) return null;
373
- if (touches.length === 1) {
374
- const touch = touches[0];
375
- if (!touch) return null;
376
- return {
377
- x: touch.clientX,
378
- y: touch.clientY
379
- };
380
- }
381
- const touch1 = touches[0];
382
- const touch2 = touches[1];
383
- if (!touch1 || !touch2) return null;
384
- return {
385
- x: (touch1.clientX + touch2.clientX) / 2,
386
- y: (touch1.clientY + touch2.clientY) / 2
387
- };
388
- };
389
- const handleImageTouchStart = (event) => {
390
- if (!imageRef.value || !props.isOpen) return;
391
- event.preventDefault();
392
- event.stopPropagation();
393
- const touches = Array.from(event.touches);
394
- if (touches.length === 2) {
395
- isPinching.value = true;
396
- const distance = getTouchDistance(touches);
397
- if (getTouchCenter(touches)) touchState.value = {
398
- type: "pinch",
399
- initialDistance: distance,
400
- initialScale: scale.value,
401
- initialTranslateX: translateX.value,
402
- initialTranslateY: translateY.value,
403
- initialTouches: touches
404
- };
405
- } else if (touches.length === 1 && scale.value > 1) {
406
- isDragging.value = true;
407
- const touch = touches[0];
408
- if (touch) touchState.value = {
409
- type: "pan",
410
- lastPanX: touch.clientX,
411
- lastPanY: touch.clientY
412
- };
413
- } else {
414
- touchState.value = null;
415
- isDragging.value = false;
416
- isPinching.value = false;
417
- }
418
- };
419
- const handleImageTouchMove = (event) => {
420
- if (!imageRef.value || !props.isOpen || !touchState.value) return;
421
- event.preventDefault();
422
- event.stopPropagation();
423
- handleTouchMove(event);
424
- };
425
- const handleImageTouchEnd = (event) => {
426
- if (!imageRef.value || !props.isOpen) return;
427
- event.preventDefault();
428
- event.stopPropagation();
429
- handleTouchEnd();
430
- };
431
- const handleTouchStart = (event) => {
432
- if (!imageRef.value || !props.isOpen) return;
433
- const target = event.target;
434
- if (target.closest(".zoom-controls")) return;
435
- if (target === imageRef.value || imageRef.value.contains(target)) return;
436
- event.preventDefault();
437
- const touches = Array.from(event.touches);
438
- if (touches.length === 2) {
439
- isPinching.value = true;
440
- const distance = getTouchDistance(touches);
441
- if (getTouchCenter(touches)) touchState.value = {
442
- type: "pinch",
443
- initialDistance: distance,
444
- initialScale: scale.value,
445
- initialTranslateX: translateX.value,
446
- initialTranslateY: translateY.value,
447
- initialTouches: touches
448
- };
449
- } else if (touches.length === 1 && scale.value > 1) {
450
- isDragging.value = true;
451
- const touch = touches[0];
452
- if (touch) touchState.value = {
453
- type: "pan",
454
- lastPanX: touch.clientX,
455
- lastPanY: touch.clientY
456
- };
457
- } else {
458
- touchState.value = null;
459
- isDragging.value = false;
460
- isPinching.value = false;
461
- }
462
- };
463
- const handleTouchMove = (event) => {
464
- if (!imageRef.value || !props.isOpen || !touchState.value) return;
465
- const touches = Array.from(event.touches);
466
- requestAnimationFrame(() => {
467
- if (!touchState.value) return;
468
- if (touchState.value.type === "pinch" && touches.length === 2) {
469
- event.preventDefault();
470
- const distance = getTouchDistance(touches);
471
- if (touchState.value.initialDistance && touchState.value.initialScale !== void 0) {
472
- const scaleChange = distance / touchState.value.initialDistance;
473
- scale.value = Math.max(minScale, Math.min(maxScale, touchState.value.initialScale * scaleChange));
474
- }
475
- } else if (touchState.value.type === "pan" && touches.length === 1 && scale.value > 1) {
476
- event.preventDefault();
477
- const touch = touches[0];
478
- if (touch && touchState.value.lastPanX !== void 0 && touchState.value.lastPanY !== void 0) {
479
- const deltaX = touch.clientX - touchState.value.lastPanX;
480
- const deltaY = touch.clientY - touchState.value.lastPanY;
481
- translateX.value += deltaX / scale.value;
482
- translateY.value += deltaY / scale.value;
483
- const maxTranslate = 200;
484
- translateX.value = Math.max(-maxTranslate, Math.min(maxTranslate, translateX.value));
485
- translateY.value = Math.max(-maxTranslate, Math.min(maxTranslate, translateY.value));
486
- touchState.value.lastPanX = touch.clientX;
487
- touchState.value.lastPanY = touch.clientY;
488
- }
489
- } else {
490
- touchState.value = null;
491
- isDragging.value = false;
492
- isPinching.value = false;
493
- }
494
- });
495
- };
496
- const handleTouchEnd = () => {
497
- touchState.value = null;
498
- isDragging.value = false;
499
- isPinching.value = false;
500
- };
501
- const handleImageLoad = () => {
502
- isLoading.value = false;
503
- hasError.value = false;
504
- };
505
- const handleImageError = () => {
506
- isLoading.value = false;
507
- hasError.value = true;
508
- };
509
- const goToPrevious = () => {
510
- if (props.onPrevious) props.onPrevious();
511
- };
512
- const goToNext = () => {
513
- if (props.onNext) props.onNext();
514
- };
515
- const handleDownload = () => {
516
- if (props.onDownload) props.onDownload();
517
- };
518
- const handleClose = () => {
519
- isOpen.value = false;
520
- emit("close");
521
- };
522
- const handleBackdropClick = (event) => {
523
- if (event.target === dialogRef.value) handleClose();
524
- };
525
- onUnmounted(() => {
526
- window.removeEventListener("resize", () => {});
527
- if (imageContainerRef.value) {
528
- imageContainerRef.value.removeEventListener("wheel", handleWheel);
529
- imageContainerRef.value.removeEventListener("mousedown", handleMouseDown);
530
- imageContainerRef.value.removeEventListener("touchstart", handleTouchStart);
531
- imageContainerRef.value.removeEventListener("touchmove", handleTouchMove);
532
- imageContainerRef.value.removeEventListener("touchend", handleTouchEnd);
533
- imageContainerRef.value.removeEventListener("touchcancel", handleTouchEnd);
534
- }
535
- document.removeEventListener("mousemove", handleMouseMove);
536
- document.removeEventListener("mouseup", handleMouseUp);
537
- });
538
- return (_ctx, _cache) => {
539
- return openBlock(), createElementBlock("dialog", {
540
- ref_key: "dialogRef",
541
- ref: dialogRef,
542
- class: normalizeClass(["modal", { "modal-open": isOpen.value }]),
543
- onClick: handleBackdropClick,
544
- onKeydown: handleKeydown
545
- }, [createElementVNode("div", _hoisted_1$6, [
546
- createCommentVNode(" Header "),
547
- createElementVNode("div", _hoisted_2$3, [createElementVNode("div", _hoisted_3$3, [createElementVNode("h3", _hoisted_4$3, toDisplayString(__props.imageName), 1), __props.imageIndex !== null && __props.totalImages !== null ? (openBlock(), createElementBlock("p", _hoisted_5$3, toDisplayString(__props.imageIndex + 1) + " / " + toDisplayString(__props.totalImages), 1)) : createCommentVNode("v-if", true)]), createElementVNode("div", _hoisted_6$3, [
548
- createCommentVNode(" Navigation buttons (if multiple images) "),
549
- showNavigation.value && __props.imageIndex !== null && __props.imageIndex > 0 ? (openBlock(), createElementBlock("button", {
550
- key: 0,
551
- onClick: withModifiers(goToPrevious, ["prevent"]),
552
- class: "btn btn-sm btn-circle btn-ghost",
553
- type: "button",
554
- title: "Previous image",
555
- "aria-label": "Previous image"
556
- }, [..._cache[4] || (_cache[4] = [createElementVNode("svg", {
557
- xmlns: "http://www.w3.org/2000/svg",
558
- class: "h-5 w-5",
559
- fill: "none",
560
- viewBox: "0 0 24 24",
561
- stroke: "currentColor"
562
- }, [createElementVNode("path", {
563
- "stroke-linecap": "round",
564
- "stroke-linejoin": "round",
565
- "stroke-width": "2",
566
- d: "M15 19l-7-7 7-7"
567
- })], -1)])])) : createCommentVNode("v-if", true),
568
- showNavigation.value && __props.imageIndex !== null && __props.imageIndex < (__props.totalImages || 0) - 1 ? (openBlock(), createElementBlock("button", {
569
- key: 1,
570
- onClick: withModifiers(goToNext, ["prevent"]),
571
- class: "btn btn-sm btn-circle btn-ghost",
572
- type: "button",
573
- title: "Next image",
574
- "aria-label": "Next image"
575
- }, [..._cache[5] || (_cache[5] = [createElementVNode("svg", {
576
- xmlns: "http://www.w3.org/2000/svg",
577
- class: "h-5 w-5",
578
- fill: "none",
579
- viewBox: "0 0 24 24",
580
- stroke: "currentColor"
581
- }, [createElementVNode("path", {
582
- "stroke-linecap": "round",
583
- "stroke-linejoin": "round",
584
- "stroke-width": "2",
585
- d: "M9 5l7 7-7 7"
586
- })], -1)])])) : createCommentVNode("v-if", true),
587
- createCommentVNode(" Download button "),
588
- props.onDownload ? (openBlock(), createElementBlock("button", {
589
- key: 2,
590
- onClick: withModifiers(handleDownload, ["prevent"]),
591
- class: "btn btn-sm btn-circle btn-ghost",
592
- type: "button",
593
- title: "Download",
594
- "aria-label": "Download image"
595
- }, [..._cache[6] || (_cache[6] = [createElementVNode("svg", {
596
- xmlns: "http://www.w3.org/2000/svg",
597
- class: "h-5 w-5",
598
- fill: "none",
599
- viewBox: "0 0 24 24",
600
- stroke: "currentColor"
601
- }, [createElementVNode("path", {
602
- "stroke-linecap": "round",
603
- "stroke-linejoin": "round",
604
- "stroke-width": "2",
605
- d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
606
- })], -1)])])) : createCommentVNode("v-if", true),
607
- createCommentVNode(" Close button "),
608
- createElementVNode("button", {
609
- onClick: withModifiers(handleClose, ["prevent"]),
610
- class: "btn btn-sm btn-circle btn-ghost",
611
- type: "button",
612
- title: "Close",
613
- "aria-label": "Close modal"
614
- }, [..._cache[7] || (_cache[7] = [createElementVNode("svg", {
615
- xmlns: "http://www.w3.org/2000/svg",
616
- class: "h-5 w-5",
617
- fill: "none",
618
- viewBox: "0 0 24 24",
619
- stroke: "currentColor"
620
- }, [createElementVNode("path", {
621
- "stroke-linecap": "round",
622
- "stroke-linejoin": "round",
623
- "stroke-width": "2",
624
- d: "M6 18L18 6M6 6l12 12"
625
- })], -1)])])
626
- ])]),
627
- createCommentVNode(" Image container "),
628
- createElementVNode("div", {
629
- ref_key: "imageContainerRef",
630
- ref: imageContainerRef,
631
- class: "flex-1 flex items-center justify-center overflow-hidden bg-base-200 relative"
632
- }, [
633
- createCommentVNode(" Loading state "),
634
- isLoading.value ? (openBlock(), createElementBlock("div", _hoisted_7$2, [..._cache[8] || (_cache[8] = [createElementVNode("span", { class: "loading loading-spinner loading-lg" }, null, -1)])])) : createCommentVNode("v-if", true),
635
- createCommentVNode(" Error state "),
636
- hasError.value ? (openBlock(), createElementBlock("div", _hoisted_8$2, [..._cache[9] || (_cache[9] = [createElementVNode("svg", {
637
- xmlns: "http://www.w3.org/2000/svg",
638
- class: "h-12 w-12 text-error mb-2",
639
- fill: "none",
640
- viewBox: "0 0 24 24",
641
- stroke: "currentColor"
642
- }, [createElementVNode("path", {
643
- "stroke-linecap": "round",
644
- "stroke-linejoin": "round",
645
- "stroke-width": "2",
646
- d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
647
- })], -1), createElementVNode("p", { class: "text-sm text-base-content/70" }, "Failed to load image", -1)])])) : createCommentVNode("v-if", true),
648
- createCommentVNode(" Image "),
649
- __props.imageSrc && !hasError.value ? (openBlock(), createElementBlock("img", {
650
- key: 2,
651
- ref_key: "imageRef",
652
- ref: imageRef,
653
- src: __props.imageSrc,
654
- alt: __props.imageName,
655
- class: normalizeClass(["max-w-full max-h-full object-contain", { "transition-transform duration-300 ease-out": !isDragging.value && !isPinching.value && !isResetting.value }]),
656
- style: normalizeStyle(imageStyle.value),
657
- onLoad: handleImageLoad,
658
- onError: handleImageError,
659
- onDblclick: resetZoom,
660
- onTouchstart: withModifiers(handleImageTouchStart, ["stop"]),
661
- onTouchmove: withModifiers(handleImageTouchMove, ["stop"]),
662
- onTouchend: withModifiers(handleImageTouchEnd, ["stop"])
663
- }, null, 46, _hoisted_9$2)) : createCommentVNode("v-if", true),
664
- createCommentVNode(" Zoom controls "),
665
- __props.imageSrc && !hasError.value ? (openBlock(), createElementBlock("div", {
666
- key: 3,
667
- class: "zoom-controls absolute bottom-4 left-1/2 transform -translate-x-1/2 flex gap-1 sm:gap-2 bg-base-100/90 backdrop-blur rounded-lg p-1.5 sm:p-2 shadow-lg z-10",
668
- onTouchstart: _cache[0] || (_cache[0] = withModifiers(() => {}, ["stop"])),
669
- onTouchmove: _cache[1] || (_cache[1] = withModifiers(() => {}, ["stop"])),
670
- onTouchend: _cache[2] || (_cache[2] = withModifiers(() => {}, ["stop"])),
671
- onClick: _cache[3] || (_cache[3] = withModifiers(() => {}, ["stop"]))
672
- }, [
673
- createElementVNode("button", {
674
- onClick: withModifiers(zoomOut, ["prevent"]),
675
- class: "btn btn-sm btn-circle btn-ghost",
676
- type: "button",
677
- disabled: scale.value <= minScale,
678
- title: "Zoom out",
679
- "aria-label": "Zoom out"
680
- }, [..._cache[10] || (_cache[10] = [createElementVNode("svg", {
681
- xmlns: "http://www.w3.org/2000/svg",
682
- class: "h-4 w-4",
683
- fill: "none",
684
- viewBox: "0 0 24 24",
685
- stroke: "currentColor"
686
- }, [createElementVNode("path", {
687
- "stroke-linecap": "round",
688
- "stroke-linejoin": "round",
689
- "stroke-width": "2",
690
- d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM13 10H7"
691
- })], -1)])], 8, _hoisted_10$2),
692
- createElementVNode("button", {
693
- onClick: withModifiers(resetZoom, ["prevent"]),
694
- class: "text-xs flex items-center px-2 text-base-content/70 hover:text-base-content hover:bg-base-200 rounded transition-colors cursor-pointer",
695
- type: "button",
696
- title: "Reset zoom to 100%",
697
- "aria-label": "Reset zoom to 100%"
698
- }, toDisplayString(Math.round(scale.value * 100)) + "% ", 1),
699
- createElementVNode("button", {
700
- onClick: withModifiers(zoomIn, ["prevent"]),
701
- class: "btn btn-sm btn-circle btn-ghost",
702
- type: "button",
703
- disabled: scale.value >= maxScale,
704
- title: "Zoom in",
705
- "aria-label": "Zoom in"
706
- }, [..._cache[11] || (_cache[11] = [createElementVNode("svg", {
707
- xmlns: "http://www.w3.org/2000/svg",
708
- class: "h-4 w-4",
709
- fill: "none",
710
- viewBox: "0 0 24 24",
711
- stroke: "currentColor"
712
- }, [createElementVNode("path", {
713
- "stroke-linecap": "round",
714
- "stroke-linejoin": "round",
715
- "stroke-width": "2",
716
- d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v6m3-3H7"
717
- })], -1)])], 8, _hoisted_11$2),
718
- createElementVNode("button", {
719
- onClick: withModifiers(rotateImage, ["prevent"]),
720
- class: "btn btn-sm btn-circle btn-ghost",
721
- type: "button",
722
- title: "Rotate image",
723
- "aria-label": "Rotate image"
724
- }, [..._cache[12] || (_cache[12] = [createElementVNode("svg", {
725
- xmlns: "http://www.w3.org/2000/svg",
726
- viewBox: "0 0 24 24",
727
- "stroke-width": "1.5",
728
- stroke: "currentColor",
729
- "aria-hidden": "true",
730
- "data-slot": "icon",
731
- fill: "none",
732
- class: "size-6"
733
- }, [createElementVNode("path", {
734
- "stroke-linecap": "round",
735
- "stroke-linejoin": "round",
736
- d: "M18.363 5.634A8.997 9.002 29.494 0 0 7.5 4.206 8.997 9.002 29.494 0 0 3.306 14.33 8.997 9.002 29.494 0 0 11.996 21a8.997 9.002 29.494 0 0 8.694-6.673m-2.327-8.693L20.87 8.14m.017-4.994v5.015m0 0h-5.013"
737
- })], -1)])])
738
- ], 32)) : createCommentVNode("v-if", true)
739
- ], 512)
740
- ]), createElementVNode("form", _hoisted_12$2, [createElementVNode("button", {
741
- type: "button",
742
- onClick: withModifiers(handleClose, ["prevent"])
743
- }, "close")])], 34);
744
- };
745
- }
746
- });
747
- var ImageModal_default = _sfc_main$6;
748
-
749
- //#endregion
750
- //#region src/slices/support_ticket/utils/creditValueFormatter.ts
751
- /**
752
- * Helper function to check if credit value is empty
753
- */
754
- function isCreditValueEmpty(value) {
755
- return value === null || value === void 0 || value.trim() === "";
756
- }
757
- /**
758
- * Core credit formatting logic
759
- */
760
- function formatCreditValueCore(creditValue) {
761
- if (isCreditValueEmpty(creditValue)) return "TBD";
762
- const trimmed = creditValue.trim();
763
- if (parseFloat(trimmed) === 0) return "0";
764
- return trimmed.replace(/\.?0+$/, "");
765
- }
766
- /**
767
- * Formats credit value for staff views (includes internal ticket logic)
768
- *
769
- * @param creditValue - The credit value from the database
770
- * @param approvalStatus - The approval status to determine display logic
771
- * @returns Formatted string for display
772
- *
773
- * @example
774
- * formatStaffCreditValue("5.50", "PENDING") // "5.5"
775
- * formatStaffCreditValue("10", "INTERNAL") // "N/A"
776
- */
777
- function formatStaffCreditValue(creditValue, approvalStatus) {
778
- if (approvalStatus === "INTERNAL") return "N/A";
779
- return formatCreditValueCore(creditValue);
780
- }
781
- /**
782
- * Formats credit value for customer views (status-based logic)
783
- *
784
- * @param creditValue - The credit value from the database
785
- * @param status - The computed status from the customer query
786
- * @returns Formatted string for display
787
- *
788
- * @example
789
- * formatCustomerCreditValue("5.50", "PENDING") // "5.5"
790
- * formatCustomerCreditValue("5.50", "FOLLOWUP") // "5.5"
791
- */
792
- function formatCustomerCreditValue(creditValue) {
793
- return formatCreditValueCore(creditValue);
794
- }
795
-
796
- //#endregion
797
- //#region src/slices/support_ticket/shared/InlineAttachments.vue
798
- const _hoisted_1$5 = { class: "mt-4 sm:mt-6" };
799
- const _hoisted_2$2 = {
800
- key: 0,
801
- class: "grid grid-cols-2 gap-2 sm:gap-4 mb-4"
802
- };
803
- const _hoisted_3$2 = {
804
- key: 0,
805
- class: "hidden sm:block text-xs text-base-content/50"
806
- };
807
- const _hoisted_4$2 = {
808
- key: 1,
809
- class: "mb-4 space-y-2"
810
- };
811
- const _hoisted_5$2 = { class: "flex-1 min-w-0" };
812
- const _hoisted_6$2 = { class: "text-xs sm:text-sm font-medium truncate" };
813
- const _hoisted_7$1 = {
814
- key: 0,
815
- class: "w-full bg-base-300 rounded-full h-1.5 mt-1"
816
- };
817
- const _hoisted_8$1 = {
818
- key: 1,
819
- class: "text-xs text-error mt-1 break-words"
820
- };
821
- const _hoisted_9$1 = { class: "flex gap-2 sm:gap-1" };
822
- const _hoisted_10$1 = ["onClick"];
823
- const _hoisted_11$1 = ["onClick"];
824
- const _hoisted_12$1 = {
825
- key: 2,
826
- class: "space-y-2"
827
- };
828
- const _hoisted_13$1 = {
829
- key: 0,
830
- class: "flex justify-center py-4"
831
- };
832
- const _hoisted_14$1 = ["onClick"];
833
- const _hoisted_15$1 = { class: "flex items-center gap-2 sm:gap-3 flex-1 min-w-0 w-full sm:w-auto" };
834
- const _hoisted_16 = { class: "flex-shrink-0" };
835
- const _hoisted_17 = {
836
- key: 0,
837
- class: "w-10 h-10 sm:w-12 sm:h-12 rounded overflow-hidden bg-base-200 flex items-center justify-center"
838
- };
839
- const _hoisted_18 = ["src", "alt"];
840
- const _hoisted_19 = {
841
- key: 1,
842
- class: "w-full h-full flex items-center justify-center"
843
- };
844
- const _hoisted_20 = { class: "flex-1 min-w-0" };
845
- const _hoisted_21 = { class: "text-sm sm:text-base font-medium truncate" };
846
- const _hoisted_22 = { class: "text-xs text-base-content/60" };
847
- const _hoisted_23 = ["onClick"];
848
- const _hoisted_24 = ["onClick"];
849
- const _hoisted_25 = ["onClick"];
850
- const _hoisted_26 = {
851
- key: 3,
852
- class: "text-center py-8 text-base-content/50"
853
- };
854
- const _sfc_main$5 = /* @__PURE__ */ defineComponent({
855
- __name: "InlineAttachments",
856
- props: {
857
- recordId: {},
858
- canUpload: {
859
- type: Boolean,
860
- default: true
861
- },
862
- canDelete: {
863
- type: Boolean,
864
- default: true
865
- }
866
- },
867
- emits: [
868
- "uploaded",
869
- "deleted",
870
- "filesQueued",
871
- "update:attachmentsCount"
872
- ],
873
- setup(__props, { expose: __expose, emit: __emit }) {
874
- const props = __props;
875
- const emit = __emit;
876
- const uploadQueuedFiles = async (ticketId) => {
877
- const pendingItems = uploadQueue.value.filter((item) => item.status === "pending");
878
- for (const item of pendingItems) {
879
- item.status = "uploading";
880
- await uploadFile(item, ticketId);
881
- }
882
- };
883
- __expose({
884
- getQueuedFiles: () => uploadQueue.value.filter((item) => item.status === "pending").map((item) => item.file),
885
- uploadQueuedFiles
886
- });
887
- const fileInput = ref(null);
888
- const isDragging = ref(false);
889
- const isUploading = ref(false);
890
- const uploadQueue = ref([]);
891
- const fileToDelete = ref(null);
892
- const showDeleteModal = ref(false);
893
- const viewingImage = ref(null);
894
- const imageUrls = ref(/* @__PURE__ */ new Map());
895
- const env = useEnv();
896
- const userStore = useUserSessionStore();
897
- const { data: attachmentsData, loading: attachmentsLoading, refetch: refreshAttachments } = useQuery(async (api) => {
898
- if (!props.recordId) return {
899
- files: [],
900
- folders: []
901
- };
902
- return await api.attachments.listAttachments({
903
- record_id: props.recordId,
904
- record_type: "support_ticket",
905
- filters: {
906
- limit: 100,
907
- include_folders: false,
908
- folder_id: null
909
- }
910
- });
911
- }, {
912
- enabled: computed(() => !!props.recordId),
913
- staleTime: 120 * 1e3
914
- });
915
- const attachments = computed(() => {
916
- if (!attachmentsData.value) return [];
917
- const files = [];
918
- const fileItems = attachmentsData.value.files || [];
919
- for (const item of fileItems) files.push({
920
- id: item.id,
921
- name: item.original_name,
922
- size: parseInt(item.file_size || "0"),
923
- type: item.content_type || "",
924
- uploadedAt: new Date(item.created_at)
925
- });
926
- return files;
927
- });
928
- watch(() => attachments.value.length, (count) => emit("update:attachmentsCount", count), { immediate: true });
929
- const deleteMessage = computed(() => {
930
- if (!fileToDelete.value) return "Are you sure you want to delete this attachment? This action cannot be undone.";
931
- return `Are you sure you want to delete "${fileToDelete.value.name}"? This action cannot be undone.`;
932
- });
933
- const imageAttachments = computed(() => {
934
- return attachments.value.filter((file) => isImage(file.type));
935
- });
936
- const viewingImageIndex = computed(() => {
937
- if (!viewingImage.value) return null;
938
- const index = imageAttachments.value.findIndex((img) => img.id === viewingImage.value?.id);
939
- return index >= 0 ? index : null;
940
- });
941
- const canGoToPrevious = computed(() => {
942
- return viewingImageIndex.value !== null && viewingImageIndex.value > 0;
943
- });
944
- const canGoToNext = computed(() => {
945
- return viewingImageIndex.value !== null && viewingImageIndex.value < imageAttachments.value.length - 1;
946
- });
947
- const { mutate: deleteAttachment } = useMutation((api, input) => api.attachments.deleteAttachment(input.id), { invalidate: /^attachments?:/ });
948
- const getFileTypeLabel = (mimeType) => {
949
- if (mimeType.startsWith("image/")) return "IMG";
950
- if (mimeType.startsWith("video/")) return "VID";
951
- if (mimeType.startsWith("audio/")) return "AUD";
952
- if (mimeType.includes("pdf")) return "PDF";
953
- if (mimeType.includes("word") || mimeType.includes("document")) return "DOC";
954
- if (mimeType.includes("excel") || mimeType.includes("spreadsheet")) return "XLS";
955
- if (mimeType.includes("zip") || mimeType.includes("archive")) return "ZIP";
956
- return "FILE";
957
- };
958
- const getFileTypeBadgeClass = (mimeType) => {
959
- if (mimeType.startsWith("image/")) return "badge-primary";
960
- if (mimeType.startsWith("video/")) return "badge-secondary";
961
- if (mimeType.includes("pdf")) return "badge-error";
962
- if (mimeType.includes("word") || mimeType.includes("document")) return "badge-info";
963
- return "badge-ghost";
964
- };
965
- const formatFileSize = (bytes) => {
966
- if (bytes === 0) return "0 B";
967
- const k = 1024;
968
- const sizes = [
969
- "B",
970
- "KB",
971
- "MB",
972
- "GB"
973
- ];
974
- const i = Math.floor(Math.log(bytes) / Math.log(k));
975
- return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
976
- };
977
- const formatDate = (date) => {
978
- return new Intl.DateTimeFormat("en-US", {
979
- month: "short",
980
- day: "numeric",
981
- year: "numeric"
982
- }).format(date);
983
- };
984
- const isImage = (mimeType) => {
985
- return mimeType.startsWith("image/");
986
- };
987
- const getImageUrl = async (fileId) => {
988
- if (imageUrls.value.has(fileId)) return imageUrls.value.get(fileId);
989
- try {
990
- const res = await fetch(`${env.restApiClient.apiUrl}/attachments/support_ticket/${fileId}`, {
991
- method: "GET",
992
- headers: { Authorization: `Bearer ${userStore.accessToken}` }
993
- });
994
- if (!res.ok) throw new Error("Failed to load image");
995
- const blob = await res.blob();
996
- const url = URL.createObjectURL(blob);
997
- imageUrls.value.set(fileId, url);
998
- return url;
999
- } catch (error) {
1000
- console.error("Failed to load image:", error);
1001
- return "";
1002
- }
1003
- };
1004
- const getImageUrlSync = (fileId) => {
1005
- return imageUrls.value.get(fileId) || "";
1006
- };
1007
- const loadImageThumbnail = async (fileId) => {
1008
- if (!imageUrls.value.has(fileId)) await getImageUrl(fileId);
1009
- };
1010
- const handleImageError = (event) => {
1011
- const img = event.target;
1012
- img.style.display = "none";
1013
- };
1014
- onUnmounted(() => {
1015
- imageUrls.value.forEach((url) => {
1016
- URL.revokeObjectURL(url);
1017
- });
1018
- imageUrls.value.clear();
1019
- });
1020
- const viewImage = async (file) => {
1021
- viewingImage.value = file;
1022
- if (!imageUrls.value.has(file.id)) await getImageUrl(file.id);
1023
- };
1024
- const goToPreviousImage = () => {
1025
- if (!canGoToPrevious.value || viewingImageIndex.value === null) return;
1026
- const previousImage = imageAttachments.value[viewingImageIndex.value - 1];
1027
- if (previousImage) viewImage(previousImage);
1028
- };
1029
- const goToNextImage = () => {
1030
- if (!canGoToNext.value || viewingImageIndex.value === null) return;
1031
- const nextImage = imageAttachments.value[viewingImageIndex.value + 1];
1032
- if (nextImage) viewImage(nextImage);
1033
- };
1034
- const closeImageModal = () => {
1035
- viewingImage.value = null;
1036
- };
1037
- const openFileSelector = (event) => {
1038
- if (event) {
1039
- event.preventDefault();
1040
- event.stopPropagation();
1041
- }
1042
- if (isUploading.value) return;
1043
- fileInput.value?.click();
1044
- };
1045
- const processFiles = (files) => {
1046
- const fileArray = Array.from(files);
1047
- fileArray.forEach((file) => {
1048
- const queueItem = {
1049
- id: `${Date.now()}-${Math.random()}`,
1050
- file,
1051
- status: "pending",
1052
- progress: 0
1053
- };
1054
- uploadQueue.value.push(queueItem);
1055
- });
1056
- emit("filesQueued", fileArray);
1057
- if (props.recordId) processUploadQueue();
1058
- };
1059
- const handleFileSelect = (event) => {
1060
- const target = event.target;
1061
- const files = target.files;
1062
- if (!files) return;
1063
- processFiles(Array.from(files));
1064
- target.value = "";
1065
- };
1066
- const handleFileDrop = (event) => {
1067
- isDragging.value = false;
1068
- const files = event.dataTransfer?.files;
1069
- if (!files || files.length === 0) return;
1070
- processFiles(Array.from(files));
1071
- };
1072
- const handlePaste = (event) => {
1073
- if (!props.canUpload || isUploading.value) return;
1074
- const items = event.clipboardData?.items;
1075
- if (!items) return;
1076
- const imageFiles = [];
1077
- for (const item of items) if (item.type.startsWith("image/")) {
1078
- const file = item.getAsFile();
1079
- if (file) imageFiles.push(file);
1080
- }
1081
- if (imageFiles.length > 0) {
1082
- event.preventDefault();
1083
- event.stopPropagation();
1084
- processFiles(imageFiles);
1085
- }
1086
- };
1087
- const processUploadQueue = async (ticketId) => {
1088
- const nextItem = uploadQueue.value.find((item) => item.status === "pending");
1089
- if (!nextItem) {
1090
- isUploading.value = false;
1091
- return;
1092
- }
1093
- isUploading.value = true;
1094
- nextItem.status = "uploading";
1095
- await uploadFile(nextItem, ticketId);
1096
- if (uploadQueue.value.some((item) => item.status === "pending")) processUploadQueue(ticketId);
1097
- else isUploading.value = false;
1098
- };
1099
- const uploadFile = async (item, ticketId) => {
1100
- const recordId = ticketId || props.recordId;
1101
- if (!recordId) {
1102
- item.status = "error";
1103
- item.errorMessage = "Ticket ID is required for upload";
1104
- return;
1105
- }
1106
- const progressInterval = setInterval(() => {
1107
- item.progress += Math.random() * 10;
1108
- item.progress = Math.min(99, Math.round(item.progress * 100) / 100);
1109
- }, 200);
1110
- try {
1111
- const formData = new FormData();
1112
- formData.append("file", item.file);
1113
- formData.append("file_name", item.file.name);
1114
- formData.append("record_id", recordId);
1115
- formData.append("record_type", "support_ticket");
1116
- const response = await fetch(`${env.restApiClient.apiUrl}/attachments/support_ticket/${recordId}`, {
1117
- method: "POST",
1118
- headers: { Authorization: `Bearer ${userStore.accessToken}` },
1119
- body: formData
1120
- });
1121
- if (!response.ok) throw new Error(`Upload failed: ${response.statusText}`);
1122
- const result = await response.json();
1123
- if (result && result.id) {
1124
- clearInterval(progressInterval);
1125
- item.progress = 100;
1126
- item.status = "success";
1127
- setTimeout(() => {
1128
- removeFromQueue(item.id);
1129
- refreshAttachments();
1130
- emit("uploaded");
1131
- }, 500);
1132
- } else throw new Error("Invalid response");
1133
- } catch (error) {
1134
- clearInterval(progressInterval);
1135
- item.status = "error";
1136
- item.errorMessage = error instanceof Error ? error.message : "Upload failed";
1137
- }
1138
- };
1139
- const removeFromQueue = (id) => {
1140
- const index = uploadQueue.value.findIndex((item) => item.id === id);
1141
- if (index !== -1) uploadQueue.value.splice(index, 1);
1142
- };
1143
- const retryUpload = (item) => {
1144
- item.status = "pending";
1145
- item.progress = 0;
1146
- item.errorMessage = void 0;
1147
- if (props.recordId) processUploadQueue();
1148
- };
1149
- const downloadFile = async (file, event) => {
1150
- if (event) {
1151
- event.preventDefault();
1152
- event.stopPropagation();
1153
- }
1154
- try {
1155
- const res = await fetch(`${env.restApiClient.apiUrl}/attachments/support_ticket/${file.id}`, {
1156
- method: "GET",
1157
- headers: {
1158
- "Content-Type": "application/json",
1159
- Authorization: `Bearer ${userStore.accessToken}`
1160
- }
1161
- });
1162
- if (!res.ok) throw new Error("Failed to download file");
1163
- const blob = await res.blob();
1164
- const url = URL.createObjectURL(blob);
1165
- const a = document.createElement("a");
1166
- a.href = url;
1167
- a.download = file.name;
1168
- document.body.appendChild(a);
1169
- a.click();
1170
- a.remove();
1171
- URL.revokeObjectURL(url);
1172
- } catch (error) {
1173
- console.error("Download failed:", error);
1174
- }
1175
- };
1176
- const confirmDelete = (file) => {
1177
- fileToDelete.value = file;
1178
- showDeleteModal.value = true;
1179
- };
1180
- const closeDeleteModal = () => {
1181
- showDeleteModal.value = false;
1182
- fileToDelete.value = null;
1183
- };
1184
- const deleteFile = async () => {
1185
- if (!fileToDelete.value) return;
1186
- try {
1187
- await deleteAttachment({ id: fileToDelete.value.id });
1188
- refreshAttachments();
1189
- emit("deleted");
1190
- closeDeleteModal();
1191
- } catch (error) {
1192
- console.error("Delete failed:", error);
1193
- }
1194
- };
1195
- watch(() => props.recordId, (newRecordId, oldRecordId) => {
1196
- if (newRecordId && newRecordId !== oldRecordId) {
1197
- refreshAttachments();
1198
- if (uploadQueue.value.some((item) => item.status === "pending")) processUploadQueue(newRecordId);
1199
- }
1200
- });
1201
- watch(() => attachments.value, (newAttachments) => {
1202
- newAttachments.forEach((file) => {
1203
- if (isImage(file.type) && !imageUrls.value.has(file.id)) loadImageThumbnail(file.id);
1204
- });
1205
- }, { immediate: true });
1206
- return (_ctx, _cache) => {
1207
- return openBlock(), createElementBlock("div", _hoisted_1$5, [
1208
- createElementVNode("input", {
1209
- ref_key: "fileInput",
1210
- ref: fileInput,
1211
- type: "file",
1212
- multiple: "",
1213
- class: "hidden",
1214
- onChange: handleFileSelect
1215
- }, null, 544),
1216
- createCommentVNode(" Drag & Drop and Paste: compact button-like on mobile, expanded on desktop "),
1217
- __props.canUpload ? (openBlock(), createElementBlock("div", _hoisted_2$2, [
1218
- createCommentVNode(" Drag & Drop Zone "),
1219
- createElementVNode("div", {
1220
- class: normalizeClass(["flex flex-col items-center justify-center gap-1 sm:gap-2 py-2.5 px-2 sm:py-6 sm:px-4 rounded-lg border-2 border-dashed transition-colors cursor-pointer touch-manipulation text-center", {
1221
- "border-primary bg-primary/5": isDragging.value,
1222
- "border-base-300 hover:border-primary/50 active:border-primary/50": !isDragging.value
1223
- }]),
1224
- onDragover: _cache[0] || (_cache[0] = withModifiers(($event) => isDragging.value = true, ["prevent"])),
1225
- onDragleave: _cache[1] || (_cache[1] = withModifiers(($event) => isDragging.value = false, ["prevent"])),
1226
- onDrop: withModifiers(handleFileDrop, ["prevent"]),
1227
- onClick: withModifiers(openFileSelector, ["prevent", "stop"])
1228
- }, [
1229
- _cache[4] || (_cache[4] = createElementVNode("svg", {
1230
- xmlns: "http://www.w3.org/2000/svg",
1231
- class: "h-5 w-5 sm:h-12 sm:w-12 text-primary shrink-0",
1232
- fill: "none",
1233
- viewBox: "0 0 24 24",
1234
- stroke: "currentColor"
1235
- }, [createElementVNode("path", {
1236
- "stroke-linecap": "round",
1237
- "stroke-linejoin": "round",
1238
- "stroke-width": "2",
1239
- d: "M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
1240
- })], -1)),
1241
- _cache[5] || (_cache[5] = createElementVNode("span", { class: "text-xs sm:text-lg font-medium" }, "Browse", -1)),
1242
- _cache[6] || (_cache[6] = createElementVNode("span", { class: "hidden sm:inline text-xs text-base-content/70" }, "Drag and drop or click", -1)),
1243
- !__props.recordId ? (openBlock(), createElementBlock("p", _hoisted_3$2, " Queued until ticket is created ")) : createCommentVNode("v-if", true)
1244
- ], 34),
1245
- createCommentVNode(" Paste zone - collapse handles visibility via CSS, so no unmount during animation "),
1246
- createElementVNode("div", {
1247
- tabindex: "0",
1248
- role: "textbox",
1249
- "aria-label": "Paste image here",
1250
- class: "flex flex-col items-center justify-center gap-1 sm:gap-2 py-2.5 px-2 sm:py-6 sm:px-4 rounded-lg border-2 border-dashed border-base-300 cursor-text outline-none focus:border-primary focus:ring-2 focus:ring-primary/20 focus:bg-primary/5 touch-manipulation text-center",
1251
- onPaste: handlePaste
1252
- }, [..._cache[7] || (_cache[7] = [
1253
- createElementVNode("svg", {
1254
- xmlns: "http://www.w3.org/2000/svg",
1255
- class: "h-5 w-5 sm:h-12 sm:w-12 text-primary shrink-0",
1256
- fill: "none",
1257
- viewBox: "0 0 24 24",
1258
- stroke: "currentColor"
1259
- }, [createElementVNode("path", {
1260
- "stroke-linecap": "round",
1261
- "stroke-linejoin": "round",
1262
- "stroke-width": "2",
1263
- d: "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
1264
- })], -1),
1265
- createElementVNode("span", { class: "text-xs sm:text-lg font-medium" }, "Paste", -1),
1266
- createElementVNode("span", { class: "hidden sm:inline text-xs text-base-content/70" }, "Tap to focus, then paste", -1)
1267
- ])], 32)
1268
- ])) : createCommentVNode("v-if", true),
1269
- createCommentVNode(" Upload Queue "),
1270
- uploadQueue.value.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_4$2, [(openBlock(true), createElementBlock(Fragment, null, renderList(uploadQueue.value, (item) => {
1271
- return openBlock(), createElementBlock("div", {
1272
- key: item.id,
1273
- class: "flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-3 p-2 sm:p-3 bg-base-200 rounded-lg"
1274
- }, [createElementVNode("div", _hoisted_5$2, [
1275
- createElementVNode("div", _hoisted_6$2, toDisplayString(item.file.name), 1),
1276
- item.status === "uploading" ? (openBlock(), createElementBlock("div", _hoisted_7$1, [createElementVNode("div", {
1277
- class: "bg-primary h-1.5 rounded-full transition-all",
1278
- style: normalizeStyle({ width: `${item.progress}%` })
1279
- }, null, 4)])) : createCommentVNode("v-if", true),
1280
- item.status === "error" ? (openBlock(), createElementBlock("div", _hoisted_8$1, toDisplayString(item.errorMessage), 1)) : createCommentVNode("v-if", true)
1281
- ]), createElementVNode("div", _hoisted_9$1, [item.status === "error" ? (openBlock(), createElementBlock("button", {
1282
- key: 0,
1283
- onClick: withModifiers(($event) => retryUpload(item), ["prevent"]),
1284
- class: "btn btn-xs btn-ghost flex-1 sm:flex-none",
1285
- type: "button"
1286
- }, " Retry ", 8, _hoisted_10$1)) : createCommentVNode("v-if", true), item.status !== "uploading" ? (openBlock(), createElementBlock("button", {
1287
- key: 1,
1288
- onClick: withModifiers(($event) => removeFromQueue(item.id), ["prevent"]),
1289
- class: "btn btn-xs btn-ghost text-error flex-1 sm:flex-none",
1290
- type: "button"
1291
- }, " Remove ", 8, _hoisted_11$1)) : createCommentVNode("v-if", true)])]);
1292
- }), 128))])) : createCommentVNode("v-if", true),
1293
- createCommentVNode(" Attachments List "),
1294
- (attachments.value.length > 0 || unref(attachmentsLoading)) && __props.recordId ? (openBlock(), createElementBlock("div", _hoisted_12$1, [unref(attachmentsLoading) ? (openBlock(), createElementBlock("div", _hoisted_13$1, [..._cache[8] || (_cache[8] = [createElementVNode("span", { class: "loading loading-spinner loading-sm" }, null, -1)])])) : createCommentVNode("v-if", true), (openBlock(true), createElementBlock(Fragment, null, renderList(attachments.value, (file) => {
1295
- return openBlock(), createElementBlock("div", {
1296
- key: file.id,
1297
- class: normalizeClass(["flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-3 p-2 sm:p-3 bg-base-100 border border-base-300 rounded-lg hover:bg-base-200 transition-colors", { "cursor-pointer": isImage(file.type) }]),
1298
- onClick: withModifiers(($event) => isImage(file.type) && viewImage(file), ["prevent"])
1299
- }, [createElementVNode("div", _hoisted_15$1, [createElementVNode("div", _hoisted_16, [createCommentVNode(" Image thumbnail preview "), isImage(file.type) ? (openBlock(), createElementBlock("div", _hoisted_17, [getImageUrlSync(file.id) ? (openBlock(), createElementBlock("img", {
1300
- key: 0,
1301
- src: getImageUrlSync(file.id),
1302
- alt: file.name,
1303
- class: "w-full h-full object-cover",
1304
- onError: handleImageError
1305
- }, null, 40, _hoisted_18)) : (openBlock(), createElementBlock("div", _hoisted_19, [..._cache[9] || (_cache[9] = [createElementVNode("span", { class: "loading loading-spinner loading-xs" }, null, -1)])]))])) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createCommentVNode(" File type badge for non-images "), createElementVNode("div", { class: normalizeClass(["badge badge-sm", getFileTypeBadgeClass(file.type)]) }, toDisplayString(getFileTypeLabel(file.type)), 3)], 2112))]), createElementVNode("div", _hoisted_20, [createElementVNode("div", _hoisted_21, toDisplayString(file.name), 1), createElementVNode("div", _hoisted_22, toDisplayString(formatFileSize(file.size)) + " • " + toDisplayString(formatDate(file.uploadedAt)), 1)])]), createElementVNode("div", {
1306
- class: "flex gap-1 justify-end sm:justify-start",
1307
- onClick: _cache[2] || (_cache[2] = withModifiers(() => {}, ["stop"]))
1308
- }, [
1309
- isImage(file.type) ? (openBlock(), createElementBlock("button", {
1310
- key: 0,
1311
- onClick: withModifiers(($event) => viewImage(file), ["prevent"]),
1312
- class: "btn btn-sm btn-ghost btn-circle",
1313
- title: "View Image",
1314
- type: "button"
1315
- }, [..._cache[10] || (_cache[10] = [createElementVNode("svg", {
1316
- xmlns: "http://www.w3.org/2000/svg",
1317
- class: "h-4 w-4",
1318
- fill: "none",
1319
- viewBox: "0 0 24 24",
1320
- stroke: "currentColor"
1321
- }, [createElementVNode("path", {
1322
- "stroke-linecap": "round",
1323
- "stroke-linejoin": "round",
1324
- "stroke-width": "2",
1325
- d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z"
1326
- }), createElementVNode("path", {
1327
- "stroke-linecap": "round",
1328
- "stroke-linejoin": "round",
1329
- "stroke-width": "2",
1330
- d: "M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
1331
- })], -1)])], 8, _hoisted_23)) : createCommentVNode("v-if", true),
1332
- createElementVNode("button", {
1333
- onClick: withModifiers(($event) => downloadFile(file, $event), ["prevent"]),
1334
- class: "btn btn-sm btn-ghost btn-circle",
1335
- title: "Download",
1336
- type: "button"
1337
- }, [..._cache[11] || (_cache[11] = [createElementVNode("svg", {
1338
- xmlns: "http://www.w3.org/2000/svg",
1339
- class: "h-4 w-4",
1340
- fill: "none",
1341
- viewBox: "0 0 24 24",
1342
- stroke: "currentColor"
1343
- }, [createElementVNode("path", {
1344
- "stroke-linecap": "round",
1345
- "stroke-linejoin": "round",
1346
- "stroke-width": "2",
1347
- d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
1348
- })], -1)])], 8, _hoisted_24),
1349
- __props.canDelete ? (openBlock(), createElementBlock("button", {
1350
- key: 1,
1351
- onClick: withModifiers(($event) => confirmDelete(file), ["prevent"]),
1352
- class: "btn btn-sm btn-ghost btn-circle text-error",
1353
- title: "Delete",
1354
- type: "button"
1355
- }, [..._cache[12] || (_cache[12] = [createElementVNode("svg", {
1356
- xmlns: "http://www.w3.org/2000/svg",
1357
- class: "h-4 w-4",
1358
- fill: "none",
1359
- viewBox: "0 0 24 24",
1360
- stroke: "currentColor"
1361
- }, [createElementVNode("path", {
1362
- "stroke-linecap": "round",
1363
- "stroke-linejoin": "round",
1364
- "stroke-width": "2",
1365
- d: "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
1366
- })], -1)])], 8, _hoisted_25)) : createCommentVNode("v-if", true)
1367
- ])], 10, _hoisted_14$1);
1368
- }), 128))])) : !unref(attachmentsLoading) && __props.recordId ? (openBlock(), createElementBlock("div", _hoisted_26, [..._cache[13] || (_cache[13] = [createElementVNode("svg", {
1369
- xmlns: "http://www.w3.org/2000/svg",
1370
- class: "h-12 w-12 mx-auto mb-2 opacity-50",
1371
- fill: "none",
1372
- viewBox: "0 0 24 24",
1373
- stroke: "currentColor"
1374
- }, [createElementVNode("path", {
1375
- "stroke-linecap": "round",
1376
- "stroke-linejoin": "round",
1377
- "stroke-width": "2",
1378
- d: "M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"
1379
- })], -1), createElementVNode("p", null, "No attachments", -1)])])) : createCommentVNode("v-if", true),
1380
- createCommentVNode(" Image View Modal "),
1381
- createVNode(ImageModal_default, {
1382
- "is-open": !!viewingImage.value,
1383
- "image-src": viewingImage.value ? getImageUrlSync(viewingImage.value.id) : "",
1384
- "image-name": viewingImage.value?.name || "",
1385
- "image-index": viewingImageIndex.value,
1386
- "total-images": imageAttachments.value.length,
1387
- "on-download": viewingImage.value ? () => {
1388
- if (viewingImage.value) downloadFile(viewingImage.value, void 0);
1389
- } : void 0,
1390
- "on-previous": canGoToPrevious.value ? goToPreviousImage : void 0,
1391
- "on-next": canGoToNext.value ? goToNextImage : void 0,
1392
- onClose: closeImageModal
1393
- }, null, 8, [
1394
- "is-open",
1395
- "image-src",
1396
- "image-name",
1397
- "image-index",
1398
- "total-images",
1399
- "on-download",
1400
- "on-previous",
1401
- "on-next"
1402
- ]),
1403
- createCommentVNode(" Delete Confirmation Modal "),
1404
- createVNode(ConfirmDialog_default, {
1405
- modelValue: showDeleteModal.value,
1406
- "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => showDeleteModal.value = $event),
1407
- title: "Delete Attachment",
1408
- message: deleteMessage.value,
1409
- "confirm-text": "Delete",
1410
- "cancel-text": "Cancel",
1411
- "confirm-button-class": "btn-error",
1412
- onConfirm: deleteFile,
1413
- onCancel: closeDeleteModal
1414
- }, null, 8, ["modelValue", "message"])
1415
- ]);
1416
- };
1417
- }
1418
- });
1419
- var InlineAttachments_default = _sfc_main$5;
1420
-
1421
- //#endregion
1422
- //#region src/slices/support_ticket/shared/SupportTicketPriorityBadge.vue
1423
- const _hoisted_1$4 = ["aria-label"];
1424
- const _sfc_main$4 = /* @__PURE__ */ defineComponent({
1425
- __name: "SupportTicketPriorityBadge",
1426
- props: {
1427
- priority: {},
1428
- size: { default: "md" },
1429
- variant: { default: "default" }
1430
- },
1431
- setup(__props) {
1432
- /**
1433
- * SupportTicketPriorityBadge - A reusable Vue component for displaying support ticket priority
1434
- * as color-coded badges with consistent DaisyUI styling and accessibility features.
1435
- *
1436
- * @example
1437
- * <SupportTicketPriorityBadge :priority="'HIGH'" size="md" />
1438
- * <SupportTicketPriorityBadge :priority="'CRITICAL'" size="sm" variant="outline" />
1439
- */
1440
- const props = __props;
1441
- /**
1442
- * Configuration for each priority badge
1443
- */
1444
- const priorityConfig = {
1445
- LOW: {
1446
- color: "badge-neutral",
1447
- text: "Low",
1448
- ariaLabel: "Priority: Low priority"
1449
- },
1450
- MEDIUM: {
1451
- color: "badge-neutral",
1452
- text: "Medium",
1453
- ariaLabel: "Priority: Medium priority"
1454
- },
1455
- HIGH: {
1456
- color: "badge-neutral",
1457
- text: "High",
1458
- ariaLabel: "Priority: High priority"
1459
- },
1460
- CRITICAL: {
1461
- color: "badge-neutral",
1462
- text: "Critical",
1463
- ariaLabel: "Priority: Critical priority"
1464
- }
1465
- };
1466
- const getPriorityConfig = (priority) => {
1467
- const config$1 = priorityConfig[priority];
1468
- if (!config$1) return {
1469
- color: "badge-neutral",
1470
- text: priority || "Unknown",
1471
- ariaLabel: `Priority: ${priority || "Unknown priority"}`
1472
- };
1473
- return config$1;
1474
- };
1475
- const config = computed(() => getPriorityConfig(props.priority));
1476
- const badgeClasses = computed(() => {
1477
- const baseClasses = ["badge", "text-xs"];
1478
- baseClasses.push(config.value.color);
1479
- if (props.size === "sm") baseClasses.push("badge-sm", "text-xs");
1480
- else if (props.size === "lg") baseClasses.push("badge-lg", "text-sm");
1481
- else baseClasses.push("text-xs", "sm:text-sm");
1482
- if (props.variant === "outline") baseClasses.push("badge-outline");
1483
- return baseClasses.join(" ");
1484
- });
1485
- const displayText = computed(() => config.value.text);
1486
- const ariaLabel = computed(() => config.value.ariaLabel);
1487
- return (_ctx, _cache) => {
1488
- return openBlock(), createElementBlock("div", {
1489
- class: normalizeClass(badgeClasses.value),
1490
- "aria-label": ariaLabel.value,
1491
- role: "status"
1492
- }, toDisplayString(displayText.value), 11, _hoisted_1$4);
1493
- };
1494
- }
1495
- });
1496
- var SupportTicketPriorityBadge_default = _sfc_main$4;
1497
-
1498
- //#endregion
1499
- //#region src/slices/support_ticket/shared/SupportTicketTypeBadge.vue
1500
- const _hoisted_1$3 = ["aria-label"];
1501
- const _sfc_main$3 = /* @__PURE__ */ defineComponent({
1502
- __name: "SupportTicketTypeBadge",
1503
- props: {
1504
- type: {},
1505
- size: { default: "md" },
1506
- variant: { default: "default" }
1507
- },
1508
- setup(__props) {
1509
- /**
1510
- * SupportTicketTypeBadge - A reusable Vue component for displaying support ticket type
1511
- * as color-coded badges with consistent DaisyUI styling and accessibility features.
1512
- *
1513
- * @example
1514
- * <SupportTicketTypeBadge :type="'BUG'" size="md" />
1515
- * <SupportTicketTypeBadge :type="'FEATURE_REQUEST'" size="sm" variant="outline" />
1516
- */
1517
- const props = __props;
1518
- /**
1519
- * Configuration for each type badge
1520
- */
1521
- const typeConfig = {
1522
- BUG: {
1523
- color: "badge-neutral",
1524
- text: "Bug",
1525
- ariaLabel: "Type: Bug report requiring fix"
1526
- },
1527
- FEATURE_REQUEST: {
1528
- color: "badge-neutral",
1529
- text: "Feature",
1530
- ariaLabel: "Type: New feature request"
1531
- },
1532
- IMPROVEMENT: {
1533
- color: "badge-neutral",
1534
- text: "Improvement",
1535
- ariaLabel: "Type: Enhancement to existing feature"
1536
- },
1537
- OPERATIONAL: {
1538
- color: "badge-neutral",
1539
- text: "Ops",
1540
- ariaLabel: "Type: Operational/admin work"
1541
- }
1542
- };
1543
- const getTypeConfig = (type) => {
1544
- const config$1 = typeConfig[type];
1545
- if (!config$1) return {
1546
- color: "badge-neutral",
1547
- text: type || "Unknown",
1548
- ariaLabel: `Type: ${type || "Unknown type"}`
1549
- };
1550
- return config$1;
1551
- };
1552
- const config = computed(() => getTypeConfig(props.type));
1553
- const badgeClasses = computed(() => {
1554
- const baseClasses = ["badge", "text-xs"];
1555
- baseClasses.push(config.value.color);
1556
- if (props.size === "sm") baseClasses.push("badge-sm", "text-xs");
1557
- else if (props.size === "lg") baseClasses.push("badge-lg", "text-sm");
1558
- else baseClasses.push("text-xs", "sm:text-sm");
1559
- if (props.variant === "outline") baseClasses.push("badge-outline");
1560
- return baseClasses.join(" ");
1561
- });
1562
- const displayText = computed(() => config.value.text);
1563
- const ariaLabel = computed(() => config.value.ariaLabel);
1564
- return (_ctx, _cache) => {
1565
- return openBlock(), createElementBlock("div", {
1566
- class: normalizeClass(badgeClasses.value),
1567
- "aria-label": ariaLabel.value,
1568
- role: "status"
1569
- }, toDisplayString(displayText.value), 11, _hoisted_1$3);
1570
- };
1571
- }
1572
- });
1573
- var SupportTicketTypeBadge_default = _sfc_main$3;
1574
-
1575
- //#endregion
1576
- //#region src/slices/support_ticket/shared/SupportTicketApprovalBadge.vue
1577
- const _hoisted_1$2 = ["aria-label"];
1578
- const _sfc_main$2 = /* @__PURE__ */ defineComponent({
1579
- __name: "SupportTicketApprovalBadge",
1580
- props: {
1581
- approvalStatus: {},
1582
- size: { default: "md" },
1583
- variant: { default: "default" }
1584
- },
1585
- setup(__props) {
1586
- /**
1587
- * SupportTicketApprovalBadge - A reusable Vue component for displaying support ticket approval status
1588
- * as color-coded badges with consistent DaisyUI styling and accessibility features.
1589
- *
1590
- * @example
1591
- * <SupportTicketApprovalBadge :approvalStatus="'PENDING'" size="md" />
1592
- * <SupportTicketApprovalBadge :approvalStatus="'APPROVED'" size="sm" variant="outline" />
1593
- */
1594
- const props = __props;
1595
- /**
1596
- * Configuration for each approval status badge
1597
- */
1598
- const approvalConfig = {
1599
- PENDING: {
1600
- color: "badge-warning",
1601
- text: "Pending",
1602
- ariaLabel: "Approval: Awaiting staff decision"
1603
- },
1604
- APPROVED: {
1605
- color: "badge-success",
1606
- text: "Approved",
1607
- ariaLabel: "Approval: Approved by staff"
1608
- },
1609
- REJECTED: {
1610
- color: "badge-error",
1611
- text: "Rejected",
1612
- ariaLabel: "Approval: Rejected by staff"
1613
- },
1614
- INTERNAL: {
1615
- color: "badge-info",
1616
- text: "Internal",
1617
- ariaLabel: "Approval: Internal staff ticket"
1618
- }
1619
- };
1620
- const getApprovalConfig = (approvalStatus) => {
1621
- const config$1 = approvalConfig[approvalStatus];
1622
- if (!config$1) return {
1623
- color: "badge-neutral",
1624
- text: approvalStatus || "Unknown",
1625
- ariaLabel: `Approval: ${approvalStatus || "Unknown status"}`
1626
- };
1627
- return config$1;
1628
- };
1629
- const config = computed(() => getApprovalConfig(props.approvalStatus));
1630
- const badgeClasses = computed(() => {
1631
- const baseClasses = ["badge", "text-xs"];
1632
- baseClasses.push(config.value.color);
1633
- if (props.size === "sm") baseClasses.push("badge-sm", "text-xs");
1634
- else if (props.size === "lg") baseClasses.push("badge-lg", "text-sm");
1635
- else baseClasses.push("text-xs", "sm:text-sm");
1636
- if (props.variant === "outline") baseClasses.push("badge-outline");
1637
- return baseClasses.join(" ");
1638
- });
1639
- const displayText = computed(() => config.value.text);
1640
- const ariaLabel = computed(() => config.value.ariaLabel);
1641
- return (_ctx, _cache) => {
1642
- return openBlock(), createElementBlock("div", {
1643
- class: normalizeClass(badgeClasses.value),
1644
- "aria-label": ariaLabel.value,
1645
- role: "status"
1646
- }, toDisplayString(displayText.value), 11, _hoisted_1$2);
1647
- };
1648
- }
1649
- });
1650
- var SupportTicketApprovalBadge_default = _sfc_main$2;
1651
-
1652
- //#endregion
1653
- //#region src/slices/support_ticket/utils/formatTicketDate.ts
1654
- /**
1655
- * Takes an ISO date string and returns:
1656
- * - formatted: "Jan 15, 2025"
1657
- * - relative: "3 days ago" / "2 hours ago" / "just now"
1658
- * - localTime: "2:30 PM" (user's local time)
1659
- */
1660
- function formatTicketDate(isoString) {
1661
- const date = new Date(isoString);
1662
- if (isNaN(date.getTime())) return {
1663
- formatted: isoString,
1664
- relative: "",
1665
- localTime: ""
1666
- };
1667
- const formatted = date.toLocaleDateString(void 0, {
1668
- year: "numeric",
1669
- month: "short",
1670
- day: "numeric"
1671
- });
1672
- const localTime = date.toLocaleTimeString(void 0, {
1673
- hour: "numeric",
1674
- minute: "2-digit"
1675
- });
1676
- const diffMs = (/* @__PURE__ */ new Date()).getTime() - date.getTime();
1677
- const diffSec = Math.floor(diffMs / 1e3);
1678
- const diffMin = Math.floor(diffSec / 60);
1679
- const diffHour = Math.floor(diffMin / 60);
1680
- const diffDay = Math.floor(diffHour / 24);
1681
- let relative;
1682
- if (Math.abs(diffSec) < 60) relative = "just now";
1683
- else if (diffSec < 0) {
1684
- const sec = Math.abs(diffSec);
1685
- const min = Math.floor(sec / 60);
1686
- const hr = Math.floor(min / 60);
1687
- const day = Math.floor(hr / 24);
1688
- if (min < 60) relative = `in ${min} minute${min === 1 ? "" : "s"}`;
1689
- else if (hr < 24) relative = `in ${hr} hour${hr === 1 ? "" : "s"}`;
1690
- else relative = `in ${day} day${day === 1 ? "" : "s"}`;
1691
- } else if (diffMin < 60) relative = diffMin === 1 ? "1 minute ago" : `${diffMin} minutes ago`;
1692
- else if (diffHour < 24) relative = diffHour === 1 ? "1 hour ago" : `${diffHour} hours ago`;
1693
- else if (diffDay < 30) relative = diffDay === 1 ? "1 day ago" : `${diffDay} days ago`;
1694
- else {
1695
- const diffWeeks = Math.floor(diffDay / 7);
1696
- const diffMonths = Math.floor(diffDay / 30);
1697
- if (diffWeeks < 4) relative = diffWeeks === 1 ? "1 week ago" : `${diffWeeks} weeks ago`;
1698
- else if (diffMonths < 12) relative = diffMonths === 1 ? "1 month ago" : `${diffMonths} months ago`;
1699
- else {
1700
- const diffYears = Math.floor(diffMonths / 12);
1701
- relative = diffYears === 1 ? "1 year ago" : `${diffYears} years ago`;
1702
- }
1703
- }
1704
- return {
1705
- formatted,
1706
- relative,
1707
- localTime
1708
- };
1709
- }
1710
-
1711
- //#endregion
1712
- //#region src/slices/support_ticket/shared/TimelineItem.vue
1713
- const _hoisted_1$1 = { class: "flex items-center gap-2 mb-3 flex-wrap" };
1714
- const _hoisted_2$1 = {
1715
- key: 0,
1716
- class: "badge badge-warning badge-sm gap-1 shrink-0",
1717
- "aria-label": "Internal note"
1718
- };
1719
- const _hoisted_3$1 = {
1720
- key: 1,
1721
- class: "w-6 h-6 rounded-full bg-primary text-primary-content text-xs flex items-center justify-center shrink-0",
1722
- "aria-hidden": "true"
1723
- };
1724
- const _hoisted_4$1 = { class: "font-semibold text-sm" };
1725
- const _hoisted_5$1 = { class: "text-base-content/50 text-sm" };
1726
- const _hoisted_6$1 = { class: "text-sm text-base-content break-words whitespace-pre-wrap" };
1727
- const _sfc_main$1 = /* @__PURE__ */ defineComponent({
1728
- __name: "TimelineItem",
1729
- props: {
1730
- authorName: {},
1731
- createdAt: {},
1732
- variant: {}
1733
- },
1734
- setup(__props) {
1735
- const props = __props;
1736
- const authorInitial = computed(() => {
1737
- return (props.authorName?.trim() || "?").charAt(0).toUpperCase();
1738
- });
1739
- const relativeTime = computed(() => {
1740
- return props.createdAt ? formatTicketDate(props.createdAt).relative : "";
1741
- });
1742
- return (_ctx, _cache) => {
1743
- return openBlock(), createElementBlock("div", { class: normalizeClass(["card card-bordered p-4 w-full", __props.variant === "internal" ? "bg-warning/10 border-warning/30" : "bg-base-100"]) }, [createElementVNode("div", _hoisted_1$1, [
1744
- __props.variant === "internal" ? (openBlock(), createElementBlock("span", _hoisted_2$1, [..._cache[0] || (_cache[0] = [createElementVNode("svg", {
1745
- xmlns: "http://www.w3.org/2000/svg",
1746
- fill: "none",
1747
- viewBox: "0 0 24 24",
1748
- "stroke-width": "1.5",
1749
- stroke: "currentColor",
1750
- class: "w-3.5 h-3.5"
1751
- }, [createElementVNode("path", {
1752
- "stroke-linecap": "round",
1753
- "stroke-linejoin": "round",
1754
- d: "M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z"
1755
- })], -1), createTextVNode(" Internal ", -1)])])) : (openBlock(), createElementBlock("span", _hoisted_3$1, toDisplayString(authorInitial.value), 1)),
1756
- createElementVNode("span", _hoisted_4$1, toDisplayString(__props.authorName), 1),
1757
- createElementVNode("span", _hoisted_5$1, "· " + toDisplayString(relativeTime.value), 1)
1758
- ]), createElementVNode("div", _hoisted_6$1, [renderSlot(_ctx.$slots, "default")])], 2);
1759
- };
1760
- }
1761
- });
1762
- var TimelineItem_default = _sfc_main$1;
1763
-
1764
- //#endregion
1765
- //#region src/slices/support_ticket/shared/TimelineSystemEvent.vue
1766
- const _hoisted_1 = { class: "py-2 flex items-start gap-2 text-sm" };
1767
- const _hoisted_2 = { class: "flex-1 min-w-0 space-y-0.5" };
1768
- const _hoisted_3 = { class: "flex flex-wrap items-baseline gap-x-2 gap-y-1" };
1769
- const _hoisted_4 = { class: "text-base-content/60 font-medium" };
1770
- const _hoisted_5 = { class: "font-semibold text-base-content" };
1771
- const _hoisted_6 = { class: "badge badge-sm badge-outline border-base-content/20 text-base-content/80 font-normal px-1.5 py-0 capitalize" };
1772
- const _hoisted_7 = { class: "text-base-content/70" };
1773
- const _hoisted_8 = { class: "text-base-content/70" };
1774
- const _hoisted_9 = { class: "line-through text-base-content/50" };
1775
- const _hoisted_10 = {
1776
- key: 0,
1777
- class: "mt-1.5 ml-6 list-disc list-inside text-base-content/70 text-sm space-y-0.5"
1778
- };
1779
- const _hoisted_11 = { class: "italic text-base-content/60" };
1780
- const _hoisted_12 = { class: "line-through text-base-content/50 ml-1" };
1781
- const _hoisted_13 = {
1782
- key: 1,
1783
- class: "ml-1"
1784
- };
1785
- const _hoisted_14 = { class: "text-base-content/40 text-xs" };
1786
- const _hoisted_15 = ["title"];
1787
- const RECENT_THRESHOLD_HOURS = 18;
1788
- const _sfc_main = /* @__PURE__ */ defineComponent({
1789
- __name: "TimelineSystemEvent",
1790
- props: {
1791
- author: {},
1792
- message: {},
1793
- timestamp: {},
1794
- action: {},
1795
- type: {},
1796
- details: {},
1797
- oldValue: {},
1798
- newValue: {},
1799
- changes: {}
1800
- },
1801
- setup(__props) {
1802
- const props = __props;
1803
- const showFull = ref(false);
1804
- const displayAction = computed(() => props.action ?? props.message);
1805
- const displayDetails = computed(() => {
1806
- if (props.details) return props.details;
1807
- if (props.oldValue != null && props.newValue != null) return `${props.oldValue} → ${props.newValue}`;
1808
- return props.newValue ?? null;
1809
- });
1810
- const hasChangeDetails = computed(() => props.oldValue != null && props.newValue != null || props.details != null || props.newValue != null);
1811
- const detailsPrefix = computed(() => {
1812
- if (!props.details || props.oldValue == null && props.newValue == null) return null;
1813
- if (props.details.includes(" · ")) {
1814
- const parts = props.details.split(" · ");
1815
- return parts.length > 1 ? parts[0] : null;
1816
- }
1817
- return props.details;
1818
- });
1819
- /** Use relative time when within ~18 hours, otherwise full date and time */
1820
- const shortTime = computed(() => {
1821
- if (!props.timestamp) return "";
1822
- const { relative, formatted, localTime } = formatTicketDate(props.timestamp);
1823
- const dateTime = [formatted, localTime].filter(Boolean).join(", ");
1824
- if (!relative && !dateTime) return "";
1825
- const date = new Date(props.timestamp);
1826
- const hoursAgo = (Date.now() - date.getTime()) / (1e3 * 60 * 60);
1827
- return hoursAgo >= 0 && hoursAgo < RECENT_THRESHOLD_HOURS ? relative : dateTime;
1828
- });
1829
- const fullDateTime = computed(() => {
1830
- if (!props.timestamp) return "";
1831
- const { formatted, localTime } = formatTicketDate(props.timestamp);
1832
- return [formatted, localTime].filter(Boolean).join(", ");
1833
- });
1834
- const displayTime = computed(() => showFull.value ? fullDateTime.value : shortTime.value);
1835
- return (_ctx, _cache) => {
1836
- return openBlock(), createElementBlock("div", _hoisted_1, [_cache[7] || (_cache[7] = createElementVNode("svg", {
1837
- xmlns: "http://www.w3.org/2000/svg",
1838
- fill: "none",
1839
- viewBox: "0 0 24 24",
1840
- "stroke-width": "1.5",
1841
- stroke: "currentColor",
1842
- class: "w-4 h-4 text-base-content/30 shrink-0 mt-0.5",
1843
- "aria-hidden": "true"
1844
- }, [createElementVNode("path", {
1845
- "stroke-linecap": "round",
1846
- "stroke-linejoin": "round",
1847
- d: "M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124a6.57 6.57 0 01.22-.128c.332-.183.582-.495.644-.869l.214-1.281z"
1848
- }), createElementVNode("path", {
1849
- "stroke-linecap": "round",
1850
- "stroke-linejoin": "round",
1851
- d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z"
1852
- })], -1)), createElementVNode("div", _hoisted_2, [
1853
- createElementVNode("div", _hoisted_3, [
1854
- createElementVNode("span", _hoisted_4, toDisplayString(__props.author), 1),
1855
- _cache[5] || (_cache[5] = createElementVNode("span", {
1856
- class: "text-base-content/40 text-xs",
1857
- "aria-hidden": "true"
1858
- }, "·", -1)),
1859
- createElementVNode("span", _hoisted_5, toDisplayString(displayAction.value), 1),
1860
- __props.type ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [_cache[1] || (_cache[1] = createElementVNode("span", {
1861
- class: "text-base-content/40 text-xs",
1862
- "aria-hidden": "true"
1863
- }, "·", -1)), createElementVNode("span", _hoisted_6, toDisplayString(__props.type), 1)], 64)) : createCommentVNode("v-if", true),
1864
- __props.changes && __props.changes.length > 1 ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [__props.details ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [_cache[2] || (_cache[2] = createElementVNode("span", {
1865
- class: "text-base-content/40 text-xs",
1866
- "aria-hidden": "true"
1867
- }, "·", -1)), createElementVNode("span", _hoisted_7, toDisplayString(__props.details), 1)], 64)) : createCommentVNode("v-if", true)], 64)) : hasChangeDetails.value ? (openBlock(), createElementBlock(Fragment, { key: 2 }, [_cache[4] || (_cache[4] = createElementVNode("span", {
1868
- class: "text-base-content/40 text-xs",
1869
- "aria-hidden": "true"
1870
- }, "·", -1)), createElementVNode("span", _hoisted_8, [detailsPrefix.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createTextVNode(toDisplayString(detailsPrefix.value), 1)], 64)) : createCommentVNode("v-if", true), __props.oldValue != null && __props.newValue != null ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [
1871
- createElementVNode("span", _hoisted_9, toDisplayString(__props.oldValue), 1),
1872
- _cache[3] || (_cache[3] = createElementVNode("span", { class: "mx-1 text-base-content/40" }, "→", -1)),
1873
- createElementVNode("span", null, toDisplayString(__props.newValue), 1)
1874
- ], 64)) : displayDetails.value ? (openBlock(), createElementBlock(Fragment, { key: 2 }, [createTextVNode(toDisplayString(displayDetails.value), 1)], 64)) : createCommentVNode("v-if", true)])], 64)) : createCommentVNode("v-if", true)
1875
- ]),
1876
- __props.changes && __props.changes.length > 1 ? (openBlock(), createElementBlock("ul", _hoisted_10, [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.changes, (change, i) => {
1877
- return openBlock(), createElementBlock("li", { key: i }, [createElementVNode("span", _hoisted_11, toDisplayString(change.action) + ":", 1), change.oldValue != null && change.newValue != null ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
1878
- createElementVNode("span", _hoisted_12, toDisplayString(change.oldValue), 1),
1879
- _cache[6] || (_cache[6] = createElementVNode("span", { class: "mx-1 text-base-content/40" }, "→", -1)),
1880
- createElementVNode("span", null, toDisplayString(change.newValue), 1)
1881
- ], 64)) : change.newValue != null ? (openBlock(), createElementBlock("span", _hoisted_13, toDisplayString(change.newValue), 1)) : createCommentVNode("v-if", true)]);
1882
- }), 128))])) : createCommentVNode("v-if", true),
1883
- createElementVNode("div", _hoisted_14, [createElementVNode("button", {
1884
- type: "button",
1885
- class: "cursor-pointer hover:text-base-content/60 hover:underline focus:outline-none focus:underline",
1886
- title: fullDateTime.value,
1887
- onClick: _cache[0] || (_cache[0] = ($event) => showFull.value = !showFull.value)
1888
- }, toDisplayString(displayTime.value), 9, _hoisted_15)])
1889
- ])]);
1890
- };
1891
- }
1892
- });
1893
- var TimelineSystemEvent_default = _sfc_main;
1894
-
1895
- //#endregion
1896
- export { SupportTicketTypeBadge_default as a, formatCustomerCreditValue as c, ConfirmDialog_default as d, SupportTicketApprovalBadge_default as i, formatStaffCreditValue as l, TimelineItem_default as n, SupportTicketPriorityBadge_default as o, formatTicketDate as r, InlineAttachments_default as s, TimelineSystemEvent_default as t, ImageModal_default as u };
1897
- //# sourceMappingURL=TimelineSystemEvent-D58zN850.js.map