@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/hooks.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 useSWR, { 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 = client || 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 = client || 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) {
@@ -2049,78 +2033,6 @@ var en = {
2049
2033
  }
2050
2034
  };
2051
2035
 
2052
- // src/i18n/locales/ru.ts
2053
- var ru = {
2054
- layout: {
2055
- title: "\u0426\u0435\u043D\u0442\u0440 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438",
2056
- titleShort: "\u041F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0430",
2057
- 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",
2058
- newTicket: "\u041D\u043E\u0432\u043E\u0435 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435"
2059
- },
2060
- hero: {
2061
- openTickets: "\u041E\u0442\u043A\u0440\u044B\u0442\u044B\u0445 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439",
2062
- unreadMessages: "\u041D\u0435\u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u043D\u044B\u0445"
2063
- },
2064
- status: {
2065
- open: "\u041E\u0442\u043A\u0440\u044B\u0442",
2066
- waitingForUser: "\u041E\u0436\u0438\u0434\u0430\u0435\u0442 \u0432\u0430\u0441",
2067
- waitingForAdmin: "\u041E\u0436\u0438\u0434\u0430\u0435\u0442 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0443",
2068
- resolved: "\u0420\u0435\u0448\u0451\u043D",
2069
- closed: "\u0417\u0430\u043A\u0440\u044B\u0442"
2070
- },
2071
- ticketList: {
2072
- noTickets: "\u041D\u0435\u0442 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439",
2073
- 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",
2074
- loadingMore: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430...",
2075
- loadMore: "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0435\u0449\u0451",
2076
- allLoaded: "\u0412\u0441\u0435 {count} \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D\u043E"
2077
- },
2078
- createTicket: {
2079
- title: "\u041D\u043E\u0432\u043E\u0435 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435",
2080
- 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",
2081
- subjectLabel: "\u0422\u0435\u043C\u0430",
2082
- subjectPlaceholder: "\u041A\u0440\u0430\u0442\u043A\u043E\u0435 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 \u043F\u0440\u043E\u0431\u043B\u0435\u043C\u044B",
2083
- messageLabel: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435",
2084
- 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...",
2085
- cancel: "\u041E\u0442\u043C\u0435\u043D\u0430",
2086
- creating: "\u0421\u043E\u0437\u0434\u0430\u043D\u0438\u0435...",
2087
- create: "\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435"
2088
- },
2089
- validation: {
2090
- subjectRequired: "\u0422\u0435\u043C\u0430 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u0430",
2091
- 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)",
2092
- messageRequired: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E",
2093
- 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)"
2094
- },
2095
- messages: {
2096
- ticketCreated: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u0441\u043E\u0437\u0434\u0430\u043D\u043E",
2097
- 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",
2098
- messageSent: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E",
2099
- 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"
2100
- },
2101
- messageInput: {
2102
- placeholder: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435...",
2103
- ticketClosed: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u043E",
2104
- 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."
2105
- },
2106
- messageList: {
2107
- noTicketSelected: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u043D\u0435 \u0432\u044B\u0431\u0440\u0430\u043D\u043E",
2108
- 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",
2109
- noMessages: "\u041D\u0435\u0442 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439",
2110
- 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",
2111
- loadingOlder: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u0441\u0442\u0430\u0440\u044B\u0445 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439...",
2112
- loadOlder: "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0441\u0442\u0430\u0440\u044B\u0435",
2113
- supportTeam: "\u0421\u043B\u0443\u0436\u0431\u0430 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438",
2114
- staff: "\u0421\u043E\u0442\u0440\u0443\u0434\u043D\u0438\u043A"
2115
- },
2116
- time: {
2117
- justNow: "\u0422\u043E\u043B\u044C\u043A\u043E \u0447\u0442\u043E",
2118
- minutesAgo: "{count} \u043C\u0438\u043D \u043D\u0430\u0437\u0430\u0434",
2119
- hoursAgo: "{count}\u0447 \u043D\u0430\u0437\u0430\u0434",
2120
- daysAgo: "{count}\u0434 \u043D\u0430\u0437\u0430\u0434"
2121
- }
2122
- };
2123
-
2124
2036
  // src/i18n/locales/ko.ts
