@greatapps/common 1.1.81 → 1.1.82

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.
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { Bell, Check } from "lucide-react";
2
+ import { Bell, Check, Loader2 } from "lucide-react";
3
3
  import { Button } from "../../ui/buttons/Button";
4
4
  import {
5
5
  Tabs,
@@ -7,24 +7,65 @@ import {
7
7
  TabsList,
8
8
  TabsTrigger
9
9
  } from "../../ui/data-display/Tabs";
10
- import { useMessages } from "../../../modules/users/hooks/messages.hook";
10
+ import { useInfiniteMessages } from "../../../modules/users/hooks/messages.hook";
11
11
  import NotificationCard from "../../widgets/notifications/NotificationCard";
12
12
  import { formatDistanceToNow } from "date-fns";
13
13
  import { ptBR } from "date-fns/locale";
14
+ function NotificationsList({
15
+ pages,
16
+ isFetchingNextPage,
17
+ hasNextPage,
18
+ fetchNextPage
19
+ }) {
20
+ const notifications = pages?.pages.flatMap((p) => p.data) ?? [];
21
+ const handleScroll = (e) => {
22
+ const target = e.currentTarget;
23
+ const nearBottom = target.scrollTop + target.clientHeight >= target.scrollHeight - 50;
24
+ if (!nearBottom) return;
25
+ if (hasNextPage && !isFetchingNextPage) {
26
+ fetchNextPage();
27
+ }
28
+ };
29
+ return /* @__PURE__ */ jsxs(
30
+ "div",
31
+ {
32
+ className: "flex flex-col border border-gray-200 rounded-lg w-full bg-white max-h-[70vh] overflow-y-auto custom-scrollbar",
33
+ onScroll: handleScroll,
34
+ children: [
35
+ notifications.map((notification, index) => /* @__PURE__ */ jsx(
36
+ NotificationCard,
37
+ {
38
+ icon: /* @__PURE__ */ jsx(Bell, { size: 20, className: "text-pages-600" }),
39
+ iconBgColor: "bg-pages-100",
40
+ title: notification.subject,
41
+ description: notification.message,
42
+ gapPx: 16,
43
+ time: formatDistanceToNow(new Date(notification.datetime_add), {
44
+ addSuffix: true,
45
+ locale: ptBR
46
+ }),
47
+ isUnread: notification.status === 1,
48
+ showBorder: index < notifications.length - 1
49
+ },
50
+ notification.id
51
+ )),
52
+ isFetchingNextPage && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-4", children: /* @__PURE__ */ jsx(Loader2, { size: 20, className: "animate-spin text-gray-400" }) })
53
+ ]
54
+ }
55
+ );
56
+ }
14
57
  function NotificationPageContent() {
15
- const { data: unreadedNotifications } = useMessages({
16
- page: 1,
17
- limit: 10,
18
- status: 1,
58
+ const unreadNotifications = useInfiniteMessages({
59
+ unread: true,
19
60
  type: "notification",
20
61
  order: "desc"
21
62
  });
22
- const { data: allNotifications } = useMessages({
23
- page: 1,
24
- limit: 10,
63
+ const allNotifications = useInfiniteMessages({
25
64
  type: "notification",
26
65
  order: "desc"
27
66
  });
67
+ const totalAll = allNotifications.data?.pages[0]?.total ?? 0;
68
+ const totalUnread = unreadNotifications.data?.pages[0]?.total ?? 0;
28
69
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-6 lg:gap-10 px-4 py-6 lg:px-20 lg:py-14", children: [
29
70
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4", children: [
30
71
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
@@ -49,7 +90,7 @@ function NotificationPageContent() {
49
90
  " ",
50
91
  /* @__PURE__ */ jsxs("span", { className: "text-gray-500", children: [
51
92
  "(",
52
- allNotifications?.data.length,
93
+ totalAll,
53
94
  ")"
54
95
  ] })
55
96
  ] }),
@@ -58,45 +99,29 @@ function NotificationPageContent() {
58
99
  " ",
59
100
  /* @__PURE__ */ jsxs("span", { className: "text-gray-500", children: [
60
101
  "(",
61
- unreadedNotifications?.data.length,
102
+ totalUnread,
62
103
  ")"
63
104
  ] })
64
105
  ] })
65
106
  ] }),
