@djangocfg/ext-support 1.0.24 → 1.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.cjs +1 -1
- package/dist/config.js +1 -1
- package/dist/hooks.cjs +398 -334
- package/dist/hooks.d.cts +6 -2
- package/dist/hooks.d.ts +6 -2
- package/dist/hooks.js +356 -291
- package/dist/i18n.cjs +72 -72
- package/dist/i18n.js +73 -73
- package/dist/index-D-xo66K9.d.cts +862 -0
- package/dist/index-D-xo66K9.d.ts +863 -0
- package/dist/index-Dov7pn8Z.d.ts +2 -1
- package/dist/index-rR_XqXq1.d.cts +838 -0
- package/dist/index-rR_XqXq1.d.ts +838 -0
- package/dist/index.cjs +398 -334
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +356 -291
- package/package.json +8 -8
- package/src/adapters/api.ts +4 -5
- package/src/adapters/demo.ts +2 -1
- package/src/api/generated/ext_support/_utils/fetchers/ext_support__support.ts +4 -4
- package/src/api/generated/ext_support/_utils/hooks/ext_support__support.ts +2 -2
- package/src/api/generated/ext_support/_utils/schemas/Ticket.schema.ts +1 -1
- package/src/api/generated/ext_support/_utils/schemas/TicketRequest.schema.ts +1 -1
- package/src/api/generated/ext_support/enums.ts +0 -30
- package/src/api/generated/ext_support/ext_support__support/client.ts +6 -6
- package/src/api/generated/ext_support/ext_support__support/models.ts +2 -2
- package/src/api/generated/ext_support/schema.json +36 -0
- package/src/components/CreateTicketSheet.tsx +7 -12
- package/src/components/SupportHero.tsx +35 -47
- package/src/components/TicketItem.tsx +28 -74
- package/src/components/TicketList.tsx +30 -23
- package/src/components/TicketSheet.tsx +246 -162
- package/src/contexts/SupportExtensionProvider.tsx +4 -4
- package/src/contexts/types.ts +2 -2
- package/src/i18n/useSupportT.ts +3 -3
- package/src/utils/status.ts +44 -0
- package/src/utils/time.ts +88 -0
package/dist/index.cjs
CHANGED
|
@@ -4,7 +4,7 @@ var api = require('@djangocfg/ext-base/api');
|
|
|
4
4
|
var consola = require('consola');
|
|
5
5
|
var pRetry = require('p-retry');
|
|
6
6
|
var zod = require('zod');
|
|
7
|
-
var
|
|
7
|
+
var react = require('react');
|
|
8
8
|
var jsxRuntime = require('react/jsx-runtime');
|
|
9
9
|
var useSWRInfinite = require('swr/infinite');
|
|
10
10
|
var auth = require('@djangocfg/api/auth');
|
|
@@ -13,7 +13,7 @@ var lucideReact = require('lucide-react');
|
|
|
13
13
|
var nextIntl = require('next-intl');
|
|
14
14
|
var uiCore = require('@djangocfg/ui-core');
|
|
15
15
|
var lib = require('@djangocfg/ui-core/lib');
|
|
16
|
-
var
|
|
16
|
+
var moment = require('moment');
|
|
17
17
|
var reactHookForm = require('react-hook-form');
|
|
18
18
|
var zod$1 = require('@hookform/resolvers/zod');
|
|
19
19
|
var extBase = require('@djangocfg/ext-base');
|
|
@@ -21,9 +21,8 @@ var extBase = require('@djangocfg/ext-base');
|
|
|
21
21
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
22
22
|
|
|
23
23
|
var pRetry__default = /*#__PURE__*/_interopDefault(pRetry);
|
|
24
|
-
var React4__default = /*#__PURE__*/_interopDefault(React4);
|
|
25
24
|
var useSWRInfinite__default = /*#__PURE__*/_interopDefault(useSWRInfinite);
|
|
26
|
-
var
|
|
25
|
+
var moment__default = /*#__PURE__*/_interopDefault(moment);
|
|
27
26
|
|
|
28
27
|
// src/api/generated/ext_support/ext_support__support/client.ts
|
|
29
28
|
var ExtSupportSupport = class {
|
|
@@ -42,7 +41,7 @@ var ExtSupportSupport = class {
|
|
|
42
41
|
if (isParamsObject) {
|
|
43
42
|
params = args[0];
|
|
44
43
|
} else {
|
|
45
|
-
params = {
|
|
44
|
+
params = { ordering: args[0], page: args[1], page_size: args[2], search: args[3] };
|
|
46
45
|
}
|
|
47
46
|
const response = await this.client.request("GET", "/cfg/support/tickets/", { params });
|
|
48
47
|
return response;
|
|
@@ -67,7 +66,7 @@ var ExtSupportSupport = class {
|
|
|
67
66
|
if (isParamsObject) {
|
|
68
67
|
params = args[1];
|
|
69
68
|
} else {
|
|
70
|
-
params = {
|
|
69
|
+
params = { ordering: args[1], page: args[2], page_size: args[3], search: args[4] };
|
|
71
70
|
}
|
|
72
71
|
const response = await this.client.request("GET", `/cfg/support/tickets/${ticket_uuid}/messages/`, { params });
|
|
73
72
|
return response;
|
|
@@ -758,22 +757,6 @@ var PatchedTicketRequestStatus = /* @__PURE__ */ ((PatchedTicketRequestStatus2)
|
|
|
758
757
|
PatchedTicketRequestStatus2["CLOSED"] = "closed";
|
|
759
758
|
return PatchedTicketRequestStatus2;
|
|
760
759
|
})(PatchedTicketRequestStatus || {});
|
|
761
|
-
var TicketStatus = /* @__PURE__ */ ((TicketStatus2) => {
|
|
762
|
-
TicketStatus2["OPEN"] = "open";
|
|
763
|
-
TicketStatus2["WAITING_FOR_USER"] = "waiting_for_user";
|
|
764
|
-
TicketStatus2["WAITING_FOR_ADMIN"] = "waiting_for_admin";
|
|
765
|
-
TicketStatus2["RESOLVED"] = "resolved";
|
|
766
|
-
TicketStatus2["CLOSED"] = "closed";
|
|
767
|
-
return TicketStatus2;
|
|
768
|
-
})(TicketStatus || {});
|
|
769
|
-
var TicketRequestStatus = /* @__PURE__ */ ((TicketRequestStatus2) => {
|
|
770
|
-
TicketRequestStatus2["OPEN"] = "open";
|
|
771
|
-
TicketRequestStatus2["WAITING_FOR_USER"] = "waiting_for_user";
|
|
772
|
-
TicketRequestStatus2["WAITING_FOR_ADMIN"] = "waiting_for_admin";
|
|
773
|
-
TicketRequestStatus2["RESOLVED"] = "resolved";
|
|
774
|
-
TicketRequestStatus2["CLOSED"] = "closed";
|
|
775
|
-
return TicketRequestStatus2;
|
|
776
|
-
})(TicketRequestStatus || {});
|
|
777
760
|
var SenderSchema = zod.z.object({
|
|
778
761
|
id: zod.z.int(),
|
|
779
762
|
display_username: zod.z.string(),
|
|
@@ -817,7 +800,7 @@ var TicketSchema = zod.z.object({
|
|
|
817
800
|
uuid: zod.z.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i),
|
|
818
801
|
user: zod.z.int(),
|
|
819
802
|
subject: zod.z.string().max(255),
|
|
820
|
-
status: zod.z.nativeEnum(
|
|
803
|
+
status: zod.z.nativeEnum(PatchedTicketRequestStatus).optional(),
|
|
821
804
|
created_at: zod.z.string().datetime({ offset: true }),
|
|
822
805
|
unanswered_messages_count: zod.z.int()
|
|
823
806
|
});
|
|
@@ -845,7 +828,7 @@ zod.z.object({
|
|
|
845
828
|
zod.z.object({
|
|
846
829
|
user: zod.z.int(),
|
|
847
830
|
subject: zod.z.string().min(1).max(255),
|
|
848
|
-
status: zod.z.nativeEnum(
|
|
831
|
+
status: zod.z.nativeEnum(PatchedTicketRequestStatus).optional()
|
|
849
832
|
});
|
|
850
833
|
|
|
851
834
|
// src/api/generated/ext_support/api-instance.ts
|
|
@@ -881,7 +864,7 @@ function configureAPI(config) {
|
|
|
881
864
|
// src/api/generated/ext_support/_utils/fetchers/ext_support__support.ts
|
|
882
865
|
async function getSupportTicketsList(params, client) {
|
|
883
866
|
const api = getAPIInstance();
|
|
884
|
-
const response = await api.ext_support_support.ticketsList(params?.page, params?.page_size);
|
|
867
|
+
const response = await api.ext_support_support.ticketsList(params?.ordering, params?.page, params?.page_size, params?.search);
|
|
885
868
|
try {
|
|
886
869
|
return PaginatedTicketListSchema.parse(response);
|
|
887
870
|
} catch (error) {
|
|
@@ -965,7 +948,7 @@ Method: POST`);
|
|
|
965
948
|
}
|
|
966
949
|
async function getSupportTicketsMessagesList(ticket_uuid, params, client) {
|
|
967
950
|
const api = getAPIInstance();
|
|
968
|
-
const response = await api.ext_support_support.ticketsMessagesList(ticket_uuid, params?.page, params?.page_size);
|
|
951
|
+
const response = await api.ext_support_support.ticketsMessagesList(ticket_uuid, params?.ordering, params?.page, params?.page_size, params?.search);
|
|
969
952
|
try {
|
|
970
953
|
return PaginatedMessageListSchema.parse(response);
|
|
971
954
|
} catch (error) {
|
|
@@ -1177,13 +1160,13 @@ var API = class {
|
|
|
1177
1160
|
// src/api/index.ts
|
|
1178
1161
|
api.initializeExtensionAPI(configureAPI);
|
|
1179
1162
|
var apiSupport = api.createExtensionAPI(API);
|
|
1180
|
-
var SupportContext =
|
|
1163
|
+
var SupportContext = react.createContext(void 0);
|
|
1181
1164
|
function SupportProvider({ children, adapter }) {
|
|
1182
|
-
const value =
|
|
1165
|
+
const value = react.useMemo(() => ({ adapter }), [adapter]);
|
|
1183
1166
|
return /* @__PURE__ */ jsxRuntime.jsx(SupportContext.Provider, { value, children });
|
|
1184
1167
|
}
|
|
1185
1168
|
function useAdapter() {
|
|
1186
|
-
const context =
|
|
1169
|
+
const context = react.useContext(SupportContext);
|
|
1187
1170
|
if (!context) {
|
|
1188
1171
|
throw new Error("useSupport hooks must be used within SupportProvider");
|
|
1189
1172
|
}
|
|
@@ -1267,18 +1250,18 @@ function useApiTickets() {
|
|
|
1267
1250
|
revalidateFirstPage: false,
|
|
1268
1251
|
parallel: false
|
|
1269
1252
|
});
|
|
1270
|
-
const tickets =
|
|
1253
|
+
const tickets = react.useMemo(() => {
|
|
1271
1254
|
return ticketsData ? ticketsData.flatMap((page) => page.results) : [];
|
|
1272
1255
|
}, [ticketsData]);
|
|
1273
1256
|
const hasMore = ticketsData && ticketsData[ticketsData.length - 1]?.has_next || false;
|
|
1274
1257
|
const totalCount = ticketsData && ticketsData[ticketsData.length - 1]?.count || 0;
|
|
1275
1258
|
const isLoadingMore = isValidating && ticketsData && typeof ticketsData[size - 1] !== "undefined";
|
|
1276
|
-
const loadMore =
|
|
1259
|
+
const loadMore = react.useCallback(() => {
|
|
1277
1260
|
if (hasMore && !isLoadingMore) {
|
|
1278
1261
|
setSize(size + 1);
|
|
1279
1262
|
}
|
|
1280
1263
|
}, [hasMore, isLoadingMore, size, setSize]);
|
|
1281
|
-
const refresh =
|
|
1264
|
+
const refresh = react.useCallback(async () => {
|
|
1282
1265
|
await mutate();
|
|
1283
1266
|
}, [mutate]);
|
|
1284
1267
|
return {
|
|
@@ -1312,21 +1295,21 @@ function useApiMessages(ticketUuid) {
|
|
|
1312
1295
|
revalidateFirstPage: false,
|
|
1313
1296
|
parallel: false
|
|
1314
1297
|
});
|
|
1315
|
-
const messages =
|
|
1298
|
+
const messages = react.useMemo(() => {
|
|
1316
1299
|
return data ? data.flatMap((page) => page.results) : [];
|
|
1317
1300
|
}, [data]);
|
|
1318
1301
|
const hasMore = data && data[data.length - 1]?.has_next || false;
|
|
1319
1302
|
const totalCount = data && data[data.length - 1]?.count || 0;
|
|
1320
1303
|
const isLoadingMore = isValidating && data && typeof data[size - 1] !== "undefined";
|
|
1321
|
-
const loadMore =
|
|
1304
|
+
const loadMore = react.useCallback(() => {
|
|
1322
1305
|
if (hasMore && !isLoadingMore) {
|
|
1323
1306
|
setSize(size + 1);
|
|
1324
1307
|
}
|
|
1325
1308
|
}, [hasMore, isLoadingMore, size, setSize]);
|
|
1326
|
-
const refresh =
|
|
1309
|
+
const refresh = react.useCallback(async () => {
|
|
1327
1310
|
await mutate();
|
|
1328
1311
|
}, [mutate]);
|
|
1329
|
-
const addOptimistic =
|
|
1312
|
+
const addOptimistic = react.useCallback((message) => {
|
|
1330
1313
|
if (!data || !data[0]) return;
|
|
1331
1314
|
const newData = [...data];
|
|
1332
1315
|
const firstPage = newData[0];
|
|
@@ -1356,7 +1339,7 @@ function useApiOperations() {
|
|
|
1356
1339
|
const { user } = auth.useAuth();
|
|
1357
1340
|
const createTicketMutation = useCreateSupportTicketsCreate();
|
|
1358
1341
|
const createMessageMutation = useCreateSupportTicketsMessagesCreate();
|
|
1359
|
-
const createTicket =
|
|
1342
|
+
const createTicket = react.useCallback(async (input) => {
|
|
1360
1343
|
if (!user?.id) {
|
|
1361
1344
|
throw new Error("User must be authenticated to create tickets");
|
|
1362
1345
|
}
|
|
@@ -1371,7 +1354,7 @@ function useApiOperations() {
|
|
|
1371
1354
|
}
|
|
1372
1355
|
return ticket;
|
|
1373
1356
|
}, [user, createTicketMutation, createMessageMutation]);
|
|
1374
|
-
const sendMessage =
|
|
1357
|
+
const sendMessage = react.useCallback(async (input) => {
|
|
1375
1358
|
const message = await createMessageMutation(input.ticketUuid, {
|
|
1376
1359
|
text: input.text
|
|
1377
1360
|
});
|
|
@@ -1399,7 +1382,7 @@ function createApiAdapter() {
|
|
|
1399
1382
|
}
|
|
1400
1383
|
function useApiAdapter() {
|
|
1401
1384
|
const { createTicket, sendMessage } = useApiOperations();
|
|
1402
|
-
return
|
|
1385
|
+
return react.useMemo(() => ({
|
|
1403
1386
|
useTickets: useApiTickets,
|
|
1404
1387
|
useMessages: useApiMessages,
|
|
1405
1388
|
createTicket,
|
|
@@ -1548,8 +1531,8 @@ function addMessage(ticketUuid, message) {
|
|
|
1548
1531
|
notifySubscribers();
|
|
1549
1532
|
}
|
|
1550
1533
|
function useDemoTickets() {
|
|
1551
|
-
const [, forceUpdate] =
|
|
1552
|
-
|
|
1534
|
+
const [, forceUpdate] = react.useState({});
|
|
1535
|
+
react.useMemo(() => {
|
|
1553
1536
|
const update = () => forceUpdate({});
|
|
1554
1537
|
subscribers.add(update);
|
|
1555
1538
|
return () => {
|
|
@@ -1569,15 +1552,15 @@ function useDemoTickets() {
|
|
|
1569
1552
|
};
|
|
1570
1553
|
}
|
|
1571
1554
|
function useDemoMessages(ticketUuid) {
|
|
1572
|
-
const [, forceUpdate] =
|
|
1573
|
-
|
|
1555
|
+
const [, forceUpdate] = react.useState({});
|
|
1556
|
+
react.useMemo(() => {
|
|
1574
1557
|
const update = () => forceUpdate({});
|
|
1575
1558
|
subscribers.add(update);
|
|
1576
1559
|
return () => {
|
|
1577
1560
|
subscribers.delete(update);
|
|
1578
1561
|
};
|
|
1579
1562
|
}, []);
|
|
1580
|
-
const messages =
|
|
1563
|
+
const messages = react.useMemo(() => {
|
|
1581
1564
|
if (!ticketUuid) return [];
|
|
1582
1565
|
return demoMessages[ticketUuid] || [];
|
|
1583
1566
|
}, [ticketUuid]);
|
|
@@ -1720,78 +1703,6 @@ var en = {
|
|
|
1720
1703
|
}
|
|
1721
1704
|
};
|
|
1722
1705
|
|
|
1723
|
-
// src/i18n/locales/ru.ts
|
|
1724
|
-
var ru = {
|
|
1725
|
-
layout: {
|
|
1726
|
-
title: "\u0426\u0435\u043D\u0442\u0440 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438",
|
|
1727
|
-
titleShort: "\u041F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0430",
|
|
1728
|
-
subtitle: "\u041F\u043E\u043B\u0443\u0447\u0438\u0442\u0435 \u043F\u043E\u043C\u043E\u0449\u044C \u043E\u0442 \u043D\u0430\u0448\u0435\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u044B \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438",
|
|
1729
|
-
newTicket: "\u041D\u043E\u0432\u043E\u0435 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435"
|
|
1730
|
-
},
|
|
1731
|
-
hero: {
|
|
1732
|
-
openTickets: "\u041E\u0442\u043A\u0440\u044B\u0442\u044B\u0445 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439",
|
|
1733
|
-
unreadMessages: "\u041D\u0435\u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u043D\u044B\u0445"
|
|
1734
|
-
},
|
|
1735
|
-
status: {
|
|
1736
|
-
open: "\u041E\u0442\u043A\u0440\u044B\u0442",
|
|
1737
|
-
waitingForUser: "\u041E\u0436\u0438\u0434\u0430\u0435\u0442 \u0432\u0430\u0441",
|
|
1738
|
-
waitingForAdmin: "\u041E\u0436\u0438\u0434\u0430\u0435\u0442 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0443",
|
|
1739
|
-
resolved: "\u0420\u0435\u0448\u0451\u043D",
|
|
1740
|
-
closed: "\u0417\u0430\u043A\u0440\u044B\u0442"
|
|
1741
|
-
},
|
|
1742
|
-
ticketList: {
|
|
1743
|
-
noTickets: "\u041D\u0435\u0442 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439",
|
|
1744
|
-
noTicketsDescription: "\u0421\u043E\u0437\u0434\u0430\u0439\u0442\u0435 \u043F\u0435\u0440\u0432\u043E\u0435 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435, \u0447\u0442\u043E\u0431\u044B \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C \u043F\u043E\u043C\u043E\u0449\u044C",
|
|
1745
|
-
loadingMore: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430...",
|
|
1746
|
-
loadMore: "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0435\u0449\u0451",
|
|
1747
|
-
allLoaded: "\u0412\u0441\u0435 {count} \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D\u043E"
|
|
1748
|
-
},
|
|
1749
|
-
createTicket: {
|
|
1750
|
-
title: "\u041D\u043E\u0432\u043E\u0435 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435",
|
|
1751
|
-
description: "\u041E\u043F\u0438\u0448\u0438\u0442\u0435 \u0432\u0430\u0448\u0443 \u043F\u0440\u043E\u0431\u043B\u0435\u043C\u0443, \u0438 \u043C\u044B \u043F\u043E\u043C\u043E\u0436\u0435\u043C \u0432\u0430\u043C",
|
|
1752
|
-
subjectLabel: "\u0422\u0435\u043C\u0430",
|
|
1753
|
-
subjectPlaceholder: "\u041A\u0440\u0430\u0442\u043A\u043E\u0435 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 \u043F\u0440\u043E\u0431\u043B\u0435\u043C\u044B",
|
|
1754
|
-
messageLabel: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435",
|
|
1755
|
-
messagePlaceholder: "\u041E\u043F\u0438\u0448\u0438\u0442\u0435 \u0432\u0430\u0448\u0443 \u043F\u0440\u043E\u0431\u043B\u0435\u043C\u0443 \u043F\u043E\u0434\u0440\u043E\u0431\u043D\u043E...",
|
|
1756
|
-
cancel: "\u041E\u0442\u043C\u0435\u043D\u0430",
|
|
1757
|
-
creating: "\u0421\u043E\u0437\u0434\u0430\u043D\u0438\u0435...",
|
|
1758
|
-
create: "\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435"
|
|
1759
|
-
},
|
|
1760
|
-
validation: {
|
|
1761
|
-
subjectRequired: "\u0422\u0435\u043C\u0430 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u0430",
|
|
1762
|
-
subjectTooLong: "\u0422\u0435\u043C\u0430 \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0434\u043B\u0438\u043D\u043D\u0430\u044F (\u043C\u0430\u043A\u0441. 200 \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432)",
|
|
1763
|
-
messageRequired: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E",
|
|
1764
|
-
messageTooLong: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0434\u043B\u0438\u043D\u043D\u043E\u0435 (\u043C\u0430\u043A\u0441. 5000 \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432)"
|
|
1765
|
-
},
|
|
1766
|
-
messages: {
|
|
1767
|
-
ticketCreated: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u0441\u043E\u0437\u0434\u0430\u043D\u043E",
|
|
1768
|
-
ticketCreateFailed: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043E\u0437\u0434\u0430\u0442\u044C \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435",
|
|
1769
|
-
messageSent: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E",
|
|
1770
|
-
messageSendFailed: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435"
|
|
1771
|
-
},
|
|
1772
|
-
messageInput: {
|
|
1773
|
-
placeholder: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435...",
|
|
1774
|
-
ticketClosed: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u043E",
|
|
1775
|
-
ticketClosedDescription: "\u042D\u0442\u043E \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u043E. \u0421\u043E\u0437\u0434\u0430\u0439\u0442\u0435 \u043D\u043E\u0432\u043E\u0435, \u0435\u0441\u043B\u0438 \u0432\u0430\u043C \u043D\u0443\u0436\u043D\u0430 \u043F\u043E\u043C\u043E\u0449\u044C."
|
|
1776
|
-
},
|
|
1777
|
-
messageList: {
|
|
1778
|
-
noTicketSelected: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u043D\u0435 \u0432\u044B\u0431\u0440\u0430\u043D\u043E",
|
|
1779
|
-
noTicketSelectedDescription: "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u0438\u0437 \u0441\u043F\u0438\u0441\u043A\u0430 \u0434\u043B\u044F \u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440\u0430 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439",
|
|
1780
|
-
noMessages: "\u041D\u0435\u0442 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439",
|
|
1781
|
-
noMessagesDescription: "\u041D\u0430\u0447\u043D\u0438\u0442\u0435 \u0434\u0438\u0430\u043B\u043E\u0433, \u043E\u0442\u043F\u0440\u0430\u0432\u0438\u0432 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435",
|
|
1782
|
-
loadingOlder: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u0441\u0442\u0430\u0440\u044B\u0445 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439...",
|
|
1783
|
-
loadOlder: "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0441\u0442\u0430\u0440\u044B\u0435",
|
|
1784
|
-
supportTeam: "\u0421\u043B\u0443\u0436\u0431\u0430 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438",
|
|
1785
|
-
staff: "\u0421\u043E\u0442\u0440\u0443\u0434\u043D\u0438\u043A"
|
|
1786
|
-
},
|
|
1787
|
-
time: {
|
|
1788
|
-
justNow: "\u0422\u043E\u043B\u044C\u043A\u043E \u0447\u0442\u043E",
|
|
1789
|
-
minutesAgo: "{count} \u043C\u0438\u043D \u043D\u0430\u0437\u0430\u0434",
|
|
1790
|
-
hoursAgo: "{count}\u0447 \u043D\u0430\u0437\u0430\u0434",
|
|
1791
|
-
daysAgo: "{count}\u0434 \u043D\u0430\u0437\u0430\u0434"
|
|
1792
|
-
}
|
|
1793
|
-
};
|
|
1794
|
-
|
|
1795
1706
|
// src/i18n/locales/ko.ts
|
|
1796
1707
|
var ko = {
|
|
1797
1708
|
layout: {
|
|
@@ -1864,6 +1775,78 @@ var ko = {
|
|
|
1864
1775
|
}
|
|
1865
1776
|
};
|
|
1866
1777
|
|
|
1778
|
+
// src/i18n/locales/ru.ts
|
|
1779
|
+
var ru = {
|
|
1780
|
+
layout: {
|
|
1781
|
+
title: "\u0426\u0435\u043D\u0442\u0440 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438",
|
|
1782
|
+
titleShort: "\u041F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0430",
|
|
1783
|
+
subtitle: "\u041F\u043E\u043B\u0443\u0447\u0438\u0442\u0435 \u043F\u043E\u043C\u043E\u0449\u044C \u043E\u0442 \u043D\u0430\u0448\u0435\u0439 \u043A\u043E\u043C\u0430\u043D\u0434\u044B \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438",
|
|
1784
|
+
newTicket: "\u041D\u043E\u0432\u043E\u0435 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435"
|
|
1785
|
+
},
|
|
1786
|
+
hero: {
|
|
1787
|
+
openTickets: "\u041E\u0442\u043A\u0440\u044B\u0442\u044B\u0445 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439",
|
|
1788
|
+
unreadMessages: "\u041D\u0435\u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u043D\u044B\u0445"
|
|
1789
|
+
},
|
|
1790
|
+
status: {
|
|
1791
|
+
open: "\u041E\u0442\u043A\u0440\u044B\u0442",
|
|
1792
|
+
waitingForUser: "\u041E\u0436\u0438\u0434\u0430\u0435\u0442 \u0432\u0430\u0441",
|
|
1793
|
+
waitingForAdmin: "\u041E\u0436\u0438\u0434\u0430\u0435\u0442 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0443",
|
|
1794
|
+
resolved: "\u0420\u0435\u0448\u0451\u043D",
|
|
1795
|
+
closed: "\u0417\u0430\u043A\u0440\u044B\u0442"
|
|
1796
|
+
},
|
|
1797
|
+
ticketList: {
|
|
1798
|
+
noTickets: "\u041D\u0435\u0442 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439",
|
|
1799
|
+
noTicketsDescription: "\u0421\u043E\u0437\u0434\u0430\u0439\u0442\u0435 \u043F\u0435\u0440\u0432\u043E\u0435 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435, \u0447\u0442\u043E\u0431\u044B \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u044C \u043F\u043E\u043C\u043E\u0449\u044C",
|
|
1800
|
+
loadingMore: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430...",
|
|
1801
|
+
loadMore: "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0435\u0449\u0451",
|
|
1802
|
+
allLoaded: "\u0412\u0441\u0435 {count} \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D\u043E"
|
|
1803
|
+
},
|
|
1804
|
+
createTicket: {
|
|
1805
|
+
title: "\u041D\u043E\u0432\u043E\u0435 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435",
|
|
1806
|
+
description: "\u041E\u043F\u0438\u0448\u0438\u0442\u0435 \u0432\u0430\u0448\u0443 \u043F\u0440\u043E\u0431\u043B\u0435\u043C\u0443, \u0438 \u043C\u044B \u043F\u043E\u043C\u043E\u0436\u0435\u043C \u0432\u0430\u043C",
|
|
1807
|
+
subjectLabel: "\u0422\u0435\u043C\u0430",
|
|
1808
|
+
subjectPlaceholder: "\u041A\u0440\u0430\u0442\u043A\u043E\u0435 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 \u043F\u0440\u043E\u0431\u043B\u0435\u043C\u044B",
|
|
1809
|
+
messageLabel: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435",
|
|
1810
|
+
messagePlaceholder: "\u041E\u043F\u0438\u0448\u0438\u0442\u0435 \u0432\u0430\u0448\u0443 \u043F\u0440\u043E\u0431\u043B\u0435\u043C\u0443 \u043F\u043E\u0434\u0440\u043E\u0431\u043D\u043E...",
|
|
1811
|
+
cancel: "\u041E\u0442\u043C\u0435\u043D\u0430",
|
|
1812
|
+
creating: "\u0421\u043E\u0437\u0434\u0430\u043D\u0438\u0435...",
|
|
1813
|
+
create: "\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435"
|
|
1814
|
+
},
|
|
1815
|
+
validation: {
|
|
1816
|
+
subjectRequired: "\u0422\u0435\u043C\u0430 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u0430",
|
|
1817
|
+
subjectTooLong: "\u0422\u0435\u043C\u0430 \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0434\u043B\u0438\u043D\u043D\u0430\u044F (\u043C\u0430\u043A\u0441. 200 \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432)",
|
|
1818
|
+
messageRequired: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E",
|
|
1819
|
+
messageTooLong: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0434\u043B\u0438\u043D\u043D\u043E\u0435 (\u043C\u0430\u043A\u0441. 5000 \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432)"
|
|
1820
|
+
},
|
|
1821
|
+
messages: {
|
|
1822
|
+
ticketCreated: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u0441\u043E\u0437\u0434\u0430\u043D\u043E",
|
|
1823
|
+
ticketCreateFailed: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u043E\u0437\u0434\u0430\u0442\u044C \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435",
|
|
1824
|
+
messageSent: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E",
|
|
1825
|
+
messageSendFailed: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435"
|
|
1826
|
+
},
|
|
1827
|
+
messageInput: {
|
|
1828
|
+
placeholder: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435...",
|
|
1829
|
+
ticketClosed: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u043E",
|
|
1830
|
+
ticketClosedDescription: "\u042D\u0442\u043E \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u043E. \u0421\u043E\u0437\u0434\u0430\u0439\u0442\u0435 \u043D\u043E\u0432\u043E\u0435, \u0435\u0441\u043B\u0438 \u0432\u0430\u043C \u043D\u0443\u0436\u043D\u0430 \u043F\u043E\u043C\u043E\u0449\u044C."
|
|
1831
|
+
},
|
|
1832
|
+
messageList: {
|
|
1833
|
+
noTicketSelected: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u043D\u0435 \u0432\u044B\u0431\u0440\u0430\u043D\u043E",
|
|
1834
|
+
noTicketSelectedDescription: "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u0438\u0437 \u0441\u043F\u0438\u0441\u043A\u0430 \u0434\u043B\u044F \u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440\u0430 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439",
|
|
1835
|
+
noMessages: "\u041D\u0435\u0442 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439",
|
|
1836
|
+
noMessagesDescription: "\u041D\u0430\u0447\u043D\u0438\u0442\u0435 \u0434\u0438\u0430\u043B\u043E\u0433, \u043E\u0442\u043F\u0440\u0430\u0432\u0438\u0432 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435",
|
|
1837
|
+
loadingOlder: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u0441\u0442\u0430\u0440\u044B\u0445 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439...",
|
|
1838
|
+
loadOlder: "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0441\u0442\u0430\u0440\u044B\u0435",
|
|
1839
|
+
supportTeam: "\u0421\u043B\u0443\u0436\u0431\u0430 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438",
|
|
1840
|
+
staff: "\u0421\u043E\u0442\u0440\u0443\u0434\u043D\u0438\u043A"
|
|
1841
|
+
},
|
|
1842
|
+
time: {
|
|
1843
|
+
justNow: "\u0422\u043E\u043B\u044C\u043A\u043E \u0447\u0442\u043E",
|
|
1844
|
+
minutesAgo: "{count} \u043C\u0438\u043D \u043D\u0430\u0437\u0430\u0434",
|
|
1845
|
+
hoursAgo: "{count}\u0447 \u043D\u0430\u0437\u0430\u0434",
|
|
1846
|
+
daysAgo: "{count}\u0434 \u043D\u0430\u0437\u0430\u0434"
|
|
1847
|
+
}
|
|
1848
|
+
};
|
|
1849
|
+
|
|
1867
1850
|
// src/i18n/useSupportT.ts
|
|
1868
1851
|
var translations = { en, ru, ko };
|
|
1869
1852
|
function getNestedValue(obj, path) {
|
|
@@ -1880,8 +1863,8 @@ function getNestedValue(obj, path) {
|
|
|
1880
1863
|
}
|
|
1881
1864
|
function useSupportT() {
|
|
1882
1865
|
const locale = nextIntl.useLocale();
|
|
1883
|
-
const t =
|
|
1884
|
-
return
|
|
1866
|
+
const t = react.useMemo(() => translations[locale] || translations.en, [locale]);
|
|
1867
|
+
return react.useCallback(
|
|
1885
1868
|
(key) => getNestedValue(t, key),
|
|
1886
1869
|
[t]
|
|
1887
1870
|
);
|
|
@@ -1889,7 +1872,7 @@ function useSupportT() {
|
|
|
1889
1872
|
function SupportHero({ onCreateTicket, className }) {
|
|
1890
1873
|
const st = useSupportT();
|
|
1891
1874
|
const { tickets, unreadCount, isLoadingTickets, refreshTickets } = useSupport();
|
|
1892
|
-
const labels =
|
|
1875
|
+
const labels = react.useMemo(() => ({
|
|
1893
1876
|
title: st("layout.title"),
|
|
1894
1877
|
subtitle: st("layout.subtitle"),
|
|
1895
1878
|
newTicket: st("layout.newTicket"),
|
|
@@ -1897,27 +1880,22 @@ function SupportHero({ onCreateTicket, className }) {
|
|
|
1897
1880
|
unreadMessages: st("hero.unreadMessages")
|
|
1898
1881
|
}), [st]);
|
|
1899
1882
|
const openTicketsCount = tickets.filter((t) => t.status !== "closed" && t.status !== "resolved").length;
|
|
1900
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("flex flex-col items-center py-
|
|
1901
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1902
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiCore.Skeleton, { className: "h-
|
|
1903
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiCore.Skeleton, { className: "h-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
1911
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1883
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("flex flex-col items-center py-12 px-4", className), children: [
|
|
1884
|
+
isLoadingTickets ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
1885
|
+
/* @__PURE__ */ jsxRuntime.jsx(uiCore.Skeleton, { className: "h-10 w-48 mx-auto mb-2" }),
|
|
1886
|
+
/* @__PURE__ */ jsxRuntime.jsx(uiCore.Skeleton, { className: "h-5 w-64 mx-auto" })
|
|
1887
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
1888
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-4xl font-bold tracking-tight", children: labels.title }),
|
|
1889
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-lg text-muted-foreground mt-2", children: labels.subtitle })
|
|
1890
|
+
] }),
|
|
1891
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 mt-6", children: [
|
|
1892
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1912
1893
|
uiCore.Button,
|
|
1913
1894
|
{
|
|
1914
1895
|
size: "lg",
|
|
1915
1896
|
onClick: onCreateTicket,
|
|
1916
|
-
className: "rounded-full px-
|
|
1917
|
-
children:
|
|
1918
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-5 w-5 mr-2" }),
|
|
1919
|
-
labels.newTicket
|
|
1920
|
-
]
|
|
1897
|
+
className: "rounded-full px-8 h-12 text-base",
|
|
1898
|
+
children: labels.newTicket
|
|
1921
1899
|
}
|
|
1922
1900
|
),
|
|
1923
1901
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -1926,46 +1904,103 @@ function SupportHero({ onCreateTicket, className }) {
|
|
|
1926
1904
|
size: "icon",
|
|
1927
1905
|
variant: "ghost",
|
|
1928
1906
|
onClick: () => refreshTickets(),
|
|
1929
|
-
className: "rounded-full",
|
|
1907
|
+
className: "rounded-full h-10 w-10",
|
|
1930
1908
|
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-4 w-4" })
|
|
1931
1909
|
}
|
|
1932
1910
|
)
|
|
1933
1911
|
] }),
|
|
1934
|
-
!isLoadingTickets && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-
|
|
1935
|
-
/* @__PURE__ */ jsxRuntime.jsxs("
|
|
1936
|
-
|
|
1937
|
-
|
|
1912
|
+
!isLoadingTickets && (openTicketsCount > 0 || unreadCount > 0) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mt-4", children: [
|
|
1913
|
+
openTicketsCount > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-muted-foreground bg-muted px-3 py-1 rounded-full", children: [
|
|
1914
|
+
openTicketsCount,
|
|
1915
|
+
" ",
|
|
1916
|
+
labels.openTickets.toLowerCase()
|
|
1938
1917
|
] }),
|
|
1939
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
unreadCount > 0 ? "text-destructive" : "text-foreground"
|
|
1944
|
-
), children: unreadCount }),
|
|
1945
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: labels.unreadMessages })
|
|
1918
|
+
unreadCount > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-destructive bg-destructive/10 px-3 py-1 rounded-full", children: [
|
|
1919
|
+
unreadCount,
|
|
1920
|
+
" ",
|
|
1921
|
+
labels.unreadMessages.toLowerCase()
|
|
1946
1922
|
] })
|
|
1947
1923
|
] })
|
|
1948
1924
|
] });
|
|
1949
1925
|
}
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1926
|
+
|
|
1927
|
+
// src/utils/status.ts
|
|
1928
|
+
var ticketStatusConfig = {
|
|
1929
|
+
open: {
|
|
1930
|
+
color: "text-blue-600 dark:text-blue-400",
|
|
1931
|
+
bg: "bg-blue-500/10"
|
|
1932
|
+
},
|
|
1933
|
+
waiting_for_user: {
|
|
1934
|
+
color: "text-orange-600 dark:text-orange-400",
|
|
1935
|
+
bg: "bg-orange-500/10"
|
|
1936
|
+
},
|
|
1937
|
+
waiting_for_admin: {
|
|
1938
|
+
color: "text-muted-foreground",
|
|
1939
|
+
bg: "bg-muted"
|
|
1940
|
+
},
|
|
1941
|
+
resolved: {
|
|
1942
|
+
color: "text-green-600 dark:text-green-400",
|
|
1943
|
+
bg: "bg-green-500/10"
|
|
1944
|
+
},
|
|
1945
|
+
closed: {
|
|
1946
|
+
color: "text-muted-foreground",
|
|
1947
|
+
bg: "bg-muted"
|
|
1964
1948
|
}
|
|
1965
1949
|
};
|
|
1950
|
+
function getStatusConfig(status) {
|
|
1951
|
+
return ticketStatusConfig[status] || ticketStatusConfig.open;
|
|
1952
|
+
}
|
|
1953
|
+
function formatMessageTime(date) {
|
|
1954
|
+
if (!date) return "";
|
|
1955
|
+
return moment__default.default.utc(date).local().format("h:mm A");
|
|
1956
|
+
}
|
|
1957
|
+
function formatDateSeparator(date) {
|
|
1958
|
+
if (!date) return "";
|
|
1959
|
+
const m = moment__default.default.utc(date).local();
|
|
1960
|
+
const now = moment__default.default();
|
|
1961
|
+
const today = now.clone().startOf("day");
|
|
1962
|
+
const yesterday = now.clone().subtract(1, "day").startOf("day");
|
|
1963
|
+
if (m.isSame(today, "day")) {
|
|
1964
|
+
return "Today";
|
|
1965
|
+
}
|
|
1966
|
+
if (m.isSame(yesterday, "day")) {
|
|
1967
|
+
return "Yesterday";
|
|
1968
|
+
}
|
|
1969
|
+
if (m.isSame(now, "year")) {
|
|
1970
|
+
return m.format("MMM D");
|
|
1971
|
+
}
|
|
1972
|
+
return m.format("MMM D, YYYY");
|
|
1973
|
+
}
|
|
1974
|
+
function formatRelativeTime(date, labels) {
|
|
1975
|
+
if (!date) return "N/A";
|
|
1976
|
+
const m = moment__default.default.utc(date).local();
|
|
1977
|
+
const now = moment__default.default();
|
|
1978
|
+
const diffInSeconds = now.diff(m, "seconds");
|
|
1979
|
+
if (diffInSeconds < 60) {
|
|
1980
|
+
return labels.justNow;
|
|
1981
|
+
}
|
|
1982
|
+
if (diffInSeconds < 3600) {
|
|
1983
|
+
return labels.minutesAgo.replace("{count}", String(Math.floor(diffInSeconds / 60)));
|
|
1984
|
+
}
|
|
1985
|
+
if (diffInSeconds < 86400) {
|
|
1986
|
+
return labels.hoursAgo.replace("{count}", String(Math.floor(diffInSeconds / 3600)));
|
|
1987
|
+
}
|
|
1988
|
+
if (diffInSeconds < 604800) {
|
|
1989
|
+
return labels.daysAgo.replace("{count}", String(Math.floor(diffInSeconds / 86400)));
|
|
1990
|
+
}
|
|
1991
|
+
return m.format("MMM D, YYYY");
|
|
1992
|
+
}
|
|
1993
|
+
function isSameDay(date1, date2) {
|
|
1994
|
+
if (!date1 || !date2) return false;
|
|
1995
|
+
return moment__default.default.utc(date1).format("YYYY-MM-DD") === moment__default.default.utc(date2).format("YYYY-MM-DD");
|
|
1996
|
+
}
|
|
1997
|
+
function formatTicketDate(date) {
|
|
1998
|
+
if (!date) return "";
|
|
1999
|
+
return moment__default.default.utc(date).local().format("MMM D, YYYY");
|
|
2000
|
+
}
|
|
1966
2001
|
function TicketItem({ ticket, onClick }) {
|
|
1967
2002
|
const st = useSupportT();
|
|
1968
|
-
const labels =
|
|
2003
|
+
const labels = react.useMemo(() => ({
|
|
1969
2004
|
status: {
|
|
1970
2005
|
open: st("status.open"),
|
|
1971
2006
|
waiting_for_user: st("status.waitingForUser"),
|
|
@@ -1980,49 +2015,37 @@ function TicketItem({ ticket, onClick }) {
|
|
|
1980
2015
|
daysAgo: st("time.daysAgo")
|
|
1981
2016
|
}
|
|
1982
2017
|
}), [st]);
|
|
1983
|
-
const
|
|
1984
|
-
|
|
1985
|
-
const m = moment2__default.default.utc(date).local();
|
|
1986
|
-
const now = moment2__default.default();
|
|
1987
|
-
const diffInSeconds = now.diff(m, "seconds");
|
|
1988
|
-
if (diffInSeconds < 60) return labels.time.justNow;
|
|
1989
|
-
if (diffInSeconds < 3600) return labels.time.minutesAgo.replace("{count}", String(Math.floor(diffInSeconds / 60)));
|
|
1990
|
-
if (diffInSeconds < 86400) return labels.time.hoursAgo.replace("{count}", String(Math.floor(diffInSeconds / 3600)));
|
|
1991
|
-
if (diffInSeconds < 604800) return labels.time.daysAgo.replace("{count}", String(Math.floor(diffInSeconds / 86400)));
|
|
1992
|
-
return m.format("MMM D, YYYY");
|
|
2018
|
+
const getRelativeTime = react.useCallback((date) => {
|
|
2019
|
+
return formatRelativeTime(date, labels.time);
|
|
1993
2020
|
}, [labels.time]);
|
|
1994
2021
|
const statusLabel = labels.status[ticket.status] || ticket.status || labels.status.open;
|
|
2022
|
+
const statusConfig = getStatusConfig(ticket.status || "open");
|
|
1995
2023
|
const hasUnread = (ticket.unanswered_messages_count || 0) > 0;
|
|
1996
2024
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1997
|
-
"
|
|
2025
|
+
"button",
|
|
1998
2026
|
{
|
|
2027
|
+
type: "button",
|
|
1999
2028
|
className: lib.cn(
|
|
2000
|
-
"
|
|
2001
|
-
"
|
|
2002
|
-
"hover:bg-accent/50"
|
|
2003
|
-
"active:scale-[0.98]",
|
|
2004
|
-
hasUnread && "bg-primary/5"
|
|
2029
|
+
"w-full flex items-start gap-3 p-4 text-left",
|
|
2030
|
+
"transition-colors duration-150",
|
|
2031
|
+
"hover:bg-accent/50 active:bg-accent"
|
|
2005
2032
|
),
|
|
2006
2033
|
onClick,
|
|
2007
2034
|
children: [
|
|
2008
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2
|
|
2035
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2 pt-1.5 shrink-0", children: hasUnread && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2 h-2 rounded-full bg-blue-500" }) }),
|
|
2009
2036
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
2010
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
2020
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-3 w-3" }),
|
|
2021
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: formatRelativeTime(ticket.created_at) })
|
|
2037
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: lib.cn(
|
|
2038
|
+
"text-[15px] line-clamp-1",
|
|
2039
|
+
hasUnread ? "font-semibold" : "font-medium"
|
|
2040
|
+
), children: ticket.subject }),
|
|
2041
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mt-0.5", children: [
|
|
2042
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: lib.cn("text-[13px]", statusConfig.color), children: statusLabel }),
|
|
2043
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[13px] text-muted-foreground", children: [
|
|
2044
|
+
"\xB7 ",
|
|
2045
|
+
getRelativeTime(ticket.created_at)
|
|
2022
2046
|
] })
|
|
2023
2047
|
] })
|
|
2024
|
-
] })
|
|
2025
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-5 w-5 text-muted-foreground/50 group-hover:text-muted-foreground transition-colors flex-shrink-0" })
|
|
2048
|
+
] })
|
|
2026
2049
|
]
|
|
2027
2050
|
}
|
|
2028
2051
|
);
|
|
@@ -2037,15 +2060,15 @@ function TicketList({ onTicketClick, className }) {
|
|
|
2037
2060
|
totalCount: totalTicketsCount,
|
|
2038
2061
|
loadMore: loadMoreTickets
|
|
2039
2062
|
} = useTickets();
|
|
2040
|
-
const labels =
|
|
2063
|
+
const labels = react.useMemo(() => ({
|
|
2041
2064
|
noTickets: st("ticketList.noTickets"),
|
|
2042
2065
|
noTicketsDescription: st("ticketList.noTicketsDescription"),
|
|
2043
2066
|
loadingMore: st("ticketList.loadingMore"),
|
|
2044
2067
|
loadMore: st("ticketList.loadMore")
|
|
2045
2068
|
}), [st]);
|
|
2046
|
-
const observerRef =
|
|
2047
|
-
const loadMoreRef =
|
|
2048
|
-
|
|
2069
|
+
const observerRef = react.useRef(null);
|
|
2070
|
+
const loadMoreRef = react.useRef(null);
|
|
2071
|
+
react.useEffect(() => {
|
|
2049
2072
|
if (observerRef.current) {
|
|
2050
2073
|
observerRef.current.disconnect();
|
|
2051
2074
|
}
|
|
@@ -2067,24 +2090,23 @@ function TicketList({ onTicketClick, className }) {
|
|
|
2067
2090
|
};
|
|
2068
2091
|
}, [hasMoreTickets, isLoadingMoreTickets, loadMoreTickets]);
|
|
2069
2092
|
if (isLoadingTickets) {
|
|
2070
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: lib.cn("px-4 py-6
|
|
2093
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: lib.cn("px-4 py-6", className), children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-border/50", children: [1, 2, 3, 4, 5].map((i) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-4", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2071
2094
|
uiCore.Skeleton,
|
|
2072
2095
|
{
|
|
2073
|
-
className: "h-
|
|
2096
|
+
className: "h-12 w-full",
|
|
2074
2097
|
style: { animationDelay: `${i * 100}ms` }
|
|
2075
|
-
}
|
|
2076
|
-
|
|
2077
|
-
)) });
|
|
2098
|
+
}
|
|
2099
|
+
) }, i)) }) });
|
|
2078
2100
|
}
|
|
2079
2101
|
if (!tickets || tickets.length === 0) {
|
|
2080
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2081
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2082
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2083
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2102
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(uiCore.Empty, { className: lib.cn("py-16", className), children: [
|
|
2103
|
+
/* @__PURE__ */ jsxRuntime.jsx(uiCore.EmptyMedia, { variant: "icon", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquare, { className: "h-8 w-8" }) }),
|
|
2104
|
+
/* @__PURE__ */ jsxRuntime.jsx(uiCore.EmptyTitle, { children: labels.noTickets }),
|
|
2105
|
+
/* @__PURE__ */ jsxRuntime.jsx(uiCore.EmptyDescription, { children: labels.noTicketsDescription })
|
|
2084
2106
|
] });
|
|
2085
2107
|
}
|
|
2086
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("px-4 py-
|
|
2087
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "
|
|
2108
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("px-4 py-2", className), children: [
|
|
2109
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-border/50", children: tickets.map((ticket) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2088
2110
|
TicketItem,
|
|
2089
2111
|
{
|
|
2090
2112
|
ticket,
|
|
@@ -2114,59 +2136,81 @@ function TicketList({ onTicketClick, className }) {
|
|
|
2114
2136
|
) })
|
|
2115
2137
|
] });
|
|
2116
2138
|
}
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
return "outline";
|
|
2135
|
-
case "closed":
|
|
2136
|
-
return "secondary";
|
|
2137
|
-
default:
|
|
2138
|
-
return "default";
|
|
2139
|
+
function groupMessages(messages, currentUserId) {
|
|
2140
|
+
const groups = [];
|
|
2141
|
+
let currentGroup = null;
|
|
2142
|
+
for (const msg of messages) {
|
|
2143
|
+
const isFromUser = msg.sender?.id && currentUserId && String(msg.sender.id) === String(currentUserId) || msg.is_from_author;
|
|
2144
|
+
const senderId = msg.sender?.id || null;
|
|
2145
|
+
const shouldStartNewGroup = !currentGroup || currentGroup.isFromUser !== isFromUser || currentGroup.senderId !== senderId;
|
|
2146
|
+
if (shouldStartNewGroup) {
|
|
2147
|
+
currentGroup = {
|
|
2148
|
+
senderId,
|
|
2149
|
+
isFromUser: !!isFromUser,
|
|
2150
|
+
messages: [msg]
|
|
2151
|
+
};
|
|
2152
|
+
groups.push(currentGroup);
|
|
2153
|
+
} else {
|
|
2154
|
+
currentGroup.messages.push(msg);
|
|
2155
|
+
}
|
|
2139
2156
|
}
|
|
2140
|
-
|
|
2141
|
-
|
|
2157
|
+
return groups;
|
|
2158
|
+
}
|
|
2159
|
+
var DateSeparator = ({ date }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center my-4", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] text-muted-foreground bg-muted/60 px-3 py-1 rounded-full", children: formatDateSeparator(date) }) });
|
|
2160
|
+
var MessageBubble = ({
|
|
2161
|
+
message,
|
|
2162
|
+
isFromUser,
|
|
2163
|
+
showAvatar,
|
|
2164
|
+
showTimestamp,
|
|
2165
|
+
currentUser,
|
|
2166
|
+
labels
|
|
2167
|
+
}) => {
|
|
2142
2168
|
const sender = message.sender;
|
|
2143
2169
|
const senderInitial = sender?.display_username?.charAt(0)?.toUpperCase() || sender?.initials || "S";
|
|
2144
2170
|
const userInitial = currentUser?.display_username?.charAt(0)?.toUpperCase() || currentUser?.email?.charAt(0)?.toUpperCase() || currentUser?.initials || null;
|
|
2145
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("flex gap-
|
|
2146
|
-
!isFromUser && /* @__PURE__ */ jsxRuntime.jsx(uiCore.Avatar, { className: "h-8 w-8
|
|
2147
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("flex flex-col gap-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2171
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("flex gap-2", isFromUser ? "justify-end" : "justify-start"), children: [
|
|
2172
|
+
!isFromUser && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-8 shrink-0", children: showAvatar && /* @__PURE__ */ jsxRuntime.jsx(uiCore.Avatar, { className: "h-8 w-8", children: sender?.avatar ? /* @__PURE__ */ jsxRuntime.jsx(uiCore.AvatarImage, { src: sender.avatar, alt: sender.display_username || labels.supportTeam }) : /* @__PURE__ */ jsxRuntime.jsx(uiCore.AvatarFallback, { className: "bg-primary text-primary-foreground text-xs", children: sender?.is_staff ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Headphones, { className: "h-4 w-4" }) : senderInitial }) }) }),
|
|
2173
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("flex flex-col gap-0.5 max-w-[85%]", isFromUser ? "items-end" : "items-start"), children: [
|
|
2174
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2175
|
+
"div",
|
|
2176
|
+
{
|
|
2177
|
+
className: lib.cn(
|
|
2178
|
+
"px-4 py-2.5 rounded-[20px]",
|
|
2179
|
+
isFromUser ? "bg-blue-500 text-white" : "bg-[#e9e9eb] dark:bg-zinc-800 text-foreground"
|
|
2180
|
+
),
|
|
2181
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[15px] leading-relaxed whitespace-pre-wrap break-words", children: message.text })
|
|
2182
|
+
}
|
|
2183
|
+
),
|
|
2184
|
+
showTimestamp && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] text-muted-foreground px-2 mt-0.5", children: formatMessageTime(message.created_at) })
|
|
2156
2185
|
] }),
|
|
2157
|
-
isFromUser && /* @__PURE__ */ jsxRuntime.jsx(uiCore.Avatar, { className: "h-8 w-8
|
|
2186
|
+
isFromUser && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-8 shrink-0", children: showAvatar && /* @__PURE__ */ jsxRuntime.jsx(uiCore.Avatar, { className: "h-8 w-8", children: currentUser?.avatar ? /* @__PURE__ */ jsxRuntime.jsx(uiCore.AvatarImage, { src: currentUser.avatar, alt: currentUser.display_username || currentUser.email || "You" }) : /* @__PURE__ */ jsxRuntime.jsx(uiCore.AvatarFallback, { className: "bg-blue-500 text-white text-xs font-medium", children: userInitial || /* @__PURE__ */ jsxRuntime.jsx(lucideReact.User, { className: "h-4 w-4" }) }) }) })
|
|
2158
2187
|
] });
|
|
2159
2188
|
};
|
|
2189
|
+
var MessageGroupComponent = ({ group, currentUser, labels }) => {
|
|
2190
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1", children: group.messages.map((msg, idx) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2191
|
+
MessageBubble,
|
|
2192
|
+
{
|
|
2193
|
+
message: msg,
|
|
2194
|
+
isFromUser: group.isFromUser,
|
|
2195
|
+
showAvatar: idx === 0,
|
|
2196
|
+
showTimestamp: idx === group.messages.length - 1,
|
|
2197
|
+
currentUser,
|
|
2198
|
+
labels
|
|
2199
|
+
},
|
|
2200
|
+
msg.uuid
|
|
2201
|
+
)) });
|
|
2202
|
+
};
|
|
2160
2203
|
function TicketSheet({ ticket, open, onOpenChange }) {
|
|
2161
2204
|
const st = useSupportT();
|
|
2162
2205
|
const { user } = auth.useAuth();
|
|
2163
2206
|
const { sendMessage } = useSupport();
|
|
2164
2207
|
const { toast } = uiCore.useToast();
|
|
2165
|
-
const [message, setMessage] =
|
|
2166
|
-
const [isSending, setIsSending] =
|
|
2167
|
-
const scrollAreaRef =
|
|
2168
|
-
const
|
|
2169
|
-
const
|
|
2208
|
+
const [message, setMessage] = react.useState("");
|
|
2209
|
+
const [isSending, setIsSending] = react.useState(false);
|
|
2210
|
+
const scrollAreaRef = react.useRef(null);
|
|
2211
|
+
const textareaRef = react.useRef(null);
|
|
2212
|
+
const firstRender = react.useRef(true);
|
|
2213
|
+
const labels = react.useMemo(() => ({
|
|
2170
2214
|
noMessages: st("messageList.noMessages"),
|
|
2171
2215
|
noMessagesDescription: st("messageList.noMessagesDescription"),
|
|
2172
2216
|
loadingOlder: st("messageList.loadingOlder"),
|
|
@@ -2196,10 +2240,11 @@ function TicketSheet({ ticket, open, onOpenChange }) {
|
|
|
2196
2240
|
refresh: refreshMessages,
|
|
2197
2241
|
addOptimistic
|
|
2198
2242
|
} = useMessages(ticket?.uuid || null);
|
|
2199
|
-
|
|
2243
|
+
const messageGroups = react.useMemo(() => groupMessages(messages, user?.id), [messages, user?.id]);
|
|
2244
|
+
react.useEffect(() => {
|
|
2200
2245
|
firstRender.current = true;
|
|
2201
2246
|
}, [ticket?.uuid]);
|
|
2202
|
-
|
|
2247
|
+
react.useEffect(() => {
|
|
2203
2248
|
if (firstRender.current && messages.length > 0) {
|
|
2204
2249
|
const scrollContainer = scrollAreaRef.current?.querySelector("[data-radix-scroll-area-viewport]");
|
|
2205
2250
|
if (scrollContainer) {
|
|
@@ -2208,6 +2253,14 @@ function TicketSheet({ ticket, open, onOpenChange }) {
|
|
|
2208
2253
|
firstRender.current = false;
|
|
2209
2254
|
}
|
|
2210
2255
|
}, [messages]);
|
|
2256
|
+
const handleTextareaChange = react.useCallback((e) => {
|
|
2257
|
+
setMessage(e.target.value);
|
|
2258
|
+
const textarea = textareaRef.current;
|
|
2259
|
+
if (textarea) {
|
|
2260
|
+
textarea.style.height = "auto";
|
|
2261
|
+
textarea.style.height = `${Math.min(textarea.scrollHeight, 120)}px`;
|
|
2262
|
+
}
|
|
2263
|
+
}, []);
|
|
2211
2264
|
const handleSend = async (e) => {
|
|
2212
2265
|
e.preventDefault();
|
|
2213
2266
|
if (!message.trim() || !ticket?.uuid) return;
|
|
@@ -2233,6 +2286,9 @@ function TicketSheet({ ticket, open, onOpenChange }) {
|
|
|
2233
2286
|
addOptimistic(optimisticMessage);
|
|
2234
2287
|
await sendMessage({ ticketUuid: ticket.uuid, text: messageText });
|
|
2235
2288
|
setMessage("");
|
|
2289
|
+
if (textareaRef.current) {
|
|
2290
|
+
textareaRef.current.style.height = "auto";
|
|
2291
|
+
}
|
|
2236
2292
|
await refreshMessages();
|
|
2237
2293
|
const scrollContainer = scrollAreaRef.current?.querySelector("[data-radix-scroll-area-viewport]");
|
|
2238
2294
|
if (scrollContainer) {
|
|
@@ -2253,7 +2309,7 @@ function TicketSheet({ ticket, open, onOpenChange }) {
|
|
|
2253
2309
|
handleSend(e);
|
|
2254
2310
|
}
|
|
2255
2311
|
};
|
|
2256
|
-
const handleLoadMore =
|
|
2312
|
+
const handleLoadMore = react.useCallback(() => {
|
|
2257
2313
|
const scrollContainer = scrollAreaRef.current?.querySelector("[data-radix-scroll-area-viewport]");
|
|
2258
2314
|
const previousHeight = scrollContainer?.scrollHeight || 0;
|
|
2259
2315
|
loadMore();
|
|
@@ -2266,32 +2322,61 @@ function TicketSheet({ ticket, open, onOpenChange }) {
|
|
|
2266
2322
|
}, [loadMore]);
|
|
2267
2323
|
const canSendMessage = ticket?.status !== "closed";
|
|
2268
2324
|
const statusLabel = ticket ? labels.status[ticket.status] || ticket.status || labels.status.open : "";
|
|
2325
|
+
const statusConfig = getStatusConfig(ticket?.status || "open");
|
|
2326
|
+
const renderMessages = () => {
|
|
2327
|
+
const elements = [];
|
|
2328
|
+
let lastDate = null;
|
|
2329
|
+
for (const group of messageGroups) {
|
|
2330
|
+
const firstMessage = group.messages[0];
|
|
2331
|
+
if (!firstMessage) continue;
|
|
2332
|
+
const messageDate = firstMessage.created_at;
|
|
2333
|
+
if (!lastDate || !isSameDay(lastDate, messageDate)) {
|
|
2334
|
+
elements.push(
|
|
2335
|
+
/* @__PURE__ */ jsxRuntime.jsx(DateSeparator, { date: messageDate || "" }, `date-${messageDate}`)
|
|
2336
|
+
);
|
|
2337
|
+
lastDate = messageDate || null;
|
|
2338
|
+
}
|
|
2339
|
+
elements.push(
|
|
2340
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2341
|
+
MessageGroupComponent,
|
|
2342
|
+
{
|
|
2343
|
+
group,
|
|
2344
|
+
currentUser: user,
|
|
2345
|
+
labels: { supportTeam: labels.supportTeam, staff: labels.staff }
|
|
2346
|
+
},
|
|
2347
|
+
`group-${firstMessage.uuid}`
|
|
2348
|
+
)
|
|
2349
|
+
);
|
|
2350
|
+
}
|
|
2351
|
+
return elements;
|
|
2352
|
+
};
|
|
2269
2353
|
return /* @__PURE__ */ jsxRuntime.jsx(uiCore.ResponsiveSheet, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(uiCore.ResponsiveSheetContent, { className: "sm:max-w-xl p-0 flex flex-col h-full", children: [
|
|
2270
2354
|
/* @__PURE__ */ jsxRuntime.jsx(uiCore.ResponsiveSheetHeader, { className: "px-6 py-4 border-b flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-start justify-between gap-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
2271
2355
|
/* @__PURE__ */ jsxRuntime.jsx(uiCore.ResponsiveSheetTitle, { className: "text-lg font-semibold line-clamp-2", children: ticket?.subject || "Ticket" }),
|
|
2272
2356
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mt-1", children: [
|
|
2273
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2274
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-
|
|
2357
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: lib.cn("text-[13px] px-2 py-0.5 rounded-full", statusConfig.bg, statusConfig.color), children: statusLabel }),
|
|
2358
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[13px] text-muted-foreground", children: formatTicketDate(ticket?.created_at) })
|
|
2275
2359
|
] })
|
|
2276
2360
|
] }) }) }),
|
|
2277
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0 overflow-hidden", children: isLoadingMessages ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-6 space-y-4", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-
|
|
2278
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiCore.Skeleton, { className: "h-8 w-8 rounded-full" }),
|
|
2279
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiCore.Skeleton, { className: "h-16
|
|
2361
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-h-0 overflow-hidden", children: isLoadingMessages ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-6 space-y-4", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("flex gap-2", i % 2 === 0 ? "justify-end" : "justify-start"), children: [
|
|
2362
|
+
i % 2 !== 0 && /* @__PURE__ */ jsxRuntime.jsx(uiCore.Skeleton, { className: "h-8 w-8 rounded-full shrink-0" }),
|
|
2363
|
+
/* @__PURE__ */ jsxRuntime.jsx(uiCore.Skeleton, { className: lib.cn("h-16 rounded-[20px]", i % 2 === 0 ? "w-[60%]" : "w-[70%]") }),
|
|
2364
|
+
i % 2 === 0 && /* @__PURE__ */ jsxRuntime.jsx(uiCore.Skeleton, { className: "h-8 w-8 rounded-full shrink-0" })
|
|
2280
2365
|
] }, i)) }) : messages.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center h-full p-8 text-center", children: [
|
|
2281
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquare, { className: "h-
|
|
2366
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-16 w-16 mx-auto mb-4 bg-muted rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquare, { className: "h-8 w-8 text-muted-foreground" }) }),
|
|
2282
2367
|
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold mb-2", children: labels.noMessages }),
|
|
2283
2368
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground max-w-sm", children: labels.noMessagesDescription })
|
|
2284
|
-
] }) : /* @__PURE__ */ jsxRuntime.jsx(uiCore.ScrollArea, { className: "h-full", viewportRef: scrollAreaRef, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-
|
|
2369
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(uiCore.ScrollArea, { className: "h-full", viewportRef: scrollAreaRef, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 space-y-3", children: [
|
|
2285
2370
|
hasMore && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center pb-4", children: isLoadingMore ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-muted-foreground", children: [
|
|
2286
2371
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin" }),
|
|
2287
2372
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: labels.loadingOlder })
|
|
2288
2373
|
] }) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2289
2374
|
uiCore.Button,
|
|
2290
2375
|
{
|
|
2291
|
-
variant: "
|
|
2376
|
+
variant: "ghost",
|
|
2292
2377
|
size: "sm",
|
|
2293
2378
|
onClick: handleLoadMore,
|
|
2294
|
-
className: "text-xs",
|
|
2379
|
+
className: "text-xs text-muted-foreground",
|
|
2295
2380
|
children: [
|
|
2296
2381
|
labels.loadOlder,
|
|
2297
2382
|
" (",
|
|
@@ -2300,67 +2385,52 @@ function TicketSheet({ ticket, open, onOpenChange }) {
|
|
|
2300
2385
|
]
|
|
2301
2386
|
}
|
|
2302
2387
|
) }),
|
|
2303
|
-
|
|
2304
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 h-px bg-border" }),
|
|
2305
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: formatDate(messages[0]?.created_at) }),
|
|
2306
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 h-px bg-border" })
|
|
2307
|
-
] }),
|
|
2308
|
-
messages.map((msg, index) => {
|
|
2309
|
-
const isFromUser = msg.sender?.id && user?.id && String(msg.sender.id) === String(user.id) || msg.sender?.email && user?.email && msg.sender.email === user.email || msg.is_from_author && ticket?.user && user?.id && String(ticket.user) === String(user.id);
|
|
2310
|
-
const previousMessage = index > 0 ? messages[index - 1] : null;
|
|
2311
|
-
const showDateSeparator = previousMessage && moment2__default.default.utc(previousMessage.created_at || "").format("YYYY-MM-DD") !== moment2__default.default.utc(msg.created_at || "").format("YYYY-MM-DD");
|
|
2312
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(React4__default.default.Fragment, { children: [
|
|
2313
|
-
showDateSeparator && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 my-4", children: [
|
|
2314
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 h-px bg-border" }),
|
|
2315
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: formatDate(msg.created_at) }),
|
|
2316
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 h-px bg-border" })
|
|
2317
|
-
] }),
|
|
2318
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2319
|
-
MessageBubble,
|
|
2320
|
-
{
|
|
2321
|
-
message: msg,
|
|
2322
|
-
isFromUser: !!isFromUser,
|
|
2323
|
-
currentUser: user,
|
|
2324
|
-
labels: { supportTeam: labels.supportTeam, staff: labels.staff }
|
|
2325
|
-
}
|
|
2326
|
-
)
|
|
2327
|
-
] }, msg.uuid);
|
|
2328
|
-
})
|
|
2388
|
+
renderMessages()
|
|
2329
2389
|
] }) }) }),
|
|
2330
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2331
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2390
|
+
/* @__PURE__ */ jsxRuntime.jsx("form", { onSubmit: handleSend, className: "p-3 border-t bg-background/95 backdrop-blur flex-shrink-0", children: canSendMessage ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-end gap-2 bg-muted/50 rounded-[24px] px-4 py-2", children: [
|
|
2391
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2392
|
+
"textarea",
|
|
2393
|
+
{
|
|
2394
|
+
ref: textareaRef,
|
|
2395
|
+
value: message,
|
|
2396
|
+
onChange: handleTextareaChange,
|
|
2397
|
+
onKeyDown: handleKeyDown,
|
|
2398
|
+
placeholder: labels.placeholder,
|
|
2399
|
+
className: lib.cn(
|
|
2400
|
+
"flex-1 bg-transparent border-none resize-none",
|
|
2401
|
+
"text-[15px] placeholder:text-muted-foreground",
|
|
2402
|
+
"min-h-[24px] max-h-[120px] focus:outline-none"
|
|
2403
|
+
),
|
|
2404
|
+
rows: 1,
|
|
2405
|
+
disabled: isSending
|
|
2406
|
+
}
|
|
2407
|
+
),
|
|
2408
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2409
|
+
uiCore.Button,
|
|
2410
|
+
{
|
|
2411
|
+
type: "submit",
|
|
2412
|
+
size: "icon",
|
|
2413
|
+
variant: "ghost",
|
|
2414
|
+
className: lib.cn(
|
|
2415
|
+
"rounded-full h-8 w-8 shrink-0",
|
|
2416
|
+
message.trim() ? "bg-blue-500 text-white hover:bg-blue-600" : ""
|
|
2417
|
+
),
|
|
2418
|
+
disabled: !message.trim() || isSending,
|
|
2419
|
+
children: isSending ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowUp, { className: "h-4 w-4" })
|
|
2420
|
+
}
|
|
2421
|
+
)
|
|
2422
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-2", children: [
|
|
2423
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: labels.ticketClosed }),
|
|
2424
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground mt-1", children: labels.ticketClosedDescription })
|
|
2425
|
+
] }) })
|
|
2356
2426
|
] }) });
|
|
2357
2427
|
}
|
|
2358
2428
|
function CreateTicketSheet({ open, onOpenChange, onSuccess }) {
|
|
2359
2429
|
const st = useSupportT();
|
|
2360
2430
|
const { createTicket } = useSupport();
|
|
2361
2431
|
const { toast } = uiCore.useToast();
|
|
2362
|
-
const [isSubmitting, setIsSubmitting] =
|
|
2363
|
-
const labels =
|
|
2432
|
+
const [isSubmitting, setIsSubmitting] = react.useState(false);
|
|
2433
|
+
const labels = react.useMemo(() => ({
|
|
2364
2434
|
title: st("createTicket.title"),
|
|
2365
2435
|
description: st("createTicket.description"),
|
|
2366
2436
|
subjectLabel: st("createTicket.subjectLabel"),
|
|
@@ -2379,7 +2449,7 @@ function CreateTicketSheet({ open, onOpenChange, onSuccess }) {
|
|
|
2379
2449
|
messageTooLong: st("validation.messageTooLong")
|
|
2380
2450
|
}
|
|
2381
2451
|
}), [st]);
|
|
2382
|
-
const createTicketSchema =
|
|
2452
|
+
const createTicketSchema = react.useMemo(() => zod.z.object({
|
|
2383
2453
|
subject: zod.z.string().min(1, labels.validation.subjectRequired).max(200, labels.validation.subjectTooLong),
|
|
2384
2454
|
message: zod.z.string().min(1, labels.validation.messageRequired).max(5e3, labels.validation.messageTooLong)
|
|
2385
2455
|
}), [labels.validation]);
|
|
@@ -2413,13 +2483,10 @@ function CreateTicketSheet({ open, onOpenChange, onSuccess }) {
|
|
|
2413
2483
|
};
|
|
2414
2484
|
return /* @__PURE__ */ jsxRuntime.jsx(uiCore.ResponsiveSheet, { open, onOpenChange: handleClose, children: /* @__PURE__ */ jsxRuntime.jsxs(uiCore.ResponsiveSheetContent, { className: "sm:max-w-lg", children: [
|
|
2415
2485
|
/* @__PURE__ */ jsxRuntime.jsxs(uiCore.ResponsiveSheetHeader, { children: [
|
|
2416
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2417
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-5 w-5" }),
|
|
2418
|
-
labels.title
|
|
2419
|
-
] }),
|
|
2486
|
+
/* @__PURE__ */ jsxRuntime.jsx(uiCore.ResponsiveSheetTitle, { children: labels.title }),
|
|
2420
2487
|
/* @__PURE__ */ jsxRuntime.jsx(uiCore.ResponsiveSheetDescription, { children: labels.description })
|
|
2421
2488
|
] }),
|
|
2422
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiCore.Form, { ...form, children: /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "space-y-
|
|
2489
|
+
/* @__PURE__ */ jsxRuntime.jsx(uiCore.Form, { ...form, children: /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "space-y-5 mt-4", children: [
|
|
2423
2490
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2424
2491
|
uiCore.FormField,
|
|
2425
2492
|
{
|
|
@@ -2459,7 +2526,7 @@ function CreateTicketSheet({ open, onOpenChange, onSuccess }) {
|
|
|
2459
2526
|
] })
|
|
2460
2527
|
}
|
|
2461
2528
|
),
|
|
2462
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-3 pt-
|
|
2529
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-3 pt-2", children: [
|
|
2463
2530
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2464
2531
|
uiCore.Button,
|
|
2465
2532
|
{
|
|
@@ -2473,23 +2540,20 @@ function CreateTicketSheet({ open, onOpenChange, onSuccess }) {
|
|
|
2473
2540
|
/* @__PURE__ */ jsxRuntime.jsx(uiCore.Button, { type: "submit", disabled: isSubmitting, children: isSubmitting ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2474
2541
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 mr-2 animate-spin" }),
|
|
2475
2542
|
labels.creating
|
|
2476
|
-
] }) :
|
|
2477
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-4 w-4 mr-2" }),
|
|
2478
|
-
labels.create
|
|
2479
|
-
] }) })
|
|
2543
|
+
] }) : labels.create })
|
|
2480
2544
|
] })
|
|
2481
2545
|
] }) })
|
|
2482
2546
|
] }) });
|
|
2483
2547
|
}
|
|
2484
2548
|
var SupportContent = () => {
|
|
2485
|
-
const [createSheetOpen, setCreateSheetOpen] =
|
|
2486
|
-
const [selectedTicket, setSelectedTicket] =
|
|
2487
|
-
const [ticketSheetOpen, setTicketSheetOpen] =
|
|
2488
|
-
const handleTicketClick =
|
|
2549
|
+
const [createSheetOpen, setCreateSheetOpen] = react.useState(false);
|
|
2550
|
+
const [selectedTicket, setSelectedTicket] = react.useState(null);
|
|
2551
|
+
const [ticketSheetOpen, setTicketSheetOpen] = react.useState(false);
|
|
2552
|
+
const handleTicketClick = react.useCallback((ticket) => {
|
|
2489
2553
|
setSelectedTicket(ticket);
|
|
2490
2554
|
setTicketSheetOpen(true);
|
|
2491
2555
|
}, []);
|
|
2492
|
-
const handleTicketCreated =
|
|
2556
|
+
const handleTicketCreated = react.useCallback((ticket) => {
|
|
2493
2557
|
setSelectedTicket(ticket);
|
|
2494
2558
|
setTicketSheetOpen(true);
|
|
2495
2559
|
}, []);
|
|
@@ -2531,7 +2595,7 @@ function ApiSupportPage() {
|
|
|
2531
2595
|
return /* @__PURE__ */ jsxRuntime.jsx(SupportProvider, { adapter, children: /* @__PURE__ */ jsxRuntime.jsx(SupportContent, {}) });
|
|
2532
2596
|
}
|
|
2533
2597
|
function DemoSupportPage() {
|
|
2534
|
-
const adapter =
|
|
2598
|
+
const adapter = react.useMemo(() => createDemoAdapter(), []);
|
|
2535
2599
|
return /* @__PURE__ */ jsxRuntime.jsx(SupportProvider, { adapter, children: /* @__PURE__ */ jsxRuntime.jsx(SupportContent, {}) });
|
|
2536
2600
|
}
|
|
2537
2601
|
function SupportPageInner({ isDemo = false }) {
|
|
@@ -2547,7 +2611,7 @@ function SupportPage({ isDemo = false }) {
|
|
|
2547
2611
|
// package.json
|
|
2548
2612
|
var package_default = {
|
|
2549
2613
|
name: "@djangocfg/ext-support",
|
|
2550
|
-
version: "1.0.
|
|
2614
|
+
version: "1.0.26",
|
|
2551
2615
|
description: "Support ticket system extension for DjangoCFG",
|
|
2552
2616
|
keywords: [
|
|
2553
2617
|
"django",
|