2125
2037
  var ko = {
2126
2038
  layout: {
@@ -2193,6 +2105,78 @@ var ko = {
2193
2105
  }
2194
2106
  };
2195
2107
 
2108
+ // src/i18n/locales/ru.ts
2109
+ var ru = {
2110
+ layout: {
2111
+ title: "\u0426\u0435\u043D\u0442\u0440 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438",
2112
+ titleShort: "\u041F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0430",
2113
+ 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",
2114
+ newTicket: "\u041D\u043E\u0432\u043E\u0435 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435"
2115
+ },
2116
+ hero: {
2117
+ openTickets: "\u041E\u0442\u043A\u0440\u044B\u0442\u044B\u0445 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439",
2118
+ unreadMessages: "\u041D\u0435\u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u043D\u044B\u0445"
2119
+ },
2120
+ status: {
2121
+ open: "\u041E\u0442\u043A\u0440\u044B\u0442",
2122
+ waitingForUser: "\u041E\u0436\u0438\u0434\u0430\u0435\u0442 \u0432\u0430\u0441",
2123
+ waitingForAdmin: "\u041E\u0436\u0438\u0434\u0430\u0435\u0442 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0443",
2124
+ resolved: "\u0420\u0435\u0448\u0451\u043D",
2125
+ closed: "\u0417\u0430\u043A\u0440\u044B\u0442"
2126
+ },
2127
+ ticketList: {
2128
+ noTickets: "\u041D\u0435\u0442 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439",
2129
+ 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",
2130
+ loadingMore: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430...",
2131
+ loadMore: "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0435\u0449\u0451",
2132
+ allLoaded: "\u0412\u0441\u0435 {count} \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0439 \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043D\u043E"
2133
+ },
2134
+ createTicket: {
2135
+ title: "\u041D\u043E\u0432\u043E\u0435 \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435",
2136
+ 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",
2137
+ subjectLabel: "\u0422\u0435\u043C\u0430",
2138
+ subjectPlaceholder: "\u041A\u0440\u0430\u0442\u043A\u043E\u0435 \u043E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 \u043F\u0440\u043E\u0431\u043B\u0435\u043C\u044B",
2139
+ messageLabel: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435",
2140
+ 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...",
2141
+ cancel: "\u041E\u0442\u043C\u0435\u043D\u0430",
2142
+ creating: "\u0421\u043E\u0437\u0434\u0430\u043D\u0438\u0435...",
2143
+ create: "\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u043E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435"
2144
+ },
2145
+ validation: {
2146
+ subjectRequired: "\u0422\u0435\u043C\u0430 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u0430",
2147
+ 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)",
2148
+ messageRequired: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E",
2149
+ 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)"
2150
+ },
2151
+ messages: {
2152
+ ticketCreated: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u0441\u043E\u0437\u0434\u0430\u043D\u043E",
2153
+ 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",
2154
+ messageSent: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E",
2155
+ 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"
2156
+ },
2157
+ messageInput: {
2158
+ placeholder: "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435...",
2159
+ ticketClosed: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u0437\u0430\u043A\u0440\u044B\u0442\u043E",
2160
+ 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."
2161
+ },
2162
+ messageList: {
2163
+ noTicketSelected: "\u041E\u0431\u0440\u0430\u0449\u0435\u043D\u0438\u0435 \u043D\u0435 \u0432\u044B\u0431\u0440\u0430\u043D\u043E",
2164
+ 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",
2165
+ noMessages: "\u041D\u0435\u0442 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439",
2166
+ 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",
2167
+ loadingOlder: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u0441\u0442\u0430\u0440\u044B\u0445 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439...",
2168
+ loadOlder: "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0441\u0442\u0430\u0440\u044B\u0435",
2169
+ supportTeam: "\u0421\u043B\u0443\u0436\u0431\u0430 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u043A\u0438",
2170
+ staff: "\u0421\u043E\u0442\u0440\u0443\u0434\u043D\u0438\u043A"
2171
+ },
2172
+ time: {
2173
+ justNow: "\u0422\u043E\u043B\u044C\u043A\u043E \u0447\u0442\u043E",
2174
+ minutesAgo: "{count} \u043C\u0438\u043D \u043D\u0430\u0437\u0430\u0434",
2175
+ hoursAgo: "{count}\u0447 \u043D\u0430\u0437\u0430\u0434",
2176
+ daysAgo: "{count}\u0434 \u043D\u0430\u0437\u0430\u0434"
2177
+ }
2178
+ };
2179
+
2196
2180
  // src/i18n/useSupportT.ts
2197
2181
  var translations = { en, ru, ko };
2198
2182
  function getNestedValue(obj, path) {
@@ -2226,27 +2210,22 @@ function SupportHero({ onCreateTicket, className }) {
2226
2210
  unreadMessages: st("hero.unreadMessages")
2227
2211
  }), [st]);
2228
2212
  const openTicketsCount = tickets.filter((t) => t.status !== "closed" && t.status !== "resolved").length;
2229
- return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-center py-16 px-4", className), children: [
2230
- /* @__PURE__ */ jsx("div", { className: "text-center mb-6", children: isLoadingTickets ? /* @__PURE__ */ jsxs(Fragment, { children: [
2231
- /* @__PURE__ */ jsx(Skeleton, { className: "h-12 w-12 mx-auto mb-4 rounded-full" }),
2232
- /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-32 mx-auto mb-2" }),
2233
- /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-48 mx-auto" })
2234
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2235
- /* @__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" }) }),
2236
- /* @__PURE__ */ jsx("h1", { className: "text-3xl font-bold tracking-tight", children: labels.title }),
2237
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mt-1", children: labels.subtitle })
2238
- ] }) }),
2239
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
2240
- /* @__PURE__ */ jsxs(
2213
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-center py-12 px-4", className), children: [
2214
+ isLoadingTickets ? /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
2215
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-10 w-48 mx-auto mb-2" }),
2216
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-64 mx-auto" })
2217
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
2218
+ /* @__PURE__ */ jsx("h1", { className: "text-4xl font-bold tracking-tight", children: labels.title }),
2219
+ /* @__PURE__ */ jsx("p", { className: "text-lg text-muted-foreground mt-2", children: labels.subtitle })
2220
+ ] }),
2221
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mt-6", children: [
2222
+ /* @__PURE__ */ jsx(
2241
2223
  Button,
2242
2224
  {
2243
2225
  size: "lg",
2244
2226
  onClick: onCreateTicket,
2245
- className: "rounded-full px-6",
2246
- children: [
2247
- /* @__PURE__ */ jsx(Plus, { className: "h-5 w-5 mr-2" }),
2248
- labels.newTicket
2249
- ]
2227
+ className: "rounded-full px-8 h-12 text-base",
2228
+ children: labels.newTicket
2250
2229
  }
2251
2230
  ),