66
- /* @__PURE__ */ jsx(TabsContent, { value: "all", children: /* @__PURE__ */ jsx("div", { className: "flex flex-col border border-gray-200 rounded-lg w-full bg-white", children: allNotifications?.data.map((notification, index) => /* @__PURE__ */ jsx(
67
- NotificationCard,
107
+ /* @__PURE__ */ jsx(TabsContent, { value: "all", children: /* @__PURE__ */ jsx(
108
+ NotificationsList,
68
109
  {
69
- icon: /* @__PURE__ */ jsx(Bell, { size: 20, className: "text-pages-600" }),
70
- iconBgColor: "bg-pages-100",
71
- title: notification.subject,
72
- description: notification.message,
73
- gapPx: 16,
74
- time: formatDistanceToNow(new Date(notification.datetime_add), {
75
- addSuffix: true,
76
- locale: ptBR
77
- }),
78
- isUnread: notification.status === 1,
79
- showBorder: index < allNotifications.data.length - 1
80
- },
81
- notification.id
82
- )) }) }),
83
- /* @__PURE__ */ jsx(TabsContent, { value: "unreads", children: /* @__PURE__ */ jsx("div", { className: "flex flex-col border border-gray-200 rounded-lg w-full bg-white", children: unreadedNotifications?.data.map((notification, index) => /* @__PURE__ */ jsx(
84
- NotificationCard,
110
+ pages: allNotifications.data,
111
+ isFetchingNextPage: allNotifications.isFetchingNextPage,
112
+ hasNextPage: allNotifications.hasNextPage,
113
+ fetchNextPage: allNotifications.fetchNextPage
114
+ }
115
+ ) }),
116
+ /* @__PURE__ */ jsx(TabsContent, { value: "unreads", children: /* @__PURE__ */ jsx(
117
+ NotificationsList,
85
118
  {
86
- icon: /* @__PURE__ */ jsx(Bell, { size: 20, className: "text-pages-600" }),
87
- iconBgColor: "bg-pages-100",
88
- title: notification.subject,
89
- description: notification.message,
90
- gapPx: 16,
91
- time: formatDistanceToNow(new Date(notification.datetime_add), {
92
- addSuffix: true,
93
- locale: ptBR
94
- }),
95
- isUnread: notification.status === 1,
96
- showBorder: index < unreadedNotifications.data.length - 1
97
- },
98
- notification.id
99
- )) }) })
119
+ pages: unreadNotifications.data,
120
+ isFetchingNextPage: unreadNotifications.isFetchingNextPage,
121
+ hasNextPage: unreadNotifications.hasNextPage,
122
+ fetchNextPage: unreadNotifications.fetchNextPage
123
+ }
124
+ ) })
100
125
  ]
101
126
  }
