@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.
Files changed (38) hide show
  1. package/dist/config.cjs +1 -1
  2. package/dist/config.js +1 -1
  3. package/dist/hooks.cjs +398 -334
  4. package/dist/hooks.d.cts +6 -2
  5. package/dist/hooks.d.ts +6 -2
  6. package/dist/hooks.js +356 -291
  7. package/dist/i18n.cjs +72 -72
  8. package/dist/i18n.js +73 -73
  9. package/dist/index-D-xo66K9.d.cts +862 -0
  10. package/dist/index-D-xo66K9.d.ts +863 -0
  11. package/dist/index-Dov7pn8Z.d.ts +2 -1
  12. package/dist/index-rR_XqXq1.d.cts +838 -0
  13. package/dist/index-rR_XqXq1.d.ts +838 -0
  14. package/dist/index.cjs +398 -334
  15. package/dist/index.d.cts +1 -1
  16. package/dist/index.d.ts +1 -1
  17. package/dist/index.js +356 -291
  18. package/package.json +8 -8
  19. package/src/adapters/api.ts +4 -5
  20. package/src/adapters/demo.ts +2 -1
  21. package/src/api/generated/ext_support/_utils/fetchers/ext_support__support.ts +4 -4
  22. package/src/api/generated/ext_support/_utils/hooks/ext_support__support.ts +2 -2
  23. package/src/api/generated/ext_support/_utils/schemas/Ticket.schema.ts +1 -1
  24. package/src/api/generated/ext_support/_utils/schemas/TicketRequest.schema.ts +1 -1
  25. package/src/api/generated/ext_support/enums.ts +0 -30
  26. package/src/api/generated/ext_support/ext_support__support/client.ts +6 -6
  27. package/src/api/generated/ext_support/ext_support__support/models.ts +2 -2
  28. package/src/api/generated/ext_support/schema.json +36 -0
  29. package/src/components/CreateTicketSheet.tsx +7 -12
  30. package/src/components/SupportHero.tsx +35 -47
  31. package/src/components/TicketItem.tsx +28 -74
  32. package/src/components/TicketList.tsx +30 -23
  33. package/src/components/TicketSheet.tsx +246 -162
  34. package/src/contexts/SupportExtensionProvider.tsx +4 -4
  35. package/src/contexts/types.ts +2 -2
  36. package/src/i18n/useSupportT.ts +3 -3
  37. package/src/utils/status.ts +44 -0
  38. package/src/utils/time.ts +88 -0
package/dist/index.js CHANGED
@@ -2,16 +2,16 @@ import { initializeExtensionAPI, createExtensionAPI } from '@djangocfg/ext-base/
2
2
  import { createConsola, consola } from 'consola';
3
3
  import pRetry, { AbortError } from 'p-retry';
4
4
  import { z } from 'zod';
5
- import React4, { createContext, useMemo, useCallback, useRef, useEffect, useState, useContext } from 'react';
5
+ import { createContext, useMemo, useCallback, useRef, useEffect, useState, useContext } from 'react';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
7
  import useSWRInfinite from 'swr/infinite';
8
8
  import { useAuth } from '@djangocfg/api/auth';
9
9
  import { useSWRConfig } from 'swr';
10
- import { LifeBuoy, Plus, RefreshCw, Clock, ChevronRight, MessageSquare, Loader2, Send, Headphones, User } from 'lucide-react';
10
+ import { RefreshCw, MessageSquare, Loader2, ArrowUp, Headphones, User } from 'lucide-react';
11
11
  import { useLocale } from 'next-intl';
12
- import { Skeleton, Button, Badge, useToast, ResponsiveSheet, ResponsiveSheetContent, ResponsiveSheetHeader, ResponsiveSheetTitle, ScrollArea, Textarea, ResponsiveSheetDescription, Form, FormField, FormItem, FormLabel, FormControl, Input, FormMessage, Avatar, AvatarImage, AvatarFallback, Card, CardContent } from '@djangocfg/ui-core';
12
+ import { Skeleton, Button, Empty, EmptyMedia, EmptyTitle, EmptyDescription, useToast, ResponsiveSheet, ResponsiveSheetContent, ResponsiveSheetHeader, ResponsiveSheetTitle, ScrollArea, ResponsiveSheetDescription, Form, FormField, FormItem, FormLabel, FormControl, Input, FormMessage, Textarea, Avatar, AvatarImage, AvatarFallback } from '@djangocfg/ui-core';
13
13
  import { cn } from '@djangocfg/ui-core/lib';
14
- import moment2 from 'moment';
14
+ import moment from 'moment';
15
15
  import { useForm } from 'react-hook-form';
16
16
  import { zodResolver } from '@hookform/resolvers/zod';
17
17
  import { createExtensionConfig } from '@djangocfg/ext-base';