2252
2231
  /* @__PURE__ */ jsx(
@@ -2255,43 +2234,100 @@ function SupportHero({ onCreateTicket, className }) {
2255
2234
  size: "icon",
2256
2235
  variant: "ghost",
2257
2236
  onClick: () => refreshTickets(),
2258
- className: "rounded-full",
2237
+ className: "rounded-full h-10 w-10",
2259
2238
  children: /* @__PURE__ */ jsx(RefreshCw, { className: "h-4 w-4" })
2260
2239
  }
2261
2240
  )
2262
2241
  ] }),
2263
- !isLoadingTickets && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-6 mt-6 text-sm text-muted-foreground", children: [
2264
- /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
2265
- /* @__PURE__ */ jsx("span", { className: "block font-medium text-foreground text-xl tabular-nums", children: openTicketsCount }),
2266
- /* @__PURE__ */ jsx("span", { children: labels.openTickets })
2242
+ !isLoadingTickets && (openTicketsCount > 0 || unreadCount > 0) && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-4", children: [
2243
+ openTicketsCount > 0 && /* @__PURE__ */ jsxs("span", { className: "text-sm text-muted-foreground bg-muted px-3 py-1 rounded-full", children: [
2244
+ openTicketsCount,
2245
+ " ",
2246
+ labels.openTickets.toLowerCase()
2267
2247
  ] }),
2268
- /* @__PURE__ */ jsx("div", { className: "h-8 w-px bg-border" }),
2269
- /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
2270
- /* @__PURE__ */ jsx("span", { className: cn(
2271
- "block font-medium text-xl tabular-nums",
2272
- unreadCount > 0 ? "text-destructive" : "text-foreground"
2273
- ), children: unreadCount }),
2274
- /* @__PURE__ */ jsx("span", { children: labels.unreadMessages })
2248
+ unreadCount > 0 && /* @__PURE__ */ jsxs("span", { className: "text-sm text-destructive bg-destructive/10 px-3 py-1 rounded-full", children: [
2249
+ unreadCount,
2250
+ " ",
2251
+ labels.unreadMessages.toLowerCase()
2275
2252
  ] })
2276
2253
  ] })
2277
2254
  ] });
2278
2255
  }
