@antelopejs/dms-saas 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -86
- package/dist/config/runtime.d.ts +0 -1
- package/dist/config/runtime.js +0 -7
- package/dist/config/runtime.js.map +1 -1
- package/dist/data-api/platformOwner/index.d.ts +0 -1
- package/dist/data-api/platformOwner/index.js +0 -1
- package/dist/data-api/platformOwner/index.js.map +1 -1
- package/dist/data-api/tenant/index.d.ts +0 -1
- package/dist/data-api/tenant/index.js +0 -1
- package/dist/data-api/tenant/index.js.map +1 -1
- package/dist/notifications/subjects.d.ts +0 -2
- package/dist/notifications/subjects.js +1 -13
- package/dist/notifications/subjects.js.map +1 -1
- package/dist/pages/platform/categories.d.ts +0 -1
- package/dist/pages/platform/categories.js +1 -8
- package/dist/pages/platform/categories.js.map +1 -1
- package/dist/pages/platform/index.d.ts +0 -1
- package/dist/pages/platform/index.js +0 -1
- package/dist/pages/platform/index.js.map +1 -1
- package/dist/pages/tenant/index.d.ts +0 -1
- package/dist/pages/tenant/index.js +0 -1
- package/dist/pages/tenant/index.js.map +1 -1
- package/dist/routes/platformOwner/index.d.ts +0 -1
- package/dist/routes/platformOwner/index.js +0 -1
- package/dist/routes/platformOwner/index.js.map +1 -1
- package/dist/routes/public/register.js +1 -3
- package/dist/routes/public/register.js.map +1 -1
- package/dist/routes/tenant/index.d.ts +0 -1
- package/dist/routes/tenant/index.js +0 -1
- package/dist/routes/tenant/index.js.map +1 -1
- package/dist/routes/tenant/tenant-billing.d.ts +2 -1
- package/dist/routes/tenant/tenant-billing.js +14 -11
- package/dist/routes/tenant/tenant-billing.js.map +1 -1
- package/dist/stripe/webhook-handlers.js +19 -20
- package/dist/stripe/webhook-handlers.js.map +1 -1
- package/dist/stripe/webhook-shared.js +9 -11
- package/dist/stripe/webhook-shared.js.map +1 -1
- package/dist/types/config.d.ts +0 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/keyed-insert.d.ts +7 -0
- package/dist/utils/keyed-insert.js +14 -0
- package/dist/utils/keyed-insert.js.map +1 -0
- package/dist/workspaces/provisioning.js +22 -27
- package/dist/workspaces/provisioning.js.map +1 -1
- package/frontend-vue/i18n/locales/saas-en-GB.json +1 -142
- package/frontend-vue/i18n/locales/saas-fr-FR.json +1 -142
- package/package.json +7 -9
- package/dist/data-api/platformOwner/support.d.ts +0 -58
- package/dist/data-api/platformOwner/support.js +0 -111
- package/dist/data-api/platformOwner/support.js.map +0 -1
- package/dist/data-api/support-fields.d.ts +0 -49
- package/dist/data-api/support-fields.js +0 -166
- package/dist/data-api/support-fields.js.map +0 -1
- package/dist/data-api/support-routes.d.ts +0 -10
- package/dist/data-api/support-routes.js +0 -71
- package/dist/data-api/support-routes.js.map +0 -1
- package/dist/data-api/tenant/support.d.ts +0 -39
- package/dist/data-api/tenant/support.js +0 -53
- package/dist/data-api/tenant/support.js.map +0 -1
- package/dist/pages/platform/support.d.ts +0 -9
- package/dist/pages/platform/support.js +0 -29
- package/dist/pages/platform/support.js.map +0 -1
- package/dist/pages/tenant/support.d.ts +0 -9
- package/dist/pages/tenant/support.js +0 -27
- package/dist/pages/tenant/support.js.map +0 -1
- package/dist/routes/platformOwner/support.d.ts +0 -21
- package/dist/routes/platformOwner/support.js +0 -180
- package/dist/routes/platformOwner/support.js.map +0 -1
- package/dist/routes/tenant/support.d.ts +0 -27
- package/dist/routes/tenant/support.js +0 -185
- package/dist/routes/tenant/support.js.map +0 -1
- package/dist/support/admissions.d.ts +0 -36
- package/dist/support/admissions.js +0 -136
- package/dist/support/admissions.js.map +0 -1
- package/dist/support/attachments.d.ts +0 -12
- package/dist/support/attachments.js +0 -56
- package/dist/support/attachments.js.map +0 -1
- package/dist/support/config.d.ts +0 -19
- package/dist/support/config.js +0 -75
- package/dist/support/config.js.map +0 -1
- package/dist/support/index.d.ts +0 -6
- package/dist/support/index.js +0 -23
- package/dist/support/index.js.map +0 -1
- package/dist/support/operations.d.ts +0 -51
- package/dist/support/operations.js +0 -109
- package/dist/support/operations.js.map +0 -1
- package/dist/support/plans.d.ts +0 -6
- package/dist/support/plans.js +0 -130
- package/dist/support/plans.js.map +0 -1
- package/dist/support/publications.d.ts +0 -8
- package/dist/support/publications.js +0 -106
- package/dist/support/publications.js.map +0 -1
- package/dist/support/recovery.d.ts +0 -7
- package/dist/support/recovery.js +0 -43
- package/dist/support/recovery.js.map +0 -1
- package/dist/support/service.d.ts +0 -51
- package/dist/support/service.js +0 -143
- package/dist/support/service.js.map +0 -1
- package/dist/support/transitions.d.ts +0 -4
- package/dist/support/transitions.js +0 -29
- package/dist/support/transitions.js.map +0 -1
- package/dist/support/uploads.d.ts +0 -39
- package/dist/support/uploads.js +0 -115
- package/dist/support/uploads.js.map +0 -1
- package/dist/support/validation.d.ts +0 -15
- package/dist/support/validation.js +0 -54
- package/dist/support/validation.js.map +0 -1
- package/frontend-vue/app/components/PlatformSupport.vue +0 -461
- package/frontend-vue/app/components/SupportAttachmentUpload.vue +0 -140
- package/frontend-vue/app/components/SupportThreadHistory.vue +0 -139
- package/frontend-vue/app/components/TenantSupport.vue +0 -415
- package/frontend-vue/app/composables/useSupportRequest.ts +0 -74
- package/frontend-vue/app/types/support.ts +0 -123
package/dist/support/uploads.js
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
-
};
|
|
8
|
-
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
-
};
|
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.SupportUploadModel = exports.SupportUpload = void 0;
|
|
13
|
-
exports.supportUploadId = supportUploadId;
|
|
14
|
-
exports.parseSupportUpload = parseSupportUpload;
|
|
15
|
-
exports.createSupportUpload = createSupportUpload;
|
|
16
|
-
exports.getSupportUpload = getSupportUpload;
|
|
17
|
-
const node_crypto_1 = require("node:crypto");
|
|
18
|
-
const interface_api_util_1 = require("@antelopejs/interface-api-util");
|
|
19
|
-
const interface_database_decorators_1 = require("@antelopejs/interface-database-decorators");
|
|
20
|
-
const interface_file_storage_1 = require("@antelopejs/interface-file-storage");
|
|
21
|
-
const constants_1 = require("@antelopejs/interface-dms/constants");
|
|
22
|
-
const config_1 = require("../config");
|
|
23
|
-
const attachments_1 = require("./attachments");
|
|
24
|
-
const config_2 = require("./config");
|
|
25
|
-
const HTTP_BAD_REQUEST = 400;
|
|
26
|
-
const HTTP_UNAVAILABLE = 503;
|
|
27
|
-
const MAX_FILENAME_LENGTH = 255;
|
|
28
|
-
let SupportUpload = class SupportUpload extends interface_database_decorators_1.Table {
|
|
29
|
-
};
|
|
30
|
-
exports.SupportUpload = SupportUpload;
|
|
31
|
-
__decorate([
|
|
32
|
-
(0, interface_database_decorators_1.Field)("string"),
|
|
33
|
-
__metadata("design:type", String)
|
|
34
|
-
], SupportUpload.prototype, "_id", void 0);
|
|
35
|
-
__decorate([
|
|
36
|
-
(0, interface_database_decorators_1.Index)(),
|
|
37
|
-
(0, interface_database_decorators_1.Field)("string"),
|
|
38
|
-
__metadata("design:type", String)
|
|
39
|
-
], SupportUpload.prototype, "stagedKey", void 0);
|
|
40
|
-
__decorate([
|
|
41
|
-
(0, interface_database_decorators_1.Field)("string"),
|
|
42
|
-
__metadata("design:type", String)
|
|
43
|
-
], SupportUpload.prototype, "storage", void 0);
|
|
44
|
-
__decorate([
|
|
45
|
-
(0, interface_database_decorators_1.Field)("string"),
|
|
46
|
-
__metadata("design:type", String)
|
|
47
|
-
], SupportUpload.prototype, "tenantId", void 0);
|
|
48
|
-
exports.SupportUpload = SupportUpload = __decorate([
|
|
49
|
-
(0, interface_database_decorators_1.RegisterTable)("support_uploads", constants_1.CORE_SCHEMA_NAME)
|
|
50
|
-
], SupportUpload);
|
|
51
|
-
class SupportUploadModel extends (0, interface_database_decorators_1.BasicDataModel)(SupportUpload) {
|
|
52
|
-
async record(candidate) {
|
|
53
|
-
try {
|
|
54
|
-
await this.insert(candidate);
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
}
|
|
58
|
-
const stored = await this.get(candidate._id);
|
|
59
|
-
(0, interface_api_util_1.assert)(stored?.storage === candidate.storage &&
|
|
60
|
-
stored.stagedKey === candidate.stagedKey &&
|
|
61
|
-
stored.tenantId === candidate.tenantId, HTTP_UNAVAILABLE, "saas.errors.support.attachment_pending");
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
exports.SupportUploadModel = SupportUploadModel;
|
|
65
|
-
function supportUploadId(storage, stagedKey) {
|
|
66
|
-
return (0, node_crypto_1.createHash)("sha256")
|
|
67
|
-
.update(JSON.stringify([storage, stagedKey]))
|
|
68
|
-
.digest("hex");
|
|
69
|
-
}
|
|
70
|
-
function parseSupportUpload(body) {
|
|
71
|
-
const value = body && typeof body === "object" ? body : {};
|
|
72
|
-
(0, interface_api_util_1.assert)(typeof value.filename === "string" &&
|
|
73
|
-
value.filename.length > 0 &&
|
|
74
|
-
value.filename.length <= MAX_FILENAME_LENGTH &&
|
|
75
|
-
!/[\\/\x00-\x1f\x7f]/u.test(value.filename), HTTP_BAD_REQUEST, "saas.errors.support.invalid_attachment");
|
|
76
|
-
(0, interface_api_util_1.assert)(typeof value.size === "number" &&
|
|
77
|
-
Number.isSafeInteger(value.size) &&
|
|
78
|
-
value.size >= 0 &&
|
|
79
|
-
typeof value.mimetype === "string", HTTP_BAD_REQUEST, "saas.errors.support.invalid_attachment");
|
|
80
|
-
const input = {
|
|
81
|
-
filename: value.filename,
|
|
82
|
-
size: value.size,
|
|
83
|
-
mimetype: value.mimetype,
|
|
84
|
-
};
|
|
85
|
-
(0, attachments_1.assertSupportAttachmentMetadata)(input);
|
|
86
|
-
return input;
|
|
87
|
-
}
|
|
88
|
-
async function createSupportUpload(tenantId, input) {
|
|
89
|
-
const storage = (0, config_1.getSupportStorage)();
|
|
90
|
-
const path = (0, config_2.tenantSupportAttachmentPath)(tenantId);
|
|
91
|
-
const response = await (0, interface_file_storage_1.CreateUploadUrl)({
|
|
92
|
-
...input,
|
|
93
|
-
path,
|
|
94
|
-
staging: true,
|
|
95
|
-
visibility: "private",
|
|
96
|
-
metadata: { filename: input.filename },
|
|
97
|
-
}, {
|
|
98
|
-
maxSize: config_2.SUPPORT_ATTACHMENT_MAX_SIZE,
|
|
99
|
-
allowedMimetypes: [...config_2.SUPPORT_ATTACHMENT_MIMETYPES],
|
|
100
|
-
}, storage);
|
|
101
|
-
(0, attachments_1.assertSupportAttachmentPath)(response.resourceKey, path);
|
|
102
|
-
await (0, interface_database_decorators_1.GetModel)(SupportUploadModel).record({
|
|
103
|
-
_id: supportUploadId(storage, response.resourceKey),
|
|
104
|
-
stagedKey: response.resourceKey,
|
|
105
|
-
storage,
|
|
106
|
-
tenantId,
|
|
107
|
-
});
|
|
108
|
-
return response;
|
|
109
|
-
}
|
|
110
|
-
async function getSupportUpload(tenantId, stagedKey) {
|
|
111
|
-
const uploads = await (0, interface_database_decorators_1.GetModel)(SupportUploadModel).getBy("stagedKey", stagedKey);
|
|
112
|
-
(0, interface_api_util_1.assert)(uploads.length === 1 && uploads[0].tenantId === tenantId, HTTP_BAD_REQUEST, "saas.errors.support.invalid_attachments");
|
|
113
|
-
return uploads[0];
|
|
114
|
-
}
|
|
115
|
-
//# sourceMappingURL=uploads.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"uploads.js","sourceRoot":"","sources":["../../src/support/uploads.ts"],"names":[],"mappings":";;;;;;;;;;;;AA6EA,0CAIC;AAGD,gDA2BC;AAGD,kDA4BC;AAGD,4CAcC;AA/JD,6CAAyC;AACzC,uEAAwD;AACxD,6FAOmD;AACnD,+EAG4C;AAC5C,mEAAuE;AACvE,sCAA8C;AAC9C,+CAGuB;AACvB,qCAIkB;AAElB,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAC7B,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAC7B,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAgBzB,IAAM,aAAa,GAAnB,MAAM,aAAc,SAAQ,qCAAK;CAUvC,CAAA;AAVY,sCAAa;AAEhB;IADP,IAAA,qCAAK,EAAC,QAAQ,CAAC;;0CACI;AAGZ;IAFP,IAAA,qCAAK,GAAE;IACP,IAAA,qCAAK,EAAC,QAAQ,CAAC;;gDACU;AAElB;IADP,IAAA,qCAAK,EAAC,QAAQ,CAAC;;8CACQ;AAEhB;IADP,IAAA,qCAAK,EAAC,QAAQ,CAAC;;+CACS;wBATd,aAAa;IADzB,IAAA,6CAAa,EAAC,iBAAiB,EAAE,4BAAgB,CAAC;GACtC,aAAa,CAUzB;AAGD,MAAa,kBAAmB,SAAQ,IAAA,8CAAc,EAAC,aAAa,CAAC;IAEnE,KAAK,CAAC,MAAM,CAAC,SAAwB;QACnC,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;QAET,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAC7C,IAAA,2BAAM,EACJ,MAAM,EAAE,OAAO,KAAK,SAAS,CAAC,OAAO;YACnC,MAAM,CAAC,SAAS,KAAK,SAAS,CAAC,SAAS;YACxC,MAAM,CAAC,QAAQ,KAAK,SAAS,CAAC,QAAQ,EACxC,gBAAgB,EAChB,wCAAwC,CACzC,CAAC;IACJ,CAAC;CACF;AAjBD,gDAiBC;AAGD,SAAgB,eAAe,CAAC,OAAe,EAAE,SAAiB;IAChE,OAAO,IAAA,wBAAU,EAAC,QAAQ,CAAC;SACxB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;SAC5C,MAAM,CAAC,KAAK,CAAC,CAAC;AACnB,CAAC;AAGD,SAAgB,kBAAkB,CAAC,IAAa;IAC9C,MAAM,KAAK,GAAsB,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9E,IAAA,2BAAM,EACJ,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ;QAChC,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;QACzB,KAAK,CAAC,QAAQ,CAAC,MAAM,IAAI,mBAAmB;QAG5C,CAAC,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAC7C,gBAAgB,EAChB,wCAAwC,CACzC,CAAC;IACF,IAAA,2BAAM,EACJ,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ;QAC5B,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC;QAChC,KAAK,CAAC,IAAI,IAAI,CAAC;QACf,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ,EACpC,gBAAgB,EAChB,wCAAwC,CACzC,CAAC;IACF,MAAM,KAAK,GAAG;QACZ,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;KACzB,CAAC;IACF,IAAA,6CAA+B,EAAC,KAAK,CAAC,CAAC;IACvC,OAAO,KAAK,CAAC;AACf,CAAC;AAGM,KAAK,UAAU,mBAAmB,CACvC,QAAgB,EAChB,KAAyB;IAEzB,MAAM,OAAO,GAAG,IAAA,0BAAiB,GAAE,CAAC;IACpC,MAAM,IAAI,GAAG,IAAA,oCAA2B,EAAC,QAAQ,CAAC,CAAC;IACnD,MAAM,QAAQ,GAAG,MAAM,IAAA,wCAAe,EACpC;QACE,GAAG,KAAK;QACR,IAAI;QACJ,OAAO,EAAE,IAAI;QACb,UAAU,EAAE,SAAS;QACrB,QAAQ,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE;KACvC,EACD;QACE,OAAO,EAAE,oCAA2B;QACpC,gBAAgB,EAAE,CAAC,GAAG,qCAA4B,CAAC;KACpD,EACD,OAAO,CACR,CAAC;IACF,IAAA,yCAA2B,EAAC,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IACxD,MAAM,IAAA,wCAAQ,EAAC,kBAAkB,CAAC,CAAC,MAAM,CAAC;QACxC,GAAG,EAAE,eAAe,CAAC,OAAO,EAAE,QAAQ,CAAC,WAAW,CAAC;QACnD,SAAS,EAAE,QAAQ,CAAC,WAAW;QAC/B,OAAO;QACP,QAAQ;KACT,CAAC,CAAC;IACH,OAAO,QAAQ,CAAC;AAClB,CAAC;AAGM,KAAK,UAAU,gBAAgB,CACpC,QAAgB,EAChB,SAAiB;IAEjB,MAAM,OAAO,GAAG,MAAM,IAAA,wCAAQ,EAAC,kBAAkB,CAAC,CAAC,KAAK,CACtD,WAAW,EACX,SAAS,CACV,CAAC;IACF,IAAA,2BAAM,EACJ,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,EACxD,gBAAgB,EAChB,yCAAyC,CAC1C,CAAC;IACF,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC"}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { type SupportTicketCategory, type SupportTicketPriority, type SupportTicketStatus } from "../db";
|
|
2
|
-
export declare function assertSupportRequestId(value: unknown): asserts value is string;
|
|
3
|
-
export declare function parseSupportRequestId(body: unknown): string;
|
|
4
|
-
export interface SupportMessageInput {
|
|
5
|
-
body: string;
|
|
6
|
-
attachments: string[];
|
|
7
|
-
}
|
|
8
|
-
export interface SupportTicketInput extends SupportMessageInput {
|
|
9
|
-
subject: string;
|
|
10
|
-
category: SupportTicketCategory;
|
|
11
|
-
priority: SupportTicketPriority;
|
|
12
|
-
}
|
|
13
|
-
export declare function parseSupportMessage(body: unknown): SupportMessageInput;
|
|
14
|
-
export declare function parseSupportTicket(body: unknown): SupportTicketInput;
|
|
15
|
-
export declare function parseSupportStatus(value: unknown): SupportTicketStatus;
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.assertSupportRequestId = assertSupportRequestId;
|
|
4
|
-
exports.parseSupportRequestId = parseSupportRequestId;
|
|
5
|
-
exports.parseSupportMessage = parseSupportMessage;
|
|
6
|
-
exports.parseSupportTicket = parseSupportTicket;
|
|
7
|
-
exports.parseSupportStatus = parseSupportStatus;
|
|
8
|
-
const interface_api_util_1 = require("@antelopejs/interface-api-util");
|
|
9
|
-
const db_1 = require("../db");
|
|
10
|
-
const config_1 = require("./config");
|
|
11
|
-
const HTTP_BAD_REQUEST = 400;
|
|
12
|
-
const MAX_SUBJECT_LENGTH = 200;
|
|
13
|
-
const MAX_MESSAGE_LENGTH = 10_000;
|
|
14
|
-
const CATEGORY_SET = new Set(db_1.SUPPORT_TICKET_CATEGORIES);
|
|
15
|
-
const PRIORITY_SET = new Set(db_1.SUPPORT_TICKET_PRIORITIES);
|
|
16
|
-
const STATUS_SET = new Set(db_1.SUPPORT_TICKET_STATUSES);
|
|
17
|
-
const REQUEST_ID = /^[a-zA-Z0-9_-]{16,128}$/;
|
|
18
|
-
function assertSupportRequestId(value) {
|
|
19
|
-
(0, interface_api_util_1.assert)(typeof value === "string" && REQUEST_ID.test(value), HTTP_BAD_REQUEST, "saas.errors.support.invalid_request_id");
|
|
20
|
-
}
|
|
21
|
-
function parseSupportRequestId(body) {
|
|
22
|
-
const request = body;
|
|
23
|
-
assertSupportRequestId(request?.requestId);
|
|
24
|
-
return request.requestId;
|
|
25
|
-
}
|
|
26
|
-
function parseSupportMessage(body) {
|
|
27
|
-
const value = body;
|
|
28
|
-
const message = typeof value?.body === "string" ? value.body.trim() : "";
|
|
29
|
-
const attachments = Array.isArray(value?.attachments)
|
|
30
|
-
? value.attachments.filter((attachment) => typeof attachment === "string" && attachment.length > 0)
|
|
31
|
-
: [];
|
|
32
|
-
(0, interface_api_util_1.assert)(message.length > 0 && message.length <= MAX_MESSAGE_LENGTH, HTTP_BAD_REQUEST, "saas.errors.support.invalid_message");
|
|
33
|
-
(0, interface_api_util_1.assert)(attachments.length <= config_1.SUPPORT_ATTACHMENT_LIMIT, HTTP_BAD_REQUEST, "saas.errors.support.too_many_attachments");
|
|
34
|
-
(0, interface_api_util_1.assert)(new Set(attachments).size === attachments.length, HTTP_BAD_REQUEST, "saas.errors.support.invalid_attachments");
|
|
35
|
-
return { body: message, attachments };
|
|
36
|
-
}
|
|
37
|
-
function parseSupportTicket(body) {
|
|
38
|
-
const value = body;
|
|
39
|
-
const subject = typeof value?.subject === "string" ? value.subject.trim() : "";
|
|
40
|
-
(0, interface_api_util_1.assert)(subject.length > 0 && subject.length <= MAX_SUBJECT_LENGTH, HTTP_BAD_REQUEST, "saas.errors.support.invalid_subject");
|
|
41
|
-
(0, interface_api_util_1.assert)(CATEGORY_SET.has(value?.category ?? ""), HTTP_BAD_REQUEST, "saas.errors.support.invalid_category");
|
|
42
|
-
(0, interface_api_util_1.assert)(PRIORITY_SET.has(value?.priority ?? ""), HTTP_BAD_REQUEST, "saas.errors.support.invalid_priority");
|
|
43
|
-
return {
|
|
44
|
-
subject,
|
|
45
|
-
category: value?.category,
|
|
46
|
-
priority: value?.priority,
|
|
47
|
-
...parseSupportMessage(value),
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
function parseSupportStatus(value) {
|
|
51
|
-
(0, interface_api_util_1.assert)(typeof value === "string" && STATUS_SET.has(value), HTTP_BAD_REQUEST, "saas.errors.support.invalid_status");
|
|
52
|
-
return value;
|
|
53
|
-
}
|
|
54
|
-
//# sourceMappingURL=validation.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/support/validation.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,461 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed, onMounted, ref } from "vue";
|
|
3
|
-
import { sendSupportRequest } from "../composables/useSupportRequest";
|
|
4
|
-
import SupportThreadHistory from "./SupportThreadHistory.vue";
|
|
5
|
-
|
|
6
|
-
const { $authFetch } = useAuthFetch();
|
|
7
|
-
const { t } = useI18n();
|
|
8
|
-
const toast = useToast();
|
|
9
|
-
const { resolveApiError } = useApiErrorMessage();
|
|
10
|
-
|
|
11
|
-
const FILTER_ALL = "all";
|
|
12
|
-
const ASSIGNEE_UNASSIGNED = "unassigned";
|
|
13
|
-
|
|
14
|
-
const inbox = ref<PlatformSupportTicketView[]>([]);
|
|
15
|
-
const owners = ref<PlatformOwnerOptionView[]>([]);
|
|
16
|
-
const selected = ref<PlatformSupportThreadView | null>(null);
|
|
17
|
-
const statusFilter = ref<string>(FILTER_ALL);
|
|
18
|
-
const priorityFilter = ref<string>(FILTER_ALL);
|
|
19
|
-
const isLoading = ref(true);
|
|
20
|
-
const isSubmitting = ref(false);
|
|
21
|
-
const replyBody = ref("");
|
|
22
|
-
const PAGE_SIZE = 20;
|
|
23
|
-
const OWNER_PAGE_SIZE = 100;
|
|
24
|
-
const listPage = ref(1);
|
|
25
|
-
const messagesPage = ref(1);
|
|
26
|
-
const eventsPage = ref(1);
|
|
27
|
-
const inboxTotal = ref(0);
|
|
28
|
-
let selectionRequest = 0;
|
|
29
|
-
let messagesRequest = 0;
|
|
30
|
-
let eventsRequest = 0;
|
|
31
|
-
let inboxRequest = 0;
|
|
32
|
-
|
|
33
|
-
const statusItems = [
|
|
34
|
-
{ value: FILTER_ALL, label: t("saas.support.filters.all_statuses") },
|
|
35
|
-
...["open", "in_progress", "waiting_customer", "resolved", "closed"].map(
|
|
36
|
-
(value) => ({ value, label: t(`saas.support.status.${value}`) }),
|
|
37
|
-
),
|
|
38
|
-
];
|
|
39
|
-
const priorityItems = [
|
|
40
|
-
{ value: FILTER_ALL, label: t("saas.support.filters.all_priorities") },
|
|
41
|
-
...["low", "normal", "high", "urgent"].map((value) => ({
|
|
42
|
-
value,
|
|
43
|
-
label: t(`saas.support.priority.${value}`),
|
|
44
|
-
})),
|
|
45
|
-
];
|
|
46
|
-
const assignmentItems = computed(() => [
|
|
47
|
-
{
|
|
48
|
-
value: ASSIGNEE_UNASSIGNED,
|
|
49
|
-
label: t("saas.support.platform.unassigned"),
|
|
50
|
-
},
|
|
51
|
-
...owners.value.map((owner) => ({
|
|
52
|
-
value: owner._id,
|
|
53
|
-
label: owner.name || owner.email || owner._id,
|
|
54
|
-
})),
|
|
55
|
-
]);
|
|
56
|
-
const assigneeLabels = computed<Record<string, string>>(() =>
|
|
57
|
-
Object.fromEntries(
|
|
58
|
-
owners.value.map((owner) => [
|
|
59
|
-
owner._id,
|
|
60
|
-
owner.name || owner.email || owner._id,
|
|
61
|
-
]),
|
|
62
|
-
),
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
function formatDate(value: string): string {
|
|
66
|
-
return new Intl.DateTimeFormat(undefined, {
|
|
67
|
-
dateStyle: "medium",
|
|
68
|
-
timeStyle: "short",
|
|
69
|
-
}).format(new Date(value));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function inboxQuery(): string {
|
|
73
|
-
const query = new URLSearchParams({
|
|
74
|
-
offset: String((listPage.value - 1) * PAGE_SIZE),
|
|
75
|
-
limit: String(PAGE_SIZE),
|
|
76
|
-
sortKey: "lastMessageAt",
|
|
77
|
-
sortDirection: "desc",
|
|
78
|
-
});
|
|
79
|
-
if (statusFilter.value !== FILTER_ALL) {
|
|
80
|
-
query.set("filter_status", `is:${statusFilter.value}`);
|
|
81
|
-
}
|
|
82
|
-
if (priorityFilter.value !== FILTER_ALL) {
|
|
83
|
-
query.set("filter_priority", `is:${priorityFilter.value}`);
|
|
84
|
-
}
|
|
85
|
-
return query.toString();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function threadQuery(page: number): string {
|
|
89
|
-
return new URLSearchParams({
|
|
90
|
-
offset: String((page - 1) * PAGE_SIZE),
|
|
91
|
-
limit: String(PAGE_SIZE),
|
|
92
|
-
sortKey: "createdAt",
|
|
93
|
-
sortDirection: "desc",
|
|
94
|
-
}).toString();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
async function loadInbox(): Promise<void> {
|
|
98
|
-
const request = ++inboxRequest;
|
|
99
|
-
isLoading.value = true;
|
|
100
|
-
try {
|
|
101
|
-
const suffix = inboxQuery();
|
|
102
|
-
const page = await $authFetch<SupportPageView<PlatformSupportTicketView>>(
|
|
103
|
-
`/api/saas/tables/support-tickets/list?${suffix}`,
|
|
104
|
-
);
|
|
105
|
-
if (request !== inboxRequest) return;
|
|
106
|
-
inbox.value = page.results;
|
|
107
|
-
inboxTotal.value = page.total;
|
|
108
|
-
} catch (error) {
|
|
109
|
-
showError(error, "saas.support.error.load");
|
|
110
|
-
} finally {
|
|
111
|
-
if (request === inboxRequest) isLoading.value = false;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
async function loadOwners(): Promise<void> {
|
|
116
|
-
try {
|
|
117
|
-
const loadedOwners: PlatformOwnerOptionView[] = [];
|
|
118
|
-
let offset = 0;
|
|
119
|
-
let total = 0;
|
|
120
|
-
do {
|
|
121
|
-
const query = new URLSearchParams({
|
|
122
|
-
offset: String(offset),
|
|
123
|
-
limit: String(OWNER_PAGE_SIZE),
|
|
124
|
-
});
|
|
125
|
-
const page = await $authFetch<SupportPageView<PlatformOwnerOptionView>>(
|
|
126
|
-
`/api/saas/tables/support-owners/list?${query}`,
|
|
127
|
-
);
|
|
128
|
-
loadedOwners.push(...page.results);
|
|
129
|
-
if (!page.results.length) break;
|
|
130
|
-
offset += page.results.length;
|
|
131
|
-
total = page.total;
|
|
132
|
-
} while (offset < total);
|
|
133
|
-
owners.value = loadedOwners;
|
|
134
|
-
} catch (error) {
|
|
135
|
-
showError(error, "saas.support.error.load");
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
async function selectTicket(
|
|
140
|
-
ticket: PlatformSupportTicketView,
|
|
141
|
-
): Promise<void> {
|
|
142
|
-
messagesPage.value = 1;
|
|
143
|
-
eventsPage.value = 1;
|
|
144
|
-
const request = ++selectionRequest;
|
|
145
|
-
const messageRequest = ++messagesRequest;
|
|
146
|
-
const eventRequest = ++eventsRequest;
|
|
147
|
-
const baseUrl = `/api/saas/support/platform/${ticket._instance}/${ticket._id}`;
|
|
148
|
-
try {
|
|
149
|
-
const [detail, messages, events] = await Promise.all([
|
|
150
|
-
$authFetch<PlatformSupportTicketDetailView>(baseUrl),
|
|
151
|
-
$authFetch<SupportPageView<SupportMessageView>>(
|
|
152
|
-
`${baseUrl}/messages/list?${threadQuery(messagesPage.value)}`,
|
|
153
|
-
),
|
|
154
|
-
$authFetch<SupportPageView<SupportTicketEventView>>(
|
|
155
|
-
`${baseUrl}/events/list?${threadQuery(eventsPage.value)}`,
|
|
156
|
-
),
|
|
157
|
-
]);
|
|
158
|
-
if (
|
|
159
|
-
request === selectionRequest &&
|
|
160
|
-
messageRequest === messagesRequest &&
|
|
161
|
-
eventRequest === eventsRequest
|
|
162
|
-
) {
|
|
163
|
-
selected.value = { ...detail, messages, events };
|
|
164
|
-
}
|
|
165
|
-
} catch (error) {
|
|
166
|
-
showError(error, "saas.support.error.load");
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
async function updateTicket(patch: SupportTicketPatch): Promise<void> {
|
|
171
|
-
if (!selected.value) return;
|
|
172
|
-
isSubmitting.value = true;
|
|
173
|
-
const { tenantId, ticket } = selected.value;
|
|
174
|
-
try {
|
|
175
|
-
await sendSupportRequest(
|
|
176
|
-
`platform:${tenantId}:${ticket._id}:update`,
|
|
177
|
-
patch,
|
|
178
|
-
(body) =>
|
|
179
|
-
$authFetch(`/api/saas/support/platform/${tenantId}/${ticket._id}`, {
|
|
180
|
-
method: "PUT",
|
|
181
|
-
body,
|
|
182
|
-
}),
|
|
183
|
-
);
|
|
184
|
-
eventsPage.value = 1;
|
|
185
|
-
await Promise.all([
|
|
186
|
-
loadInbox(),
|
|
187
|
-
refreshDetail(),
|
|
188
|
-
loadThreadPage("events"),
|
|
189
|
-
]);
|
|
190
|
-
} catch (error) {
|
|
191
|
-
showError(error, "saas.support.error.update");
|
|
192
|
-
} finally {
|
|
193
|
-
isSubmitting.value = false;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function changeStatus(status: string): void {
|
|
198
|
-
void updateTicket({ status: status as SupportStatus });
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function changeAssignment(assignedTo: string): void {
|
|
202
|
-
void updateTicket({
|
|
203
|
-
assignedTo: assignedTo === ASSIGNEE_UNASSIGNED ? null : assignedTo,
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
async function sendReply(): Promise<void> {
|
|
208
|
-
if (!selected.value || !replyBody.value.trim()) return;
|
|
209
|
-
const { tenantId, ticket } = selected.value;
|
|
210
|
-
isSubmitting.value = true;
|
|
211
|
-
try {
|
|
212
|
-
await sendSupportRequest(
|
|
213
|
-
`platform:${tenantId}:${ticket._id}:reply`,
|
|
214
|
-
{ body: replyBody.value, attachments: [] },
|
|
215
|
-
(body) =>
|
|
216
|
-
$authFetch(
|
|
217
|
-
`/api/saas/support/platform/${tenantId}/${ticket._id}/messages`,
|
|
218
|
-
{ method: "POST", body },
|
|
219
|
-
),
|
|
220
|
-
);
|
|
221
|
-
replyBody.value = "";
|
|
222
|
-
messagesPage.value = 1;
|
|
223
|
-
eventsPage.value = 1;
|
|
224
|
-
await Promise.all([
|
|
225
|
-
loadInbox(),
|
|
226
|
-
refreshDetail(),
|
|
227
|
-
loadThreadPage("messages"),
|
|
228
|
-
loadThreadPage("events"),
|
|
229
|
-
]);
|
|
230
|
-
} catch (error) {
|
|
231
|
-
showError(error, "saas.support.error.reply");
|
|
232
|
-
} finally {
|
|
233
|
-
isSubmitting.value = false;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
async function openAttachment(resourceKey: string): Promise<void> {
|
|
238
|
-
if (!selected.value) return;
|
|
239
|
-
const { tenantId, ticket } = selected.value;
|
|
240
|
-
const query = new URLSearchParams({ resourceKey });
|
|
241
|
-
try {
|
|
242
|
-
const file = await $authFetch<SupportAttachmentView>(
|
|
243
|
-
`/api/saas/support/platform/${tenantId}/${ticket._id}/attachment?${query}`,
|
|
244
|
-
);
|
|
245
|
-
window.open(file.url, "_blank", "noopener,noreferrer");
|
|
246
|
-
} catch (error) {
|
|
247
|
-
showError(error, "saas.support.error.attachment");
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function showError(error: unknown, fallback: string): void {
|
|
252
|
-
toast.add({
|
|
253
|
-
title: resolveApiError(error, fallback),
|
|
254
|
-
color: "error",
|
|
255
|
-
icon: "i-ph-warning-circle",
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
onMounted(() => Promise.all([loadInbox(), loadOwners()]));
|
|
260
|
-
|
|
261
|
-
function applyFilters(): void {
|
|
262
|
-
listPage.value = 1;
|
|
263
|
-
void loadInbox();
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function changeListPage(page: number): void {
|
|
267
|
-
listPage.value = page;
|
|
268
|
-
void loadInbox();
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
async function refreshDetail(): Promise<void> {
|
|
272
|
-
if (!selected.value) return Promise.resolve();
|
|
273
|
-
const { tenantId, ticket } = selected.value;
|
|
274
|
-
const request = ++selectionRequest;
|
|
275
|
-
const detail = await $authFetch<PlatformSupportTicketDetailView>(
|
|
276
|
-
`/api/saas/support/platform/${tenantId}/${ticket._id}`,
|
|
277
|
-
);
|
|
278
|
-
if (
|
|
279
|
-
request === selectionRequest &&
|
|
280
|
-
selected.value?.ticket._id === ticket._id &&
|
|
281
|
-
selected.value?.tenantId === tenantId
|
|
282
|
-
) {
|
|
283
|
-
Object.assign(selected.value, detail);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function changeThreadPage(kind: "messages" | "events", page: number): void {
|
|
288
|
-
const pages = { messages: messagesPage, events: eventsPage };
|
|
289
|
-
pages[kind].value = page;
|
|
290
|
-
void loadThreadPage(kind);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
async function loadThreadPage(kind: "messages" | "events"): Promise<void> {
|
|
294
|
-
if (!selected.value) return;
|
|
295
|
-
const { tenantId, ticket } = selected.value;
|
|
296
|
-
const request = kind === "messages" ? ++messagesRequest : ++eventsRequest;
|
|
297
|
-
const pageNumber =
|
|
298
|
-
kind === "messages" ? messagesPage.value : eventsPage.value;
|
|
299
|
-
try {
|
|
300
|
-
const page = await $authFetch<
|
|
301
|
-
SupportPageView<SupportMessageView | SupportTicketEventView>
|
|
302
|
-
>(
|
|
303
|
-
`/api/saas/support/platform/${tenantId}/${ticket._id}/${kind}/list?${threadQuery(pageNumber)}`,
|
|
304
|
-
);
|
|
305
|
-
const currentRequest =
|
|
306
|
-
kind === "messages" ? messagesRequest : eventsRequest;
|
|
307
|
-
if (
|
|
308
|
-
request !== currentRequest ||
|
|
309
|
-
selected.value?.ticket._id !== ticket._id ||
|
|
310
|
-
selected.value?.tenantId !== tenantId
|
|
311
|
-
)
|
|
312
|
-
return;
|
|
313
|
-
if (kind === "messages") {
|
|
314
|
-
selected.value.messages = page as SupportPageView<SupportMessageView>;
|
|
315
|
-
} else {
|
|
316
|
-
selected.value.events = page as SupportPageView<SupportTicketEventView>;
|
|
317
|
-
}
|
|
318
|
-
} catch (error) {
|
|
319
|
-
showError(error, "saas.support.error.load");
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
</script>
|
|
323
|
-
|
|
324
|
-
<template>
|
|
325
|
-
<div class="flex flex-col gap-5">
|
|
326
|
-
<UCard>
|
|
327
|
-
<div class="flex flex-wrap items-end gap-4">
|
|
328
|
-
<UFormField
|
|
329
|
-
:label="$t('saas.support.filters.status')"
|
|
330
|
-
class="min-w-48"
|
|
331
|
-
>
|
|
332
|
-
<USelect
|
|
333
|
-
v-model="statusFilter"
|
|
334
|
-
:items="statusItems"
|
|
335
|
-
class="w-full"
|
|
336
|
-
/>
|
|
337
|
-
</UFormField>
|
|
338
|
-
<UFormField
|
|
339
|
-
:label="$t('saas.support.filters.priority')"
|
|
340
|
-
class="min-w-48"
|
|
341
|
-
>
|
|
342
|
-
<USelect
|
|
343
|
-
v-model="priorityFilter"
|
|
344
|
-
:items="priorityItems"
|
|
345
|
-
class="w-full"
|
|
346
|
-
/>
|
|
347
|
-
</UFormField>
|
|
348
|
-
<UButton color="neutral" variant="outline" @click="applyFilters">
|
|
349
|
-
{{ $t("saas.support.filters.apply") }}
|
|
350
|
-
</UButton>
|
|
351
|
-
</div>
|
|
352
|
-
</UCard>
|
|
353
|
-
|
|
354
|
-
<div class="grid gap-6 xl:grid-cols-[24rem_1fr]">
|
|
355
|
-
<div class="flex flex-col gap-3">
|
|
356
|
-
<USkeleton v-if="isLoading" class="h-40" />
|
|
357
|
-
<UCard v-else-if="!inbox.length">
|
|
358
|
-
<p class="text-muted text-center text-sm">
|
|
359
|
-
{{ $t("saas.support.platform.empty") }}
|
|
360
|
-
</p>
|
|
361
|
-
</UCard>
|
|
362
|
-
<template v-else>
|
|
363
|
-
<button
|
|
364
|
-
v-for="ticket in inbox"
|
|
365
|
-
:key="`${ticket._instance}:${ticket._id}`"
|
|
366
|
-
class="border-default hover:bg-elevated rounded-lg border p-4 text-left"
|
|
367
|
-
:class="{
|
|
368
|
-
'ring-primary ring-2':
|
|
369
|
-
selected?.ticket._id === ticket._id &&
|
|
370
|
-
selected?.tenantId === ticket._instance,
|
|
371
|
-
}"
|
|
372
|
-
@click="selectTicket(ticket)"
|
|
373
|
-
>
|
|
374
|
-
<div class="flex justify-between gap-2">
|
|
375
|
-
<p class="font-medium">{{ ticket.subject }}</p>
|
|
376
|
-
<UBadge size="sm" variant="subtle">
|
|
377
|
-
{{ $t(`saas.support.priority.${ticket.priority}`) }}
|
|
378
|
-
</UBadge>
|
|
379
|
-
</div>
|
|
380
|
-
<p class="text-muted mt-1 text-sm">{{ ticket.tenantName }}</p>
|
|
381
|
-
<div class="text-muted mt-3 flex justify-between text-xs">
|
|
382
|
-
<span>{{ $t(`saas.support.status.${ticket.status}`) }}</span>
|
|
383
|
-
<span>{{ formatDate(ticket.lastMessageAt) }}</span>
|
|
384
|
-
</div>
|
|
385
|
-
</button>
|
|
386
|
-
<UPagination
|
|
387
|
-
v-if="inboxTotal > PAGE_SIZE"
|
|
388
|
-
:page="listPage"
|
|
389
|
-
:total="inboxTotal"
|
|
390
|
-
:items-per-page="PAGE_SIZE"
|
|
391
|
-
size="sm"
|
|
392
|
-
class="self-center"
|
|
393
|
-
@update:page="changeListPage"
|
|
394
|
-
/>
|
|
395
|
-
</template>
|
|
396
|
-
</div>
|
|
397
|
-
|
|
398
|
-
<UCard v-if="selected">
|
|
399
|
-
<template #header>
|
|
400
|
-
<div>
|
|
401
|
-
<p class="text-muted text-sm">{{ selected.tenantName }}</p>
|
|
402
|
-
<h2 class="text-lg font-semibold">
|
|
403
|
-
{{ selected.ticket.subject }}
|
|
404
|
-
</h2>
|
|
405
|
-
</div>
|
|
406
|
-
</template>
|
|
407
|
-
<div class="flex flex-col gap-5">
|
|
408
|
-
<div class="grid gap-4 sm:grid-cols-2">
|
|
409
|
-
<UFormField :label="$t('saas.support.filters.status')">
|
|
410
|
-
<USelect
|
|
411
|
-
:model-value="selected.ticket.status"
|
|
412
|
-
:items="statusItems.slice(1)"
|
|
413
|
-
class="w-full"
|
|
414
|
-
:disabled="isSubmitting"
|
|
415
|
-
@update:model-value="changeStatus"
|
|
416
|
-
/>
|
|
417
|
-
</UFormField>
|
|
418
|
-
<UFormField :label="$t('saas.support.platform.assignee')">
|
|
419
|
-
<USelect
|
|
420
|
-
:model-value="
|
|
421
|
-
selected.ticket.assignedTo ?? ASSIGNEE_UNASSIGNED
|
|
422
|
-
"
|
|
423
|
-
:items="assignmentItems"
|
|
424
|
-
class="w-full"
|
|
425
|
-
:disabled="isSubmitting"
|
|
426
|
-
@update:model-value="changeAssignment"
|
|
427
|
-
/>
|
|
428
|
-
</UFormField>
|
|
429
|
-
</div>
|
|
430
|
-
<USeparator />
|
|
431
|
-
<SupportThreadHistory
|
|
432
|
-
:messages="selected.messages"
|
|
433
|
-
:events="selected.events"
|
|
434
|
-
:assignee-labels="assigneeLabels"
|
|
435
|
-
@attachment="openAttachment"
|
|
436
|
-
@messages-page="changeThreadPage('messages', $event)"
|
|
437
|
-
@events-page="changeThreadPage('events', $event)"
|
|
438
|
-
/>
|
|
439
|
-
<UTextarea
|
|
440
|
-
v-model="replyBody"
|
|
441
|
-
:rows="5"
|
|
442
|
-
:placeholder="$t('saas.support.reply_placeholder')"
|
|
443
|
-
class="w-full"
|
|
444
|
-
/>
|
|
445
|
-
<div class="flex justify-end">
|
|
446
|
-
<UButton :loading="isSubmitting" @click="sendReply">
|
|
447
|
-
{{ $t("saas.support.send") }}
|
|
448
|
-
</UButton>
|
|
449
|
-
</div>
|
|
450
|
-
</div>
|
|
451
|
-
</UCard>
|
|
452
|
-
<UCard v-else class="min-h-64">
|
|
453
|
-
<div
|
|
454
|
-
class="text-muted flex h-full items-center justify-center text-sm"
|
|
455
|
-
>
|
|
456
|
-
{{ $t("saas.support.platform.select") }}
|
|
457
|
-
</div>
|
|
458
|
-
</UCard>
|
|
459
|
-
</div>
|
|
460
|
-
</div>
|
|
461
|
-
</template>
|