@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/hooks.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,10 +21,9 @@ 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
25
|
var useSWR__default = /*#__PURE__*/_interopDefault(useSWR);
|
|
27
|
-
var
|
|
26
|
+
var moment__default = /*#__PURE__*/_interopDefault(moment);
|
|
28
27
|
|
|
29
28
|
// src/api/generated/ext_support/ext_support__support/client.ts
|
|
30
29
|
var ExtSupportSupport = class {
|
|
@@ -43,7 +42,7 @@ var ExtSupportSupport = class {
|
|
|
43
42
|
if (isParamsObject) {
|
|
44
43
|
params = args[0];
|
|
45
44
|
} else {
|
|
46
|
-
params = {
|
|
45
|
+
params = { ordering: args[0], page: args[1], page_size: args[2], search: args[3] };
|
|
47
46
|
}
|
|
48
47
|
const response = await this.client.request("GET", "/cfg/support/tickets/", { params });
|
|
49
48
|
return response;
|
|
@@ -68,7 +67,7 @@ var ExtSupportSupport = class {
|
|
|
68
67
|
if (isParamsObject) {
|
|
69
68
|
params = args[1];
|
|
70
69
|
} else {
|
|
71
|
-
params = {
|
|
70
|
+
params = { ordering: args[1], page: args[2], page_size: args[3], search: args[4] };
|
|
72
71
|
}
|
|
73
72
|
const response = await this.client.request("GET", `/cfg/support/tickets/${ticket_uuid}/messages/`, { params });
|
|
74
73
|
return response;
|
|
@@ -759,22 +758,6 @@ var PatchedTicketRequestStatus = /* @__PURE__ */ ((PatchedTicketRequestStatus2)
|
|
|
759
758
|
PatchedTicketRequestStatus2["CLOSED"] = "closed";
|
|
760
759
|
return PatchedTicketRequestStatus2;
|
|
761
760
|
})(PatchedTicketRequestStatus || {});
|
|
762
|
-
var TicketStatus = /* @__PURE__ */ ((TicketStatus2) => {
|
|
763
|
-
TicketStatus2["OPEN"] = "open";
|
|
764
|
-
TicketStatus2["WAITING_FOR_USER"] = "waiting_for_user";
|
|
765
|
-
TicketStatus2["WAITING_FOR_ADMIN"] = "waiting_for_admin";
|
|
766
|
-
TicketStatus2["RESOLVED"] = "resolved";
|
|
767
|
-
TicketStatus2["CLOSED"] = "closed";
|
|
768
|
-
return TicketStatus2;
|
|
769
|
-
})(TicketStatus || {});
|
|
770
|
-
var TicketRequestStatus = /* @__PURE__ */ ((TicketRequestStatus2) => {
|
|
771
|
-
TicketRequestStatus2["OPEN"] = "open";
|
|
772
|
-
TicketRequestStatus2["WAITING_FOR_USER"] = "waiting_for_user";
|
|
773
|
-
TicketRequestStatus2["WAITING_FOR_ADMIN"] = "waiting_for_admin";
|
|
774
|
-
TicketRequestStatus2["RESOLVED"] = "resolved";
|
|
775
|
-
TicketRequestStatus2["CLOSED"] = "closed";
|
|
776
|
-
return TicketRequestStatus2;
|
|
777
|
-
})(TicketRequestStatus || {});
|
|
778
761
|
var SenderSchema = zod.z.object({
|
|
779
762
|
id: zod.z.int(),
|
|
780
763
|
display_username: zod.z.string(),
|
|
@@ -818,7 +801,7 @@ var TicketSchema = zod.z.object({
|
|
|
818
801
|
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),
|
|
819
802
|
user: zod.z.int(),
|
|
820
803
|
subject: zod.z.string().max(255),
|
|
821
|
-
status: zod.z.nativeEnum(
|
|
804
|
+
status: zod.z.nativeEnum(PatchedTicketRequestStatus).optional(),
|
|
822
805
|
created_at: zod.z.string().datetime({ offset: true }),
|
|
823
806
|
unanswered_messages_count: zod.z.int()
|
|
824
807
|
});
|
|
@@ -846,7 +829,7 @@ zod.z.object({
|
|
|
846
829
|
zod.z.object({
|
|
847
830
|
user: zod.z.int(),
|
|
848
831
|
subject: zod.z.string().min(1).max(255),
|
|
849
|
-
status: zod.z.nativeEnum(
|
|
832
|
+
status: zod.z.nativeEnum(PatchedTicketRequestStatus).optional()
|
|
850
833
|
});
|
|
851
834
|
|
|
852
835
|
// src/api/generated/ext_support/api-instance.ts
|
|
@@ -882,7 +865,7 @@ function configureAPI(config) {
|
|
|
882
865
|
// src/api/generated/ext_support/_utils/fetchers/ext_support__support.ts
|
|
883
866
|
async function getSupportTicketsList(params, client) {
|
|
884
867
|
const api = client || getAPIInstance();
|
|
885
|
-
const response = await api.ext_support_support.ticketsList(params?.page, params?.page_size);
|
|
868
|
+
const response = await api.ext_support_support.ticketsList(params?.ordering, params?.page, params?.page_size, params?.search);
|
|
886
869
|
try {
|
|
887
870
|
return PaginatedTicketListSchema.parse(response);
|
|
888
871
|
} catch (error) {
|
|
@@ -966,7 +949,7 @@ Method: POST`);
|
|
|
966
949
|
}
|
|
967
950
|
async function getSupportTicketsMessagesList(ticket_uuid, params, client) {
|
|
968
951
|
const api = client || getAPIInstance();
|
|
969
|
-
const response = await api.ext_support_support.ticketsMessagesList(ticket_uuid, params?.page, params?.page_size);
|
|
952
|
+
const response = await api.ext_support_support.ticketsMessagesList(ticket_uuid, params?.ordering, params?.page, params?.page_size, params?.search);
|
|
970
953
|
try {
|
|
971
954
|
return PaginatedMessageListSchema.parse(response);
|
|
972
955
|
} catch (error) {
|
|
@@ -1440,13 +1423,13 @@ var API = class {
|
|
|
1440
1423
|
// src/api/index.ts
|
|
1441
1424
|
api.initializeExtensionAPI(configureAPI);
|
|
1442
1425
|
var apiSupport = api.createExtensionAPI(API);
|
|
1443
|
-
var SupportContext =
|
|
1426
|
+
var SupportContext = react.createContext(void 0);
|
|
1444
1427
|
function SupportProvider({ children, adapter }) {
|
|
1445
|
-
const value =
|
|
1428
|
+
const value = react.useMemo(() => ({ adapter }), [adapter]);
|
|
1446
1429
|
return /* @__PURE__ */ jsxRuntime.jsx(SupportContext.Provider, { value, children });
|
|
1447
1430
|
}
|
|
1448
1431
|
function useAdapter() {
|
|
1449
|
-
const context =
|
|
1432
|
+
const context = react.useContext(SupportContext);
|
|
1450
1433
|
if (!context) {
|
|
1451
1434
|
throw new Error("useSupport hooks must be used within SupportProvider");
|
|
1452
1435
|
}
|
|
@@ -1606,18 +1589,18 @@ function useApiTickets() {
|
|
|
1606
1589
|
revalidateFirstPage: false,
|
|
1607
1590
|
parallel: false
|
|
1608
1591
|
});
|
|
1609
|
-
const tickets =
|
|
1592
|
+
const tickets = react.useMemo(() => {
|
|
1610
1593
|
return ticketsData ? ticketsData.flatMap((page) => page.results) : [];
|
|
1611
1594
|
}, [ticketsData]);
|
|
1612
1595
|
const hasMore = ticketsData && ticketsData[ticketsData.length - 1]?.has_next || false;
|
|
1613
1596
|
const totalCount = ticketsData && ticketsData[ticketsData.length - 1]?.count || 0;
|
|
1614
1597
|
const isLoadingMore = isValidating && ticketsData && typeof ticketsData[size - 1] !== "undefined";
|
|
1615
|
-
const loadMore =
|
|
1598
|
+
const loadMore = react.useCallback(() => {
|
|
1616
1599
|
if (hasMore && !isLoadingMore) {
|
|
1617
1600
|
setSize(size + 1);
|
|
1618
1601
|
}
|
|
1619
1602
|
}, [hasMore, isLoadingMore, size, setSize]);
|
|
1620
|
-
const refresh =
|
|
1603
|
+
const refresh = react.useCallback(async () => {
|
|
1621
1604
|
await mutate();
|
|
1622
1605
|
}, [mutate]);
|
|
1623
1606
|
return {
|
|
@@ -1651,21 +1634,21 @@ function useApiMessages(ticketUuid) {
|
|
|
1651
1634
|
revalidateFirstPage: false,
|
|
1652
1635
|
parallel: false
|
|
1653
1636
|
});
|
|
1654
|
-
const messages =
|
|
1637
|
+
const messages = react.useMemo(() => {
|
|
1655
1638
|
return data ? data.flatMap((page) => page.results) : [];
|
|
1656
1639
|
}, [data]);
|
|
1657
1640
|
const hasMore = data && data[data.length - 1]?.has_next || false;
|
|
1658
1641
|
const totalCount = data && data[data.length - 1]?.count || 0;
|
|
1659
1642
|
const isLoadingMore = isValidating && data && typeof data[size - 1] !== "undefined";
|
|
1660
|
-
const loadMore =
|
|
1643
|
+
const loadMore = react.useCallback(() => {
|
|
1661
1644
|
if (hasMore && !isLoadingMore) {
|
|
1662
1645
|
setSize(size + 1);
|
|
1663
1646
|
}
|
|
1664
1647
|
}, [hasMore, isLoadingMore, size, setSize]);
|
|
1665
|
-
const refresh =
|
|
1648
|
+
const refresh = react.useCallback(async () => {
|
|
1666
1649
|
await mutate();
|
|
1667
1650
|
}, [mutate]);
|
|
1668
|
-
const addOptimistic =
|
|
1651
|
+
const addOptimistic = react.useCallback((message) => {
|
|
1669
1652
|
if (!data || !data[0]) return;
|
|
1670
1653
|
const newData = [...data];
|
|
1671
1654
|
const firstPage = newData[0];
|
|
@@ -1695,7 +1678,7 @@ function useApiOperations() {
|
|
|
1695
1678
|
const { user } = auth.useAuth();
|
|
1696
1679
|
const createTicketMutation = useCreateSupportTicketsCreate();
|
|
1697
1680
|
const createMessageMutation = useCreateSupportTicketsMessagesCreate();
|
|
1698
|
-
const createTicket =
|
|
1681
|
+
const createTicket = react.useCallback(async (input) => {
|
|
1699
1682
|
if (!user?.id) {
|
|
1700
1683
|
throw new Error("User must be authenticated to create tickets");
|
|
1701
1684
|
}
|
|
@@ -1710,7 +1693,7 @@ function useApiOperations() {
|
|
|
1710
1693
|
}
|
|
1711
1694
|
return ticket;
|
|
1712
1695
|
}, [user, createTicketMutation, createMessageMutation]);
|
|
1713
|
-
const sendMessage =
|
|
1696
|
+
const sendMessage = react.useCallback(async (input) => {
|
|
1714
1697
|
const message = await createMessageMutation(input.ticketUuid, {
|
|
1715
1698
|
text: input.text
|
|
1716
1699
|
});
|
|
@@ -1738,7 +1721,7 @@ function createApiAdapter() {
|
|
|
1738
1721
|
}
|
|
1739
1722
|
function useApiAdapter() {
|
|
1740
1723
|
const { createTicket, sendMessage } = useApiOperations();
|
|
1741
|
-
return
|
|
1724
|
+
return react.useMemo(() => ({
|
|
1742
1725
|
useTickets: useApiTickets,
|
|
1743
1726
|
useMessages: useApiMessages,
|
|
1744
1727
|
createTicket,
|
|
@@ -1887,8 +1870,8 @@ function addMessage(ticketUuid, message) {
|
|
|
1887
1870
|
notifySubscribers();
|
|
1888
1871
|
}
|
|
1889
1872
|
function useDemoTickets() {
|
|
1890
|
-
const [, forceUpdate] =
|
|
1891
|
-
|
|
1873
|
+
const [, forceUpdate] = react.useState({});
|
|
1874
|
+
react.useMemo(() => {
|
|
1892
1875
|
const update = () => forceUpdate({});
|
|
1893
1876
|
subscribers.add(update);
|
|
1894
1877
|
return () => {
|
|
@@ -1908,15 +1891,15 @@ function useDemoTickets() {
|
|
|
1908
1891
|
};
|
|
1909
1892
|
}
|
|
1910
1893
|
function useDemoMessages(ticketUuid) {
|
|
1911
|
-
const [, forceUpdate] =
|
|
1912
|
-
|
|
1894
|
+
const [, forceUpdate] = react.useState({});
|
|
1895
|
+
react.useMemo(() => {
|
|
1913
1896
|
const update = () => forceUpdate({});
|
|
1914
1897
|
subscribers.add(update);
|
|
1915
1898
|
return () => {
|
|
1916
1899
|
subscribers.delete(update);
|
|
1917
1900
|
};
|
|
1918
1901
|
}, []);
|
|
1919
|
-
const messages =
|
|
1902
|
+
const messages = react.useMemo(() => {
|
|
1920
1903
|
if (!ticketUuid) return [];
|
|
1921
1904
|
return demoMessages[ticketUuid] || [];
|
|
1922
1905
|
}, [ticketUuid]);
|
|
@@ -2059,78 +2042,6 @@ var en = {
|
|
|
2059
2042
|
}
|
|
2060
2043
|
};
|
|
2061
2044
|
|
|
2062
|
-
// src/i18n/locales/ru.ts
|
|
2063
|
-
var ru = {
|
|
2064
|
-
layout: {
|
|
2065
|
-
title: "\u0426\u0435\u043D\u0442\u0440 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438",
|
|
2066
|
-
titleShort: "\u041F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0430",
|
|
2067
|
-
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",
|
|
2068
|
-
newTicket: "\u041D\u043E\u0432\u043E\u0435 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435"
|
|
2069
|
-
},
|
|
2070
|
-
hero: {
|
|
2071
|
-
openTickets: "\u041E\u0442\u043A\u0440\u044B\u0442\u044B\u0445 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439",
|
|
2072
|
-
unreadMessages: "\u041D\u0435\u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u043D\u044B\u0445"
|
|
2073
|
-
},
|
|
2074
|
-
status: {
|
|
2075
|
-
open: "\u041E\u0442\u043A\u0440\u044B\u0442",
|
|
2076
|
-
waitingForUser: "\u041E\u0436\u0438\u0434\u0430\u0435\u0442 \u0432\u0430\u0441",
|
|
2077
|
-
waitingForAdmin: "\u041E\u0436\u0438\u0434\u0430\u0435\u0442 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0443",
|
|
2078
|
-
resolved: "\u0420\u0435\u0448\u0451\u043D",
|
|
2079
|
-
closed: "\u0417\u0430\u043A\u0440\u044B\u0442"
|
|
2080
|
-
},
|
|
2081
|
-
ticketList: {
|
|
2082
|
-
noTickets: "\u041D\u0435\u0442 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439",
|
|
2083
|
-
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",
|
|
2084
|
-
loadingMore: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430...",
|
|
2085
|
-
loadMore: "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0435\u0449\u0451",
|
|
2086
|
-
allLoaded: "\u0412\u0441\u0435 {count} \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D\u043E"
|
|
2087
|
-
},
|
|
2088
|
-
createTicket: {
|
|
2089
|
-
title: "\u041D\u043E\u0432\u043E\u0435 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435",
|
|
2090
|
-
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",
|
|
2091
|
-
subjectLabel: "\u0422\u0435\u043C\u0430",
|
|
2092
|
-
subjectPlaceholder: "\u041A\u0440\u0430\u0442\u043A\u043E\u0435 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 \u043F\u0440\u043E\u0431\u043B\u0435\u043C\u044B",
|
|
2093
|
-
messageLabel: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435",
|
|
2094
|
-
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...",
|
|
2095
|
-
cancel: "\u041E\u0442\u043C\u0435\u043D\u0430",
|
|
2096
|
-
creating: "\u0421\u043E\u0437\u0434\u0430\u043D\u0438\u0435...",
|
|
2097
|
-
create: "\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435"
|
|
2098
|
-
},
|
|
2099
|
-
validation: {
|
|
2100
|
-
subjectRequired: "\u0422\u0435\u043C\u0430 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u0430",
|
|
2101
|
-
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)",
|
|
2102
|
-
messageRequired: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E",
|
|
2103
|
-
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)"
|
|
2104
|
-
},
|
|
2105
|
-
messages: {
|
|
2106
|
-
ticketCreated: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u0441\u043E\u0437\u0434\u0430\u043D\u043E",
|
|
2107
|
-
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",
|
|
2108
|
-
messageSent: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E",
|
|
2109
|
-
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"
|
|
2110
|
-
},
|
|
2111
|
-
messageInput: {
|
|
2112
|
-
placeholder: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435...",
|
|
2113
|
-
ticketClosed: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u043E",
|
|
2114
|
-
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."
|
|
2115
|
-
},
|
|
2116
|
-
messageList: {
|
|
2117
|
-
noTicketSelected: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u043D\u0435 \u0432\u044B\u0431\u0440\u0430\u043D\u043E",
|
|
2118
|
-
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",
|
|
2119
|
-
noMessages: "\u041D\u0435\u0442 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439",
|
|
2120
|
-
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",
|
|
2121
|
-
loadingOlder: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u0441\u0442\u0430\u0440\u044B\u0445 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439...",
|
|
2122
|
-
loadOlder: "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0441\u0442\u0430\u0440\u044B\u0435",
|
|
2123
|
-
supportTeam: "\u0421\u043B\u0443\u0436\u0431\u0430 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438",
|
|
2124
|
-
staff: "\u0421\u043E\u0442\u0440\u0443\u0434\u043D\u0438\u043A"
|
|
2125
|
-
},
|
|
2126
|
-
time: {
|
|
2127
|
-
justNow: "\u0422\u043E\u043B\u044C\u043A\u043E \u0447\u0442\u043E",
|
|
2128
|
-
minutesAgo: "{count} \u043C\u0438\u043D \u043D\u0430\u0437\u0430\u0434",
|
|
2129
|
-
hoursAgo: "{count}\u0447 \u043D\u0430\u0437\u0430\u0434",
|
|
2130
|
-
daysAgo: "{count}\u0434 \u043D\u0430\u0437\u0430\u0434"
|
|
2131
|
-
}
|
|
2132
|
-
};
|
|
2133
|
-
|
|
2134
2045
|
// src/i18n/locales/ko.ts
|
|
2135
2046
|
var ko = {
|
|
2136
2047
|
layout: {
|
|
@@ -2203,6 +2114,78 @@ var ko = {
|
|
|
2203
2114
|
}
|
|
2204
2115
|
};
|
|
2205
2116
|
|
|
2117
|
+
// src/i18n/locales/ru.ts
|
|
2118
|
+
var ru = {
|
|
2119
|
+
layout: {
|
|
2120
|
+
title: "\u0426\u0435\u043D\u0442\u0440 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438",
|
|
2121
|
+
titleShort: "\u041F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0430",
|
|
2122
|
+
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",
|
|
2123
|
+
newTicket: "\u041D\u043E\u0432\u043E\u0435 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435"
|
|
2124
|
+
},
|
|
2125
|
+
hero: {
|
|
2126
|
+
openTickets: "\u041E\u0442\u043A\u0440\u044B\u0442\u044B\u0445 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439",
|
|
2127
|
+
unreadMessages: "\u041D\u0435\u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u043D\u044B\u0445"
|
|
2128
|
+
},
|
|
2129
|
+
status: {
|
|
2130
|
+
open: "\u041E\u0442\u043A\u0440\u044B\u0442",
|
|
2131
|
+
waitingForUser: "\u041E\u0436\u0438\u0434\u0430\u0435\u0442 \u0432\u0430\u0441",
|
|
2132
|
+
waitingForAdmin: "\u041E\u0436\u0438\u0434\u0430\u0435\u0442 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0443",
|
|
2133
|
+
resolved: "\u0420\u0435\u0448\u0451\u043D",
|
|
2134
|
+
closed: "\u0417\u0430\u043A\u0440\u044B\u0442"
|
|
2135
|
+
},
|
|
2136
|
+
ticketList: {
|
|
2137
|
+
noTickets: "\u041D\u0435\u0442 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439",
|
|
2138
|
+
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",
|
|
2139
|
+
loadingMore: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430...",
|
|
2140
|
+
loadMore: "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0435\u0449\u0451",
|
|
2141
|
+
allLoaded: "\u0412\u0441\u0435 {count} \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D\u043E"
|
|
2142
|
+
},
|
|
2143
|
+
createTicket: {
|
|
2144
|
+
title: "\u041D\u043E\u0432\u043E\u0435 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435",
|
|
2145
|
+
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",
|
|
2146
|
+
subjectLabel: "\u0422\u0435\u043C\u0430",
|
|
2147
|
+
subjectPlaceholder: "\u041A\u0440\u0430\u0442\u043A\u043E\u0435 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 \u043F\u0440\u043E\u0431\u043B\u0435\u043C\u044B",
|
|
2148
|
+
messageLabel: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435",
|
|
2149
|
+
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...",
|
|
2150
|
+
cancel: "\u041E\u0442\u043C\u0435\u043D\u0430",
|
|
2151
|
+
creating: "\u0421\u043E\u0437\u0434\u0430\u043D\u0438\u0435...",
|
|
2152
|
+
create: "\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435"
|
|
2153
|
+
},
|
|
2154
|
+
validation: {
|
|
2155
|
+
subjectRequired: "\u0422\u0435\u043C\u0430 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u0430",
|
|
2156
|
+
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)",
|
|
2157
|
+
messageRequired: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E",
|
|
2158
|
+
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)"
|
|
2159
|
+
},
|
|
2160
|
+
messages: {
|
|
2161
|
+
ticketCreated: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u0441\u043E\u0437\u0434\u0430\u043D\u043E",
|
|
2162
|
+
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",
|
|
2163
|
+
messageSent: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E",
|
|
2164
|
+
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"
|
|
2165
|
+
},
|
|
2166
|
+
messageInput: {
|
|
2167
|
+
placeholder: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435...",
|
|
2168
|
+
ticketClosed: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u043E",
|
|
2169
|
+
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."
|
|
2170
|
+
},
|
|
2171
|
+
messageList: {
|
|
2172
|
+
noTicketSelected: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u043D\u0435 \u0432\u044B\u0431\u0440\u0430\u043D\u043E",
|
|
2173
|
+
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",
|
|
2174
|
+
noMessages: "\u041D\u0435\u0442 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439",
|
|
2175
|
+
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",
|
|
2176
|
+
loadingOlder: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u0441\u0442\u0430\u0440\u044B\u0445 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439...",
|
|
2177
|
+
loadOlder: "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0441\u0442\u0430\u0440\u044B\u0435",
|
|
2178
|
+
supportTeam: "\u0421\u043B\u0443\u0436\u0431\u0430 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438",
|
|
2179
|
+
staff: "\u0421\u043E\u0442\u0440\u0443\u0434\u043D\u0438\u043A"
|
|
2180
|
+
},
|
|
2181
|
+
time: {
|
|
2182
|
+
justNow: "\u0422\u043E\u043B\u044C\u043A\u043E \u0447\u0442\u043E",
|
|
2183
|
+
minutesAgo: "{count} \u043C\u0438\u043D \u043D\u0430\u0437\u0430\u0434",
|
|
2184
|
+
hoursAgo: "{count}\u0447 \u043D\u0430\u0437\u0430\u0434",
|
|
2185
|
+
daysAgo: "{count}\u0434 \u043D\u0430\u0437\u0430\u0434"
|
|
2186
|
+
}
|
|
2187
|
+
};
|
|
2188
|
+
|
|
2206
2189
|
// src/i18n/useSupportT.ts
|
|
2207
2190
|
var translations = { en, ru, ko };
|
|
2208
2191
|
function getNestedValue(obj, path) {
|
|
@@ -2219,8 +2202,8 @@ function getNestedValue(obj, path) {
|
|
|
2219
2202
|
}
|
|
2220
2203
|
function useSupportT() {
|
|
2221
2204
|
const locale = nextIntl.useLocale();
|
|
2222
|
-
const t =
|
|
2223
|
-
return
|
|
2205
|
+
const t = react.useMemo(() => translations[locale] || translations.en, [locale]);
|
|
2206
|
+
return react.useCallback(
|
|
2224
2207
|
(key) => getNestedValue(t, key),
|
|
2225
2208
|
[t]
|
|
2226
2209
|
);
|
|
@@ -2228,7 +2211,7 @@ function useSupportT() {
|
|
|
2228
2211
|
function SupportHero({ onCreateTicket, className }) {
|
|
2229
2212
|
const st = useSupportT();
|
|
2230
2213
|
const { tickets, unreadCount, isLoadingTickets, refreshTickets } = useSupport();
|
|
2231
|
-
const labels =
|
|
2214
|
+
const labels = react.useMemo(() => ({
|
|
2232
2215
|
title: st("layout.title"),
|
|
2233
2216
|
subtitle: st("layout.subtitle"),
|
|
2234
2217
|
newTicket: st("layout.newTicket"),
|
|
@@ -2236,27 +2219,22 @@ function SupportHero({ onCreateTicket, className }) {
|
|
|
2236
2219
|
unreadMessages: st("hero.unreadMessages")
|
|
2237
2220
|
}), [st]);
|
|
2238
2221
|
const openTicketsCount = tickets.filter((t) => t.status !== "closed" && t.status !== "resolved").length;
|
|
2239
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("flex flex-col items-center py-
|
|
2240
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2241
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiCore.Skeleton, { className: "h-
|
|
2242
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiCore.Skeleton, { className: "h-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
2250
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2222
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("flex flex-col items-center py-12 px-4", className), children: [
|
|
2223
|
+
isLoadingTickets ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
2224
|
+
/* @__PURE__ */ jsxRuntime.jsx(uiCore.Skeleton, { className: "h-10 w-48 mx-auto mb-2" }),
|
|
2225
|
+
/* @__PURE__ */ jsxRuntime.jsx(uiCore.Skeleton, { className: "h-5 w-64 mx-auto" })
|
|
2226
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
2227
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-4xl font-bold tracking-tight", children: labels.title }),
|
|
2228
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-lg text-muted-foreground mt-2", children: labels.subtitle })
|
|
2229
|
+
] }),
|
|
2230
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 mt-6", children: [
|
|
2231
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2251
2232
|
uiCore.Button,
|
|
2252
2233
|
{
|
|
2253
2234
|
size: "lg",
|
|
2254
2235
|
onClick: onCreateTicket,
|
|
2255
|
-
className: "rounded-full px-
|
|
2256
|
-
children:
|
|
2257
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-5 w-5 mr-2" }),
|
|
2258
|
-
labels.newTicket
|
|
2259
|
-
]
|
|
2236
|
+
className: "rounded-full px-8 h-12 text-base",
|
|
2237
|
+
children: labels.newTicket
|
|
2260
2238
|
}
|
|
2261
2239
|
),
|
|
2262
2240
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2265,46 +2243,103 @@ function SupportHero({ onCreateTicket, className }) {
|
|
|
2265
2243
|
size: "icon",
|
|
2266
2244
|
variant: "ghost",
|
|
2267
2245
|
onClick: () => refreshTickets(),
|
|
2268
|
-
className: "rounded-full",
|
|
2246
|
+
className: "rounded-full h-10 w-10",
|
|
2269
2247
|
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-4 w-4" })
|
|
2270
2248
|
}
|
|
2271
2249
|
)
|
|
2272
2250
|
] }),
|
|
2273
|
-
!isLoadingTickets && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-
|
|
2274
|
-
/* @__PURE__ */ jsxRuntime.jsxs("
|
|
2275
|
-
|
|
2276
|
-
|
|
2251
|
+
!isLoadingTickets && (openTicketsCount > 0 || unreadCount > 0) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mt-4", children: [
|
|
2252
|
+
openTicketsCount > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-muted-foreground bg-muted px-3 py-1 rounded-full", children: [
|
|
2253
|
+
openTicketsCount,
|
|
2254
|
+
" ",
|
|
2255
|
+
labels.openTickets.toLowerCase()
|
|
2277
2256
|
] }),
|
|
2278
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
unreadCount > 0 ? "text-destructive" : "text-foreground"
|
|
2283
|
-
), children: unreadCount }),
|
|
2284
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: labels.unreadMessages })
|
|
2257
|
+
unreadCount > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-destructive bg-destructive/10 px-3 py-1 rounded-full", children: [
|
|
2258
|
+
unreadCount,
|
|
2259
|
+
" ",
|
|
2260
|
+
labels.unreadMessages.toLowerCase()
|
|
2285
2261
|
] })
|
|
2286
2262
|
] })
|
|
2287
2263
|
] });
|
|
2288
2264
|
}
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2265
|
+
|
|
2266
|
+
// src/utils/status.ts
|
|
2267
|
+
var ticketStatusConfig = {
|
|
2268
|
+
open: {
|
|
2269
|
+
color: "text-blue-600 dark:text-blue-400",
|
|
2270
|
+
bg: "bg-blue-500/10"
|
|
2271
|
+
},
|
|
2272
|
+
waiting_for_user: {
|
|
2273
|
+
color: "text-orange-600 dark:text-orange-400",
|
|
2274
|
+
bg: "bg-orange-500/10"
|
|
2275
|
+
},
|
|
2276
|
+
waiting_for_admin: {
|
|
2277
|
+
color: "text-muted-foreground",
|
|
2278
|
+
bg: "bg-muted"
|
|
2279
|
+
},
|
|
2280
|
+
resolved: {
|
|
2281
|
+
color: "text-green-600 dark:text-green-400",
|
|
2282
|
+
bg: "bg-green-500/10"
|
|
2283
|
+
},
|
|
2284
|
+
closed: {
|
|
2285
|
+
color: "text-muted-foreground",
|
|
2286
|
+
bg: "bg-muted"
|
|
2303
2287
|
}
|
|
2304
2288
|
};
|
|
2289
|
+
function getStatusConfig(status) {
|
|
2290
|
+
return ticketStatusConfig[status] || ticketStatusConfig.open;
|
|
2291
|
+
}
|
|
2292
|
+
function formatMessageTime(date) {
|
|
2293
|
+
if (!date) return "";
|
|
2294
|
+
return moment__default.default.utc(date).local().format("h:mm A");
|
|
2295
|
+
}
|
|
2296
|
+
function formatDateSeparator(date) {
|
|
2297
|
+
if (!date) return "";
|
|
2298
|
+
const m = moment__default.default.utc(date).local();
|
|
2299
|
+
const now = moment__default.default();
|
|
2300
|
+
const today = now.clone().startOf("day");
|
|
2301
|
+
const yesterday = now.clone().subtract(1, "day").startOf("day");
|
|
2302
|
+
if (m.isSame(today, "day")) {
|
|
2303
|
+
return "Today";
|
|
2304
|
+
}
|
|
2305
|
+
if (m.isSame(yesterday, "day")) {
|
|
2306
|
+
return "Yesterday";
|
|
2307
|
+
}
|
|
2308
|
+
if (m.isSame(now, "year")) {
|
|
2309
|
+
return m.format("MMM D");
|
|
2310
|
+
}
|
|
2311
|
+
return m.format("MMM D, YYYY");
|
|
2312
|
+
}
|
|
2313
|
+
function formatRelativeTime(date, labels) {
|
|
2314
|
+
if (!date) return "N/A";
|
|
2315
|
+
const m = moment__default.default.utc(date).local();
|
|
2316
|
+
const now = moment__default.default();
|
|
2317
|
+
const diffInSeconds = now.diff(m, "seconds");
|
|
2318
|
+
if (diffInSeconds < 60) {
|
|
2319
|
+
return labels.justNow;
|
|
2320
|
+
}
|
|
2321
|
+
if (diffInSeconds < 3600) {
|
|
2322
|
+
return labels.minutesAgo.replace("{count}", String(Math.floor(diffInSeconds / 60)));
|
|
2323
|
+
}
|
|
2324
|
+
if (diffInSeconds < 86400) {
|
|
2325
|
+
return labels.hoursAgo.replace("{count}", String(Math.floor(diffInSeconds / 3600)));
|
|
2326
|
+
}
|
|
2327
|
+
if (diffInSeconds < 604800) {
|
|
2328
|
+
return labels.daysAgo.replace("{count}", String(Math.floor(diffInSeconds / 86400)));
|
|
2329
|
+
}
|
|
2330
|
+
return m.format("MMM D, YYYY");
|
|
2331
|
+
}
|
|
2332
|
+
function isSameDay(date1, date2) {
|
|
2333
|
+
if (!date1 || !date2) return false;
|
|
2334
|
+
return moment__default.default.utc(date1).format("YYYY-MM-DD") === moment__default.default.utc(date2).format("YYYY-MM-DD");
|
|
2335
|
+
}
|
|
2336
|
+
function formatTicketDate(date) {
|
|
2337
|
+
if (!date) return "";
|
|
2338
|
+
return moment__default.default.utc(date).local().format("MMM D, YYYY");
|
|
2339
|
+
}
|
|
2305
2340
|
function TicketItem({ ticket, onClick }) {
|
|
2306
2341
|
const st = useSupportT();
|
|
2307
|
-
const labels =
|
|
2342
|
+
const labels = react.useMemo(() => ({
|
|
2308
2343
|
status: {
|
|
2309
2344
|
open: st("status.open"),
|
|
2310
2345
|
waiting_for_user: st("status.waitingForUser"),
|
|
@@ -2319,49 +2354,37 @@ function TicketItem({ ticket, onClick }) {
|
|
|
2319
2354
|
daysAgo: st("time.daysAgo")
|
|
2320
2355
|
}
|
|
2321
2356
|
}), [st]);
|
|
2322
|
-
const
|
|
2323
|
-
|
|
2324
|
-
const m = moment2__default.default.utc(date).local();
|
|
2325
|
-
const now = moment2__default.default();
|
|
2326
|
-
const diffInSeconds = now.diff(m, "seconds");
|
|
2327
|
-
if (diffInSeconds < 60) return labels.time.justNow;
|
|
2328
|
-
if (diffInSeconds < 3600) return labels.time.minutesAgo.replace("{count}", String(Math.floor(diffInSeconds / 60)));
|
|
2329
|
-
if (diffInSeconds < 86400) return labels.time.hoursAgo.replace("{count}", String(Math.floor(diffInSeconds / 3600)));
|
|
2330
|
-
if (diffInSeconds < 604800) return labels.time.daysAgo.replace("{count}", String(Math.floor(diffInSeconds / 86400)));
|
|
2331
|
-
return m.format("MMM D, YYYY");
|
|
2357
|
+
const getRelativeTime = react.useCallback((date) => {
|
|
2358
|
+
return formatRelativeTime(date, labels.time);
|
|
2332
2359
|
}, [labels.time]);
|
|
2333
2360
|
const statusLabel = labels.status[ticket.status] || ticket.status || labels.status.open;
|
|
2361
|
+
const statusConfig = getStatusConfig(ticket.status || "open");
|
|
2334
2362
|
const hasUnread = (ticket.unanswered_messages_count || 0) > 0;
|
|
2335
2363
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2336
|
-
"
|
|
2364
|
+
"button",
|
|
2337
2365
|
{
|
|
2366
|
+
type: "button",
|
|
2338
2367
|
className: lib.cn(
|
|
2339
|
-
"
|
|
2340
|
-
"
|
|
2341
|
-
"hover:bg-accent/50"
|
|
2342
|
-
"active:scale-[0.98]",
|
|
2343
|
-
hasUnread && "bg-primary/5"
|
|
2368
|
+
"w-full flex items-start gap-3 p-4 text-left",
|
|
2369
|
+
"transition-colors duration-150",
|
|
2370
|
+
"hover:bg-accent/50 active:bg-accent"
|
|
2344
2371
|
),
|
|
2345
2372
|
onClick,
|
|
2346
2373
|
children: [
|
|
2347
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2
|
|
2374
|
+
/* @__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" }) }),
|
|
2348
2375
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
2349
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
2359
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-3 w-3" }),
|
|
2360
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: formatRelativeTime(ticket.created_at) })
|
|
2376
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: lib.cn(
|
|
2377
|
+
"text-[15px] line-clamp-1",
|
|
2378
|
+
hasUnread ? "font-semibold" : "font-medium"
|
|
2379
|
+
), children: ticket.subject }),
|
|
2380
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mt-0.5", children: [
|
|
2381
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: lib.cn("text-[13px]", statusConfig.color), children: statusLabel }),
|
|
2382
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[13px] text-muted-foreground", children: [
|
|
2383
|
+
"\xB7 ",
|
|
2384
|
+
getRelativeTime(ticket.created_at)
|
|
2361
2385
|
] })
|
|
2362
2386
|
] })
|
|
2363
|
-
] })
|
|
2364
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-5 w-5 text-muted-foreground/50 group-hover:text-muted-foreground transition-colors flex-shrink-0" })
|
|
2387
|
+
] })
|
|
2365
2388
|
]
|
|
2366
2389
|
}
|
|
2367
2390
|
);
|
|
@@ -2376,15 +2399,15 @@ function TicketList({ onTicketClick, className }) {
|
|
|
2376
2399
|
totalCount: totalTicketsCount,
|
|
2377
2400
|
loadMore: loadMoreTickets
|
|
2378
2401
|
} = useTickets();
|
|
2379
|
-
const labels =
|
|
2402
|
+
const labels = react.useMemo(() => ({
|
|
2380
2403
|
noTickets: st("ticketList.noTickets"),
|
|
2381
2404
|
noTicketsDescription: st("ticketList.noTicketsDescription"),
|
|
2382
2405
|
loadingMore: st("ticketList.loadingMore"),
|
|
2383
2406
|
loadMore: st("ticketList.loadMore")
|
|
2384
2407
|
}), [st]);
|
|
2385
|
-
const observerRef =
|
|
2386
|
-
const loadMoreRef =
|
|
2387
|
-
|
|
2408
|
+
const observerRef = react.useRef(null);
|
|
2409
|
+
const loadMoreRef = react.useRef(null);
|
|
2410
|
+
react.useEffect(() => {
|
|
2388
2411
|
if (observerRef.current) {
|
|
2389
2412
|
observerRef.current.disconnect();
|
|
2390
2413
|
}
|
|
@@ -2406,24 +2429,23 @@ function TicketList({ onTicketClick, className }) {
|
|
|
2406
2429
|
};
|
|
2407
2430
|
}, [hasMoreTickets, isLoadingMoreTickets, loadMoreTickets]);
|
|
2408
2431
|
if (isLoadingTickets) {
|
|
2409
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: lib.cn("px-4 py-6
|
|
2432
|
+
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(
|
|
2410
2433
|
uiCore.Skeleton,
|
|
2411
2434
|
{
|
|
2412
|
-
className: "h-
|
|
2435
|
+
className: "h-12 w-full",
|
|
2413
2436
|
style: { animationDelay: `${i * 100}ms` }
|
|
2414
|
-
}
|
|
2415
|
-
|
|
2416
|
-
)) });
|
|
2437
|
+
}
|
|
2438
|
+
) }, i)) }) });
|
|
2417
2439
|
}
|
|
2418
2440
|
if (!tickets || tickets.length === 0) {
|
|
2419
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2420
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2421
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2422
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2441
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(uiCore.Empty, { className: lib.cn("py-16", className), children: [
|
|
2442
|
+
/* @__PURE__ */ jsxRuntime.jsx(uiCore.EmptyMedia, { variant: "icon", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquare, { className: "h-8 w-8" }) }),
|
|
2443
|
+
/* @__PURE__ */ jsxRuntime.jsx(uiCore.EmptyTitle, { children: labels.noTickets }),
|
|
2444
|
+
/* @__PURE__ */ jsxRuntime.jsx(uiCore.EmptyDescription, { children: labels.noTicketsDescription })
|
|
2423
2445
|
] });
|
|
2424
2446
|
}
|
|
2425
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("px-4 py-
|
|
2426
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "
|
|
2447
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("px-4 py-2", className), children: [
|
|
2448
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-border/50", children: tickets.map((ticket) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2427
2449
|
TicketItem,
|
|
2428
2450
|
{
|
|
2429
2451
|
ticket,
|
|
@@ -2453,59 +2475,81 @@ function TicketList({ onTicketClick, className }) {
|
|
|
2453
2475
|
) })
|
|
2454
2476
|
] });
|
|
2455
2477
|
}
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
return "outline";
|
|
2474
|
-
case "closed":
|
|
2475
|
-
return "secondary";
|
|
2476
|
-
default:
|
|
2477
|
-
return "default";
|
|
2478
|
+
function groupMessages(messages, currentUserId) {
|
|
2479
|
+
const groups = [];
|
|
2480
|
+
let currentGroup = null;
|
|
2481
|
+
for (const msg of messages) {
|
|
2482
|
+
const isFromUser = msg.sender?.id && currentUserId && String(msg.sender.id) === String(currentUserId) || msg.is_from_author;
|
|
2483
|
+
const senderId = msg.sender?.id || null;
|
|
2484
|
+
const shouldStartNewGroup = !currentGroup || currentGroup.isFromUser !== isFromUser || currentGroup.senderId !== senderId;
|
|
2485
|
+
if (shouldStartNewGroup) {
|
|
2486
|
+
currentGroup = {
|
|
2487
|
+
senderId,
|
|
2488
|
+
isFromUser: !!isFromUser,
|
|
2489
|
+
messages: [msg]
|
|
2490
|
+
};
|
|
2491
|
+
groups.push(currentGroup);
|
|
2492
|
+
} else {
|
|
2493
|
+
currentGroup.messages.push(msg);
|
|
2494
|
+
}
|
|
2478
2495
|
}
|
|
2479
|
-
|
|
2480
|
-
|
|
2496
|
+
return groups;
|
|
2497
|
+
}
|
|
2498
|
+
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) }) });
|
|
2499
|
+
var MessageBubble = ({
|
|
2500
|
+
message,
|
|
2501
|
+
isFromUser,
|
|
2502
|
+
showAvatar,
|
|
2503
|
+
showTimestamp,
|
|
2504
|
+
currentUser,
|
|
2505
|
+
labels
|
|
2506
|
+
}) => {
|
|
2481
2507
|
const sender = message.sender;
|
|
2482
2508
|
const senderInitial = sender?.display_username?.charAt(0)?.toUpperCase() || sender?.initials || "S";
|
|
2483
2509
|
const userInitial = currentUser?.display_username?.charAt(0)?.toUpperCase() || currentUser?.email?.charAt(0)?.toUpperCase() || currentUser?.initials || null;
|
|
2484
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("flex gap-
|
|
2485
|
-
!isFromUser && /* @__PURE__ */ jsxRuntime.jsx(uiCore.Avatar, { className: "h-8 w-8
|
|
2486
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("flex flex-col gap-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2510
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("flex gap-2", isFromUser ? "justify-end" : "justify-start"), children: [
|
|
2511
|
+
!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 }) }) }),
|
|
2512
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("flex flex-col gap-0.5 max-w-[85%]", isFromUser ? "items-end" : "items-start"), children: [
|
|
2513
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2514
|
+
"div",
|
|
2515
|
+
{
|
|
2516
|
+
className: lib.cn(
|
|
2517
|
+
"px-4 py-2.5 rounded-[20px]",
|
|
2518
|
+
isFromUser ? "bg-blue-500 text-white" : "bg-[#e9e9eb] dark:bg-zinc-800 text-foreground"
|
|
2519
|
+
),
|
|
2520
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[15px] leading-relaxed whitespace-pre-wrap break-words", children: message.text })
|
|
2521
|
+
}
|
|
2522
|
+
),
|
|
2523
|
+
showTimestamp && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] text-muted-foreground px-2 mt-0.5", children: formatMessageTime(message.created_at) })
|
|
2495
2524
|
] }),
|
|
2496
|
-
isFromUser && /* @__PURE__ */ jsxRuntime.jsx(uiCore.Avatar, { className: "h-8 w-8
|
|
2525
|
+
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" }) }) }) })
|
|
2497
2526
|
] });
|
|
2498
2527
|
};
|
|
2528
|
+
var MessageGroupComponent = ({ group, currentUser, labels }) => {
|
|
2529
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1", children: group.messages.map((msg, idx) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2530
|
+
MessageBubble,
|
|
2531
|
+
{
|
|
2532
|
+
message: msg,
|
|
2533
|
+
isFromUser: group.isFromUser,
|
|
2534
|
+
showAvatar: idx === 0,
|
|
2535
|
+
showTimestamp: idx === group.messages.length - 1,
|
|
2536
|
+
currentUser,
|
|
2537
|
+
labels
|
|
2538
|
+
},
|
|
2539
|
+
msg.uuid
|
|
2540
|
+
)) });
|
|
2541
|
+
};
|
|
2499
2542
|
function TicketSheet({ ticket, open, onOpenChange }) {
|
|
2500
2543
|
const st = useSupportT();
|
|
2501
2544
|
const { user } = auth.useAuth();
|
|
2502
2545
|
const { sendMessage } = useSupport();
|
|
2503
2546
|
const { toast } = uiCore.useToast();
|
|
2504
|
-
const [message, setMessage] =
|
|
2505
|
-
const [isSending, setIsSending] =
|
|
2506
|
-
const scrollAreaRef =
|
|
2507
|
-
const
|
|
2508
|
-
const
|
|
2547
|
+
const [message, setMessage] = react.useState("");
|
|
2548
|
+
const [isSending, setIsSending] = react.useState(false);
|
|
2549
|
+
const scrollAreaRef = react.useRef(null);
|
|
2550
|
+
const textareaRef = react.useRef(null);
|
|
2551
|
+
const firstRender = react.useRef(true);
|
|
2552
|
+
const labels = react.useMemo(() => ({
|
|
2509
2553
|
noMessages: st("messageList.noMessages"),
|
|
2510
2554
|
noMessagesDescription: st("messageList.noMessagesDescription"),
|
|
2511
2555
|
loadingOlder: st("messageList.loadingOlder"),
|
|
@@ -2535,10 +2579,11 @@ function TicketSheet({ ticket, open, onOpenChange }) {
|
|
|
2535
2579
|
refresh: refreshMessages,
|
|
2536
2580
|
addOptimistic
|
|
2537
2581
|
} = useMessages(ticket?.uuid || null);
|
|
2538
|
-
|
|
2582
|
+
const messageGroups = react.useMemo(() => groupMessages(messages, user?.id), [messages, user?.id]);
|
|
2583
|
+
react.useEffect(() => {
|
|
2539
2584
|
firstRender.current = true;
|
|
2540
2585
|
}, [ticket?.uuid]);
|
|
2541
|
-
|
|
2586
|
+
react.useEffect(() => {
|
|
2542
2587
|
if (firstRender.current && messages.length > 0) {
|
|
2543
2588
|
const scrollContainer = scrollAreaRef.current?.querySelector("[data-radix-scroll-area-viewport]");
|
|
2544
2589
|
if (scrollContainer) {
|
|
@@ -2547,6 +2592,14 @@ function TicketSheet({ ticket, open, onOpenChange }) {
|
|
|
2547
2592
|
firstRender.current = false;
|
|
2548
2593
|
}
|
|
2549
2594
|
}, [messages]);
|
|
2595
|
+
const handleTextareaChange = react.useCallback((e) => {
|
|
2596
|
+
setMessage(e.target.value);
|
|
2597
|
+
const textarea = textareaRef.current;
|
|
2598
|
+
if (textarea) {
|
|
2599
|
+
textarea.style.height = "auto";
|
|
2600
|
+
textarea.style.height = `${Math.min(textarea.scrollHeight, 120)}px`;
|
|
2601
|
+
}
|
|
2602
|
+
}, []);
|
|
2550
2603
|
const handleSend = async (e) => {
|
|
2551
2604
|
e.preventDefault();
|
|
2552
2605
|
if (!message.trim() || !ticket?.uuid) return;
|
|
@@ -2572,6 +2625,9 @@ function TicketSheet({ ticket, open, onOpenChange }) {
|
|
|
2572
2625
|
addOptimistic(optimisticMessage);
|
|
2573
2626
|
await sendMessage({ ticketUuid: ticket.uuid, text: messageText });
|
|
2574
2627
|
setMessage("");
|
|
2628
|
+
if (textareaRef.current) {
|
|
2629
|
+
textareaRef.current.style.height = "auto";
|
|
2630
|
+
}
|
|
2575
2631
|
await refreshMessages();
|
|
2576
2632
|
const scrollContainer = scrollAreaRef.current?.querySelector("[data-radix-scroll-area-viewport]");
|
|
2577
2633
|
if (scrollContainer) {
|
|
@@ -2592,7 +2648,7 @@ function TicketSheet({ ticket, open, onOpenChange }) {
|
|
|
2592
2648
|
handleSend(e);
|
|
2593
2649
|
}
|
|
2594
2650
|
};
|
|
2595
|
-
const handleLoadMore =
|
|
2651
|
+
const handleLoadMore = react.useCallback(() => {
|
|
2596
2652
|
const scrollContainer = scrollAreaRef.current?.querySelector("[data-radix-scroll-area-viewport]");
|
|
2597
2653
|
const previousHeight = scrollContainer?.scrollHeight || 0;
|
|
2598
2654
|
loadMore();
|
|
@@ -2605,32 +2661,61 @@ function TicketSheet({ ticket, open, onOpenChange }) {
|
|
|
2605
2661
|
}, [loadMore]);
|
|
2606
2662
|
const canSendMessage = ticket?.status !== "closed";
|
|
2607
2663
|
const statusLabel = ticket ? labels.status[ticket.status] || ticket.status || labels.status.open : "";
|
|
2664
|
+
const statusConfig = getStatusConfig(ticket?.status || "open");
|
|
2665
|
+
const renderMessages = () => {
|
|
2666
|
+
const elements = [];
|
|
2667
|
+
let lastDate = null;
|
|
2668
|
+
for (const group of messageGroups) {
|
|
2669
|
+
const firstMessage = group.messages[0];
|
|
2670
|
+
if (!firstMessage) continue;
|
|
2671
|
+
const messageDate = firstMessage.created_at;
|
|
2672
|
+
if (!lastDate || !isSameDay(lastDate, messageDate)) {
|
|
2673
|
+
elements.push(
|
|
2674
|
+
/* @__PURE__ */ jsxRuntime.jsx(DateSeparator, { date: messageDate || "" }, `date-${messageDate}`)
|
|
2675
|
+
);
|
|
2676
|
+
lastDate = messageDate || null;
|
|
2677
|
+
}
|
|
2678
|
+
elements.push(
|
|
2679
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2680
|
+
MessageGroupComponent,
|
|
2681
|
+
{
|
|
2682
|
+
group,
|
|
2683
|
+
currentUser: user,
|
|
2684
|
+
labels: { supportTeam: labels.supportTeam, staff: labels.staff }
|
|
2685
|
+
},
|
|
2686
|
+
`group-${firstMessage.uuid}`
|
|
2687
|
+
)
|
|
2688
|
+
);
|
|
2689
|
+
}
|
|
2690
|
+
return elements;
|
|
2691
|
+
};
|
|
2608
2692
|
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: [
|
|
2609
2693
|
/* @__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: [
|
|
2610
2694
|
/* @__PURE__ */ jsxRuntime.jsx(uiCore.ResponsiveSheetTitle, { className: "text-lg font-semibold line-clamp-2", children: ticket?.subject || "Ticket" }),
|
|
2611
2695
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mt-1", children: [
|
|
2612
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2613
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-
|
|
2696
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: lib.cn("text-[13px] px-2 py-0.5 rounded-full", statusConfig.bg, statusConfig.color), children: statusLabel }),
|
|
2697
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[13px] text-muted-foreground", children: formatTicketDate(ticket?.created_at) })
|
|
2614
2698
|
] })
|
|
2615
2699
|
] }) }) }),
|
|
2616
|
-
/* @__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-
|
|
2617
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiCore.Skeleton, { className: "h-8 w-8 rounded-full" }),
|
|
2618
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiCore.Skeleton, { className: "h-16
|
|
2700
|
+
/* @__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: [
|
|
2701
|
+
i % 2 !== 0 && /* @__PURE__ */ jsxRuntime.jsx(uiCore.Skeleton, { className: "h-8 w-8 rounded-full shrink-0" }),
|
|
2702
|
+
/* @__PURE__ */ jsxRuntime.jsx(uiCore.Skeleton, { className: lib.cn("h-16 rounded-[20px]", i % 2 === 0 ? "w-[60%]" : "w-[70%]") }),
|
|
2703
|
+
i % 2 === 0 && /* @__PURE__ */ jsxRuntime.jsx(uiCore.Skeleton, { className: "h-8 w-8 rounded-full shrink-0" })
|
|
2619
2704
|
] }, i)) }) : messages.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center h-full p-8 text-center", children: [
|
|
2620
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.MessageSquare, { className: "h-
|
|
2705
|
+
/* @__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" }) }),
|
|
2621
2706
|
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold mb-2", children: labels.noMessages }),
|
|
2622
2707
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground max-w-sm", children: labels.noMessagesDescription })
|
|
2623
|
-
] }) : /* @__PURE__ */ jsxRuntime.jsx(uiCore.ScrollArea, { className: "h-full", viewportRef: scrollAreaRef, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-
|
|
2708
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(uiCore.ScrollArea, { className: "h-full", viewportRef: scrollAreaRef, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 space-y-3", children: [
|
|
2624
2709
|
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: [
|
|
2625
2710
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin" }),
|
|
2626
2711
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: labels.loadingOlder })
|
|
2627
2712
|
] }) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2628
2713
|
uiCore.Button,
|
|
2629
2714
|
{
|
|
2630
|
-
variant: "
|
|
2715
|
+
variant: "ghost",
|
|
2631
2716
|
size: "sm",
|
|
2632
2717
|
onClick: handleLoadMore,
|
|
2633
|
-
className: "text-xs",
|
|
2718
|
+
className: "text-xs text-muted-foreground",
|
|
2634
2719
|
children: [
|
|
2635
2720
|
labels.loadOlder,
|
|
2636
2721
|
" (",
|
|
@@ -2639,67 +2724,52 @@ function TicketSheet({ ticket, open, onOpenChange }) {
|
|
|
2639
2724
|
]
|
|
2640
2725
|
}
|
|
2641
2726
|
) }),
|
|
2642
|
-
|
|
2643
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 h-px bg-border" }),
|
|
2644
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: formatDate(messages[0]?.created_at) }),
|
|
2645
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 h-px bg-border" })
|
|
2646
|
-
] }),
|
|
2647
|
-
messages.map((msg, index) => {
|
|
2648
|
-
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);
|
|
2649
|
-
const previousMessage = index > 0 ? messages[index - 1] : null;
|
|
2650
|
-
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");
|
|
2651
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(React4__default.default.Fragment, { children: [
|
|
2652
|
-
showDateSeparator && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 my-4", children: [
|
|
2653
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 h-px bg-border" }),
|
|
2654
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: formatDate(msg.created_at) }),
|
|
2655
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 h-px bg-border" })
|
|
2656
|
-
] }),
|
|
2657
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2658
|
-
MessageBubble,
|
|
2659
|
-
{
|
|
2660
|
-
message: msg,
|
|
2661
|
-
isFromUser: !!isFromUser,
|
|
2662
|
-
currentUser: user,
|
|
2663
|
-
labels: { supportTeam: labels.supportTeam, staff: labels.staff }
|
|
2664
|
-
}
|
|
2665
|
-
)
|
|
2666
|
-
] }, msg.uuid);
|
|
2667
|
-
})
|
|
2727
|
+
renderMessages()
|
|
2668
2728
|
] }) }) }),
|
|
2669
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2670
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2729
|
+
/* @__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: [
|
|
2730
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2731
|
+
"textarea",
|
|
2732
|
+
{
|
|
2733
|
+
ref: textareaRef,
|
|
2734
|
+
value: message,
|
|
2735
|
+
onChange: handleTextareaChange,
|
|
2736
|
+
onKeyDown: handleKeyDown,
|
|
2737
|
+
placeholder: labels.placeholder,
|
|
2738
|
+
className: lib.cn(
|
|
2739
|
+
"flex-1 bg-transparent border-none resize-none",
|
|
2740
|
+
"text-[15px] placeholder:text-muted-foreground",
|
|
2741
|
+
"min-h-[24px] max-h-[120px] focus:outline-none"
|
|
2742
|
+
),
|
|
2743
|
+
rows: 1,
|
|
2744
|
+
disabled: isSending
|
|
2745
|
+
}
|
|
2746
|
+
),
|
|
2747
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2748
|
+
uiCore.Button,
|
|
2749
|
+
{
|
|
2750
|
+
type: "submit",
|
|
2751
|
+
size: "icon",
|
|
2752
|
+
variant: "ghost",
|
|
2753
|
+
className: lib.cn(
|
|
2754
|
+
"rounded-full h-8 w-8 shrink-0",
|
|
2755
|
+
message.trim() ? "bg-blue-500 text-white hover:bg-blue-600" : ""
|
|
2756
|
+
),
|
|
2757
|
+
disabled: !message.trim() || isSending,
|
|
2758
|
+
children: isSending ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowUp, { className: "h-4 w-4" })
|
|
2759
|
+
}
|
|
2760
|
+
)
|
|
2761
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-2", children: [
|
|
2762
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: labels.ticketClosed }),
|
|
2763
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground mt-1", children: labels.ticketClosedDescription })
|
|
2764
|
+
] }) })
|
|
2695
2765
|
] }) });
|
|
2696
2766
|
}
|
|
2697
2767
|
function CreateTicketSheet({ open, onOpenChange, onSuccess }) {
|
|
2698
2768
|
const st = useSupportT();
|
|
2699
2769
|
const { createTicket } = useSupport();
|
|
2700
2770
|
const { toast } = uiCore.useToast();
|
|
2701
|
-
const [isSubmitting, setIsSubmitting] =
|
|
2702
|
-
const labels =
|
|
2771
|
+
const [isSubmitting, setIsSubmitting] = react.useState(false);
|
|
2772
|
+
const labels = react.useMemo(() => ({
|
|
2703
2773
|
title: st("createTicket.title"),
|
|
2704
2774
|
description: st("createTicket.description"),
|
|
2705
2775
|
subjectLabel: st("createTicket.subjectLabel"),
|
|
@@ -2718,7 +2788,7 @@ function CreateTicketSheet({ open, onOpenChange, onSuccess }) {
|
|
|
2718
2788
|
messageTooLong: st("validation.messageTooLong")
|
|
2719
2789
|
}
|
|
2720
2790
|
}), [st]);
|
|
2721
|
-
const createTicketSchema =
|
|
2791
|
+
const createTicketSchema = react.useMemo(() => zod.z.object({
|
|
2722
2792
|
subject: zod.z.string().min(1, labels.validation.subjectRequired).max(200, labels.validation.subjectTooLong),
|
|
2723
2793
|
message: zod.z.string().min(1, labels.validation.messageRequired).max(5e3, labels.validation.messageTooLong)
|
|
2724
2794
|
}), [labels.validation]);
|
|
@@ -2752,13 +2822,10 @@ function CreateTicketSheet({ open, onOpenChange, onSuccess }) {
|
|
|
2752
2822
|
};
|
|
2753
2823
|
return /* @__PURE__ */ jsxRuntime.jsx(uiCore.ResponsiveSheet, { open, onOpenChange: handleClose, children: /* @__PURE__ */ jsxRuntime.jsxs(uiCore.ResponsiveSheetContent, { className: "sm:max-w-lg", children: [
|
|
2754
2824
|
/* @__PURE__ */ jsxRuntime.jsxs(uiCore.ResponsiveSheetHeader, { children: [
|
|
2755
|
-
/* @__PURE__ */ jsxRuntime.
|
|
2756
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-5 w-5" }),
|
|
2757
|
-
labels.title
|
|
2758
|
-
] }),
|
|
2825
|
+
/* @__PURE__ */ jsxRuntime.jsx(uiCore.ResponsiveSheetTitle, { children: labels.title }),
|
|
2759
2826
|
/* @__PURE__ */ jsxRuntime.jsx(uiCore.ResponsiveSheetDescription, { children: labels.description })
|
|
2760
2827
|
] }),
|
|
2761
|
-
/* @__PURE__ */ jsxRuntime.jsx(uiCore.Form, { ...form, children: /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "space-y-
|
|
2828
|
+
/* @__PURE__ */ jsxRuntime.jsx(uiCore.Form, { ...form, children: /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "space-y-5 mt-4", children: [
|
|
2762
2829
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2763
2830
|
uiCore.FormField,
|
|
2764
2831
|
{
|
|
@@ -2798,7 +2865,7 @@ function CreateTicketSheet({ open, onOpenChange, onSuccess }) {
|
|
|
2798
2865
|
] })
|
|
2799
2866
|
}
|
|
2800
2867
|
),
|
|
2801
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-3 pt-
|
|
2868
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-end gap-3 pt-2", children: [
|
|
2802
2869
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2803
2870
|
uiCore.Button,
|
|
2804
2871
|
{
|
|
@@ -2812,23 +2879,20 @@ function CreateTicketSheet({ open, onOpenChange, onSuccess }) {
|
|
|
2812
2879
|
/* @__PURE__ */ jsxRuntime.jsx(uiCore.Button, { type: "submit", disabled: isSubmitting, children: isSubmitting ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2813
2880
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 mr-2 animate-spin" }),
|
|
2814
2881
|
labels.creating
|
|
2815
|
-
] }) :
|
|
2816
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-4 w-4 mr-2" }),
|
|
2817
|
-
labels.create
|
|
2818
|
-
] }) })
|
|
2882
|
+
] }) : labels.create })
|
|
2819
2883
|
] })
|
|
2820
2884
|
] }) })
|
|
2821
2885
|
] }) });
|
|
2822
2886
|
}
|
|
2823
2887
|
var SupportContent = () => {
|
|
2824
|
-
const [createSheetOpen, setCreateSheetOpen] =
|
|
2825
|
-
const [selectedTicket, setSelectedTicket] =
|
|
2826
|
-
const [ticketSheetOpen, setTicketSheetOpen] =
|
|
2827
|
-
const handleTicketClick =
|
|
2888
|
+
const [createSheetOpen, setCreateSheetOpen] = react.useState(false);
|
|
2889
|
+
const [selectedTicket, setSelectedTicket] = react.useState(null);
|
|
2890
|
+
const [ticketSheetOpen, setTicketSheetOpen] = react.useState(false);
|
|
2891
|
+
const handleTicketClick = react.useCallback((ticket) => {
|
|
2828
2892
|
setSelectedTicket(ticket);
|
|
2829
2893
|
setTicketSheetOpen(true);
|
|
2830
2894
|
}, []);
|
|
2831
|
-
const handleTicketCreated =
|
|
2895
|
+
const handleTicketCreated = react.useCallback((ticket) => {
|
|
2832
2896
|
setSelectedTicket(ticket);
|
|
2833
2897
|
setTicketSheetOpen(true);
|
|
2834
2898
|
}, []);
|
|
@@ -2870,7 +2934,7 @@ function ApiSupportPage() {
|
|
|
2870
2934
|
return /* @__PURE__ */ jsxRuntime.jsx(SupportProvider, { adapter, children: /* @__PURE__ */ jsxRuntime.jsx(SupportContent, {}) });
|
|
2871
2935
|
}
|
|
2872
2936
|
function DemoSupportPage() {
|
|
2873
|
-
const adapter =
|
|
2937
|
+
const adapter = react.useMemo(() => createDemoAdapter(), []);
|
|
2874
2938
|
return /* @__PURE__ */ jsxRuntime.jsx(SupportProvider, { adapter, children: /* @__PURE__ */ jsxRuntime.jsx(SupportContent, {}) });
|
|
2875
2939
|
}
|
|
2876
2940
|
function SupportPageInner({ isDemo = false }) {
|
|
@@ -2886,7 +2950,7 @@ function SupportPage({ isDemo = false }) {
|
|
|
2886
2950
|
// package.json
|
|
2887
2951
|
var package_default = {
|
|
2888
2952
|
name: "@djangocfg/ext-support",
|
|
2889
|
-
version: "1.0.
|
|
2953
|
+
version: "1.0.26",
|
|
2890
2954
|
description: "Support ticket system extension for DjangoCFG",
|
|
2891
2955
|
keywords: [
|
|
2892
2956
|
"django",
|