2279
- var getStatusBadgeVariant = (status) => {
2280
- switch (status) {
2281
- case "open":
2282
- return "default";
2283
- case "waiting_for_user":
2284
- return "secondary";
2285
- case "waiting_for_admin":
2286
- return "outline";
2287
- case "resolved":
2288
- return "outline";
2289
- case "closed":
2290
- return "secondary";
2291
- default:
2292
- return "default";
2256
+
2257
+ // src/utils/status.ts
2258
+ var ticketStatusConfig = {
2259
+ open: {
2260
+ color: "text-blue-600 dark:text-blue-400",
2261
+ bg: "bg-blue-500/10"
2262
+ },
2263
+ waiting_for_user: {
2264
+ color: "text-orange-600 dark:text-orange-400",
2265
+ bg: "bg-orange-500/10"
2266
+ },
2267
+ waiting_for_admin: {
2268
+ color: "text-muted-foreground",
2269
+ bg: "bg-muted"
2270
+ },
2271
+ resolved: {
2272
+ color: "text-green-600 dark:text-green-400",
2273
+ bg: "bg-green-500/10"
2274
+ },
2275
+ closed: {
2276
+ color: "text-muted-foreground",
2277
+ bg: "bg-muted"
2293
2278
  }
2294
2279
  };
2280
+ function getStatusConfig(status) {
2281
+ return ticketStatusConfig[status] || ticketStatusConfig.open;
2282
+ }
2283
+ function formatMessageTime(date) {
2284
+ if (!date) return "";
2285
+ return moment.utc(date).local().format("h:mm A");
2286
+ }
2287
+ function formatDateSeparator(date) {
2288
+ if (!date) return "";
2289
+ const m = moment.utc(date).local();
2290
+ const now = moment();
2291
+ const today = now.clone().startOf("day");
2292
+ const yesterday = now.clone().subtract(1, "day").startOf("day");
2293
+ if (m.isSame(today, "day")) {
2294
+ return "Today";
2295
+ }
2296
+ if (m.isSame(yesterday, "day")) {
2297
+ return "Yesterday";
2298
+ }
2299
+ if (m.isSame(now, "year")) {
2300
+ return m.format("MMM D");
2301
+ }
2302
+ return m.format("MMM D, YYYY");
2303
+ }
2304
+ function formatRelativeTime(date, labels) {
2305
+ if (!date) return "N/A";
2306
+ const m = moment.utc(date).local();
2307
+ const now = moment();
2308
+ const diffInSeconds = now.diff(m, "seconds");
2309
+ if (diffInSeconds < 60) {
2310
+ return labels.justNow;
2311
+ }
2312
+ if (diffInSeconds < 3600) {
2313
+ return labels.minutesAgo.replace("{count}", String(Math.floor(diffInSeconds / 60)));
2314
+ }
2315
+ if (diffInSeconds < 86400) {
2316
+ return labels.hoursAgo.replace("{count}", String(Math.floor(diffInSeconds / 3600)));
2317
+ }
2318
+ if (diffInSeconds < 604800) {
2319
+ return labels.daysAgo.replace("{count}", String(Math.floor(diffInSeconds / 86400)));
2320
+ }
2321
+ return m.format("MMM D, YYYY");
2322
+ }
2323
+ function isSameDay(date1, date2) {
2324
+ if (!date1 || !date2) return false;
2325
+ return moment.utc(date1).format("YYYY-MM-DD") === moment.utc(date2).format("YYYY-MM-DD");
2326
+ }
2327
+ function formatTicketDate(date) {
2328
+ if (!date) return "";
2329
+ return moment.utc(date).local().format("MMM D, YYYY");
2330
+ }
2295
2331
  function TicketItem({ ticket, onClick }) {
2296
2332
  const st = useSupportT();
2297
2333
  const labels = useMemo(() => ({
@@ -2309,49 +2345,37 @@ function TicketItem({ ticket, onClick }) {
2309
2345
  daysAgo: st("time.daysAgo")
2310
2346
  }
2311
2347
  }), [st]);
2312
- const formatRelativeTime = useCallback((date) => {
2313
- if (!date) return "N/A";
2314
- const m = moment2.utc(date).local();
2315
- const now = moment2();
2316
- const diffInSeconds = now.diff(m, "seconds");
2317
- if (diffInSeconds < 60) return labels.time.justNow;
2318
- if (diffInSeconds < 3600) return labels.time.minutesAgo.replace("{count}", String(Math.floor(diffInSeconds / 60)));
2319
- if (diffInSeconds < 86400) return labels.time.hoursAgo.replace("{count}", String(Math.floor(diffInSeconds / 3600)));
2320
- if (diffInSeconds < 604800) return labels.time.daysAgo.replace("{count}", String(Math.floor(diffInSeconds / 86400)));
2321
- return m.format("MMM D, YYYY");
2348
+ const getRelativeTime = useCallback((date) => {
2349
+ return formatRelativeTime(date, labels.time);
2322
2350
  }, [labels.time]);
2323
2351
  const statusLabel = labels.status[ticket.status] || ticket.status || labels.status.open;
2352
+ const statusConfig = getStatusConfig(ticket.status || "open");
2324
2353
  const hasUnread = (ticket.unanswered_messages_count || 0) > 0;
2325
2354
  return /* @__PURE__ */ jsxs(
2326
- "div",
2355
+ "button",
2327
2356
  {
2357
+ type: "button",
2328
2358
  className: cn(
2329
- "group flex items-center gap-4 p-4 rounded-xl",
2330
- "cursor-pointer transition-all duration-200",
2331
- "hover:bg-accent/50",
2332
- "active:scale-[0.98]",
2333
- hasUnread && "bg-primary/5"
2359
+ "w-full flex items-start gap-3 p-4 text-left",
2360
+ "transition-colors duration-150",
2361
+ "hover:bg-accent/50 active:bg-accent"
2334
2362
  ),
2335
2363
  onClick,
2336
2364
  children: [
2337
- /* @__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" }) }),
2365
+ /* @__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" }) }),
2338
2366
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
2339
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
2340
- /* @__PURE__ */ jsx("h3", { className: cn(
2341
- "font-medium text-sm line-clamp-1",
2342
- hasUnread && "font-semibold"
2343
- ), children: ticket.subject }),
2344
- hasUnread && /* @__PURE__ */ jsx(Badge, { variant: "destructive", className: "shrink-0 text-xs", children: ticket.unanswered_messages_count })
2345
- ] }),
2346
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mt-1 text-xs text-muted-foreground", children: [
2347
- /* @__PURE__ */ jsx(Badge, { variant: getStatusBadgeVariant(ticket.status || "open"), className: "text-xs py-0", children: statusLabel }),
2348
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
2349
- /* @__PURE__ */ jsx(Clock, { className: "h-3 w-3" }),
2350
- /* @__PURE__ */ jsx("span", { children: formatRelativeTime(ticket.created_at) })
2367
+ /* @__PURE__ */ jsx("h3", { className: cn(
2368
+ "text-[15px] line-clamp-1",
2369
+ hasUnread ? "font-semibold" : "font-medium"
2370
+ ), children: ticket.subject }),
2371
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-0.5", children: [
2372
+ /* @__PURE__ */ jsx("span", { className: cn("text-[13px]", statusConfig.color), children: statusLabel }),
2373
+ /* @__PURE__ */ jsxs("span", { className: "text-[13px] text-muted-foreground", children: [
2374
+ "\xB7 ",
2375
+ getRelativeTime(ticket.created_at)
2351
2376
  ] })
2352
2377
  ] })
2353
- ] }),
2354
- /* @__PURE__ */ jsx(ChevronRight, { className: "h-5 w-5 text-muted-foreground/50 group-hover:text-muted-foreground transition-colors flex-shrink-0" })
2378
+ ] })
2355
2379
  ]
2356
2380
  }
2357
2381
  );
@@ -2396,24 +2420,23 @@ function TicketList({ onTicketClick, className }) {
2396
2420
  };
2397
2421
  }, [hasMoreTickets, isLoadingMoreTickets, loadMoreTickets]);