@@ -33,7 +33,7 @@ var ExtSupportSupport = class {
33
33
  if (isParamsObject) {
34
34
  params = args[0];
35
35
  } else {
36
- params = { page: args[0], page_size: args[1] };
36
+ params = { ordering: args[0], page: args[1], page_size: args[2], search: args[3] };
37
37
  }
38
38
  const response = await this.client.request("GET", "/cfg/support/tickets/", { params });
39
39
  return response;
@@ -58,7 +58,7 @@ var ExtSupportSupport = class {
58
58
  if (isParamsObject) {
59
59
  params = args[1];
60
60
  } else {
61
- params = { page: args[1], page_size: args[2] };
61
+ params = { ordering: args[1], page: args[2], page_size: args[3], search: args[4] };
62
62
  }
63
63
  const response = await this.client.request("GET", `/cfg/support/tickets/${ticket_uuid}/messages/`, { params });
64
64
  return response;
@@ -749,22 +749,6 @@ var PatchedTicketRequestStatus = /* @__PURE__ */ ((PatchedTicketRequestStatus2)
749
749
  PatchedTicketRequestStatus2["CLOSED"] = "closed";
750
750
  return PatchedTicketRequestStatus2;
751
751
  })(PatchedTicketRequestStatus || {});
752
- var TicketStatus = /* @__PURE__ */ ((TicketStatus2) => {
753
- TicketStatus2["OPEN"] = "open";
754
- TicketStatus2["WAITING_FOR_USER"] = "waiting_for_user";
755
- TicketStatus2["WAITING_FOR_ADMIN"] = "waiting_for_admin";
756
- TicketStatus2["RESOLVED"] = "resolved";
757
- TicketStatus2["CLOSED"] = "closed";
758
- return TicketStatus2;
759
- })(TicketStatus || {});
760
- var TicketRequestStatus = /* @__PURE__ */ ((TicketRequestStatus2) => {
761
- TicketRequestStatus2["OPEN"] = "open";
762
- TicketRequestStatus2["WAITING_FOR_USER"] = "waiting_for_user";
763
- TicketRequestStatus2["WAITING_FOR_ADMIN"] = "waiting_for_admin";
764
- TicketRequestStatus2["RESOLVED"] = "resolved";
765
- TicketRequestStatus2["CLOSED"] = "closed";
766
- return TicketRequestStatus2;
767
- })(TicketRequestStatus || {});
768
752
  var SenderSchema = z.object({
769
753
  id: z.int(),
770
754
  display_username: z.string(),
@@ -808,7 +792,7 @@ var TicketSchema = z.object({
808
792
  uuid: z.string().regex(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i),
809
793
  user: z.int(),
810
794
  subject: z.string().max(255),
811
- status: z.nativeEnum(TicketStatus).optional(),
795
+ status: z.nativeEnum(PatchedTicketRequestStatus).optional(),
812
796
  created_at: z.string().datetime({ offset: true }),
813
797
  unanswered_messages_count: z.int()
814
798
  });
@@ -836,7 +820,7 @@ z.object({
836
820
  z.object({
837
821
  user: z.int(),
838
822
  subject: z.string().min(1).max(255),
839
- status: z.nativeEnum(TicketRequestStatus).optional()
823
+ status: z.nativeEnum(PatchedTicketRequestStatus).optional()
840
824
  });
841
825
 
842
826
  // src/api/generated/ext_support/api-instance.ts
@@ -872,7 +856,7 @@ function configureAPI(config) {
872
856
  // src/api/generated/ext_support/_utils/fetchers/ext_support__support.ts
873
857
  async function getSupportTicketsList(params, client) {
874
858
  const api = getAPIInstance();
875
- const response = await api.ext_support_support.ticketsList(params?.page, params?.page_size);
859
+ const response = await api.ext_support_support.ticketsList(params?.ordering, params?.page, params?.page_size, params?.search);
876
860
  try {
877
861
  return PaginatedTicketListSchema.parse(response);
878
862
  } catch (error) {
@@ -956,7 +940,7 @@ Method: POST`);
956
940
  }
957
941
  async function getSupportTicketsMessagesList(ticket_uuid, params, client) {
958
942
  const api = getAPIInstance();
959
- const response = await api.ext_support_support.ticketsMessagesList(ticket_uuid, params?.page, params?.page_size);
943
+ const response = await api.ext_support_support.ticketsMessagesList(ticket_uuid, params?.ordering, params?.page, params?.page_size, params?.search);
960
944
  try {
961
945
  return PaginatedMessageListSchema.parse(response);
962
946
  } catch (error) {
@@ -1711,78 +1695,6 @@ var en = {
1711
1695
  }
1712
1696
  };
1713
1697
 
1714
- // src/i18n/locales/ru.ts
1715
- var ru = {
1716
- layout: {
1717
- title: "\u0426\u0435\u043D\u0442\u0440 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438",
1718
- titleShort: "\u041F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0430",
1719
- 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",
1720
- newTicket: "\u041D\u043E\u0432\u043E\u0435 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435"
1721
- },
1722
- hero: {
1723
- openTickets: "\u041E\u0442\u043A\u0440\u044B\u0442\u044B\u0445 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439",
1724
- unreadMessages: "\u041D\u0435\u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u043D\u044B\u0445"
1725
- },
1726
- status: {
1727
- open: "\u041E\u0442\u043A\u0440\u044B\u0442",
1728
- waitingForUser: "\u041E\u0436\u0438\u0434\u0430\u0435\u0442 \u0432\u0430\u0441",
1729
- waitingForAdmin: "\u041E\u0436\u0438\u0434\u0430\u0435\u0442 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0443",
1730
- resolved: "\u0420\u0435\u0448\u0451\u043D",
1731
- closed: "\u0417\u0430\u043A\u0440\u044B\u0442"
1732
- },
1733
- ticketList: {
1734
- noTickets: "\u041D\u0435\u0442 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439",
1735
- 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",
1736
- loadingMore: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430...",
1737
- loadMore: "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0435\u0449\u0451",
1738
- allLoaded: "\u0412\u0441\u0435 {count} \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D\u043E"
1739
- },
1740
- createTicket: {
1741
- title: "\u041D\u043E\u0432\u043E\u0435 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435",
1742
- 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",
1743
- subjectLabel: "\u0422\u0435\u043C\u0430",
1744
- subjectPlaceholder: "\u041A\u0440\u0430\u0442\u043A\u043E\u0435 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 \u043F\u0440\u043E\u0431\u043B\u0435\u043C\u044B",
1745
- messageLabel: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435",
1746
- 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...",
1747
- cancel: "\u041E\u0442\u043C\u0435\u043D\u0430",
1748
- creating: "\u0421\u043E\u0437\u0434\u0430\u043D\u0438\u0435...",
1749
- create: "\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435"
1750
- },
1751
- validation: {
1752
- subjectRequired: "\u0422\u0435\u043C\u0430 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u0430",
1753
- 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)",
1754
- messageRequired: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E",
1755
- 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)"
1756
- },
1757
- messages: {
1758
- ticketCreated: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u0441\u043E\u0437\u0434\u0430\u043D\u043E",
1759
- 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",
1760
- messageSent: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E",
1761
- 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"
1762
- },
1763
- messageInput: {
1764
- placeholder: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435...",
1765
- ticketClosed: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u043E",
1766
- 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."
1767
- },
1768
- messageList: {
1769
- noTicketSelected: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u043D\u0435 \u0432\u044B\u0431\u0440\u0430\u043D\u043E",
1770
- 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",
1771
- noMessages: "\u041D\u0435\u0442 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439",
1772
- 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",
1773
- loadingOlder: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u0441\u0442\u0430\u0440\u044B\u0445 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439...",
1774
- loadOlder: "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0441\u0442\u0430\u0440\u044B\u0435",
1775
- supportTeam: "\u0421\u043B\u0443\u0436\u0431\u0430 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438",
1776
- staff: "\u0421\u043E\u0442\u0440\u0443\u0434\u043D\u0438\u043A"
1777
- },
1778
- time: {
1779
- justNow: "\u0422\u043E\u043B\u044C\u043A\u043E \u0447\u0442\u043E",
1780
- minutesAgo: "{count} \u043C\u0438\u043D \u043D\u0430\u0437\u0430\u0434",
1781
- hoursAgo: "{count}\u0447 \u043D\u0430\u0437\u0430\u0434",
1782
- daysAgo: "{count}\u0434 \u043D\u0430\u0437\u0430\u0434"
1783
- }
1784
- };
1785
-
1786
1698
  // src/i18n/locales/ko.ts
1787
1699
  var ko = {
1788
1700
  layout: {
@@ -1855,6 +1767,78 @@ var ko = {
1855
1767
  }
1856
1768
  };
1857
1769
 
1770
+ // src/i18n/locales/ru.ts
1771
+ var ru = {
1772
+ layout: {
1773
+ title: "\u0426\u0435\u043D\u0442\u0440 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438",
1774
+ titleShort: "\u041F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0430",
1775
+ 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",
1776
+ newTicket: "\u041D\u043E\u0432\u043E\u0435 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435"
1777
+ },
1778
+ hero: {
1779
+ openTickets: "\u041E\u0442\u043A\u0440\u044B\u0442\u044B\u0445 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439",
1780
+ unreadMessages: "\u041D\u0435\u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u043D\u044B\u0445"
1781
+ },
1782
+ status: {
1783
+ open: "\u041E\u0442\u043A\u0440\u044B\u0442",
1784
+ waitingForUser: "\u041E\u0436\u0438\u0434\u0430\u0435\u0442 \u0432\u0430\u0441",
1785
+ waitingForAdmin: "\u041E\u0436\u0438\u0434\u0430\u0435\u0442 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0443",
1786
+ resolved: "\u0420\u0435\u0448\u0451\u043D",
1787
+ closed: "\u0417\u0430\u043A\u0440\u044B\u0442"
1788
+ },
1789
+ ticketList: {
1790
+ noTickets: "\u041D\u0435\u0442 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439",
1791
+ 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",
1792
+ loadingMore: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430...",
1793
+ loadMore: "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0435\u0449\u0451",
1794
+ allLoaded: "\u0412\u0441\u0435 {count} \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D\u043E"
1795
+ },
1796
+ createTicket: {
1797
+ title: "\u041D\u043E\u0432\u043E\u0435 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435",
1798
+ 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",
1799
+ subjectLabel: "\u0422\u0435\u043C\u0430",
1800
+ subjectPlaceholder: "\u041A\u0440\u0430\u0442\u043A\u043E\u0435 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 \u043F\u0440\u043E\u0431\u043B\u0435\u043C\u044B",
1801
+ messageLabel: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435",
1802
+ 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...",
1803
+ cancel: "\u041E\u0442\u043C\u0435\u043D\u0430",
1804
+ creating: "\u0421\u043E\u0437\u0434\u0430\u043D\u0438\u0435...",
1805
+ create: "\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435"
1806
+ },
1807
+ validation: {
1808
+ subjectRequired: "\u0422\u0435\u043C\u0430 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u0430",
1809
+ 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)",
1810
+ messageRequired: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E",
1811
+ 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)"
1812
+ },
1813
+ messages: {
1814
+ ticketCreated: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u0441\u043E\u0437\u0434\u0430\u043D\u043E",
1815
+ 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",
1816
+ messageSent: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E",
1817
+ 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"
1818
+ },
1819
+ messageInput: {
1820
+ placeholder: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435...",
1821
+ ticketClosed: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u043E",
1822
+ 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."
1823
+ },
1824
+ messageList: {
1825
+ noTicketSelected: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u043D\u0435 \u0432\u044B\u0431\u0440\u0430\u043D\u043E",
1826
+ 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",
1827
+ noMessages: "\u041D\u0435\u0442 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439",
1828
+ 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",
1829
+ loadingOlder: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u0441\u0442\u0430\u0440\u044B\u0445 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439...",
1830
+ loadOlder: "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0441\u0442\u0430\u0440\u044B\u0435",
1831
+ supportTeam: "\u0421\u043B\u0443\u0436\u0431\u0430 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438",
1832
+ staff: "\u0421\u043E\u0442\u0440\u0443\u0434\u043D\u0438\u043A"
1833
+ },
1834
+ time: {
1835
+ justNow: "\u0422\u043E\u043B\u044C\u043A\u043E \u0447\u0442\u043E",
1836
+ minutesAgo: "{count} \u043C\u0438\u043D \u043D\u0430\u0437\u0430\u0434",
1837
+ hoursAgo: "{count}\u0447 \u043D\u0430\u0437\u0430\u0434",
1838
+ daysAgo: "{count}\u0434 \u043D\u0430\u0437\u0430\u0434"
1839
+ }
1840
+ };
1841
+
1858
1842
  // src/i18n/useSupportT.ts
1859
1843
  var translations = { en, ru, ko };
1860
1844
  function getNestedValue(obj, path) {
@@ -1888,27 +1872,22 @@ function SupportHero({ onCreateTicket, className }) {
1888
1872
  unreadMessages: st("hero.unreadMessages")
1889
1873
  }), [st]);
1890
1874
  const openTicketsCount = tickets.filter((t) => t.status !== "closed" && t.status !== "resolved").length;
1891
- return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-center py-16 px-4", className), children: [
1892
- /* @__PURE__ */ jsx("div", { className: "text-center mb-6", children: isLoadingTickets ? /* @__PURE__ */ jsxs(Fragment, { children: [
1893
- /* @__PURE__ */ jsx(Skeleton, { className: "h-12 w-12 mx-auto mb-4 rounded-full" }),
1894
- /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-32 mx-auto mb-2" }),
1895
- /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-48 mx-auto" })
1896
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1897
- /* @__PURE__ */ jsx("div", { className: "h-16 w-16 mx-auto mb-4 bg-primary/10 rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsx(LifeBuoy, { className: "h-8 w-8 text-primary" }) }),
1898
- /* @__PURE__ */ jsx("h1", { className: "text-3xl font-bold tracking-tight", children: labels.title }),
1899
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mt-1", children: labels.subtitle })
1900
- ] }) }),
1901
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1902
- /* @__PURE__ */ jsxs(
1875
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-center py-12 px-4", className), children: [
1876
+ isLoadingTickets ? /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
1877
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-10 w-48 mx-auto mb-2" }),
1878
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-64 mx-auto" })
1879
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
1880
+ /* @__PURE__ */ jsx("h1", { className: "text-4xl font-bold tracking-tight", children: labels.title }),
1881
+ /* @__PURE__ */ jsx("p", { className: "text-lg text-muted-foreground mt-2", children: labels.subtitle })
1882
+ ] }),
1883
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mt-6", children: [
1884
+ /* @__PURE__ */ jsx(
1903
1885
  Button,
1904
1886
  {
1905
1887
  size: "lg",
1906
1888
  onClick: onCreateTicket,
1907
- className: "rounded-full px-6",
1908
- children: [
1909
- /* @__PURE__ */ jsx(Plus, { className: "h-5 w-5 mr-2" }),
1910
- labels.newTicket
1911
- ]
1889
+ className: "rounded-full px-8 h-12 text-base",
1890
+ children: labels.newTicket
1912
1891
  }
1913
1892
  ),
1914
1893
  /* @__PURE__ */ jsx(
@@ -1917,43 +1896,100 @@ function SupportHero({ onCreateTicket, className }) {
1917
1896
  size: "icon",
1918
1897
  variant: "ghost",
1919
1898
  onClick: () => refreshTickets(),
1920
- className: "rounded-full",
1899
+ className: "rounded-full h-10 w-10",
1921
1900
  children: /* @__PURE__ */ jsx(RefreshCw, { className: "h-4 w-4" })
1922
1901
  }
1923
1902
  )
1924
1903
  ] }),
1925
- !isLoadingTickets && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-6 mt-6 text-sm text-muted-foreground", children: [
1926
- /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
1927
- /* @__PURE__ */ jsx("span", { className: "block font-medium text-foreground text-xl tabular-nums", children: openTicketsCount }),
1928
- /* @__PURE__ */ jsx("span", { children: labels.openTickets })
1904
+ !isLoadingTickets && (openTicketsCount > 0 || unreadCount > 0) && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-4", children: [
1905
+ openTicketsCount > 0 && /* @__PURE__ */ jsxs("span", { className: "text-sm text-muted-foreground bg-muted px-3 py-1 rounded-full", children: [
1906
+ openTicketsCount,
1907
+ " ",
1908
+ labels.openTickets.toLowerCase()
1929
1909
  ] }),
1930
- /* @__PURE__ */ jsx("div", { className: "h-8 w-px bg-border" }),
1931
- /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
1932
- /* @__PURE__ */ jsx("span", { className: cn(
1933
- "block font-medium text-xl tabular-nums",
1934
- unreadCount > 0 ? "text-destructive" : "text-foreground"
1935
- ), children: unreadCount }),
1936
- /* @__PURE__ */ jsx("span", { children: labels.unreadMessages })
1910
+ unreadCount > 0 && /* @__PURE__ */ jsxs("span", { className: "text-sm text-destructive bg-destructive/10 px-3 py-1 rounded-full", children: [
1911
+ unreadCount,
1912
+ " ",
1913
+ labels.unreadMessages.toLowerCase()
1937
1914
  ] })
1938
1915
  ] })
1939
1916
  ] });
1940
1917
  }
1941
- var getStatusBadgeVariant = (status) => {
1942
- switch (status) {
1943
- case "open":
1944
- return "default";
1945
- case "waiting_for_user":
1946
- return "secondary";
1947
- case "waiting_for_admin":
1948
- return "outline";
1949
- case "resolved":
1950
- return "outline";
1951
- case "closed":
1952
- return "secondary";
1953
- default:
1954
- return "default";
1918
+
1919
+ // src/utils/status.ts
1920
+ var ticketStatusConfig = {
1921
+ open: {
1922
+ color: "text-blue-600 dark:text-blue-400",
1923
+ bg: "bg-blue-500/10"
1924
+ },
1925
+ waiting_for_user: {
1926
+ color: "text-orange-600 dark:text-orange-400",
1927
+ bg: "bg-orange-500/10"
1928
+ },
1929
+ waiting_for_admin: {
1930
+ color: "text-muted-foreground",
1931
+ bg: "bg-muted"
1932
+ },
1933
+ resolved: {
1934
+ color: "text-green-600 dark:text-green-400",
1935
+ bg: "bg-green-500/10"
1936
+ },
1937
+ closed: {
1938
+ color: "text-muted-foreground",
1939
+ bg: "bg-muted"
1955
1940
  }
1956
1941
  };
1942
+ function getStatusConfig(status) {
1943
+ return ticketStatusConfig[status] || ticketStatusConfig.open;
1944
+ }
1945
+ function formatMessageTime(date) {
1946
+ if (!date) return "";
1947
+ return moment.utc(date).local().format("h:mm A");
1948
+ }
1949
+ function formatDateSeparator(date) {
1950
+ if (!date) return "";
1951
+ const m = moment.utc(date).local();
1952
+ const now = moment();
1953
+ const today = now.clone().startOf("day");
1954
+ const yesterday = now.clone().subtract(1, "day").startOf("day");
1955
+ if (m.isSame(today, "day")) {
1956
+ return "Today";
1957
+ }
1958
+ if (m.isSame(yesterday, "day")) {
1959
+ return "Yesterday";
1960
+ }
1961
+ if (m.isSame(now, "year")) {
1962
+ return m.format("MMM D");
1963
+ }
1964
+ return m.format("MMM D, YYYY");
1965
+ }
1966
+ function formatRelativeTime(date, labels) {
1967
+ if (!date) return "N/A";
1968
+ const m = moment.utc(date).local();
1969
+ const now = moment();
1970
+ const diffInSeconds = now.diff(m, "seconds");
1971
+ if (diffInSeconds < 60) {
1972
+ return labels.justNow;
1973
+ }
1974
+ if (diffInSeconds < 3600) {
1975
+ return labels.minutesAgo.replace("{count}", String(Math.floor(diffInSeconds / 60)));
1976
+ }
1977
+ if (diffInSeconds < 86400) {
1978
+ return labels.hoursAgo.replace("{count}", String(Math.floor(diffInSeconds / 3600)));
1979
+ }
1980
+ if (diffInSeconds < 604800) {
1981
+ return labels.daysAgo.replace("{count}", String(Math.floor(diffInSeconds / 86400)));
1982
+ }
1983
+ return m.format("MMM D, YYYY");
1984
+ }
1985
+ function isSameDay(date1, date2) {
1986
+ if (!date1 || !date2) return false;
1987
+ return moment.utc(date1).format("YYYY-MM-DD") === moment.utc(date2).format("YYYY-MM-DD");
1988
+ }
1989
+ function formatTicketDate(date) {
1990
+ if (!date) return "";
1991
+ return moment.utc(date).local().format("MMM D, YYYY");
1992
+ }
1957
1993
  function TicketItem({ ticket, onClick }) {
1958
1994
  const st = useSupportT();
1959
1995
  const labels = useMemo(() => ({
@@ -1971,49 +2007,37 @@ function TicketItem({ ticket, onClick }) {
1971
2007
  daysAgo: st("time.daysAgo")
1972
2008
  }
1973
2009
  }), [st]);
1974
- const formatRelativeTime = useCallback((date) => {
1975
- if (!date) return "N/A";
1976
- const m = moment2.utc(date).local();
1977
- const now = moment2();
1978
- const diffInSeconds = now.diff(m, "seconds");
1979
- if (diffInSeconds < 60) return labels.time.justNow;
1980
- if (diffInSeconds < 3600) return labels.time.minutesAgo.replace("{count}", String(Math.floor(diffInSeconds / 60)));
1981
- if (diffInSeconds < 86400) return labels.time.hoursAgo.replace("{count}", String(Math.floor(diffInSeconds / 3600)));
1982
- if (diffInSeconds < 604800) return labels.time.daysAgo.replace("{count}", String(Math.floor(diffInSeconds / 86400)));
1983
- return m.format("MMM D, YYYY");
2010
+ const getRelativeTime = useCallback((date) => {
2011
+ return formatRelativeTime(date, labels.time);
1984
2012
  }, [labels.time]);
1985
2013
  const statusLabel = labels.status[ticket.status] || ticket.status || labels.status.open;
2014
+ const statusConfig = getStatusConfig(ticket.status || "open");
1986
2015
  const hasUnread = (ticket.unanswered_messages_count || 0) > 0;
1987
2016
  return /* @__PURE__ */ jsxs(
1988
- "div",
2017
+ "button",
1989
2018
  {
2019
+ type: "button",
1990
2020
  className: cn(
1991
- "group flex items-center gap-4 p-4 rounded-xl",
1992
- "cursor-pointer transition-all duration-200",
1993
- "hover:bg-accent/50",
1994
- "active:scale-[0.98]",
1995
- hasUnread && "bg-primary/5"
2021
+ "w-full flex items-start gap-3 p-4 text-left",
2022
+ "transition-colors duration-150",
2023
+ "hover:bg-accent/50 active:bg-accent"
1996
2024
  ),
1997
2025
  onClick,
1998
2026
  children: [
1999
- /* @__PURE__ */ jsx("div", { className: "w-2 flex-shrink-0", children: hasUnread && /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-primary animate-pulse" }) }),
2027
+ /* @__PURE__ */ jsx("div", { className: "w-2 pt-1.5 shrink-0", children: hasUnread && /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-blue-500" }) }),
2000
2028
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
2001
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
2002
- /* @__PURE__ */ jsx("h3", { className: cn(
2003
- "font-medium text-sm line-clamp-1",
2004
- hasUnread && "font-semibold"
2005
- ), children: ticket.subject }),
2006
- hasUnread && /* @__PURE__ */ jsx(Badge, { variant: "destructive", className: "shrink-0 text-xs", children: ticket.unanswered_messages_count })
2007
- ] }),
2008
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mt-1 text-xs text-muted-foreground", children: [
2009
- /* @__PURE__ */ jsx(Badge, { variant: getStatusBadgeVariant(ticket.status || "open"), className: "text-xs py-0", children: statusLabel }),
2010
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
2011
- /* @__PURE__ */ jsx(Clock, { className: "h-3 w-3" }),
2012
- /* @__PURE__ */ jsx("span", { children: formatRelativeTime(ticket.created_at) })
2029
+ /* @__PURE__ */ jsx("h3", { className: cn(
2030
+ "text-[15px] line-clamp-1",
2031
+ hasUnread ? "font-semibold" : "font-medium"
2032
+ ), children: ticket.subject }),
2033
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-0.5", children: [
2034
+ /* @__PURE__ */ jsx("span", { className: cn("text-[13px]", statusConfig.color), children: statusLabel }),
2035
+ /* @__PURE__ */ jsxs("span", { className: "text-[13px] text-muted-foreground", children: [
2036
+ "\xB7 ",
2037
+ getRelativeTime(ticket.created_at)
2013
2038
  ] })
2014
2039
  ] })
2015
- ] }),
2016
- /* @__PURE__ */ jsx(ChevronRight, { className: "h-5 w-5 text-muted-foreground/50 group-hover:text-muted-foreground transition-colors flex-shrink-0" })
2040
+ ] })
2017
2041
  ]
2018
2042
  }
2019
2043
  );
@@ -2058,24 +2082,23 @@ function TicketList({ onTicketClick, className }) {
2058
2082
  };
2059
2083
  }, [hasMoreTickets, isLoadingMoreTickets, loadMoreTickets]);
2060
2084
  if (isLoadingTickets) {
2061
- return /* @__PURE__ */ jsx("div", { className: cn("px-4 py-6 space-y-3", className), children: [1, 2, 3, 4, 5].map((i) => /* @__PURE__ */ jsx(
2085
+ return /* @__PURE__ */ jsx("div", { className: cn("px-4 py-6", className), children: /* @__PURE__ */ jsx("div", { className: "divide-y divide-border/50", children: [1, 2, 3, 4, 5].map((i) => /* @__PURE__ */ jsx("div", { className: "py-4", children: /* @__PURE__ */ jsx(
2062
2086
  Skeleton,
2063
2087
  {
2064
- className: "h-20 w-full",
2088
+ className: "h-12 w-full",
2065
2089
  style: { animationDelay: `${i * 100}ms` }
2066
- },
2067
- i
2068
- )) });
2090
+ }
2091
+ ) }, i)) }) });
2069
2092
  }
2070
2093
  if (!tickets || tickets.length === 0) {
2071
- return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-center justify-center py-16 px-8 text-center", className), children: [
2072
- /* @__PURE__ */ jsx("div", { className: "h-16 w-16 mx-auto mb-4 bg-muted rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsx(MessageSquare, { className: "h-8 w-8 text-muted-foreground" }) }),
2073
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold mb-2", children: labels.noTickets }),
2074
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground max-w-sm", children: labels.noTicketsDescription })
2094
+ return /* @__PURE__ */ jsxs(Empty, { className: cn("py-16", className), children: [
2095
+ /* @__PURE__ */ jsx(EmptyMedia, { variant: "icon", children: /* @__PURE__ */ jsx(MessageSquare, { className: "h-8 w-8" }) }),
2096
+ /* @__PURE__ */ jsx(EmptyTitle, { children: labels.noTickets }),
2097
+ /* @__PURE__ */ jsx(EmptyDescription, { children: labels.noTicketsDescription })
2075
2098
  ] });
2076
2099
  }
2077
- return /* @__PURE__ */ jsxs("div", { className: cn("px-4 py-6", className), children: [
2078
- /* @__PURE__ */ jsx("div", { className: "space-y-2", children: tickets.map((ticket) => /* @__PURE__ */ jsx(
2100
+ return /* @__PURE__ */ jsxs("div", { className: cn("px-4 py-2", className), children: [
2101
+ /* @__PURE__ */ jsx("div", { className: "divide-y divide-border/50", children: tickets.map((ticket) => /* @__PURE__ */ jsx(
2079
2102
  TicketItem,
2080
2103
  {
2081
2104
  ticket,
@@ -2105,49 +2128,70 @@ function TicketList({ onTicketClick, className }) {
2105
2128
  ) })
2106
2129
  ] });
2107
2130
  }
2108
- var formatTime = (date) => {
2109
- if (!date) return "";
2110
- return moment2.utc(date).local().format("hh:mm A");
2111
- };
2112
- var formatDate = (date) => {
2113
- if (!date) return "";
2114
- return moment2.utc(date).local().format("MMM D, YYYY");
2115
- };
2116
- var getStatusBadgeVariant2 = (status) => {
2117
- switch (status) {
2118
- case "open":
2119
- return "default";
2120
- case "waiting_for_user":
2121
- return "secondary";
2122
- case "waiting_for_admin":
2123
- return "outline";
2124
- case "resolved":
2125
- return "outline";
2126
- case "closed":
2127
- return "secondary";
2128
- default:
2129
- return "default";
2131
+ function groupMessages(messages, currentUserId) {
2132
+ const groups = [];
2133
+ let currentGroup = null;
2134
+ for (const msg of messages) {
2135
+ const isFromUser = msg.sender?.id && currentUserId && String(msg.sender.id) === String(currentUserId) || msg.is_from_author;
2136
+ const senderId = msg.sender?.id || null;
2137
+ const shouldStartNewGroup = !currentGroup || currentGroup.isFromUser !== isFromUser || currentGroup.senderId !== senderId;
2138
+ if (shouldStartNewGroup) {
2139
+ currentGroup = {
2140
+ senderId,
2141
+ isFromUser: !!isFromUser,
2142
+ messages: [msg]
2143
+ };
2144
+ groups.push(currentGroup);
2145
+ } else {
2146
+ currentGroup.messages.push(msg);
2147
+ }
2130
2148
  }
2131
- };
2132
- var MessageBubble = ({ message, isFromUser, currentUser, labels }) => {
2149
+ return groups;
2150
+ }
2151
+ var DateSeparator = ({ date }) => /* @__PURE__ */ jsx("div", { className: "flex justify-center my-4", children: /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground bg-muted/60 px-3 py-1 rounded-full", children: formatDateSeparator(date) }) });
2152
+ var MessageBubble = ({
2153
+ message,
2154
+ isFromUser,
2155
+ showAvatar,
2156
+ showTimestamp,
2157
+ currentUser,
2158
+ labels
2159
+ }) => {
2133
2160
  const sender = message.sender;
2134
2161
  const senderInitial = sender?.display_username?.charAt(0)?.toUpperCase() || sender?.initials || "S";
2135
2162
  const userInitial = currentUser?.display_username?.charAt(0)?.toUpperCase() || currentUser?.email?.charAt(0)?.toUpperCase() || currentUser?.initials || null;
2136
- return /* @__PURE__ */ jsxs("div", { className: cn("flex gap-3", isFromUser ? "justify-end" : "justify-start"), children: [
2137
- !isFromUser && /* @__PURE__ */ jsx(Avatar, { className: "h-8 w-8 shrink-0", children: sender?.avatar ? /* @__PURE__ */ jsx(AvatarImage, { src: sender.avatar, alt: sender.display_username || labels.supportTeam }) : /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-primary text-primary-foreground", children: sender?.is_staff ? /* @__PURE__ */ jsx(Headphones, { className: "h-4 w-4" }) : senderInitial }) }),
2138
- /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-1 flex-1 max-w-[80%]", isFromUser ? "items-end" : "items-start"), children: [
2139
- !isFromUser && sender && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground px-1", children: [
2140
- sender.display_username || sender.email || labels.supportTeam,
2141
- sender.is_staff && ` (${labels.staff})`
2142
- ] }),
2143
- /* @__PURE__ */ jsx(Card, { className: cn(
2144
- isFromUser ? "bg-primary text-primary-foreground" : "bg-muted"
2145
- ), children: /* @__PURE__ */ jsx(CardContent, { className: "p-3", children: /* @__PURE__ */ jsx("p", { className: "text-sm whitespace-pre-wrap break-words", children: message.text }) }) }),
2146
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground px-1", children: formatTime(message.created_at) })
2163
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex gap-2", isFromUser ? "justify-end" : "justify-start"), children: [
2164
+ !isFromUser && /* @__PURE__ */ jsx("div", { className: "w-8 shrink-0", children: showAvatar && /* @__PURE__ */ jsx(Avatar, { className: "h-8 w-8", children: sender?.avatar ? /* @__PURE__ */ jsx(AvatarImage, { src: sender.avatar, alt: sender.display_username || labels.supportTeam }) : /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-primary text-primary-foreground text-xs", children: sender?.is_staff ? /* @__PURE__ */ jsx(Headphones, { className: "h-4 w-4" }) : senderInitial }) }) }),
2165
+ /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-0.5 max-w-[85%]", isFromUser ? "items-end" : "items-start"), children: [
2166
+ /* @__PURE__ */ jsx(
2167
+ "div",
2168
+ {
2169
+ className: cn(
2170
+ "px-4 py-2.5 rounded-[20px]",
2171
+ isFromUser ? "bg-blue-500 text-white" : "bg-[#e9e9eb] dark:bg-zinc-800 text-foreground"
2172
+ ),
2173
+ children: /* @__PURE__ */ jsx("p", { className: "text-[15px] leading-relaxed whitespace-pre-wrap break-words", children: message.text })
2174
+ }
2175
+ ),
2176
+ showTimestamp && /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground px-2 mt-0.5", children: formatMessageTime(message.created_at) })
2147
2177
  ] }),
2148
- isFromUser && /* @__PURE__ */ jsx(Avatar, { className: "h-8 w-8 shrink-0", children: currentUser?.avatar ? /* @__PURE__ */ jsx(AvatarImage, { src: currentUser.avatar, alt: currentUser.display_username || currentUser.email || "You" }) : /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-primary/10 text-primary font-semibold", children: userInitial || /* @__PURE__ */ jsx(User, { className: "h-4 w-4" }) }) })
2178
+ isFromUser && /* @__PURE__ */ jsx("div", { className: "w-8 shrink-0", children: showAvatar && /* @__PURE__ */ jsx(Avatar, { className: "h-8 w-8", children: currentUser?.avatar ? /* @__PURE__ */ jsx(AvatarImage, { src: currentUser.avatar, alt: currentUser.display_username || currentUser.email || "You" }) : /* @__PURE__ */ jsx(AvatarFallback, { className: "bg-blue-500 text-white text-xs font-medium", children: userInitial || /* @__PURE__ */ jsx(User, { className: "h-4 w-4" }) }) }) })
2149
2179
  ] });
2150
2180
  };
2181
+ var MessageGroupComponent = ({ group, currentUser, labels }) => {
2182
+ return /* @__PURE__ */ jsx("div", { className: "space-y-1", children: group.messages.map((msg, idx) => /* @__PURE__ */ jsx(
2183
+ MessageBubble,
2184
+ {
2185
+ message: msg,
2186
+ isFromUser: group.isFromUser,
2187
+ showAvatar: idx === 0,
2188
+ showTimestamp: idx === group.messages.length - 1,
2189
+ currentUser,
2190
+ labels
2191
+ },
2192
+ msg.uuid
2193
+ )) });
2194
+ };
2151
2195
  function TicketSheet({ ticket, open, onOpenChange }) {
2152
2196
  const st = useSupportT();
2153
2197
  const { user } = useAuth();
@@ -2156,6 +2200,7 @@ function TicketSheet({ ticket, open, onOpenChange }) {
2156
2200
  const [message, setMessage] = useState("");
2157
2201
  const [isSending, setIsSending] = useState(false);
2158
2202
  const scrollAreaRef = useRef(null);
2203
+ const textareaRef = useRef(null);
2159
2204
  const firstRender = useRef(true);
2160
2205
  const labels = useMemo(() => ({
2161
2206
  noMessages: st("messageList.noMessages"),
@@ -2187,6 +2232,7 @@ function TicketSheet({ ticket, open, onOpenChange }) {
2187
2232
  refresh: refreshMessages,
2188
2233
  addOptimistic
2189
2234
  } = useMessages(ticket?.uuid || null);
2235
+ const messageGroups = useMemo(() => groupMessages(messages, user?.id), [messages, user?.id]);
2190
2236
  useEffect(() => {
2191
2237
  firstRender.current = true;
2192
2238
  }, [ticket?.uuid]);
@@ -2199,6 +2245,14 @@ function TicketSheet({ ticket, open, onOpenChange }) {
2199
2245
  firstRender.current = false;
2200
2246
  }
2201
2247
  }, [messages]);
2248
+ const handleTextareaChange = useCallback((e) => {
2249
+ setMessage(e.target.value);
2250
+ const textarea = textareaRef.current;
2251
+ if (textarea) {
2252
+ textarea.style.height = "auto";
2253
+ textarea.style.height = `${Math.min(textarea.scrollHeight, 120)}px`;
2254
+ }
2255
+ }, []);
2202
2256
  const handleSend = async (e) => {
2203
2257
  e.preventDefault();
2204
2258
  if (!message.trim() || !ticket?.uuid) return;
@@ -2224,6 +2278,9 @@ function TicketSheet({ ticket, open, onOpenChange }) {
2224
2278
  addOptimistic(optimisticMessage);
2225
2279
  await sendMessage({ ticketUuid: ticket.uuid, text: messageText });
2226
2280
  setMessage("");
2281
+ if (textareaRef.current) {
2282
+ textareaRef.current.style.height = "auto";
2283
+ }
2227
2284
  await refreshMessages();
2228
2285
  const scrollContainer = scrollAreaRef.current?.querySelector("[data-radix-scroll-area-viewport]");
2229
2286
  if (scrollContainer) {
@@ -2257,32 +2314,61 @@ function TicketSheet({ ticket, open, onOpenChange }) {
2257
2314
  }, [loadMore]);
2258
2315
  const canSendMessage = ticket?.status !== "closed";
2259
2316
  const statusLabel = ticket ? labels.status[ticket.status] || ticket.status || labels.status.open : "";
2317
+ const statusConfig = getStatusConfig(ticket?.status || "open");
2318
+ const renderMessages = () => {
2319
+ const elements = [];
2320
+ let lastDate = null;
2321
+ for (const group of messageGroups) {
2322
+ const firstMessage = group.messages[0];
2323
+ if (!firstMessage) continue;
2324
+ const messageDate = firstMessage.created_at;
2325
+ if (!lastDate || !isSameDay(lastDate, messageDate)) {
2326
+ elements.push(
2327
+ /* @__PURE__ */ jsx(DateSeparator, { date: messageDate || "" }, `date-${messageDate}`)
2328
+ );
2329
+ lastDate = messageDate || null;
2330
+ }
2331
+ elements.push(
2332
+ /* @__PURE__ */ jsx(
2333
+ MessageGroupComponent,
2334
+ {
2335
+ group,
2336
+ currentUser: user,
2337
+ labels: { supportTeam: labels.supportTeam, staff: labels.staff }
2338
+ },
2339
+ `group-${firstMessage.uuid}`
2340
+ )
2341
+ );
2342
+ }
2343
+ return elements;
2344
+ };
2260
2345
  return /* @__PURE__ */ jsx(ResponsiveSheet, { open, onOpenChange, children: /* @__PURE__ */ jsxs(ResponsiveSheetContent, { className: "sm:max-w-xl p-0 flex flex-col h-full", children: [
2261
2346
  /* @__PURE__ */ jsx(ResponsiveSheetHeader, { className: "px-6 py-4 border-b flex-shrink-0", children: /* @__PURE__ */ jsx("div", { className: "flex items-start justify-between gap-4", children: /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
2262
2347
  /* @__PURE__ */ jsx(ResponsiveSheetTitle, { className: "text-lg font-semibold line-clamp-2", children: ticket?.subject || "Ticket" }),
2263
2348
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-1", children: [
2264
- /* @__PURE__ */ jsx(Badge, { variant: getStatusBadgeVariant2(ticket?.status || "open"), className: "text-xs", children: statusLabel }),
2265
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: formatDate(ticket?.created_at) })
2349
+ /* @__PURE__ */ jsx("span", { className: cn("text-[13px] px-2 py-0.5 rounded-full", statusConfig.bg, statusConfig.color), children: statusLabel }),
2350
+ /* @__PURE__ */ jsx("span", { className: "text-[13px] text-muted-foreground", children: formatTicketDate(ticket?.created_at) })
2266
2351
  ] })
2267
2352
  ] }) }) }),
2268
- /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 overflow-hidden", children: isLoadingMessages ? /* @__PURE__ */ jsx("div", { className: "p-6 space-y-4", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
2269
- /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-8 rounded-full" }),
2270
- /* @__PURE__ */ jsx(Skeleton, { className: "h-16 flex-1 max-w-[70%]" })
2353
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 overflow-hidden", children: isLoadingMessages ? /* @__PURE__ */ jsx("div", { className: "p-6 space-y-4", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsxs("div", { className: cn("flex gap-2", i % 2 === 0 ? "justify-end" : "justify-start"), children: [
2354
+ i % 2 !== 0 && /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-8 rounded-full shrink-0" }),
2355
+ /* @__PURE__ */ jsx(Skeleton, { className: cn("h-16 rounded-[20px]", i % 2 === 0 ? "w-[60%]" : "w-[70%]") }),
2356
+ i % 2 === 0 && /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-8 rounded-full shrink-0" })
2271
2357
  ] }, i)) }) : messages.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center h-full p-8 text-center", children: [
2272
- /* @__PURE__ */ jsx(MessageSquare, { className: "h-12 w-12 text-muted-foreground mb-4" }),
2358
+ /* @__PURE__ */ jsx("div", { className: "h-16 w-16 mx-auto mb-4 bg-muted rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsx(MessageSquare, { className: "h-8 w-8 text-muted-foreground" }) }),
2273
2359
  /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold mb-2", children: labels.noMessages }),
2274
2360
  /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground max-w-sm", children: labels.noMessagesDescription })
2275
- ] }) : /* @__PURE__ */ jsx(ScrollArea, { className: "h-full", viewportRef: scrollAreaRef, children: /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-4", children: [
2361
+ ] }) : /* @__PURE__ */ jsx(ScrollArea, { className: "h-full", viewportRef: scrollAreaRef, children: /* @__PURE__ */ jsxs("div", { className: "p-4 space-y-3", children: [
2276
2362
  hasMore && /* @__PURE__ */ jsx("div", { className: "flex justify-center pb-4", children: isLoadingMore ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-muted-foreground", children: [
2277
2363
  /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }),
2278
2364
  /* @__PURE__ */ jsx("span", { className: "text-sm", children: labels.loadingOlder })
2279
2365
  ] }) : /* @__PURE__ */ jsxs(
2280
2366
  Button,
2281
2367
  {
2282
- variant: "outline",
2368
+ variant: "ghost",
2283
2369
  size: "sm",
2284
2370
  onClick: handleLoadMore,
2285
- className: "text-xs",
2371
+ className: "text-xs text-muted-foreground",
2286
2372
  children: [
2287
2373
  labels.loadOlder,
2288
2374
  " (",
@@ -2291,59 +2377,44 @@ function TicketSheet({ ticket, open, onOpenChange }) {
2291
2377
  ]
2292
2378
  }
2293
2379
  ) }),
2294
- messages.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
2295
- /* @__PURE__ */ jsx("div", { className: "flex-1 h-px bg-border" }),
2296
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: formatDate(messages[0]?.created_at) }),
2297
- /* @__PURE__ */ jsx("div", { className: "flex-1 h-px bg-border" })
2298
- ] }),
2299
- messages.map((msg, index) => {
2300
- 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);
2301
- const previousMessage = index > 0 ? messages[index - 1] : null;
2302
- const showDateSeparator = previousMessage && moment2.utc(previousMessage.created_at || "").format("YYYY-MM-DD") !== moment2.utc(msg.created_at || "").format("YYYY-MM-DD");
2303
- return /* @__PURE__ */ jsxs(React4.Fragment, { children: [
2304
- showDateSeparator && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 my-4", children: [
2305
- /* @__PURE__ */ jsx("div", { className: "flex-1 h-px bg-border" }),
2306
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: formatDate(msg.created_at) }),
2307
- /* @__PURE__ */ jsx("div", { className: "flex-1 h-px bg-border" })
2308
- ] }),
2309
- /* @__PURE__ */ jsx(
2310
- MessageBubble,
2311
- {
2312
- message: msg,
2313
- isFromUser: !!isFromUser,
2314
- currentUser: user,
2315
- labels: { supportTeam: labels.supportTeam, staff: labels.staff }
2316
- }
2317
- )
2318
- ] }, msg.uuid);
2319
- })
2380
+ renderMessages()
2320
2381
  ] }) }) }),
2321
- /* @__PURE__ */ jsxs("form", { onSubmit: handleSend, className: "p-4 border-t bg-background flex-shrink-0", children: [
2322
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
2323
- /* @__PURE__ */ jsx(
2324
- Textarea,
2325
- {
2326
- value: message,
2327
- onChange: (e) => setMessage(e.target.value),
2328
- onKeyDown: handleKeyDown,
2329
- placeholder: canSendMessage ? labels.placeholder : labels.ticketClosed,
2330
- className: "min-h-[60px] max-h-[150px] resize-none",
2331
- disabled: !canSendMessage || isSending
2332
- }
2333
- ),
2334
- /* @__PURE__ */ jsx(
2335
- Button,
2336
- {
2337
- type: "submit",
2338
- size: "icon",
2339
- disabled: !message.trim() || !canSendMessage || isSending,
2340
- className: "shrink-0",
2341
- children: isSending ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(Send, { className: "h-4 w-4" })
2342
- }
2343
- )
2344
- ] }),
2345
- !canSendMessage && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-2", children: labels.ticketClosedDescription })
2346
- ] })
2382
+ /* @__PURE__ */ jsx("form", { onSubmit: handleSend, className: "p-3 border-t bg-background/95 backdrop-blur flex-shrink-0", children: canSendMessage ? /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-2 bg-muted/50 rounded-[24px] px-4 py-2", children: [
2383
+ /* @__PURE__ */ jsx(
2384
+ "textarea",
2385
+ {
2386
+ ref: textareaRef,
2387
+ value: message,
2388
+ onChange: handleTextareaChange,
2389
+ onKeyDown: handleKeyDown,
2390
+ placeholder: labels.placeholder,
2391
+ className: cn(
2392
+ "flex-1 bg-transparent border-none resize-none",
2393
+ "text-[15px] placeholder:text-muted-foreground",
2394
+ "min-h-[24px] max-h-[120px] focus:outline-none"
2395
+ ),
2396
+ rows: 1,
2397
+ disabled: isSending
2398
+ }
2399
+ ),
2400
+ /* @__PURE__ */ jsx(
2401
+ Button,
2402
+ {
2403
+ type: "submit",
2404
+ size: "icon",
2405
+ variant: "ghost",
2406
+ className: cn(
2407
+ "rounded-full h-8 w-8 shrink-0",
2408
+ message.trim() ? "bg-blue-500 text-white hover:bg-blue-600" : ""
2409
+ ),
2410
+ disabled: !message.trim() || isSending,
2411
+ children: isSending ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(ArrowUp, { className: "h-4 w-4" })
2412
+ }
2413
+ )
2414
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "text-center py-2", children: [
2415
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: labels.ticketClosed }),
2416
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: labels.ticketClosedDescription })
2417
+ ] }) })
2347
2418
  ] }) });
2348
2419
  }
2349
2420
  function CreateTicketSheet({ open, onOpenChange, onSuccess }) {
@@ -2404,13 +2475,10 @@ function CreateTicketSheet({ open, onOpenChange, onSuccess }) {
2404
2475
  };
2405
2476
  return /* @__PURE__ */ jsx(ResponsiveSheet, { open, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(ResponsiveSheetContent, { className: "sm:max-w-lg", children: [
2406
2477
  /* @__PURE__ */ jsxs(ResponsiveSheetHeader, { children: [
2407
- /* @__PURE__ */ jsxs(ResponsiveSheetTitle, { className: "flex items-center gap-2", children: [
2408
- /* @__PURE__ */ jsx(Plus, { className: "h-5 w-5" }),
2409
- labels.title
2410
- ] }),
2478
+ /* @__PURE__ */ jsx(ResponsiveSheetTitle, { children: labels.title }),
2411
2479
  /* @__PURE__ */ jsx(ResponsiveSheetDescription, { children: labels.description })
2412
2480
  ] }),
2413
- /* @__PURE__ */ jsx(Form, { ...form, children: /* @__PURE__ */ jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "space-y-6 mt-6", children: [
2481
+ /* @__PURE__ */ jsx(Form, { ...form, children: /* @__PURE__ */ jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "space-y-5 mt-4", children: [
2414
2482
  /* @__PURE__ */ jsx(
2415
2483
  FormField,
2416
2484
  {
@@ -2450,7 +2518,7 @@ function CreateTicketSheet({ open, onOpenChange, onSuccess }) {
2450
2518
  ] })
2451
2519
  }
2452
2520
  ),
2453
- /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-3 pt-4", children: [
2521
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-3 pt-2", children: [
2454
2522
  /* @__PURE__ */ jsx(
2455
2523
  Button,
2456
2524
  {
@@ -2464,10 +2532,7 @@ function CreateTicketSheet({ open, onOpenChange, onSuccess }) {
2464
2532
  /* @__PURE__ */ jsx(Button, { type: "submit", disabled: isSubmitting, children: isSubmitting ? /* @__PURE__ */ jsxs(Fragment, { children: [
2465
2533
  /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }),
2466
2534
  labels.creating
2467
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2468
- /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4 mr-2" }),
2469
- labels.create
2470
- ] }) })
2535
+ ] }) : labels.create })
2471
2536
  ] })
2472
2537
  ] }) })
2473
2538
  ] }) });
@@ -2538,7 +2603,7 @@ function SupportPage({ isDemo = false }) {
2538
2603
  // package.json
2539
2604
  var package_default = {
2540
2605
  name: "@djangocfg/ext-support",
2541
- version: "1.0.24",
2606
+ version: "1.0.26",
2542
2607
  description: "Support ticket system extension for DjangoCFG",
2543
2608
  keywords: [
2544
2609
  "django",