@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.
- package/dist/components/pages/notifications/Notifications.mjs +68 -43
- package/dist/components/pages/notifications/Notifications.mjs.map +1 -1
- package/dist/modules/users/hooks/messages.hook.mjs +18 -1
- package/dist/modules/users/hooks/messages.hook.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/pages/notifications/Notifications.tsx +77 -51
- package/src/modules/users/hooks/messages.hook.ts +20 -1
|
@@ -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 {
|
|
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
|
|
16
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
102
|
+
totalUnread,
|
|
62
103
|
")"
|
|
63
104
|
] })
|
|
64
105
|
] })
|
|
65
106
|
] }),
|
|
66
|
-
/* @__PURE__ */ jsx(TabsContent, { value: "all", children: /* @__PURE__ */ jsx(
|
|
67
|
-
|
|
107
|
+
/* @__PURE__ */ jsx(TabsContent, { value: "all", children: /* @__PURE__ */ jsx(
|
|
108
|
+
NotificationsList,
|
|
68
109
|
{
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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 {
|
|
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;
|
|
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,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 {
|
|
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
|
|
16
|
-
|
|
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
|
|
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
|
-
<
|
|
66
|
-
{allNotifications
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
<
|
|
87
|
-
{
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
|