2398
2422
  if (isLoadingTickets) {
2399
- return /* @__PURE__ */ jsx("div", { className: cn("px-4 py-6 space-y-3", className), children: [1, 2, 3, 4, 5].map((i) => /* @__PURE__ */ jsx(
2423
+ 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(
2400
2424
  Skeleton,
2401
2425
  {
2402
- className: "h-20 w-full",
2426
+ className: "h-12 w-full",
2403
2427
  style: { animationDelay: `${i * 100}ms` }
2404
- },
2405
- i
2406
- )) });
2428
+ }
2429
+ ) }, i)) }) });
2407
2430
  }
2408
2431
  if (!tickets || tickets.length === 0) {
2409
- return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-center justify-center py-16 px-8 text-center", className), children: [
2410
- /* @__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" }) }),
2411
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold mb-2", children: labels.noTickets }),
2412
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground max-w-sm", children: labels.noTicketsDescription })
2432
+ return /* @__PURE__ */ jsxs(Empty, { className: cn("py-16", className), children: [
2433
+ /* @__PURE__ */ jsx(EmptyMedia, { variant: "icon", children: /* @__PURE__ */ jsx(MessageSquare, { className: "h-8 w-8" }) }),
2434
+ /* @__PURE__ */ jsx(EmptyTitle, { children: labels.noTickets }),
2435
+ /* @__PURE__ */ jsx(EmptyDescription, { children: labels.noTicketsDescription })
2413
2436
  ] });
2414
2437
  }
2415
- return /* @__PURE__ */ jsxs("div", { className: cn("px-4 py-6", className), children: [
2416
- /* @__PURE__ */ jsx("div", { className: "space-y-2", children: tickets.map((ticket) => /* @__PURE__ */ jsx(
2438
+ return /* @__PURE__ */ jsxs("div", { className: cn("px-4 py-2", className), children: [
2439
+ /* @__PURE__ */ jsx("div", { className: "divide-y divide-border/50", children: tickets.map((ticket) => /* @__PURE__ */ jsx(
2417
2440
  TicketItem,
2418
2441
  {
2419
2442
  ticket,
@@ -2443,49 +2466,70 @@ function TicketList({ onTicketClick, className }) {
2443
2466
  ) })
2444
2467
  ] });
2445
2468
  }
2446
- var formatTime = (date) => {
2447
- if (!date) return "";
2448
- return moment2.utc(date).local().format("hh:mm A");
2449
- };
2450
- var formatDate = (date) => {
2451
- if (!date) return "";
2452
- return moment2.utc(date).local().format("MMM D, YYYY");
2453
- };
2454
- var getStatusBadgeVariant2 = (status) => {
2455
- switch (status) {
2456
- case "open":
2457
- return "default";
2458
- case "waiting_for_user":
2459
- return "secondary";
2460
- case "waiting_for_admin":
2461
- return "outline";
2462
- case "resolved":
2463
- return "outline";
2464
- case "closed":
2465
- return "secondary";
2466
- default:
2467
- return "default";
2469
+ function groupMessages(messages, currentUserId) {
2470
+ const groups = [];
2471
+ let currentGroup = null;
2472
+ for (const msg of messages) {
2473
+ const isFromUser = msg.sender?.id && currentUserId && String(msg.sender.id) === String(currentUserId) || msg.is_from_author;
2474
+ const senderId = msg.sender?.id || null;
2475
+ const shouldStartNewGroup = !currentGroup || currentGroup.isFromUser !== isFromUser || currentGroup.senderId !== senderId;
2476
+ if (shouldStartNewGroup) {
2477
+ currentGroup = {
2478
+ senderId,
2479
+ isFromUser: !!isFromUser,
2480
+ messages: [msg]
2481
+ };
2482
+ groups.push(currentGroup);
2483
+ } else {
2484
+ currentGroup.messages.push(msg);
2485
+ }
2468
2486
  }
2469
- };
2470
- var MessageBubble = ({ message, isFromUser, currentUser, labels }) => {
2487
+ return groups;
2488
+ }
2489
+ 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) }) });
2490
+ var MessageBubble = ({
2491
+ message,
2492
+ isFromUser,
2493
+ showAvatar,
2494
+ showTimestamp,
2495
+ currentUser,
2496
+ labels
2497
+ }) => {
2471
2498
  const sender = message.sender;
2472
2499
  const senderInitial = sender?.display_username?.charAt(0)?.toUpperCase() || sender?.initials || "S";
2473
2500
  const userInitial = currentUser?.display_username?.charAt(0)?.toUpperCase() || currentUser?.email?.charAt(0)?.toUpperCase() || currentUser?.initials || null;
2474
- return /* @__PURE__ */ jsxs("div", { className: cn("flex gap-3", isFromUser ? "justify-end" : "justify-start"), children: [
2475
- !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 }) }),
2476
- /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-1 flex-1 max-w-[80%]", isFromUser ? "items-end" : "items-start"), children: [
2477
- !isFromUser && sender && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground px-1", children: [
2478
- sender.display_username || sender.email || labels.supportTeam,
2479
- sender.is_staff && ` (${labels.staff})`
2480
- ] }),
2481
- /* @__PURE__ */ jsx(Card, { className: cn(
2482
- isFromUser ? "bg-primary text-primary-foreground" : "bg-muted"
2483
- ), children: /* @__PURE__ */ jsx(CardContent, { className: "p-3", children: /* @__PURE__ */ jsx("p", { className: "text-sm whitespace-pre-wrap break-words", children: message.text }) }) }),
2484
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground px-1", children: formatTime(message.created_at) })
2501
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex gap-2", isFromUser ? "justify-end" : "justify-start"), children: [
2502
+ !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 }) }) }),
2503
+ /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-0.5 max-w-[85%]", isFromUser ? "items-end" : "items-start"), children: [
2504
+ /* @__PURE__ */ jsx(
2505
+ "div",
2506
+ {
2507
+ className: cn(
2508
+ "px-4 py-2.5 rounded-[20px]",
2509
+ isFromUser ? "bg-blue-500 text-white" : "bg-[#e9e9eb] dark:bg-zinc-800 text-foreground"
2510
+ ),
2511
+ children: /* @__PURE__ */ jsx("p", { className: "text-[15px] leading-relaxed whitespace-pre-wrap break-words", children: message.text })
2512
+ }
2513
+ ),
2514
+ showTimestamp && /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground px-2 mt-0.5", children: formatMessageTime(message.created_at) })
2485
2515
  ] }),
