@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.
- package/package.json +4 -13
- package/dist/AppLink-CHMMrSFI.js +0 -54
- package/dist/AppLink-CHMMrSFI.js.map +0 -1
- package/dist/Appearance-BfPdKMXw.js +0 -70
- package/dist/Appearance-BfPdKMXw.js.map +0 -1
- package/dist/Appearance-C3WguxT-.js +0 -3
- package/dist/ChangePasswordPage-DCews8GU.js +0 -86
- package/dist/ChangePasswordPage-DCews8GU.js.map +0 -1
- package/dist/ChangePasswordPage-Dm5vW0nl.js +0 -6
- package/dist/CreateTeamForm-Cg4sD65k.js +0 -27
- package/dist/CreateTeamMemberForm-CiG-fCJD.js +0 -27
- package/dist/CreateUserPage-B8qeBZij.js +0 -76
- package/dist/CreateUserPage-B8qeBZij.js.map +0 -1
- package/dist/CreateUserPage-WjYDkwpb.js +0 -6
- package/dist/CreditBalanceDashboard-BUdKWieE.js +0 -27
- package/dist/CreditManagement-BcyUY_J0.js +0 -27
- package/dist/CustomerCreateSupportTicketForm-BplS0xSi.js +0 -27
- package/dist/CustomerSupportTicketDetailPage-DZQCplSM.js +0 -717
- package/dist/CustomerSupportTicketDetailPage-DZQCplSM.js.map +0 -1
- package/dist/CustomerSupportTicketList-DGwy4Wje.js +0 -27
- package/dist/CustomerSupportTicketParent-BnmTFigo.js +0 -7
- package/dist/CustomerSupportTicketParent-BzY4pmBk.js +0 -66
- package/dist/CustomerSupportTicketParent-BzY4pmBk.js.map +0 -1
- package/dist/CustomerSupportTicketSuccess-DC1jJG1E.js +0 -27
- package/dist/EditTeamForm-BnPwhv5B.js +0 -27
- package/dist/EditTeamMemberForm-B8-pI6Xm.js +0 -6
- package/dist/EditTeamMemberForm-CKbKomrL.js +0 -191
- package/dist/EditTeamMemberForm-CKbKomrL.js.map +0 -1
- package/dist/EditUserPage-BG-Fkx_c.js +0 -7
- package/dist/EditUserPage-XqF25iwz.js +0 -112
- package/dist/EditUserPage-XqF25iwz.js.map +0 -1
- package/dist/ForgotPassword-CjWv2V7p.js +0 -7
- package/dist/ForgotPassword-D3bjL48L.js +0 -73
- package/dist/ForgotPassword-D3bjL48L.js.map +0 -1
- package/dist/LoginForm--br4Il85.js +0 -7
- package/dist/LoginForm-C85U2E2r.js +0 -116
- package/dist/LoginForm-C85U2E2r.js.map +0 -1
- package/dist/Logout-DHT-5Qz3.js +0 -6
- package/dist/Logout-DZuWLh0O.js +0 -38
- package/dist/Logout-DZuWLh0O.js.map +0 -1
- package/dist/ResetPassword-M6mvTS24.js +0 -27
- package/dist/SavedFiltersPage-D3vJrfzt.js +0 -419
- package/dist/SavedFiltersPage-D3vJrfzt.js.map +0 -1
- package/dist/Signup-VZa7U-Ur.js +0 -7
- package/dist/Signup-hpV8J5cM.js +0 -106
- package/dist/Signup-hpV8J5cM.js.map +0 -1
- package/dist/StaffCreateSupportTicketForm-Bc7UnK0Q.js +0 -27
- package/dist/StaffSupportTicketDetailPage-DY07Ez0R.js +0 -1928
- package/dist/StaffSupportTicketDetailPage-DY07Ez0R.js.map +0 -1
- package/dist/StaffSupportTicketList-ChJP_67k.js +0 -27
- package/dist/StaffSupportTicketParent-CWWhaM37.js +0 -66
- package/dist/StaffSupportTicketParent-CWWhaM37.js.map +0 -1
- package/dist/StaffSupportTicketParent-Dp1G85wc.js +0 -7
- package/dist/StaffSupportTicketSuccess-B6X_dP4f.js +0 -27
- package/dist/SupportStaffPage-nd0HowtH.js +0 -156
- package/dist/SupportStaffPage-nd0HowtH.js.map +0 -1
- package/dist/SupportTicketDevLifecycleBadge-Ba-Rm6QW.js +0 -116
- package/dist/SupportTicketDevLifecycleBadge-Ba-Rm6QW.js.map +0 -1
- package/dist/SupportTicketMaintenancePage-rcJ7EfDj.js +0 -56
- package/dist/SupportTicketMaintenancePage-rcJ7EfDj.js.map +0 -1
- package/dist/TeamAttachmentsTab-Dk_Bnk-1.js +0 -27
- package/dist/TeamHistoryTab-CNelXR3Q.js +0 -232
- package/dist/TeamHistoryTab-CNelXR3Q.js.map +0 -1
- package/dist/TeamHistoryTab-siesF93u.js +0 -4
- package/dist/TeamList-BtLzbjls.js +0 -27
- package/dist/TeamMemberList-EoDXIr0w.js +0 -27
- package/dist/TeamMemberParent-DZ5YVyi6.js +0 -27
- package/dist/TeamMembersTab-4gmnP9sD.js +0 -21
- package/dist/TeamMembersTab-4gmnP9sD.js.map +0 -1
- package/dist/TeamMembersTab-DTJxmb-M.js +0 -3
- package/dist/TeamNotesTab-BhVRLG8h.js +0 -458
- package/dist/TeamNotesTab-BhVRLG8h.js.map +0 -1
- package/dist/TeamNotesTab-Crp-afAe.js +0 -7
- package/dist/TeamParent-CbrXXzAr.js +0 -27
- package/dist/TimelineNoteInput-BVqF4MtZ.js +0 -513
- package/dist/TimelineNoteInput-BVqF4MtZ.js.map +0 -1
- package/dist/TimelineSystemEvent-D58zN850.js +0 -1897
- package/dist/TimelineSystemEvent-D58zN850.js.map +0 -1
- package/dist/UserListPage-D68AjrjM.js +0 -4
- package/dist/UserListPage-OGYOLwlw.js +0 -153
- package/dist/UserListPage-OGYOLwlw.js.map +0 -1
- package/dist/UserProfilePage-Q68NAGQQ.js +0 -7
- package/dist/UserProfilePage-uAIfC_NW.js +0 -125
- package/dist/UserProfilePage-uAIfC_NW.js.map +0 -1
- package/dist/ViewTeam-DpE_NfRq.js +0 -27
- package/dist/ViewTeamMember-BdBwkuXC.js +0 -27
- package/dist/convertToLocalDateTime-DOSGtMn8.js +0 -121
- package/dist/convertToLocalDateTime-DOSGtMn8.js.map +0 -1
- package/dist/displayIdFormatter-B1ZKgofu.js +0 -13
- package/dist/displayIdFormatter-B1ZKgofu.js.map +0 -1
- package/dist/extractRpcErrorMessage-C_UbKgHL.js +0 -20
- package/dist/extractRpcErrorMessage-C_UbKgHL.js.map +0 -1
- package/dist/index.d.ts +0 -6366
- package/dist/index.js +0 -30
- package/dist/src-CEBiyg_f.css +0 -13
- package/dist/src-CEBiyg_f.css.map +0 -1
- package/dist/src-CHw8DdkR.js +0 -9200
- package/dist/src-CHw8DdkR.js.map +0 -1
- package/dist/useBreadcrumbs-DmgSucoe.js +0 -41
- package/dist/useBreadcrumbs-DmgSucoe.js.map +0 -1
- package/dist/useMutation-B4_S4Xoa.js +0 -50
- package/dist/useMutation-B4_S4Xoa.js.map +0 -1
- package/dist/useQuery-B7ndu5_P.js +0 -107
- package/dist/useQuery-B7ndu5_P.js.map +0 -1
- package/dist/useQueryCache-DqcDMsxb.js +0 -254
- package/dist/useQueryCache-DqcDMsxb.js.map +0 -1
- package/dist/useRpcAuth-Dp2sec-X.js +0 -731
- 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
|