102
127
  )
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/pages/notifications/Notifications.tsx"],"sourcesContent":["import { Bell, Check } from \"lucide-react\";\nimport { Button } from \"../../ui/buttons/Button\";\nimport {\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n} from \"../../ui/data-display/Tabs\";\nimport { useMessages } from \"../../../modules/users/hooks/messages.hook\";\nimport NotificationCard from \"../../widgets/notifications/NotificationCard\";\nimport { formatDistanceToNow } from \"date-fns\";\nimport { ptBR } from \"date-fns/locale\";\n\nexport function NotificationPageContent() {\n const { data: unreadedNotifications } = useMessages({\n page: 1,\n limit: 10,\n status: 1,\n type: \"notification\",\n order: \"desc\",\n });\n const { data: allNotifications } = useMessages({\n page: 1,\n limit: 10,\n type: \"notification\",\n order: \"desc\",\n });\n return (\n <div className=\"flex flex-col gap-6 lg:gap-10 px-4 py-6 lg:px-20 lg:py-14\">\n <div className=\"flex items-center justify-between gap-4\">\n <div className=\"flex flex-col gap-2\">\n <h1 className=\"paragraph-xlarge-semibold lg:title-large-semibold text-gray-950\">\n Notificações\n </h1>\n <p className=\"paragraph-medium-regular text-gray-600 hidden lg:block\">\n Tenha controle total sobre páginas, experiências e conversões.\n </p>\n </div>\n <Button variant={\"secondary\"} className=\"h-10! lg:h-12! shrink-0\">\n <Check size={20} />\n <span className=\"lg:block hidden\">Marcar todos como lidos</span>\n <span className=\"lg:hidden\">Marcar como lidos</span>\n </Button>\n </div>\n <Tabs\n defaultValue=\"all\"\n className=\"flex flex-col w-full bg-transparent gap-6 lg:gap-8\"\n >\n <TabsList className=\"h-auto pb-0 bg-transparent\">\n <TabsTrigger className=\"gap-2\" value=\"all\">\n Todas as notificações{\" \"}\n <span className=\"text-gray-500\">\n ({allNotifications?.data.length})\n </span>\n </TabsTrigger>\n <TabsTrigger className=\"gap-2\" value=\"unreads\">\n Não lidas{\" \"}\n <span className=\"text-gray-500\">\n ({unreadedNotifications?.data.length})\n </span>\n </TabsTrigger>\n </TabsList>\n\n <TabsContent value=\"all\">\n <div className=\"flex flex-col border border-gray-200 rounded-lg w-full bg-white\">\n {allNotifications?.data.map((notification, index) => (\n <NotificationCard\n key={notification.id}\n icon={<Bell size={20} className=\"text-pages-600\" />}\n iconBgColor=\"bg-pages-100\"\n title={notification.subject}\n description={notification.message}\n gapPx={16}\n time={formatDistanceToNow(new Date(notification.datetime_add), {\n addSuffix: true,\n locale: ptBR,\n })}\n isUnread={notification.status === 1}\n showBorder={index < allNotifications.data.length - 1}\n />\n ))}\n </div>\n </TabsContent>\n\n <TabsContent value=\"unreads\">\n <div className=\"flex flex-col border border-gray-200 rounded-lg w-full bg-white\">\n {unreadedNotifications?.data.map((notification, index) => (\n <NotificationCard\n key={notification.id}\n icon={<Bell size={20} className=\"text-pages-600\" />}\n iconBgColor=\"bg-pages-100\"\n title={notification.subject}\n description={notification.message}\n gapPx={16}\n time={formatDistanceToNow(new Date(notification.datetime_add), {\n addSuffix: true,\n locale: ptBR,\n })}\n isUnread={notification.status === 1}\n showBorder={index < unreadedNotifications.data.length - 1}\n />\n ))}\n </div>\n </TabsContent>\n </Tabs>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/pages/notifications/Notifications.tsx"],"sourcesContent":["import { Bell, Check, Loader2 } from \"lucide-react\";\nimport { Button } from \"../../ui/buttons/Button\";\nimport {\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n} from \"../../ui/data-display/Tabs\";\nimport { useInfiniteMessages } from \"../../../modules/users/hooks/messages.hook\";\nimport NotificationCard from \"../../widgets/notifications/NotificationCard\";\nimport { formatDistanceToNow } from \"date-fns\";\nimport { ptBR } from \"date-fns/locale\";\nimport React from \"react\";\n\nfunction NotificationsList({\n pages,\n isFetchingNextPage,\n hasNextPage,\n fetchNextPage,\n}: {\n pages: ReturnType<typeof useInfiniteMessages>[\"data\"];\n isFetchingNextPage: boolean;\n hasNextPage: boolean;\n fetchNextPage: () => void;\n}) {\n const notifications = pages?.pages.flatMap((p) => p.data) ?? [];\n\n const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {\n const target = e.currentTarget;\n const nearBottom =\n target.scrollTop + target.clientHeight >= target.scrollHeight - 50;\n if (!nearBottom) return;\n if (hasNextPage && !isFetchingNextPage) {\n fetchNextPage();\n }\n };\n\n return (\n <div\n className=\"flex flex-col border border-gray-200 rounded-lg w-full bg-white max-h-[70vh] overflow-y-auto custom-scrollbar\"\n onScroll={handleScroll}\n >\n {notifications.map((notification, index) => (\n <NotificationCard\n key={notification.id}\n icon={<Bell size={20} className=\"text-pages-600\" />}\n iconBgColor=\"bg-pages-100\"\n title={notification.subject}\n description={notification.message}\n gapPx={16}\n time={formatDistanceToNow(new Date(notification.datetime_add), {\n addSuffix: true,\n locale: ptBR,\n })}\n isUnread={notification.status === 1}\n showBorder={index < notifications.length - 1}\n />\n ))}\n {isFetchingNextPage && (\n <div className=\"flex items-center justify-center py-4\">\n <Loader2 size={20} className=\"animate-spin text-gray-400\" />\n </div>\n )}\n </div>\n );\n}\n\nexport function NotificationPageContent() {\n const unreadNotifications = useInfiniteMessages({\n unread: true,\n type: \"notification\",\n order: \"desc\",\n });\n const allNotifications = useInfiniteMessages({\n type: \"notification\",\n order: \"desc\",\n });\n\n const totalAll = allNotifications.data?.pages[0]?.total ?? 0;\n const totalUnread = unreadNotifications.data?.pages[0]?.total ?? 0;\n\n return (\n <div className=\"flex flex-col gap-6 lg:gap-10 px-4 py-6 lg:px-20 lg:py-14\">\n <div className=\"flex items-center justify-between gap-4\">\n <div className=\"flex flex-col gap-2\">\n <h1 className=\"paragraph-xlarge-semibold lg:title-large-semibold text-gray-950\">\n Notificações\n </h1>\n <p className=\"paragraph-medium-regular text-gray-600 hidden lg:block\">\n Tenha controle total sobre páginas, experiências e conversões.\n </p>\n </div>\n <Button variant={\"secondary\"} className=\"h-10! lg:h-12! shrink-0\">\n <Check size={20} />\n <span className=\"lg:block hidden\">Marcar todos como lidos</span>\n <span className=\"lg:hidden\">Marcar como lidos</span>\n </Button>\n </div>\n <Tabs\n defaultValue=\"all\"\n className=\"flex flex-col w-full bg-transparent gap-6 lg:gap-8\"\n >\n <TabsList className=\"h-auto pb-0 bg-transparent\">\n <TabsTrigger className=\"gap-2\" value=\"all\">\n Todas as notificações{\" \"}\n <span className=\"text-gray-500\">({totalAll})</span>\n </TabsTrigger>\n <TabsTrigger className=\"gap-2\" value=\"unreads\">\n Não lidas{\" \"}\n <span className=\"text-gray-500\">({totalUnread})</span>\n </TabsTrigger>\n </TabsList>\n\n <TabsContent value=\"all\">\n <NotificationsList\n pages={allNotifications.data}\n isFetchingNextPage={allNotifications.isFetchingNextPage}\n hasNextPage={allNotifications.hasNextPage}\n fetchNextPage={allNotifications.fetchNextPage}\n />\n </TabsContent>\n\n <TabsContent value=\"unreads\">\n <NotificationsList\n pages={unreadNotifications.data}\n isFetchingNextPage={unreadNotifications.isFetchingNextPage}\n hasNextPage={unreadNotifications.hasNextPage}\n fetchNextPage={unreadNotifications.fetchNextPage}\n />\n </TabsContent>\n </Tabs>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1,16 +1,32 @@
1
1
  "use client";
2
- import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
2
+ import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
3
3
  import { listMessagesAction } from "../action/list-messages.action";
4
4
  import { markMessageAsReadAction } from "../action/mark-message-as-read.action";
5
5
  import { markAllMessagesAsReadAction } from "../action/mark-all-messages-as-read.action";
6
6
  import { safeServerAction } from "../../../utils/safeServerAction";
7
7
  const MESSAGES_QUERY_KEY = ["messages"];
8
+ const INFINITE_MESSAGES_PAGE_SIZE = 10;
8
9
  function useMessages(params) {
9
10
  return useQuery({
10
11
  queryKey: [...MESSAGES_QUERY_KEY, params],
11
12
  queryFn: () => safeServerAction(listMessagesAction, params)
12
13
  });
13
14
  }
15
+ function useInfiniteMessages(params) {
16
+ return useInfiniteQuery({
17
+ queryKey: [...MESSAGES_QUERY_KEY, "infinite", params],
18
+ queryFn: ({ pageParam }) => safeServerAction(listMessagesAction, {
19
+ ...params,
20
+ page: pageParam,
21
+ limit: INFINITE_MESSAGES_PAGE_SIZE
22
+ }),
23
+ getNextPageParam: (lastPage, allPages) => {
24
+ const loaded = allPages.reduce((sum, p) => sum + p.data.length, 0);
25
+ return loaded < lastPage.total ? allPages.length + 1 : void 0;
26
+ },
27
+ initialPageParam: 1
28
+ });
29
+ }
14
30
  function useMarkMessageAsRead() {
15
31
  const queryClient = useQueryClient();
16
32
  return useMutation({
@@ -31,6 +47,7 @@ function useMarkAllMessagesAsRead() {
31
47
  }
32
48
  export {
33
49
  MESSAGES_QUERY_KEY,
50
+ useInfiniteMessages,
34
51
  useMarkAllMessagesAsRead,
35
52
  useMarkMessageAsRead,
36
53
  useMessages
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/modules/users/hooks/messages.hook.ts"],"sourcesContent":["'use client';\r\n\r\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\r\nimport { listMessagesAction } from '../action/list-messages.action';\r\nimport { markMessageAsReadAction } from '../action/mark-message-as-read.action';\r\nimport { markAllMessagesAsReadAction } from '../action/mark-all-messages-as-read.action';\r\nimport { safeServerAction } from '../../../utils/safeServerAction';\r\nimport type { FindMessagesParams } from '../schema/messages.schema';\r\n\r\nexport const MESSAGES_QUERY_KEY = ['messages'];\r\n\r\nexport function useMessages(params?: Partial<FindMessagesParams>) {\r\n return useQuery({\r\n queryKey: [...MESSAGES_QUERY_KEY, params],\r\n queryFn: () => safeServerAction(listMessagesAction, params),\r\n });\r\n}\r\n\r\nexport function useMarkMessageAsRead() {\r\n const queryClient = useQueryClient();\r\n\r\n return useMutation({\r\n mutationFn: (messageId: string) => safeServerAction(markMessageAsReadAction, messageId),\r\n onSuccess: () => {\r\n queryClient.invalidateQueries({ queryKey: MESSAGES_QUERY_KEY });\r\n },\r\n });\r\n}\r\n\r\nexport function useMarkAllMessagesAsRead() {\r\n const queryClient = useQueryClient();\r\n\r\n return useMutation({\r\n mutationFn: () => safeServerAction(markAllMessagesAsReadAction),\r\n onSuccess: () => {\r\n queryClient.invalidateQueries({ queryKey: MESSAGES_QUERY_KEY });\r\n },\r\n });\r\n}\r\n"],"mappings":";AAEA,SAAS,aAAa,UAAU,sBAAsB;AACtD,SAAS,0BAA0B;AACnC,SAAS,+BAA+B;AACxC,SAAS,mCAAmC;AAC5C,SAAS,wBAAwB;AAG1B,MAAM,qBAAqB,CAAC,UAAU;AAEtC,SAAS,YAAY,QAAsC;AAChE,SAAO,SAAS;AAAA,IACd,UAAU,CAAC,GAAG,oBAAoB,MAAM;AAAA,IACxC,SAAS,MAAM,iBAAiB,oBAAoB,MAAM;AAAA,EAC5D,CAAC;AACH;AAEO,SAAS,uBAAuB;AACrC,QAAM,cAAc,eAAe;AAEnC,SAAO,YAAY;AAAA,IACjB,YAAY,CAAC,cAAsB,iBAAiB,yBAAyB,SAAS;AAAA,IACtF,WAAW,MAAM;AACf,kBAAY,kBAAkB,EAAE,UAAU,mBAAmB,CAAC;AAAA,IAChE;AAAA,EACF,CAAC;AACH;AAEO,SAAS,2BAA2B;AACzC,QAAM,cAAc,eAAe;AAEnC,SAAO,YAAY;AAAA,IACjB,YAAY,MAAM,iBAAiB,2BAA2B;AAAA,IAC9D,WAAW,MAAM;AACf,kBAAY,kBAAkB,EAAE,UAAU,mBAAmB,CAAC;AAAA,IAChE;AAAA,EACF,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../../../../src/modules/users/hooks/messages.hook.ts"],"sourcesContent":["'use client';\r\n\r\nimport { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\r\nimport { listMessagesAction } from '../action/list-messages.action';\r\nimport { markMessageAsReadAction } from '../action/mark-message-as-read.action';\r\nimport { markAllMessagesAsReadAction } from '../action/mark-all-messages-as-read.action';\r\nimport { safeServerAction } from '../../../utils/safeServerAction';\r\nimport type { FindMessagesParams } from '../schema/messages.schema';\r\n\r\nexport const MESSAGES_QUERY_KEY = ['messages'];\r\n\r\nconst INFINITE_MESSAGES_PAGE_SIZE = 10;\r\n\r\nexport function useMessages(params?: Partial<FindMessagesParams>) {\r\n return useQuery({\r\n queryKey: [...MESSAGES_QUERY_KEY, params],\r\n queryFn: () => safeServerAction(listMessagesAction, params),\r\n });\r\n}\r\n\r\nexport function useInfiniteMessages(params?: Partial<Omit<FindMessagesParams, 'page' | 'limit'>>) {\r\n return useInfiniteQuery({\r\n queryKey: [...MESSAGES_QUERY_KEY, 'infinite', params],\r\n queryFn: ({ pageParam }) =>\r\n safeServerAction(listMessagesAction, {\r\n ...params,\r\n page: pageParam as number,\r\n limit: INFINITE_MESSAGES_PAGE_SIZE,\r\n }),\r\n getNextPageParam: (lastPage, allPages) => {\r\n const loaded = allPages.reduce((sum, p) => sum + p.data.length, 0);\r\n return loaded < lastPage.total ? allPages.length + 1 : undefined;\r\n },\r\n initialPageParam: 1,\r\n });\r\n}\r\n\r\nexport function useMarkMessageAsRead() {\r\n const queryClient = useQueryClient();\r\n\r\n return useMutation({\r\n mutationFn: (messageId: string) => safeServerAction(markMessageAsReadAction, messageId),\r\n onSuccess: () => {\r\n queryClient.invalidateQueries({ queryKey: MESSAGES_QUERY_KEY });\r\n },\r\n });\r\n}\r\n\r\nexport function useMarkAllMessagesAsRead() {\r\n const queryClient = useQueryClient();\r\n\r\n return useMutation({\r\n mutationFn: () => safeServerAction(markAllMessagesAsReadAction),\r\n onSuccess: () => {\r\n queryClient.invalidateQueries({ queryKey: MESSAGES_QUERY_KEY });\r\n },\r\n });\r\n}\r\n"],"mappings":";AAEA,SAAS,kBAAkB,aAAa,UAAU,sBAAsB;AACxE,SAAS,0BAA0B;AACnC,SAAS,+BAA+B;AACxC,SAAS,mCAAmC;AAC5C,SAAS,wBAAwB;AAG1B,MAAM,qBAAqB,CAAC,UAAU;AAE7C,MAAM,8BAA8B;AAE7B,SAAS,YAAY,QAAsC;AAChE,SAAO,SAAS;AAAA,IACd,UAAU,CAAC,GAAG,oBAAoB,MAAM;AAAA,IACxC,SAAS,MAAM,iBAAiB,oBAAoB,MAAM;AAAA,EAC5D,CAAC;AACH;AAEO,SAAS,oBAAoB,QAA8D;AAChG,SAAO,iBAAiB;AAAA,IACtB,UAAU,CAAC,GAAG,oBAAoB,YAAY,MAAM;AAAA,IACpD,SAAS,CAAC,EAAE,UAAU,MACpB,iBAAiB,oBAAoB;AAAA,MACnC,GAAG;AAAA,MACH,MAAM;AAAA,MACN,OAAO;AAAA,IACT,CAAC;AAAA,IACH,kBAAkB,CAAC,UAAU,aAAa;AACxC,YAAM,SAAS,SAAS,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,KAAK,QAAQ,CAAC;AACjE,aAAO,SAAS,SAAS,QAAQ,SAAS,SAAS,IAAI;AAAA,IACzD;AAAA,IACA,kBAAkB;AAAA,EACpB,CAAC;AACH;AAEO,SAAS,uBAAuB;AACrC,QAAM,cAAc,eAAe;AAEnC,SAAO,YAAY;AAAA,IACjB,YAAY,CAAC,cAAsB,iBAAiB,yBAAyB,SAAS;AAAA,IACtF,WAAW,MAAM;AACf,kBAAY,kBAAkB,EAAE,UAAU,mBAAmB,CAAC;AAAA,IAChE;AAAA,EACF,CAAC;AACH;AAEO,SAAS,2BAA2B;AACzC,QAAM,cAAc,eAAe;AAEnC,SAAO,YAAY;AAAA,IACjB,YAAY,MAAM,iBAAiB,2BAA2B;AAAA,IAC9D,WAAW,MAAM;AACf,kBAAY,kBAAkB,EAAE,UAAU,mBAAmB,CAAC;AAAA,IAChE;AAAA,EACF,CAAC;AACH;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greatapps/common",
3
- "version": "1.1.81",
3
+ "version": "1.1.82",
4
4
  "description": "Shared library for GreatApps frontend applications",
5
5
  "main": "./dist/index.mjs",
6
6
  "types": "./src/index.ts",
@@ -1,4 +1,4 @@
1
- import { Bell, Check } from "lucide-react";
1
+ import { Bell, Check, Loader2 } from "lucide-react";
2
2
  import { Button } from "../../ui/buttons/Button";
3
3
  import {
4
4
  Tabs,
@@ -6,25 +6,79 @@ import {
6
6
  TabsList,
7
7
  TabsTrigger,
8
8
  } from "../../ui/data-display/Tabs";
9
- import { useMessages } from "../../../modules/users/hooks/messages.hook";
9
+ import { useInfiniteMessages } from "../../../modules/users/hooks/messages.hook";
10
10
  import NotificationCard from "../../widgets/notifications/NotificationCard";
11
11
  import { formatDistanceToNow } from "date-fns";
12
12
  import { ptBR } from "date-fns/locale";
13
+ import React from "react";
14
+
15
+ function NotificationsList({
16
+ pages,
17
+ isFetchingNextPage,
18
+ hasNextPage,
19
+ fetchNextPage,
20
+ }: {
21
+ pages: ReturnType<typeof useInfiniteMessages>["data"];
22
+ isFetchingNextPage: boolean;
23
+ hasNextPage: boolean;
24
+ fetchNextPage: () => void;
25
+ }) {
26
+ const notifications = pages?.pages.flatMap((p) => p.data) ?? [];
27
+
28
+ const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
29
+ const target = e.currentTarget;
30
+ const nearBottom =
31
+ target.scrollTop + target.clientHeight >= target.scrollHeight - 50;
32
+ if (!nearBottom) return;
33
+ if (hasNextPage && !isFetchingNextPage) {
34
+ fetchNextPage();
35
+ }
36
+ };
37
+
38
+ return (
39
+ <div
40
+ className="flex flex-col border border-gray-200 rounded-lg w-full bg-white max-h-[70vh] overflow-y-auto custom-scrollbar"
41
+ onScroll={handleScroll}
42
+ >
43
+ {notifications.map((notification, index) => (
44
+ <NotificationCard
45
+ key={notification.id}
46
+ icon={<Bell size={20} className="text-pages-600" />}
47
+ iconBgColor="bg-pages-100"
48
+ title={notification.subject}
49
+ description={notification.message}
50
+ gapPx={16}
51
+ time={formatDistanceToNow(new Date(notification.datetime_add), {
52
+ addSuffix: true,
53
+ locale: ptBR,
54
+ })}
55
+ isUnread={notification.status === 1}
56
+ showBorder={index < notifications.length - 1}
57
+ />
58
+ ))}
59
+ {isFetchingNextPage && (
60
+ <div className="flex items-center justify-center py-4">
61
+ <Loader2 size={20} className="animate-spin text-gray-400" />
62
+ </div>
63
+ )}
64
+ </div>
65
+ );
66
+ }
13
67
 
14
68
  export function NotificationPageContent() {
15
- const { data: unreadedNotifications } = useMessages({
16
- page: 1,
17
- limit: 10,
18
- status: 1,
69
+ const unreadNotifications = useInfiniteMessages({
70
+ unread: true,
19
71
  type: "notification",
20
72
  order: "desc",
21
73
  });
22
- const { data: allNotifications } = useMessages({
23
- page: 1,
24
- limit: 10,
74
+ const allNotifications = useInfiniteMessages({
25
75
  type: "notification",
26
76
  order: "desc",
27
77
  });
78
+
79
+ const totalAll = allNotifications.data?.pages[0]?.total ?? 0;
80
+ const totalUnread = unreadNotifications.data?.pages[0]?.total ?? 0;
81
+
28
82
  return (
29
83
  <div className="flex flex-col gap-6 lg:gap-10 px-4 py-6 lg:px-20 lg:py-14">
30
84
  <div className="flex items-center justify-between gap-4">
@@ -49,58 +103,30 @@ export function NotificationPageContent() {
49
103
  <TabsList className="h-auto pb-0 bg-transparent">
50
104
  <TabsTrigger className="gap-2" value="all">
51
105
  Todas as notificações{" "}
52
- <span className="text-gray-500">
53
- ({allNotifications?.data.length})
54
- </span>
106
+ <span className="text-gray-500">({totalAll})</span>
55
107
  </TabsTrigger>
56
108
  <TabsTrigger className="gap-2" value="unreads">
57
109
  Não lidas{" "}
58
- <span className="text-gray-500">
59
- ({unreadedNotifications?.data.length})
60
- </span>
110
+ <span className="text-gray-500">({totalUnread})</span>
61
111
  </TabsTrigger>
62
112
  </TabsList>
63
113
 
64
114
  <TabsContent value="all">
65
- <div className="flex flex-col border border-gray-200 rounded-lg w-full bg-white">
66
- {allNotifications?.data.map((notification, index) => (
67
- <NotificationCard
68
- key={notification.id}
69
- icon={<Bell size={20} className="text-pages-600" />}
70
- iconBgColor="bg-pages-100"
71
- title={notification.subject}
72
- description={notification.message}
73
- gapPx={16}
74
- time={formatDistanceToNow(new Date(notification.datetime_add), {
75
- addSuffix: true,
76
- locale: ptBR,
77
- })}
78
- isUnread={notification.status === 1}
79
- showBorder={index < allNotifications.data.length - 1}
80
- />
81
- ))}
82
- </div>
115
+ <NotificationsList
116
+ pages={allNotifications.data}
117
+ isFetchingNextPage={allNotifications.isFetchingNextPage}
118
+ hasNextPage={allNotifications.hasNextPage}
119
+ fetchNextPage={allNotifications.fetchNextPage}
120
+ />
83
121
  </TabsContent>
84
122
 
85
123
  <TabsContent value="unreads">
86
- <div className="flex flex-col border border-gray-200 rounded-lg w-full bg-white">
87
- {unreadedNotifications?.data.map((notification, index) => (
88
- <NotificationCard
89
- key={notification.id}
90
- icon={<Bell size={20} className="text-pages-600" />}
91
- iconBgColor="bg-pages-100"
92
- title={notification.subject}
93
- description={notification.message}
94
- gapPx={16}
95
- time={formatDistanceToNow(new Date(notification.datetime_add), {
96
- addSuffix: true,
97
- locale: ptBR,
98
- })}
99
- isUnread={notification.status === 1}
100
- showBorder={index < unreadedNotifications.data.length - 1}
101
- />
102
- ))}
103
- </div>
124
+ <NotificationsList
125
+ pages={unreadNotifications.data}
126
+ isFetchingNextPage={unreadNotifications.isFetchingNextPage}
127
+ hasNextPage={unreadNotifications.hasNextPage}
128
+ fetchNextPage={unreadNotifications.fetchNextPage}
129
+ />
104
130
  </TabsContent>
105
131
  </Tabs>
106
132
  </div>
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
3
+ import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
4
4
  import { listMessagesAction } from '../action/list-messages.action';
5
5
  import { markMessageAsReadAction } from '../action/mark-message-as-read.action';
6
6
  import { markAllMessagesAsReadAction } from '../action/mark-all-messages-as-read.action';
@@ -9,6 +9,8 @@ import type { FindMessagesParams } from '../schema/messages.schema';
9
9
 
10
10
  export const MESSAGES_QUERY_KEY = ['messages'];
11
11
 
12
+ const INFINITE_MESSAGES_PAGE_SIZE = 10;
13
+
12
14
  export function useMessages(params?: Partial<FindMessagesParams>) {
13
15
  return useQuery({
14
16
  queryKey: [...MESSAGES_QUERY_KEY, params],
@@ -16,6 +18,23 @@ export function useMessages(params?: Partial<FindMessagesParams>) {
16
18
  });
17
19
  }
18
20
 
21
+ export function useInfiniteMessages(params?: Partial<Omit<FindMessagesParams, 'page' | 'limit'>>) {
22
+ return useInfiniteQuery({
23
+ queryKey: [...MESSAGES_QUERY_KEY, 'infinite', params],
24
+ queryFn: ({ pageParam }) =>
25
+ safeServerAction(listMessagesAction, {
26
+ ...params,
27
+ page: pageParam as number,
28
+ limit: INFINITE_MESSAGES_PAGE_SIZE,
29
+ }),
30
+ getNextPageParam: (lastPage, allPages) => {
31
+ const loaded = allPages.reduce((sum, p) => sum + p.data.length, 0);
32
+ return loaded < lastPage.total ? allPages.length + 1 : undefined;
33
+ },
34
+ initialPageParam: 1,
35
+ });
36
+ }
37
+
19
38
  export function useMarkMessageAsRead() {
20
39
  const queryClient = useQueryClient();
21
40