2486
- 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" }) }) })
2516
+ 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" }) }) }) })
2487
2517
  ] });
2488
2518
  };
2519
+ var MessageGroupComponent = ({ group, currentUser, labels }) => {
2520
+ return /* @__PURE__ */ jsx("div", { className: "space-y-1", children: group.messages.map((msg, idx) => /* @__PURE__ */ jsx(
2521
+ MessageBubble,
2522
+ {
2523
+ message: msg,
2524
+ isFromUser: group.isFromUser,
2525
+ showAvatar: idx === 0,
2526
+ showTimestamp: idx === group.messages.length - 1,
2527
+ currentUser,
2528
+ labels
2529
+ },
2530
+ msg.uuid
2531
+ )) });
2532
+ };
2489
2533
  function TicketSheet({ ticket, open, onOpenChange }) {
2490
2534
  const st = useSupportT();
2491
2535
  const { user } = useAuth();
@@ -2494,6 +2538,7 @@ function TicketSheet({ ticket, open, onOpenChange }) {
2494
2538
  const [message, setMessage] = useState("");
2495
2539
  const [isSending, setIsSending] = useState(false);
2496
2540
  const scrollAreaRef = useRef(null);
2541
+ const textareaRef = useRef(null);
2497
2542
  const firstRender = useRef(true);
2498
2543
  const labels = useMemo(() => ({
2499
2544
  noMessages: st("messageList.noMessages"),
@@ -2525,6 +2570,7 @@ function TicketSheet({ ticket, open, onOpenChange }) {
2525
2570
  refresh: refreshMessages,
2526
2571
  addOptimistic
2527
2572
  } = useMessages(ticket?.uuid || null);
2573
+ const messageGroups = useMemo(() => groupMessages(messages, user?.id), [messages, user?.id]);
2528
2574
  useEffect(() => {
2529
2575
  firstRender.current = true;
2530
2576
  }, [ticket?.uuid]);
@@ -2537,6 +2583,14 @@ function TicketSheet({ ticket, open, onOpenChange }) {
2537
2583
  firstRender.current = false;
2538
2584
  }
2539
2585
  }, [messages]);
2586
+ const handleTextareaChange = useCallback((e) => {
2587
+ setMessage(e.target.value);
2588
+ const textarea = textareaRef.current;
2589
+ if (textarea) {
2590
+ textarea.style.height = "auto";
2591
+ textarea.style.height = `${Math.min(textarea.scrollHeight, 120)}px`;
2592
+ }
2593
+ }, []);
2540
2594
  const handleSend = async (e) => {
2541
2595
  e.preventDefault();
2542
2596
  if (!message.trim() || !ticket?.uuid) return;
@@ -2562,6 +2616,9 @@ function TicketSheet({ ticket, open, onOpenChange }) {
2562
2616
  addOptimistic(optimisticMessage);
2563
2617
  await sendMessage({ ticketUuid: ticket.uuid, text: messageText });
2564
2618
  setMessage("");
2619
+ if (textareaRef.current) {
2620
+ textareaRef.current.style.height = "auto";
2621
+ }
2565
2622
  await refreshMessages();
2566
2623
  const scrollContainer = scrollAreaRef.current?.querySelector("[data-radix-scroll-area-viewport]");
2567
2624
  if (scrollContainer) {
@@ -2595,32 +2652,61 @@ function TicketSheet({ ticket, open, onOpenChange }) {
2595
2652
  }, [loadMore]);
2596
2653
  const canSendMessage = ticket?.status !== "closed";
2597
2654
  const statusLabel = ticket ? labels.status[ticket.status] || ticket.status || labels.status.open : "";
2655
+ const statusConfig = getStatusConfig(ticket?.status || "open");
2656
+ const renderMessages = () => {
2657
+ const elements = [];
2658
+ let lastDate = null;
2659
+ for (const group of messageGroups) {
2660
+ const firstMessage = group.messages[0];
2661
+ if (!firstMessage) continue;
2662
+ const messageDate = firstMessage.created_at;
2663
+ if (!lastDate || !isSameDay(lastDate, messageDate)) {
2664
+ elements.push(
2665
+ /* @__PURE__ */ jsx(DateSeparator, { date: messageDate || "" }, `date-${messageDate}`)
2666
+ );
2667
+ lastDate = messageDate || null;
2668
+ }
2669
+ elements.push(
2670
+ /* @__PURE__ */ jsx(
2671
+ MessageGroupComponent,
2672
+ {
2673
+ group,
2674
+ currentUser: user,
2675
+ labels: { supportTeam: labels.supportTeam, staff: labels.staff }
2676
+ },
2677
+ `group-${firstMessage.uuid}`
2678
+ )
2679
+ );
2680
+ }
2681
+ return elements;
2682
+ };
2598
2683
  return /* @__PURE__ */ jsx(ResponsiveSheet, { open, onOpenChange, children: /* @__PURE__ */ jsxs(ResponsiveSheetContent, { className: "sm:max-w-xl p-0 flex flex-col h-full", children: [
2599
2684
  /* @__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: [
2600
2685
  /* @__PURE__ */ jsx(ResponsiveSheetTitle, { className: "text-lg font-semibold line-clamp-2", children: ticket?.subject || "Ticket" }),
2601
2686
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-1", children: [
2602
- /* @__PURE__ */ jsx(Badge, { variant: getStatusBadgeVariant2(ticket?.status || "open"), className: "text-xs", children: statusLabel }),
2603
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: formatDate(ticket?.created_at) })
2687
+ /* @__PURE__ */ jsx("span", { className: cn("text-[13px] px-2 py-0.5 rounded-full", statusConfig.bg, statusConfig.color), children: statusLabel }),
2688
+ /* @__PURE__ */ jsx("span", { className: "text-[13px] text-muted-foreground", children: formatTicketDate(ticket?.created_at) })
2604
2689
  ] })
2605
2690
  ] }) }) }),
2606
- /* @__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: [
2607
- /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-8 rounded-full" }),
2608
- /* @__PURE__ */ jsx(Skeleton, { className: "h-16 flex-1 max-w-[70%]" })
2691
+ /* @__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: [
2692
+ i % 2 !== 0 && /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-8 rounded-full shrink-0" }),
2693
+ /* @__PURE__ */ jsx(Skeleton, { className: cn("h-16 rounded-[20px]", i % 2 === 0 ? "w-[60%]" : "w-[70%]") }),
2694
+ i % 2 === 0 && /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-8 rounded-full shrink-0" })
2609
2695
  ] }, i)) }) : messages.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center h-full p-8 text-center", children: [
2610
- /* @__PURE__ */ jsx(MessageSquare, { className: "h-12 w-12 text-muted-foreground mb-4" }),
2696
+ /* @__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" }) }),
2611
2697
  /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold mb-2", children: labels.noMessages }),
2612
2698
  /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground max-w-sm", children: labels.noMessagesDescription })
2613
- ] }) : /* @__PURE__ */ jsx(ScrollArea, { className: "h-full", viewportRef: scrollAreaRef, children: /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-4", children: [
2699
+ ] }) : /* @__PURE__ */ jsx(ScrollArea, { className: "h-full", viewportRef: scrollAreaRef, children: /* @__PURE__ */ jsxs("div", { className: "p-4 space-y-3", children: [
2614
2700
  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: [
2615
2701
  /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }),
2616
2702
  /* @__PURE__ */ jsx("span", { className: "text-sm", children: labels.loadingOlder })
2617
2703
  ] }) : /* @__PURE__ */ jsxs(
2618
2704
  Button,
2619
2705
  {
2620
- variant: "outline",
2706
+ variant: "ghost",
2621
2707
  size: "sm",
2622
2708
  onClick: handleLoadMore,
2623
- className: "text-xs",
2709
+ className: "text-xs text-muted-foreground",
2624
2710
  children: [
2625
2711
  labels.loadOlder,
2626
2712
  " (",
@@ -2629,59 +2715,44 @@ function TicketSheet({ ticket, open, onOpenChange }) {
2629
2715
  ]
2630
2716
  }
2631
2717
  ) }),
2632
- messages.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
2633
- /* @__PURE__ */ jsx("div", { className: "flex-1 h-px bg-border" }),
2634
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: formatDate(messages[0]?.created_at) }),
2635
- /* @__PURE__ */ jsx("div", { className: "flex-1 h-px bg-border" })
2636
- ] }),
2637
- messages.map((msg, index) => {
2638
- 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);
2639
- const previousMessage = index > 0 ? messages[index - 1] : null;
2640
- const showDateSeparator = previousMessage && moment2.utc(previousMessage.created_at || "").format("YYYY-MM-DD") !== moment2.utc(msg.created_at || "").format("YYYY-MM-DD");
2641
- return /* @__PURE__ */ jsxs(React4.Fragment, { children: [
2642
- showDateSeparator && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 my-4", children: [
2643
- /* @__PURE__ */ jsx("div", { className: "flex-1 h-px bg-border" }),
2644
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: formatDate(msg.created_at) }),
2645
- /* @__PURE__ */ jsx("div", { className: "flex-1 h-px bg-border" })
2646
- ] }),
2647
- /* @__PURE__ */ jsx(
2648
- MessageBubble,
2649
- {
2650
- message: msg,
2651
- isFromUser: !!isFromUser,
2652
- currentUser: user,
2653
- labels: { supportTeam: labels.supportTeam, staff: labels.staff }
2654
- }
2655
- )
2656
- ] }, msg.uuid);
2657
- })
2718
+ renderMessages()
2658
2719
  ] }) }) }),
2659
- /* @__PURE__ */ jsxs("form", { onSubmit: handleSend, className: "p-4 border-t bg-background flex-shrink-0", children: [
2660
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
2661
- /* @__PURE__ */ jsx(
2662
- Textarea,
2663
- {
2664
- value: message,
2665
- onChange: (e) => setMessage(e.target.value),
2666
- onKeyDown: handleKeyDown,
2667
- placeholder: canSendMessage ? labels.placeholder : labels.ticketClosed,
2668
- className: "min-h-[60px] max-h-[150px] resize-none",
2669
- disabled: !canSendMessage || isSending
2670
- }
2671
- ),
2672
- /* @__PURE__ */ jsx(
2673
- Button,
2674
- {
2675
- type: "submit",
2676
- size: "icon",
2677
- disabled: !message.trim() || !canSendMessage || isSending,
2678
- className: "shrink-0",
2679
- children: isSending ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(Send, { className: "h-4 w-4" })
2680
- }
2681
- )
2682
- ] }),
2683
- !canSendMessage && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-2", children: labels.ticketClosedDescription })
2684
- ] })
2720
+ /* @__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: [
2721
+ /* @__PURE__ */ jsx(
2722
+ "textarea",
2723
+ {
2724
+ ref: textareaRef,
2725
+ value: message,
2726
+ onChange: handleTextareaChange,
2727
+ onKeyDown: handleKeyDown,
2728
+ placeholder: labels.placeholder,
2729
+ className: cn(
2730
+ "flex-1 bg-transparent border-none resize-none",
2731
+ "text-[15px] placeholder:text-muted-foreground",
2732
+ "min-h-[24px] max-h-[120px] focus:outline-none"
2733
+ ),
2734
+ rows: 1,
2735
+ disabled: isSending
2736
+ }
2737
+ ),
2738
+ /* @__PURE__ */ jsx(
2739
+ Button,
2740
+ {
2741
+ type: "submit",
2742
+ size: "icon",
2743
+ variant: "ghost",
2744
+ className: cn(
2745
+ "rounded-full h-8 w-8 shrink-0",
2746
+ message.trim() ? "bg-blue-500 text-white hover:bg-blue-600" : ""
2747
+ ),
2748
+ disabled: !message.trim() || isSending,
2749
+ children: isSending ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(ArrowUp, { className: "h-4 w-4" })
2750
+ }
2751
+ )
2752
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "text-center py-2", children: [
2753
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: labels.ticketClosed }),
2754
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: labels.ticketClosedDescription })
2755
+ ] }) })
2685
2756
  ] }) });
2686
2757
  }
2687
2758
  function CreateTicketSheet({ open, onOpenChange, onSuccess }) {
@@ -2742,13 +2813,10 @@ function CreateTicketSheet({ open, onOpenChange, onSuccess }) {
2742
2813
  };
2743
2814
  return /* @__PURE__ */ jsx(ResponsiveSheet, { open, onOpenChange: handleClose, children: /* @__PURE__ */ jsxs(ResponsiveSheetContent, { className: "sm:max-w-lg", children: [
2744
2815
  /* @__PURE__ */ jsxs(ResponsiveSheetHeader, { children: [
2745
- /* @__PURE__ */ jsxs(ResponsiveSheetTitle, { className: "flex items-center gap-2", children: [
2746
- /* @__PURE__ */ jsx(Plus, { className: "h-5 w-5" }),
2747
- labels.title
2748
- ] }),
2816
+ /* @__PURE__ */ jsx(ResponsiveSheetTitle, { children: labels.title }),
2749
2817
  /* @__PURE__ */ jsx(ResponsiveSheetDescription, { children: labels.description })
2750
2818
  ] }),
2751
- /* @__PURE__ */ jsx(Form, { ...form, children: /* @__PURE__ */ jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "space-y-6 mt-6", children: [
2819
+ /* @__PURE__ */ jsx(Form, { ...form, children: /* @__PURE__ */ jsxs("form", { onSubmit: form.handleSubmit(onSubmit), className: "space-y-5 mt-4", children: [
2752
2820
  /* @__PURE__ */ jsx(
2753
2821
  FormField,
2754
2822
  {
@@ -2788,7 +2856,7 @@ function CreateTicketSheet({ open, onOpenChange, onSuccess }) {
2788
2856
  ] })
2789
2857
  }
2790
2858
  ),
2791
- /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-3 pt-4", children: [
2859
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-3 pt-2", children: [
2792
2860
  /* @__PURE__ */ jsx(
2793
2861
  Button,
2794
2862
  {
@@ -2802,10 +2870,7 @@ function CreateTicketSheet({ open, onOpenChange, onSuccess }) {
2802
2870
  /* @__PURE__ */ jsx(Button, { type: "submit", disabled: isSubmitting, children: isSubmitting ? /* @__PURE__ */ jsxs(Fragment, { children: [
2803
2871
  /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }),
2804
2872
  labels.creating
2805
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2806
- /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4 mr-2" }),
2807
- labels.create
2808
- ] }) })
2873
+ ] }) : labels.create })
2809
2874
  ] })
2810
2875
  ] }) })
2811
2876
  ] }) });
@@ -2876,7 +2941,7 @@ function SupportPage({ isDemo = false }) {
2876
2941
  // package.json
2877
2942
  var package_default = {
2878
2943
  name: "@djangocfg/ext-support",
2879
- version: "1.0.24",
2944
+ version: "1.0.26",
2880
2945
  description: "Support ticket system extension for DjangoCFG",
2881
2946
  keywords: [
2882
2947
  "django",