@greatapps/common 1.1.187 → 1.1.188

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.
@@ -21,11 +21,11 @@ import {
21
21
  LIST_PROJECT_USERS_BASE_KEY
22
22
  } from "../../modules/projects/hooks/list-project-users.hook";
23
23
  import {
24
- useListAvailableUsers,
25
- LIST_AVAILABLE_USERS_BASE_KEY
26
- } from "../../modules/projects/hooks/list-available-users.hook";
27
- import { addProjectUserAction } from "../../modules/projects/actions/add-project-user.action";
24
+ useListAllAccountUsers,
25
+ LIST_ALL_ACCOUNT_USERS_BASE_KEY
26
+ } from "../../modules/projects/hooks/list-all-account-users.hook";
28
27
  import { removeProjectUserAction } from "../../modules/projects/actions/remove-project-user.action";
28
+ import { addProjectUserAction } from "../../modules/projects/actions/add-project-user.action";
29
29
  function UserItemSkeleton() {
30
30
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-2 py-1.5 gap-2", children: [
31
31
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-1", children: [
@@ -52,61 +52,38 @@ function UsersSelectorPopover({
52
52
  const timer = setTimeout(() => setSearch(searchInput), 300);
53
53
  return () => clearTimeout(timer);
54
54
  }, [searchInput]);
55
- const projectUsers = useListProjectUsers(id, search);
56
- const availableUsers = useListAvailableUsers(id, search);
57
- const inProject = projectUsers.data?.pages.flatMap((p) => p.data) ?? [];
58
- const notInProject = availableUsers.data?.pages.flatMap((p) => p.data) ?? [];
59
- const isLoading = projectUsers.isLoading || availableUsers.isLoading;
60
- const isFetchingMore = projectUsers.isFetchingNextPage || availableUsers.isFetchingNextPage;
61
- const totalInProject = projectUsers.data?.pages[0]?.total ?? inProject.length;
55
+ const accountUsers = useListAllAccountUsers(search, id || void 0);
56
+ const projectUsers = useListProjectUsers(id, "");
57
+ const allUsers = accountUsers.data?.pages.flatMap((p) => p.data) ?? [];
58
+ const isLoading = accountUsers.isLoading;
59
+ const isFetchingMore = accountUsers.isFetchingNextPage;
60
+ const totalInProject = projectUsers.data?.pages[0]?.total ?? 0;
62
61
  const invalidate = () => {
63
- queryClient.invalidateQueries({
64
- queryKey: LIST_PROJECT_USERS_BASE_KEY(id)
65
- });
66
- queryClient.invalidateQueries({
67
- queryKey: LIST_AVAILABLE_USERS_BASE_KEY(id)
68
- });
62
+ queryClient.invalidateQueries({ queryKey: LIST_PROJECT_USERS_BASE_KEY(id) });
63
+ queryClient.invalidateQueries({ queryKey: LIST_ALL_ACCOUNT_USERS_BASE_KEY() });
69
64
  };
70
- const addMutation = useMutation({
71
- mutationFn: (userId) => addProjectUserAction({ projectId: id, users: [userId] }),
72
- onSuccess: invalidate
73
- });
74
65
  const removeMutation = useMutation({
75
66
  mutationFn: (userId) => removeProjectUserAction({ projectId: id, users: [userId] }),
76
67
  onSuccess: invalidate
77
68
  });
78
- const allUsers = [
79
- ...inProject.map((u) => ({
80
- id: u.id_user,
81
- name: u.name,
82
- photo: u.photo ?? null,
83
- checked: true,
84
- removable: u.profile !== "owner" && u.profile !== "admin"
85
- })),
86
- ...notInProject.map((u) => ({
87
- id: u.id,
88
- name: u.name,
89
- photo: u.photo ?? null,
90
- checked: false,
91
- removable: true
92
- }))
93
- ];
94
- const toggleUser = (id2, checked, removable) => {
95
- if (checked) {
69
+ const addMutation = useMutation({
70
+ mutationFn: (userId) => addProjectUserAction({ projectId: id, users: [userId] }),
71
+ onSuccess: invalidate
72
+ });
73
+ const toggleUser = (userId, inProject, removable) => {
74
+ if (inProject) {
96
75
  if (!removable) return;
97
- removeMutation.mutate(id2);
76
+ removeMutation.mutate(userId);
98
77
  } else {
99
- addMutation.mutate(id2);
78
+ addMutation.mutate(userId);
100
79
  }
101
80
  };
102
81
  const handleScroll = (e) => {
103
82
  const target = e.currentTarget;
104
83
  const nearBottom = target.scrollTop + target.clientHeight >= target.scrollHeight - 50;
105
84
  if (!nearBottom) return;
106
- if (projectUsers.hasNextPage && !projectUsers.isFetchingNextPage) {
107
- projectUsers.fetchNextPage();
108
- } else if (availableUsers.hasNextPage && !availableUsers.isFetchingNextPage) {
109
- availableUsers.fetchNextPage();
85
+ if (accountUsers.hasNextPage && !accountUsers.isFetchingNextPage) {
86
+ accountUsers.fetchNextPage();
110
87
  }
111
88
  };
112
89
  return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
@@ -178,66 +155,63 @@ function UsersSelectorPopover({
178
155
  /* @__PURE__ */ jsx(UserItemSkeleton, {})
179
156
  ] }) : allUsers.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center gap-2 h-full text-gray-400 py-4", children: [
180
157
  /* @__PURE__ */ jsx(Users, { size: 28, className: "text-gray-300" }),
181
- /* @__PURE__ */ jsxs("span", { className: "paragraph-small-medium text-center text-gray-500", children: [
182
- "Nenhum usu\xE1rio neste projeto.",
183
- /* @__PURE__ */ jsx("br", {}),
184
- "Adicione um usu\xE1rio para come\xE7ar."
185
- ] })
158
+ /* @__PURE__ */ jsx("span", { className: "paragraph-small-medium text-center text-gray-500", children: "Nenhum usu\xE1rio encontrado." })
186
159
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
187
160
  /* @__PURE__ */ jsx(CommandEmpty, { children: "Nenhum resultado encontrado." }),
188
- allUsers.map((user) => /* @__PURE__ */ jsx(
189
- CommandItem,
190
- {
191
- value: user.name || String(user.id),
192
- autoFocus: false,
193
- onPointerEnter: () => setHoveredId(user.id),
194
- onPointerLeave: () => setHoveredId(null),
195
- onFocus: () => setHoveredId(user.id),
196
- onBlur: () => setHoveredId(null),
197
- onSelect: () => toggleUser(user.id, user.checked, user.removable),
198
- className: user.removable ? "cursor-pointer" : "cursor-default",
199
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between w-full", children: [
200
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
201
- /* @__PURE__ */ jsx(
202
- UserAvatar,
203
- {
204
- photo: user.photo,
205
- name: user.name ?? void 0,
206
- size: 32
207
- }
208
- ),
209
- /* @__PURE__ */ jsx("span", { className: "truncate", children: user.name })
210
- ] }),
211
- /* @__PURE__ */ jsxs(Tooltip, { open: user.removable && hoveredId === user.id, children: [
212
- /* @__PURE__ */ jsx(
213
- TooltipTrigger,
214
- {
215
- onClick: (e) => e.stopPropagation(),
216
- onPointerDown: (e) => e.stopPropagation(),
217
- children: /* @__PURE__ */ jsx(
218
- Checkbox,
219
- {
220
- checked: user.checked,
221
- disabled: !user.removable,
222
- onCheckedChange: () => toggleUser(
223
- user.id,
224
- user.checked,
225
- user.removable
226
- ),
227
- className: cn(
228
- user.removable && hoveredId === user.id ? "border-gray-950" : "",
229
- "disabled:opacity-10"
230
- )
231
- }
232
- )
233
- }
234
- ),
235
- /* @__PURE__ */ jsx(TooltipContent, { className: "z-[1002]", children: user.checked ? "Remover do projeto" : "Adicionar ao projeto" })
161
+ allUsers.map((user) => {
162
+ const inProject = user.in_project ?? false;
163
+ const removable = inProject && user.profile !== "owner" && user.profile !== "admin";
164
+ const interactive = !inProject || removable;
165
+ return /* @__PURE__ */ jsx(
166
+ CommandItem,
167
+ {
168
+ value: user.name || String(user.id),
169
+ autoFocus: false,
170
+ onPointerEnter: () => setHoveredId(user.id),
171
+ onPointerLeave: () => setHoveredId(null),
172
+ onFocus: () => setHoveredId(user.id),
173
+ onBlur: () => setHoveredId(null),
174
+ onSelect: () => toggleUser(user.id, inProject, removable),
175
+ className: interactive ? "cursor-pointer" : "cursor-default",
176
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between w-full", children: [
177
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
178
+ /* @__PURE__ */ jsx(
179
+ UserAvatar,
180
+ {
181
+ photo: user.photo,
182
+ name: user.name ?? void 0,
183
+ size: 32
184
+ }
185
+ ),
186
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: user.name })
187
+ ] }),
188
+ /* @__PURE__ */ jsxs(Tooltip, { open: interactive && hoveredId === user.id, children: [
189
+ /* @__PURE__ */ jsx(
190
+ TooltipTrigger,
191
+ {
192
+ onClick: (e) => e.stopPropagation(),
193
+ onPointerDown: (e) => e.stopPropagation(),
194
+ children: /* @__PURE__ */ jsx(
195
+ Checkbox,
196
+ {
197
+ checked: inProject,
198
+ disabled: !interactive,
199
+ onCheckedChange: () => toggleUser(user.id, inProject, removable),
200
+ className: cn(
201
+ interactive && hoveredId === user.id ? "border-gray-950" : "",
202
+ "disabled:opacity-10"
203
+ )
204
+ }
205
+ )
206
+ }
207
+ ),
208
+ /* @__PURE__ */ jsx(TooltipContent, { className: "z-[1002]", children: inProject ? "Remover do projeto" : "Adicionar ao projeto" })
209
+ ] })
236
210
  ] })
237
- ] })
238
- },
239
- user.id
240
- )),
211
+ },
212
+ user.id
213
+ );
214
+ }),
241
215
  isFetchingMore && /* @__PURE__ */ jsxs(Fragment, { children: [
242
216
  /* @__PURE__ */ jsx(UserItemSkeleton, {}),
243
217
  /* @__PURE__ */ jsx(UserItemSkeleton, {})
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/layouts/UsersSelectorPopover.tsx"],"sourcesContent":["\"use client\";\r\n\r\n\"use client\";\r\n\r\nimport { useEffect, useState } from \"react\";\r\nimport { Plus, Settings, User, Users } from \"lucide-react\";\r\nimport { UserAvatar } from \"../ui/data-display/UserAvatar\";\r\nimport { useQueryClient, useMutation } from \"@tanstack/react-query\";\r\nimport { Popover, PopoverContent, PopoverTrigger } from \"../ui/overlay/Popover\";\r\nimport { Button } from \"../ui/buttons/Button\";\r\nimport {\r\n Command,\r\n CommandEmpty,\r\n CommandInput,\r\n CommandItem,\r\n CommandList,\r\n} from \"../ui/overlay/Command\";\r\nimport { Checkbox } from \"../ui/form/Checkbox\";\r\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"../ui/overlay/Tooltip\";\r\nimport { cn } from \"../../infra/utils/clsx\";\r\nimport {\r\n useListProjectUsers,\r\n LIST_PROJECT_USERS_BASE_KEY,\r\n} from \"../../modules/projects/hooks/list-project-users.hook\";\r\nimport {\r\n useListAvailableUsers,\r\n LIST_AVAILABLE_USERS_BASE_KEY,\r\n} from \"../../modules/projects/hooks/list-available-users.hook\";\r\nimport { addProjectUserAction } from \"../../modules/projects/actions/add-project-user.action\";\r\nimport { removeProjectUserAction } from \"../../modules/projects/actions/remove-project-user.action\";\r\n\r\nexport interface UsersSelectorPopoverProps {\r\n projectId?: number;\r\n side?: \"top\" | \"bottom\" | \"left\" | \"right\";\r\n align?: \"start\" | \"center\" | \"end\";\r\n contentClassName?: string;\r\n}\r\n\r\nfunction UserItemSkeleton() {\r\n return (\r\n <div className=\"flex items-center justify-between px-2 py-1.5 gap-2\">\r\n <div className=\"flex items-center gap-2 flex-1\">\r\n <div className=\"skeleton size-8 rounded-full shrink-0\" />\r\n <div className=\"skeleton h-3.5 w-28 rounded\" />\r\n </div>\r\n <div className=\"skeleton size-4 rounded shrink-0\" />\r\n </div>\r\n );\r\n}\r\n\r\nconst GAPPS_URL = process.env.NEXT_PUBLIC_GREAT_APPS ?? \"\";\r\n\r\nexport function UsersSelectorPopover({\r\n projectId,\r\n side,\r\n align,\r\n contentClassName,\r\n}: UsersSelectorPopoverProps) {\r\n const queryClient = useQueryClient();\r\n const [open, setOpen] = useState(false);\r\n const [hoveredId, setHoveredId] = useState<number | null>(null);\r\n const [searchInput, setSearchInput] = useState(\"\");\r\n const [search, setSearch] = useState(\"\");\r\n\r\n const id = projectId ?? 0;\r\n\r\n useEffect(() => {\r\n const timer = setTimeout(() => setSearch(searchInput), 300);\r\n return () => clearTimeout(timer);\r\n }, [searchInput]);\r\n\r\n const projectUsers = useListProjectUsers(id, search);\r\n const availableUsers = useListAvailableUsers(id, search);\r\n\r\n const inProject = projectUsers.data?.pages.flatMap((p) => p.data) ?? [];\r\n const notInProject = availableUsers.data?.pages.flatMap((p) => p.data) ?? [];\r\n const isLoading = projectUsers.isLoading || availableUsers.isLoading;\r\n const isFetchingMore =\r\n projectUsers.isFetchingNextPage || availableUsers.isFetchingNextPage;\r\n const totalInProject = projectUsers.data?.pages[0]?.total ?? inProject.length;\r\n\r\n const invalidate = () => {\r\n queryClient.invalidateQueries({\r\n queryKey: LIST_PROJECT_USERS_BASE_KEY(id),\r\n });\r\n queryClient.invalidateQueries({\r\n queryKey: LIST_AVAILABLE_USERS_BASE_KEY(id),\r\n });\r\n };\r\n\r\n const addMutation = useMutation({\r\n mutationFn: (userId: number) =>\r\n addProjectUserAction({ projectId: id, users: [userId] }),\r\n onSuccess: invalidate,\r\n });\r\n\r\n const removeMutation = useMutation({\r\n mutationFn: (userId: number) =>\r\n removeProjectUserAction({ projectId: id, users: [userId] }),\r\n onSuccess: invalidate,\r\n });\r\n\r\n const allUsers = [\r\n ...inProject.map((u) => ({\r\n id: u.id_user,\r\n name: u.name,\r\n photo: u.photo ?? null,\r\n checked: true as const,\r\n removable: u.profile !== \"owner\" && u.profile !== \"admin\",\r\n })),\r\n ...notInProject.map((u) => ({\r\n id: u.id,\r\n name: u.name,\r\n photo: u.photo ?? null,\r\n checked: false as const,\r\n removable: true,\r\n })),\r\n ];\r\n\r\n const toggleUser = (id: number, checked: boolean, removable: boolean) => {\r\n if (checked) {\r\n if (!removable) return;\r\n removeMutation.mutate(id);\r\n } else {\r\n addMutation.mutate(id);\r\n }\r\n };\r\n\r\n const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {\r\n const target = e.currentTarget;\r\n const nearBottom =\r\n target.scrollTop + target.clientHeight >= target.scrollHeight - 50;\r\n if (!nearBottom) return;\r\n if (projectUsers.hasNextPage && !projectUsers.isFetchingNextPage) {\r\n projectUsers.fetchNextPage();\r\n } else if (\r\n availableUsers.hasNextPage &&\r\n !availableUsers.isFetchingNextPage\r\n ) {\r\n availableUsers.fetchNextPage();\r\n }\r\n };\r\n\r\n return (\r\n <Popover open={open} onOpenChange={setOpen}>\r\n <PopoverTrigger className=\"cursor-pointer\">\r\n <div className=\"flex items-center gap-2 text-gray-950 px-3 py-2 border border-gray-200 rounded-full hover:border-gray-950\">\r\n <span className=\"paragraph-small-semibold\">{totalInProject}</span>\r\n <User size={16} />\r\n </div>\r\n </PopoverTrigger>\r\n\r\n <PopoverContent\r\n className={cn(\r\n \"absolute right-[-88px] w-[273px] p-0 bg-white rounded-2xl shadow-md border border-gray-200\",\r\n contentClassName,\r\n )}\r\n side={side}\r\n align={align}\r\n sideOffset={8}\r\n >\r\n <div className=\"flex flex-col p-3 gap-2\">\r\n <div className=\"flex items-center justify-between\">\r\n <span className=\"paragraph-small-semibold\">Usuários</span>\r\n <div className=\"flex items-center gap-1.5\">\r\n <Button\r\n variant=\"secondary\"\r\n className=\"h-8 paragraph-small-semibold text-gray-950\"\r\n onClick={() => {\r\n window.location.href = `${GAPPS_URL}/my-teams?createNew=true`;\r\n }}\r\n >\r\n <Plus size={16} />\r\n Adicionar\r\n </Button>\r\n <Tooltip>\r\n <TooltipTrigger asChild>\r\n <Button\r\n variant=\"secondary\"\r\n className=\"p-0 size-8\"\r\n onClick={() => {\r\n window.location.href = `${GAPPS_URL}/my-teams`;\r\n }}\r\n >\r\n <Settings size={18} />\r\n </Button>\r\n </TooltipTrigger>\r\n <TooltipContent className=\"z-[1002]\">\r\n Visualizar todos usuários\r\n </TooltipContent>\r\n </Tooltip>\r\n </div>\r\n </div>\r\n\r\n <Command shouldFilter={false} className=\"gap-2\" value=\"\">\r\n <CommandInput\r\n placeholder=\"Busque aqui\"\r\n value={searchInput}\r\n onValueChange={setSearchInput}\r\n />\r\n <CommandList\r\n className=\"custom-scrollbar paragraph-small-medium text-gray-600 h-[162px]\"\r\n onScroll={handleScroll}\r\n >\r\n {isLoading ? (\r\n <>\r\n <UserItemSkeleton />\r\n <UserItemSkeleton />\r\n <UserItemSkeleton />\r\n </>\r\n ) : allUsers.length === 0 ? (\r\n <div className=\"flex flex-col items-center justify-center gap-2 h-full text-gray-400 py-4\">\r\n <Users size={28} className=\"text-gray-300\" />\r\n <span className=\"paragraph-small-medium text-center text-gray-500\">\r\n Nenhum usuário neste projeto.\r\n <br />\r\n Adicione um usuário para começar.\r\n </span>\r\n </div>\r\n ) : (\r\n <>\r\n <CommandEmpty>Nenhum resultado encontrado.</CommandEmpty>\r\n {allUsers.map((user) => (\r\n <CommandItem\r\n key={user.id}\r\n value={user.name || String(user.id)}\r\n autoFocus={false}\r\n onPointerEnter={() => setHoveredId(user.id)}\r\n onPointerLeave={() => setHoveredId(null)}\r\n onFocus={() => setHoveredId(user.id)}\r\n onBlur={() => setHoveredId(null)}\r\n onSelect={() =>\r\n toggleUser(user.id, user.checked, user.removable)\r\n }\r\n className={\r\n user.removable ? \"cursor-pointer\" : \"cursor-default\"\r\n }\r\n >\r\n <div className=\"flex items-center justify-between w-full\">\r\n <div className=\"flex items-center gap-2 min-w-0\">\r\n <UserAvatar\r\n photo={user.photo}\r\n name={user.name ?? undefined}\r\n size={32}\r\n />\r\n <span className=\"truncate\">{user.name}</span>\r\n </div>\r\n <Tooltip open={user.removable && hoveredId === user.id}>\r\n <TooltipTrigger\r\n onClick={(e) => e.stopPropagation()}\r\n onPointerDown={(e) => e.stopPropagation()}\r\n >\r\n <Checkbox\r\n checked={user.checked}\r\n disabled={!user.removable}\r\n onCheckedChange={() =>\r\n toggleUser(\r\n user.id,\r\n user.checked,\r\n user.removable,\r\n )\r\n }\r\n className={cn(\r\n user.removable && hoveredId === user.id\r\n ? \"border-gray-950\"\r\n : \"\",\r\n \"disabled:opacity-10\",\r\n )}\r\n />\r\n </TooltipTrigger>\r\n <TooltipContent className=\"z-[1002]\">\r\n {user.checked\r\n ? \"Remover do projeto\"\r\n : \"Adicionar ao projeto\"}\r\n </TooltipContent>\r\n </Tooltip>\r\n </div>\r\n </CommandItem>\r\n ))}\r\n {isFetchingMore && (\r\n <>\r\n <UserItemSkeleton />\r\n <UserItemSkeleton />\r\n </>\r\n )}\r\n </>\r\n )}\r\n </CommandList>\r\n </Command>\r\n </div>\r\n </PopoverContent>\r\n </Popover>\r\n );\r\n}\r\n"],"mappings":"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","names":["id"]}
1
+ {"version":3,"sources":["../../../src/components/layouts/UsersSelectorPopover.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { Plus, Settings, User, Users } from \"lucide-react\";\nimport { UserAvatar } from \"../ui/data-display/UserAvatar\";\nimport { useQueryClient, useMutation } from \"@tanstack/react-query\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../ui/overlay/Popover\";\nimport { Button } from \"../ui/buttons/Button\";\nimport {\n Command,\n CommandEmpty,\n CommandInput,\n CommandItem,\n CommandList,\n} from \"../ui/overlay/Command\";\nimport { Checkbox } from \"../ui/form/Checkbox\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"../ui/overlay/Tooltip\";\nimport { cn } from \"../../infra/utils/clsx\";\nimport {\n useListProjectUsers,\n LIST_PROJECT_USERS_BASE_KEY,\n} from \"../../modules/projects/hooks/list-project-users.hook\";\nimport {\n useListAllAccountUsers,\n LIST_ALL_ACCOUNT_USERS_BASE_KEY,\n} from \"../../modules/projects/hooks/list-all-account-users.hook\";\nimport { removeProjectUserAction } from \"../../modules/projects/actions/remove-project-user.action\";\nimport { addProjectUserAction } from \"../../modules/projects/actions/add-project-user.action\";\n\nexport interface UsersSelectorPopoverProps {\n projectId?: number;\n side?: \"top\" | \"bottom\" | \"left\" | \"right\";\n align?: \"start\" | \"center\" | \"end\";\n contentClassName?: string;\n}\n\nfunction UserItemSkeleton() {\n return (\n <div className=\"flex items-center justify-between px-2 py-1.5 gap-2\">\n <div className=\"flex items-center gap-2 flex-1\">\n <div className=\"skeleton size-8 rounded-full shrink-0\" />\n <div className=\"skeleton h-3.5 w-28 rounded\" />\n </div>\n <div className=\"skeleton size-4 rounded shrink-0\" />\n </div>\n );\n}\n\nconst GAPPS_URL = process.env.NEXT_PUBLIC_GREAT_APPS ?? \"\";\n\nexport function UsersSelectorPopover({\n projectId,\n side,\n align,\n contentClassName,\n}: UsersSelectorPopoverProps) {\n const queryClient = useQueryClient();\n const [open, setOpen] = useState(false);\n const [hoveredId, setHoveredId] = useState<number | null>(null);\n const [searchInput, setSearchInput] = useState(\"\");\n const [search, setSearch] = useState(\"\");\n\n const id = projectId ?? 0;\n\n useEffect(() => {\n const timer = setTimeout(() => setSearch(searchInput), 300);\n return () => clearTimeout(timer);\n }, [searchInput]);\n\n const accountUsers = useListAllAccountUsers(search, id || undefined);\n const projectUsers = useListProjectUsers(id, \"\");\n\n const allUsers = accountUsers.data?.pages.flatMap((p) => p.data) ?? [];\n const isLoading = accountUsers.isLoading;\n const isFetchingMore = accountUsers.isFetchingNextPage;\n const totalInProject = projectUsers.data?.pages[0]?.total ?? 0;\n\n const invalidate = () => {\n queryClient.invalidateQueries({ queryKey: LIST_PROJECT_USERS_BASE_KEY(id) });\n queryClient.invalidateQueries({ queryKey: LIST_ALL_ACCOUNT_USERS_BASE_KEY() });\n };\n\n const removeMutation = useMutation({\n mutationFn: (userId: number) =>\n removeProjectUserAction({ projectId: id, users: [userId] }),\n onSuccess: invalidate,\n });\n\n const addMutation = useMutation({\n mutationFn: (userId: number) =>\n addProjectUserAction({ projectId: id, users: [userId] }),\n onSuccess: invalidate,\n });\n\n const toggleUser = (userId: number, inProject: boolean, removable: boolean) => {\n if (inProject) {\n if (!removable) return;\n removeMutation.mutate(userId);\n } else {\n addMutation.mutate(userId);\n }\n };\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 (accountUsers.hasNextPage && !accountUsers.isFetchingNextPage) {\n accountUsers.fetchNextPage();\n }\n };\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger className=\"cursor-pointer\">\n <div className=\"flex items-center gap-2 text-gray-950 px-3 py-2 border border-gray-200 rounded-full hover:border-gray-950\">\n <span className=\"paragraph-small-semibold\">{totalInProject}</span>\n <User size={16} />\n </div>\n </PopoverTrigger>\n\n <PopoverContent\n className={cn(\n \"absolute right-[-88px] w-[273px] p-0 bg-white rounded-2xl shadow-md border border-gray-200\",\n contentClassName,\n )}\n side={side}\n align={align}\n sideOffset={8}\n >\n <div className=\"flex flex-col p-3 gap-2\">\n <div className=\"flex items-center justify-between\">\n <span className=\"paragraph-small-semibold\">Usuários</span>\n <div className=\"flex items-center gap-1.5\">\n <Button\n variant=\"secondary\"\n className=\"h-8 paragraph-small-semibold text-gray-950\"\n onClick={() => {\n window.location.href = `${GAPPS_URL}/my-teams?createNew=true`;\n }}\n >\n <Plus size={16} />\n Adicionar\n </Button>\n <Tooltip>\n <TooltipTrigger asChild>\n <Button\n variant=\"secondary\"\n className=\"p-0 size-8\"\n onClick={() => {\n window.location.href = `${GAPPS_URL}/my-teams`;\n }}\n >\n <Settings size={18} />\n </Button>\n </TooltipTrigger>\n <TooltipContent className=\"z-[1002]\">\n Visualizar todos usuários\n </TooltipContent>\n </Tooltip>\n </div>\n </div>\n\n <Command shouldFilter={false} className=\"gap-2\" value=\"\">\n <CommandInput\n placeholder=\"Busque aqui\"\n value={searchInput}\n onValueChange={setSearchInput}\n />\n <CommandList\n className=\"custom-scrollbar paragraph-small-medium text-gray-600 h-[162px]\"\n onScroll={handleScroll}\n >\n {isLoading ? (\n <>\n <UserItemSkeleton />\n <UserItemSkeleton />\n <UserItemSkeleton />\n </>\n ) : allUsers.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center gap-2 h-full text-gray-400 py-4\">\n <Users size={28} className=\"text-gray-300\" />\n <span className=\"paragraph-small-medium text-center text-gray-500\">\n Nenhum usuário encontrado.\n </span>\n </div>\n ) : (\n <>\n <CommandEmpty>Nenhum resultado encontrado.</CommandEmpty>\n {allUsers.map((user) => {\n const inProject = user.in_project ?? false;\n const removable =\n inProject &&\n user.profile !== \"owner\" &&\n user.profile !== \"admin\";\n const interactive = !inProject || removable;\n\n return (\n <CommandItem\n key={user.id}\n value={user.name || String(user.id)}\n autoFocus={false}\n onPointerEnter={() => setHoveredId(user.id)}\n onPointerLeave={() => setHoveredId(null)}\n onFocus={() => setHoveredId(user.id)}\n onBlur={() => setHoveredId(null)}\n onSelect={() => toggleUser(user.id, inProject, removable)}\n className={interactive ? \"cursor-pointer\" : \"cursor-default\"}\n >\n <div className=\"flex items-center justify-between w-full\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <UserAvatar\n photo={user.photo}\n name={user.name ?? undefined}\n size={32}\n />\n <span className=\"truncate\">{user.name}</span>\n </div>\n <Tooltip open={interactive && hoveredId === user.id}>\n <TooltipTrigger\n onClick={(e) => e.stopPropagation()}\n onPointerDown={(e) => e.stopPropagation()}\n >\n <Checkbox\n checked={inProject}\n disabled={!interactive}\n onCheckedChange={() =>\n toggleUser(user.id, inProject, removable)\n }\n className={cn(\n interactive && hoveredId === user.id\n ? \"border-gray-950\"\n : \"\",\n \"disabled:opacity-10\",\n )}\n />\n </TooltipTrigger>\n <TooltipContent className=\"z-[1002]\">\n {inProject ? \"Remover do projeto\" : \"Adicionar ao projeto\"}\n </TooltipContent>\n </Tooltip>\n </div>\n </CommandItem>\n );\n })}\n {isFetchingMore && (\n <>\n <UserItemSkeleton />\n <UserItemSkeleton />\n </>\n )}\n </>\n )}\n </CommandList>\n </Command>\n </div>\n </PopoverContent>\n </Popover>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,10 @@
1
+ "use server";
2
+ import { safeServerAction } from "../../../utils/safeServerAction";
3
+ import { projectUsersService } from "../services/project-users.service";
4
+ async function listAllAccountUsersAction(params = {}) {
5
+ return safeServerAction(() => projectUsersService.listAllAccountUsers(params));
6
+ }
7
+ export {
8
+ listAllAccountUsersAction
9
+ };
10
+ //# sourceMappingURL=list-all-account-users.action.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/modules/projects/actions/list-all-account-users.action.ts"],"sourcesContent":["'use server';\n\nimport { safeServerAction } from '../../../utils/safeServerAction';\nimport { projectUsersService } from '../services/project-users.service';\nimport type { ListUsersParams } from '../types';\n\nexport async function listAllAccountUsersAction(params: ListUsersParams = {}) {\n return safeServerAction(() => projectUsersService.listAllAccountUsers(params));\n}\n"],"mappings":";AAEA,SAAS,wBAAwB;AACjC,SAAS,2BAA2B;AAGpC,eAAsB,0BAA0B,SAA0B,CAAC,GAAG;AAC5E,SAAO,iBAAiB,MAAM,oBAAoB,oBAAoB,MAAM,CAAC;AAC/E;","names":[]}
@@ -0,0 +1,35 @@
1
+ "use client";
2
+ import { useInfiniteQuery } from "@tanstack/react-query";
3
+ import { listAllAccountUsersAction } from "../actions/list-all-account-users.action";
4
+ import { withAction } from "../../../utils/withAction";
5
+ const PAGE_SIZE = 20;
6
+ const LIST_ALL_ACCOUNT_USERS_BASE_KEY = () => ["account-users", "all"];
7
+ const LIST_ALL_ACCOUNT_USERS_QUERY_KEY = (search = "", projectId) => [
8
+ ...LIST_ALL_ACCOUNT_USERS_BASE_KEY(),
9
+ search,
10
+ projectId
11
+ ];
12
+ function useListAllAccountUsers(search = "", projectId) {
13
+ return useInfiniteQuery({
14
+ queryKey: LIST_ALL_ACCOUNT_USERS_QUERY_KEY(search, projectId),
15
+ queryFn: ({ pageParam }) => withAction(
16
+ () => listAllAccountUsersAction({
17
+ search: search || void 0,
18
+ page: pageParam,
19
+ limit: PAGE_SIZE,
20
+ inProject: projectId
21
+ })
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
+ }
30
+ export {
31
+ LIST_ALL_ACCOUNT_USERS_BASE_KEY,
32
+ LIST_ALL_ACCOUNT_USERS_QUERY_KEY,
33
+ useListAllAccountUsers
34
+ };
35
+ //# sourceMappingURL=list-all-account-users.hook.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/modules/projects/hooks/list-all-account-users.hook.ts"],"sourcesContent":["'use client';\n\nimport { useInfiniteQuery } from '@tanstack/react-query';\nimport { listAllAccountUsersAction } from '../actions/list-all-account-users.action';\nimport { withAction } from '../../../utils/withAction';\n\nconst PAGE_SIZE = 20;\n\nexport const LIST_ALL_ACCOUNT_USERS_BASE_KEY = () => ['account-users', 'all'];\nexport const LIST_ALL_ACCOUNT_USERS_QUERY_KEY = (search: string = '', projectId?: number) => [\n ...LIST_ALL_ACCOUNT_USERS_BASE_KEY(),\n search,\n projectId,\n];\n\nexport function useListAllAccountUsers(search: string = '', projectId?: number) {\n return useInfiniteQuery({\n queryKey: LIST_ALL_ACCOUNT_USERS_QUERY_KEY(search, projectId),\n queryFn: ({ pageParam }) =>\n withAction(() =>\n listAllAccountUsersAction({\n search: search || undefined,\n page: pageParam as number,\n limit: PAGE_SIZE,\n inProject: projectId,\n })\n )(),\n getNextPageParam: (lastPage, allPages) => {\n const loaded = allPages.reduce((sum, p) => sum + p.data.length, 0);\n return loaded < lastPage.total ? allPages.length + 1 : undefined;\n },\n initialPageParam: 1,\n });\n}\n"],"mappings":";AAEA,SAAS,wBAAwB;AACjC,SAAS,iCAAiC;AAC1C,SAAS,kBAAkB;AAE3B,MAAM,YAAY;AAEX,MAAM,kCAAkC,MAAM,CAAC,iBAAiB,KAAK;AACrE,MAAM,mCAAmC,CAAC,SAAiB,IAAI,cAAuB;AAAA,EAC3F,GAAG,gCAAgC;AAAA,EACnC;AAAA,EACA;AACF;AAEO,SAAS,uBAAuB,SAAiB,IAAI,WAAoB;AAC9E,SAAO,iBAAiB;AAAA,IACtB,UAAU,iCAAiC,QAAQ,SAAS;AAAA,IAC5D,SAAS,CAAC,EAAE,UAAU,MACpB;AAAA,MAAW,MACT,0BAA0B;AAAA,QACxB,QAAQ,UAAU;AAAA,QAClB,MAAM;AAAA,QACN,OAAO;AAAA,QACP,WAAW;AAAA,MACb,CAAC;AAAA,IACH,EAAE;AAAA,IACJ,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;","names":[]}
@@ -26,6 +26,24 @@ class ProjectUsersService {
26
26
  page: params.page ?? 1
27
27
  };
28
28
  }
29
+ async listAllAccountUsers(params = {}) {
30
+ const { id_account } = await getUserContext();
31
+ const query = buildQueryParams(params);
32
+ const url = `/accounts/${id_account}/users${query ? `?${query}` : ""}`;
33
+ const response = await api.apps.get(url);
34
+ if (response.status === 0) {
35
+ throw new ApiError(
36
+ response.message || "Erro ao listar usu\xE1rios da conta",
37
+ "LIST_ACCOUNT_USERS_FAILED",
38
+ 400
39
+ );
40
+ }
41
+ return {
42
+ data: response.data.map((item) => AccountUserSchema.parse(item)),
43
+ total: response.total,
44
+ page: params.page ?? 1
45
+ };
46
+ }
29
47
  async listNotInProject(projectId, params = {}) {
30
48
  const { id_account } = await getUserContext();
31
49
  const query = buildQueryParams({ notInProject: projectId, ...params });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/modules/projects/services/project-users.service.ts"],"sourcesContent":["import 'server-only';\r\n\r\nimport { api } from '../../../infra/api/client';\r\nimport { ApiError } from '../../../infra/api/types';\r\nimport { buildQueryParams } from '../../../infra/utils/params';\r\nimport { getUserContext } from '../../auth/utils/get-user-context';\r\nimport {\r\n AccountUser,\r\n AccountUserSchema,\r\n AddRemoveProjectUsersParams,\r\n ListUsersParams,\r\n ProjectUser,\r\n ProjectUserSchema,\r\n UsersPage,\r\n} from '../types';\r\n\r\ninterface ApiListResponse<T> {\r\n data: T[];\r\n total: number;\r\n status: number;\r\n message?: string;\r\n}\r\n\r\ninterface ApiActionResponse {\r\n status: number;\r\n message?: string;\r\n}\r\n\r\nclass ProjectUsersService {\r\n async listInProject(projectId: number, params: ListUsersParams = {}): Promise<UsersPage<ProjectUser>> {\r\n const { id_account, id_user } = await getUserContext();\r\n const query = buildQueryParams(params as Record<string, string | number | boolean | undefined>);\r\n const url = `/account/${id_account}/user/${id_user}/projects/${projectId}/users${query ? `?${query}` : ''}`;\r\n\r\n const response = await api.apps.get<ApiListResponse<ProjectUser>>(url);\r\n\r\n if (response.status === 0) {\r\n throw new ApiError(\r\n response.message || 'Erro ao listar usuários do projeto',\r\n 'LIST_PROJECT_USERS_FAILED',\r\n 400\r\n );\r\n }\r\n\r\n return {\r\n data: response.data.map((item) => ProjectUserSchema.parse(item)),\r\n total: response.total,\r\n page: params.page ?? 1,\r\n };\r\n }\r\n\r\n async listNotInProject(projectId: number, params: ListUsersParams = {}): Promise<UsersPage<AccountUser>> {\r\n const { id_account } = await getUserContext();\r\n const query = buildQueryParams({ notInProject: projectId, ...params } as Record<string, string | number | boolean | undefined>);\r\n const url = `/accounts/${id_account}/users${query ? `?${query}` : ''}`;\r\n\r\n const response = await api.apps.get<ApiListResponse<AccountUser>>(url);\r\n\r\n if (response.status === 0) {\r\n throw new ApiError(\r\n response.message || 'Erro ao listar usuários disponíveis',\r\n 'LIST_AVAILABLE_USERS_FAILED',\r\n 400\r\n );\r\n }\r\n\r\n return {\r\n data: response.data.map((item) => AccountUserSchema.parse(item)),\r\n total: response.total,\r\n page: params.page ?? 1,\r\n };\r\n }\r\n\r\n async addToProject({ projectId, users }: AddRemoveProjectUsersParams): Promise<void> {\r\n const { id_account, id_user } = await getUserContext();\r\n const url = `/account/${id_account}/user/${id_user}/projects/${projectId}/users`;\r\n\r\n const response = await api.apps.post<ApiActionResponse>(url, { users });\r\n\r\n if (response.status === 0) {\r\n throw new ApiError(\r\n response.message || 'Erro ao adicionar usuário ao projeto',\r\n 'ADD_PROJECT_USER_FAILED',\r\n 400\r\n );\r\n }\r\n }\r\n\r\n async removeFromProject({ projectId, users }: AddRemoveProjectUsersParams): Promise<void> {\r\n const { id_account, id_user } = await getUserContext();\r\n const url = `/account/${id_account}/user/${id_user}/projects/${projectId}/users`;\r\n\r\n const response = await api.apps.delete<ApiActionResponse>(url, {\r\n body: JSON.stringify({ users }),\r\n });\r\n\r\n if (response.status === 0) {\r\n throw new ApiError(\r\n response.message || 'Erro ao remover usuário do projeto',\r\n 'REMOVE_PROJECT_USER_FAILED',\r\n 400\r\n );\r\n }\r\n }\r\n}\r\n\r\nexport const projectUsersService = new ProjectUsersService();\r\n"],"mappings":"AAAA,OAAO;AAEP,SAAS,WAAW;AACpB,SAAS,gBAAgB;AACzB,SAAS,wBAAwB;AACjC,SAAS,sBAAsB;AAC/B;AAAA,EAEE;AAAA,EAIA;AAAA,OAEK;AAcP,MAAM,oBAAoB;AAAA,EACxB,MAAM,cAAc,WAAmB,SAA0B,CAAC,GAAoC;AACpG,UAAM,EAAE,YAAY,QAAQ,IAAI,MAAM,eAAe;AACrD,UAAM,QAAQ,iBAAiB,MAA+D;AAC9F,UAAM,MAAM,YAAY,UAAU,SAAS,OAAO,aAAa,SAAS,SAAS,QAAQ,IAAI,KAAK,KAAK,EAAE;AAEzG,UAAM,WAAW,MAAM,IAAI,KAAK,IAAkC,GAAG;AAErE,QAAI,SAAS,WAAW,GAAG;AACzB,YAAM,IAAI;AAAA,QACR,SAAS,WAAW;AAAA,QACpB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL,MAAM,SAAS,KAAK,IAAI,CAAC,SAAS,kBAAkB,MAAM,IAAI,CAAC;AAAA,MAC/D,OAAO,SAAS;AAAA,MAChB,MAAM,OAAO,QAAQ;AAAA,IACvB;AAAA,EACF;AAAA,EAEA,MAAM,iBAAiB,WAAmB,SAA0B,CAAC,GAAoC;AACvG,UAAM,EAAE,WAAW,IAAI,MAAM,eAAe;AAC5C,UAAM,QAAQ,iBAAiB,EAAE,cAAc,WAAW,GAAG,OAAO,CAA0D;AAC9H,UAAM,MAAM,aAAa,UAAU,SAAS,QAAQ,IAAI,KAAK,KAAK,EAAE;AAEpE,UAAM,WAAW,MAAM,IAAI,KAAK,IAAkC,GAAG;AAErE,QAAI,SAAS,WAAW,GAAG;AACzB,YAAM,IAAI;AAAA,QACR,SAAS,WAAW;AAAA,QACpB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL,MAAM,SAAS,KAAK,IAAI,CAAC,SAAS,kBAAkB,MAAM,IAAI,CAAC;AAAA,MAC/D,OAAO,SAAS;AAAA,MAChB,MAAM,OAAO,QAAQ;AAAA,IACvB;AAAA,EACF;AAAA,EAEA,MAAM,aAAa,EAAE,WAAW,MAAM,GAA+C;AACnF,UAAM,EAAE,YAAY,QAAQ,IAAI,MAAM,eAAe;AACrD,UAAM,MAAM,YAAY,UAAU,SAAS,OAAO,aAAa,SAAS;AAExE,UAAM,WAAW,MAAM,IAAI,KAAK,KAAwB,KAAK,EAAE,MAAM,CAAC;AAEtE,QAAI,SAAS,WAAW,GAAG;AACzB,YAAM,IAAI;AAAA,QACR,SAAS,WAAW;AAAA,QACpB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,kBAAkB,EAAE,WAAW,MAAM,GAA+C;AACxF,UAAM,EAAE,YAAY,QAAQ,IAAI,MAAM,eAAe;AACrD,UAAM,MAAM,YAAY,UAAU,SAAS,OAAO,aAAa,SAAS;AAExE,UAAM,WAAW,MAAM,IAAI,KAAK,OAA0B,KAAK;AAAA,MAC7D,MAAM,KAAK,UAAU,EAAE,MAAM,CAAC;AAAA,IAChC,CAAC;AAED,QAAI,SAAS,WAAW,GAAG;AACzB,YAAM,IAAI;AAAA,QACR,SAAS,WAAW;AAAA,QACpB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEO,MAAM,sBAAsB,IAAI,oBAAoB;","names":[]}
1
+ {"version":3,"sources":["../../../../src/modules/projects/services/project-users.service.ts"],"sourcesContent":["import 'server-only';\r\n\r\nimport { api } from '../../../infra/api/client';\r\nimport { ApiError } from '../../../infra/api/types';\r\nimport { buildQueryParams } from '../../../infra/utils/params';\r\nimport { getUserContext } from '../../auth/utils/get-user-context';\r\nimport {\r\n AccountUser,\r\n AccountUserSchema,\r\n AddRemoveProjectUsersParams,\r\n ListUsersParams,\r\n ProjectUser,\r\n ProjectUserSchema,\r\n UsersPage,\r\n} from '../types';\r\n\r\ninterface ApiListResponse<T> {\r\n data: T[];\r\n total: number;\r\n status: number;\r\n message?: string;\r\n}\r\n\r\ninterface ApiActionResponse {\r\n status: number;\r\n message?: string;\r\n}\r\n\r\nclass ProjectUsersService {\r\n async listInProject(projectId: number, params: ListUsersParams = {}): Promise<UsersPage<ProjectUser>> {\r\n const { id_account, id_user } = await getUserContext();\r\n const query = buildQueryParams(params as Record<string, string | number | boolean | undefined>);\r\n const url = `/account/${id_account}/user/${id_user}/projects/${projectId}/users${query ? `?${query}` : ''}`;\r\n\r\n const response = await api.apps.get<ApiListResponse<ProjectUser>>(url);\r\n\r\n if (response.status === 0) {\r\n throw new ApiError(\r\n response.message || 'Erro ao listar usuários do projeto',\r\n 'LIST_PROJECT_USERS_FAILED',\r\n 400\r\n );\r\n }\r\n\r\n return {\r\n data: response.data.map((item) => ProjectUserSchema.parse(item)),\r\n total: response.total,\r\n page: params.page ?? 1,\r\n };\r\n }\r\n\r\n async listAllAccountUsers(params: ListUsersParams = {}): Promise<UsersPage<AccountUser>> {\r\n const { id_account } = await getUserContext();\r\n const query = buildQueryParams(params as Record<string, string | number | boolean | undefined>);\r\n const url = `/accounts/${id_account}/users${query ? `?${query}` : ''}`;\r\n\r\n const response = await api.apps.get<ApiListResponse<AccountUser>>(url);\r\n\r\n if (response.status === 0) {\r\n throw new ApiError(\r\n response.message || 'Erro ao listar usuários da conta',\r\n 'LIST_ACCOUNT_USERS_FAILED',\r\n 400\r\n );\r\n }\r\n\r\n return {\r\n data: response.data.map((item) => AccountUserSchema.parse(item)),\r\n total: response.total,\r\n page: params.page ?? 1,\r\n };\r\n }\r\n\r\n async listNotInProject(projectId: number, params: ListUsersParams = {}): Promise<UsersPage<AccountUser>> {\r\n const { id_account } = await getUserContext();\r\n const query = buildQueryParams({ notInProject: projectId, ...params } as Record<string, string | number | boolean | undefined>);\r\n const url = `/accounts/${id_account}/users${query ? `?${query}` : ''}`;\r\n\r\n const response = await api.apps.get<ApiListResponse<AccountUser>>(url);\r\n\r\n if (response.status === 0) {\r\n throw new ApiError(\r\n response.message || 'Erro ao listar usuários disponíveis',\r\n 'LIST_AVAILABLE_USERS_FAILED',\r\n 400\r\n );\r\n }\r\n\r\n return {\r\n data: response.data.map((item) => AccountUserSchema.parse(item)),\r\n total: response.total,\r\n page: params.page ?? 1,\r\n };\r\n }\r\n\r\n async addToProject({ projectId, users }: AddRemoveProjectUsersParams): Promise<void> {\r\n const { id_account, id_user } = await getUserContext();\r\n const url = `/account/${id_account}/user/${id_user}/projects/${projectId}/users`;\r\n\r\n const response = await api.apps.post<ApiActionResponse>(url, { users });\r\n\r\n if (response.status === 0) {\r\n throw new ApiError(\r\n response.message || 'Erro ao adicionar usuário ao projeto',\r\n 'ADD_PROJECT_USER_FAILED',\r\n 400\r\n );\r\n }\r\n }\r\n\r\n async removeFromProject({ projectId, users }: AddRemoveProjectUsersParams): Promise<void> {\r\n const { id_account, id_user } = await getUserContext();\r\n const url = `/account/${id_account}/user/${id_user}/projects/${projectId}/users`;\r\n\r\n const response = await api.apps.delete<ApiActionResponse>(url, {\r\n body: JSON.stringify({ users }),\r\n });\r\n\r\n if (response.status === 0) {\r\n throw new ApiError(\r\n response.message || 'Erro ao remover usuário do projeto',\r\n 'REMOVE_PROJECT_USER_FAILED',\r\n 400\r\n );\r\n }\r\n }\r\n}\r\n\r\nexport const projectUsersService = new ProjectUsersService();\r\n"],"mappings":"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","names":[]}
@@ -13,7 +13,8 @@ const AccountUserSchema = z.object({
13
13
  name: z.string().nullable(),
14
14
  email: z.string().nullable(),
15
15
  profile: z.string().nullable().optional().default("viewer"),
16
- photo: z.string().nullable().optional()
16
+ photo: z.string().nullable().optional(),
17
+ in_project: z.boolean().optional()
17
18
  });
18
19
  export {
19
20
  AccountUserSchema,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/modules/projects/types.ts"],"sourcesContent":["import { z } from \"zod\";\r\n\r\nexport const ProjectUserSchema = z.object({\r\n id_user: z.union([z.number(), z.string()]).transform(Number),\r\n id_account: z.union([z.number(), z.string()]).transform(Number),\r\n id_project: z.union([z.number(), z.string()]).transform(Number),\r\n name: z.string().nullable(),\r\n email: z.string().nullable(),\r\n profile: z.string().nullable().optional().default(\"viewer\"),\r\n photo: z.string().nullable().optional(),\r\n});\r\n\r\nexport type ProjectUser = z.infer<typeof ProjectUserSchema>;\r\n\r\nexport const AccountUserSchema = z.object({\r\n id: z.union([z.number(), z.string()]).transform(Number),\r\n name: z.string().nullable(),\r\n email: z.string().nullable(),\r\n profile: z.string().nullable().optional().default(\"viewer\"),\r\n photo: z.string().nullable().optional(),\r\n});\r\n\r\nexport type AccountUser = z.infer<typeof AccountUserSchema>;\r\n\r\nexport interface AddRemoveProjectUsersParams {\r\n projectId: number;\r\n users: number[];\r\n}\r\n\r\nexport interface ListUsersParams {\r\n search?: string;\r\n page?: number;\r\n limit?: number;\r\n}\r\n\r\nexport interface UsersPage<T> {\r\n data: T[];\r\n total: number;\r\n page: number;\r\n}\r\n"],"mappings":"AAAA,SAAS,SAAS;AAEX,MAAM,oBAAoB,EAAE,OAAO;AAAA,EACxC,SAAS,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,UAAU,MAAM;AAAA,EAC3D,YAAY,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,UAAU,MAAM;AAAA,EAC9D,YAAY,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,UAAU,MAAM;AAAA,EAC9D,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,EAC1B,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA,EAC3B,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,QAAQ;AAAA,EAC1D,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AACxC,CAAC;AAIM,MAAM,oBAAoB,EAAE,OAAO;AAAA,EACxC,IAAI,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,UAAU,MAAM;AAAA,EACtD,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,EAC1B,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA,EAC3B,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,QAAQ;AAAA,EAC1D,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AACxC,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../../src/modules/projects/types.ts"],"sourcesContent":["import { z } from \"zod\";\r\n\r\nexport const ProjectUserSchema = z.object({\r\n id_user: z.union([z.number(), z.string()]).transform(Number),\r\n id_account: z.union([z.number(), z.string()]).transform(Number),\r\n id_project: z.union([z.number(), z.string()]).transform(Number),\r\n name: z.string().nullable(),\r\n email: z.string().nullable(),\r\n profile: z.string().nullable().optional().default(\"viewer\"),\r\n photo: z.string().nullable().optional(),\r\n});\r\n\r\nexport type ProjectUser = z.infer<typeof ProjectUserSchema>;\r\n\r\nexport const AccountUserSchema = z.object({\r\n id: z.union([z.number(), z.string()]).transform(Number),\r\n name: z.string().nullable(),\r\n email: z.string().nullable(),\r\n profile: z.string().nullable().optional().default(\"viewer\"),\r\n photo: z.string().nullable().optional(),\r\n in_project: z.boolean().optional(),\r\n});\r\n\r\nexport type AccountUser = z.infer<typeof AccountUserSchema>;\r\n\r\nexport interface AddRemoveProjectUsersParams {\r\n projectId: number;\r\n users: number[];\r\n}\r\n\r\nexport interface ListUsersParams {\r\n search?: string;\r\n page?: number;\r\n limit?: number;\r\n inProject?: number;\r\n}\r\n\r\nexport interface UsersPage<T> {\r\n data: T[];\r\n total: number;\r\n page: number;\r\n}\r\n"],"mappings":"AAAA,SAAS,SAAS;AAEX,MAAM,oBAAoB,EAAE,OAAO;AAAA,EACxC,SAAS,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,UAAU,MAAM;AAAA,EAC3D,YAAY,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,UAAU,MAAM;AAAA,EAC9D,YAAY,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,UAAU,MAAM;AAAA,EAC9D,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,EAC1B,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA,EAC3B,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,QAAQ;AAAA,EAC1D,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AACxC,CAAC;AAIM,MAAM,oBAAoB,EAAE,OAAO;AAAA,EACxC,IAAI,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,CAAC,EAAE,UAAU,MAAM;AAAA,EACtD,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,EAC1B,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA,EAC3B,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,QAAQ;AAAA,EAC1D,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,EACtC,YAAY,EAAE,QAAQ,EAAE,SAAS;AACnC,CAAC;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greatapps/common",
3
- "version": "1.1.187",
3
+ "version": "1.1.188",
4
4
  "description": "Shared library for GreatApps frontend applications",
5
5
  "main": "./dist/index.mjs",
6
6
  "types": "./src/index.ts",
@@ -1,294 +1,261 @@
1
- "use client";
2
-
3
- "use client";
4
-
5
- import { useEffect, useState } from "react";
6
- import { Plus, Settings, User, Users } from "lucide-react";
7
- import { UserAvatar } from "../ui/data-display/UserAvatar";
8
- import { useQueryClient, useMutation } from "@tanstack/react-query";
9
- import { Popover, PopoverContent, PopoverTrigger } from "../ui/overlay/Popover";
10
- import { Button } from "../ui/buttons/Button";
11
- import {
12
- Command,
13
- CommandEmpty,
14
- CommandInput,
15
- CommandItem,
16
- CommandList,
17
- } from "../ui/overlay/Command";
18
- import { Checkbox } from "../ui/form/Checkbox";
19
- import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/overlay/Tooltip";
20
- import { cn } from "../../infra/utils/clsx";
21
- import {
22
- useListProjectUsers,
23
- LIST_PROJECT_USERS_BASE_KEY,
24
- } from "../../modules/projects/hooks/list-project-users.hook";
25
- import {
26
- useListAvailableUsers,
27
- LIST_AVAILABLE_USERS_BASE_KEY,
28
- } from "../../modules/projects/hooks/list-available-users.hook";
29
- import { addProjectUserAction } from "../../modules/projects/actions/add-project-user.action";
30
- import { removeProjectUserAction } from "../../modules/projects/actions/remove-project-user.action";
31
-
32
- export interface UsersSelectorPopoverProps {
33
- projectId?: number;
34
- side?: "top" | "bottom" | "left" | "right";
35
- align?: "start" | "center" | "end";
36
- contentClassName?: string;
37
- }
38
-
39
- function UserItemSkeleton() {
40
- return (
41
- <div className="flex items-center justify-between px-2 py-1.5 gap-2">
42
- <div className="flex items-center gap-2 flex-1">
43
- <div className="skeleton size-8 rounded-full shrink-0" />
44
- <div className="skeleton h-3.5 w-28 rounded" />
45
- </div>
46
- <div className="skeleton size-4 rounded shrink-0" />
47
- </div>
48
- );
49
- }
50
-
51
- const GAPPS_URL = process.env.NEXT_PUBLIC_GREAT_APPS ?? "";
52
-
53
- export function UsersSelectorPopover({
54
- projectId,
55
- side,
56
- align,
57
- contentClassName,
58
- }: UsersSelectorPopoverProps) {
59
- const queryClient = useQueryClient();
60
- const [open, setOpen] = useState(false);
61
- const [hoveredId, setHoveredId] = useState<number | null>(null);
62
- const [searchInput, setSearchInput] = useState("");
63
- const [search, setSearch] = useState("");
64
-
65
- const id = projectId ?? 0;
66
-
67
- useEffect(() => {
68
- const timer = setTimeout(() => setSearch(searchInput), 300);
69
- return () => clearTimeout(timer);
70
- }, [searchInput]);
71
-
72
- const projectUsers = useListProjectUsers(id, search);
73
- const availableUsers = useListAvailableUsers(id, search);
74
-
75
- const inProject = projectUsers.data?.pages.flatMap((p) => p.data) ?? [];
76
- const notInProject = availableUsers.data?.pages.flatMap((p) => p.data) ?? [];
77
- const isLoading = projectUsers.isLoading || availableUsers.isLoading;
78
- const isFetchingMore =
79
- projectUsers.isFetchingNextPage || availableUsers.isFetchingNextPage;
80
- const totalInProject = projectUsers.data?.pages[0]?.total ?? inProject.length;
81
-
82
- const invalidate = () => {
83
- queryClient.invalidateQueries({
84
- queryKey: LIST_PROJECT_USERS_BASE_KEY(id),
85
- });
86
- queryClient.invalidateQueries({
87
- queryKey: LIST_AVAILABLE_USERS_BASE_KEY(id),
88
- });
89
- };
90
-
91
- const addMutation = useMutation({
92
- mutationFn: (userId: number) =>
93
- addProjectUserAction({ projectId: id, users: [userId] }),
94
- onSuccess: invalidate,
95
- });
96
-
97
- const removeMutation = useMutation({
98
- mutationFn: (userId: number) =>
99
- removeProjectUserAction({ projectId: id, users: [userId] }),
100
- onSuccess: invalidate,
101
- });
102
-
103
- const allUsers = [
104
- ...inProject.map((u) => ({
105
- id: u.id_user,
106
- name: u.name,
107
- photo: u.photo ?? null,
108
- checked: true as const,
109
- removable: u.profile !== "owner" && u.profile !== "admin",
110
- })),
111
- ...notInProject.map((u) => ({
112
- id: u.id,
113
- name: u.name,
114
- photo: u.photo ?? null,
115
- checked: false as const,
116
- removable: true,
117
- })),
118
- ];
119
-
120
- const toggleUser = (id: number, checked: boolean, removable: boolean) => {
121
- if (checked) {
122
- if (!removable) return;
123
- removeMutation.mutate(id);
124
- } else {
125
- addMutation.mutate(id);
126
- }
127
- };
128
-
129
- const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
130
- const target = e.currentTarget;
131
- const nearBottom =
132
- target.scrollTop + target.clientHeight >= target.scrollHeight - 50;
133
- if (!nearBottom) return;
134
- if (projectUsers.hasNextPage && !projectUsers.isFetchingNextPage) {
135
- projectUsers.fetchNextPage();
136
- } else if (
137
- availableUsers.hasNextPage &&
138
- !availableUsers.isFetchingNextPage
139
- ) {
140
- availableUsers.fetchNextPage();
141
- }
142
- };
143
-
144
- return (
145
- <Popover open={open} onOpenChange={setOpen}>
146
- <PopoverTrigger className="cursor-pointer">
147
- <div className="flex items-center gap-2 text-gray-950 px-3 py-2 border border-gray-200 rounded-full hover:border-gray-950">
148
- <span className="paragraph-small-semibold">{totalInProject}</span>
149
- <User size={16} />
150
- </div>
151
- </PopoverTrigger>
152
-
153
- <PopoverContent
154
- className={cn(
155
- "absolute right-[-88px] w-[273px] p-0 bg-white rounded-2xl shadow-md border border-gray-200",
156
- contentClassName,
157
- )}
158
- side={side}
159
- align={align}
160
- sideOffset={8}
161
- >
162
- <div className="flex flex-col p-3 gap-2">
163
- <div className="flex items-center justify-between">
164
- <span className="paragraph-small-semibold">Usuários</span>
165
- <div className="flex items-center gap-1.5">
166
- <Button
167
- variant="secondary"
168
- className="h-8 paragraph-small-semibold text-gray-950"
169
- onClick={() => {
170
- window.location.href = `${GAPPS_URL}/my-teams?createNew=true`;
171
- }}
172
- >
173
- <Plus size={16} />
174
- Adicionar
175
- </Button>
176
- <Tooltip>
177
- <TooltipTrigger asChild>
178
- <Button
179
- variant="secondary"
180
- className="p-0 size-8"
181
- onClick={() => {
182
- window.location.href = `${GAPPS_URL}/my-teams`;
183
- }}
184
- >
185
- <Settings size={18} />
186
- </Button>
187
- </TooltipTrigger>
188
- <TooltipContent className="z-[1002]">
189
- Visualizar todos usuários
190
- </TooltipContent>
191
- </Tooltip>
192
- </div>
193
- </div>
194
-
195
- <Command shouldFilter={false} className="gap-2" value="">
196
- <CommandInput
197
- placeholder="Busque aqui"
198
- value={searchInput}
199
- onValueChange={setSearchInput}
200
- />
201
- <CommandList
202
- className="custom-scrollbar paragraph-small-medium text-gray-600 h-[162px]"
203
- onScroll={handleScroll}
204
- >
205
- {isLoading ? (
206
- <>
207
- <UserItemSkeleton />
208
- <UserItemSkeleton />
209
- <UserItemSkeleton />
210
- </>
211
- ) : allUsers.length === 0 ? (
212
- <div className="flex flex-col items-center justify-center gap-2 h-full text-gray-400 py-4">
213
- <Users size={28} className="text-gray-300" />
214
- <span className="paragraph-small-medium text-center text-gray-500">
215
- Nenhum usuário neste projeto.
216
- <br />
217
- Adicione um usuário para começar.
218
- </span>
219
- </div>
220
- ) : (
221
- <>
222
- <CommandEmpty>Nenhum resultado encontrado.</CommandEmpty>
223
- {allUsers.map((user) => (
224
- <CommandItem
225
- key={user.id}
226
- value={user.name || String(user.id)}
227
- autoFocus={false}
228
- onPointerEnter={() => setHoveredId(user.id)}
229
- onPointerLeave={() => setHoveredId(null)}
230
- onFocus={() => setHoveredId(user.id)}
231
- onBlur={() => setHoveredId(null)}
232
- onSelect={() =>
233
- toggleUser(user.id, user.checked, user.removable)
234
- }
235
- className={
236
- user.removable ? "cursor-pointer" : "cursor-default"
237
- }
238
- >
239
- <div className="flex items-center justify-between w-full">
240
- <div className="flex items-center gap-2 min-w-0">
241
- <UserAvatar
242
- photo={user.photo}
243
- name={user.name ?? undefined}
244
- size={32}
245
- />
246
- <span className="truncate">{user.name}</span>
247
- </div>
248
- <Tooltip open={user.removable && hoveredId === user.id}>
249
- <TooltipTrigger
250
- onClick={(e) => e.stopPropagation()}
251
- onPointerDown={(e) => e.stopPropagation()}
252
- >
253
- <Checkbox
254
- checked={user.checked}
255
- disabled={!user.removable}
256
- onCheckedChange={() =>
257
- toggleUser(
258
- user.id,
259
- user.checked,
260
- user.removable,
261
- )
262
- }
263
- className={cn(
264
- user.removable && hoveredId === user.id
265
- ? "border-gray-950"
266
- : "",
267
- "disabled:opacity-10",
268
- )}
269
- />
270
- </TooltipTrigger>
271
- <TooltipContent className="z-[1002]">
272
- {user.checked
273
- ? "Remover do projeto"
274
- : "Adicionar ao projeto"}
275
- </TooltipContent>
276
- </Tooltip>
277
- </div>
278
- </CommandItem>
279
- ))}
280
- {isFetchingMore && (
281
- <>
282
- <UserItemSkeleton />
283
- <UserItemSkeleton />
284
- </>
285
- )}
286
- </>
287
- )}
288
- </CommandList>
289
- </Command>
290
- </div>
291
- </PopoverContent>
292
- </Popover>
293
- );
294
- }
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import { Plus, Settings, User, Users } from "lucide-react";
5
+ import { UserAvatar } from "../ui/data-display/UserAvatar";
6
+ import { useQueryClient, useMutation } from "@tanstack/react-query";
7
+ import { Popover, PopoverContent, PopoverTrigger } from "../ui/overlay/Popover";
8
+ import { Button } from "../ui/buttons/Button";
9
+ import {
10
+ Command,
11
+ CommandEmpty,
12
+ CommandInput,
13
+ CommandItem,
14
+ CommandList,
15
+ } from "../ui/overlay/Command";
16
+ import { Checkbox } from "../ui/form/Checkbox";
17
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/overlay/Tooltip";
18
+ import { cn } from "../../infra/utils/clsx";
19
+ import {
20
+ useListProjectUsers,
21
+ LIST_PROJECT_USERS_BASE_KEY,
22
+ } from "../../modules/projects/hooks/list-project-users.hook";
23
+ import {
24
+ useListAllAccountUsers,
25
+ LIST_ALL_ACCOUNT_USERS_BASE_KEY,
26
+ } from "../../modules/projects/hooks/list-all-account-users.hook";
27
+ import { removeProjectUserAction } from "../../modules/projects/actions/remove-project-user.action";
28
+ import { addProjectUserAction } from "../../modules/projects/actions/add-project-user.action";
29
+
30
+ export interface UsersSelectorPopoverProps {
31
+ projectId?: number;
32
+ side?: "top" | "bottom" | "left" | "right";
33
+ align?: "start" | "center" | "end";
34
+ contentClassName?: string;
35
+ }
36
+
37
+ function UserItemSkeleton() {
38
+ return (
39
+ <div className="flex items-center justify-between px-2 py-1.5 gap-2">
40
+ <div className="flex items-center gap-2 flex-1">
41
+ <div className="skeleton size-8 rounded-full shrink-0" />
42
+ <div className="skeleton h-3.5 w-28 rounded" />
43
+ </div>
44
+ <div className="skeleton size-4 rounded shrink-0" />
45
+ </div>
46
+ );
47
+ }
48
+
49
+ const GAPPS_URL = process.env.NEXT_PUBLIC_GREAT_APPS ?? "";
50
+
51
+ export function UsersSelectorPopover({
52
+ projectId,
53
+ side,
54
+ align,
55
+ contentClassName,
56
+ }: UsersSelectorPopoverProps) {
57
+ const queryClient = useQueryClient();
58
+ const [open, setOpen] = useState(false);
59
+ const [hoveredId, setHoveredId] = useState<number | null>(null);
60
+ const [searchInput, setSearchInput] = useState("");
61
+ const [search, setSearch] = useState("");
62
+
63
+ const id = projectId ?? 0;
64
+
65
+ useEffect(() => {
66
+ const timer = setTimeout(() => setSearch(searchInput), 300);
67
+ return () => clearTimeout(timer);
68
+ }, [searchInput]);
69
+
70
+ const accountUsers = useListAllAccountUsers(search, id || undefined);
71
+ const projectUsers = useListProjectUsers(id, "");
72
+
73
+ const allUsers = accountUsers.data?.pages.flatMap((p) => p.data) ?? [];
74
+ const isLoading = accountUsers.isLoading;
75
+ const isFetchingMore = accountUsers.isFetchingNextPage;
76
+ const totalInProject = projectUsers.data?.pages[0]?.total ?? 0;
77
+
78
+ const invalidate = () => {
79
+ queryClient.invalidateQueries({ queryKey: LIST_PROJECT_USERS_BASE_KEY(id) });
80
+ queryClient.invalidateQueries({ queryKey: LIST_ALL_ACCOUNT_USERS_BASE_KEY() });
81
+ };
82
+
83
+ const removeMutation = useMutation({
84
+ mutationFn: (userId: number) =>
85
+ removeProjectUserAction({ projectId: id, users: [userId] }),
86
+ onSuccess: invalidate,
87
+ });
88
+
89
+ const addMutation = useMutation({
90
+ mutationFn: (userId: number) =>
91
+ addProjectUserAction({ projectId: id, users: [userId] }),
92
+ onSuccess: invalidate,
93
+ });
94
+
95
+ const toggleUser = (userId: number, inProject: boolean, removable: boolean) => {
96
+ if (inProject) {
97
+ if (!removable) return;
98
+ removeMutation.mutate(userId);
99
+ } else {
100
+ addMutation.mutate(userId);
101
+ }
102
+ };
103
+
104
+ const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
105
+ const target = e.currentTarget;
106
+ const nearBottom =
107
+ target.scrollTop + target.clientHeight >= target.scrollHeight - 50;
108
+ if (!nearBottom) return;
109
+ if (accountUsers.hasNextPage && !accountUsers.isFetchingNextPage) {
110
+ accountUsers.fetchNextPage();
111
+ }
112
+ };
113
+
114
+ return (
115
+ <Popover open={open} onOpenChange={setOpen}>
116
+ <PopoverTrigger className="cursor-pointer">
117
+ <div className="flex items-center gap-2 text-gray-950 px-3 py-2 border border-gray-200 rounded-full hover:border-gray-950">
118
+ <span className="paragraph-small-semibold">{totalInProject}</span>
119
+ <User size={16} />
120
+ </div>
121
+ </PopoverTrigger>
122
+
123
+ <PopoverContent
124
+ className={cn(
125
+ "absolute right-[-88px] w-[273px] p-0 bg-white rounded-2xl shadow-md border border-gray-200",
126
+ contentClassName,
127
+ )}
128
+ side={side}
129
+ align={align}
130
+ sideOffset={8}
131
+ >
132
+ <div className="flex flex-col p-3 gap-2">
133
+ <div className="flex items-center justify-between">
134
+ <span className="paragraph-small-semibold">Usuários</span>
135
+ <div className="flex items-center gap-1.5">
136
+ <Button
137
+ variant="secondary"
138
+ className="h-8 paragraph-small-semibold text-gray-950"
139
+ onClick={() => {
140
+ window.location.href = `${GAPPS_URL}/my-teams?createNew=true`;
141
+ }}
142
+ >
143
+ <Plus size={16} />
144
+ Adicionar
145
+ </Button>
146
+ <Tooltip>
147
+ <TooltipTrigger asChild>
148
+ <Button
149
+ variant="secondary"
150
+ className="p-0 size-8"
151
+ onClick={() => {
152
+ window.location.href = `${GAPPS_URL}/my-teams`;
153
+ }}
154
+ >
155
+ <Settings size={18} />
156
+ </Button>
157
+ </TooltipTrigger>
158
+ <TooltipContent className="z-[1002]">
159
+ Visualizar todos usuários
160
+ </TooltipContent>
161
+ </Tooltip>
162
+ </div>
163
+ </div>
164
+
165
+ <Command shouldFilter={false} className="gap-2" value="">
166
+ <CommandInput
167
+ placeholder="Busque aqui"
168
+ value={searchInput}
169
+ onValueChange={setSearchInput}
170
+ />
171
+ <CommandList
172
+ className="custom-scrollbar paragraph-small-medium text-gray-600 h-[162px]"
173
+ onScroll={handleScroll}
174
+ >
175
+ {isLoading ? (
176
+ <>
177
+ <UserItemSkeleton />
178
+ <UserItemSkeleton />
179
+ <UserItemSkeleton />
180
+ </>
181
+ ) : allUsers.length === 0 ? (
182
+ <div className="flex flex-col items-center justify-center gap-2 h-full text-gray-400 py-4">
183
+ <Users size={28} className="text-gray-300" />
184
+ <span className="paragraph-small-medium text-center text-gray-500">
185
+ Nenhum usuário encontrado.
186
+ </span>
187
+ </div>
188
+ ) : (
189
+ <>
190
+ <CommandEmpty>Nenhum resultado encontrado.</CommandEmpty>
191
+ {allUsers.map((user) => {
192
+ const inProject = user.in_project ?? false;
193
+ const removable =
194
+ inProject &&
195
+ user.profile !== "owner" &&
196
+ user.profile !== "admin";
197
+ const interactive = !inProject || removable;
198
+
199
+ return (
200
+ <CommandItem
201
+ key={user.id}
202
+ value={user.name || String(user.id)}
203
+ autoFocus={false}
204
+ onPointerEnter={() => setHoveredId(user.id)}
205
+ onPointerLeave={() => setHoveredId(null)}
206
+ onFocus={() => setHoveredId(user.id)}
207
+ onBlur={() => setHoveredId(null)}
208
+ onSelect={() => toggleUser(user.id, inProject, removable)}
209
+ className={interactive ? "cursor-pointer" : "cursor-default"}
210
+ >
211
+ <div className="flex items-center justify-between w-full">
212
+ <div className="flex items-center gap-2 min-w-0">
213
+ <UserAvatar
214
+ photo={user.photo}
215
+ name={user.name ?? undefined}
216
+ size={32}
217
+ />
218
+ <span className="truncate">{user.name}</span>
219
+ </div>
220
+ <Tooltip open={interactive && hoveredId === user.id}>
221
+ <TooltipTrigger
222
+ onClick={(e) => e.stopPropagation()}
223
+ onPointerDown={(e) => e.stopPropagation()}
224
+ >
225
+ <Checkbox
226
+ checked={inProject}
227
+ disabled={!interactive}
228
+ onCheckedChange={() =>
229
+ toggleUser(user.id, inProject, removable)
230
+ }
231
+ className={cn(
232
+ interactive && hoveredId === user.id
233
+ ? "border-gray-950"
234
+ : "",
235
+ "disabled:opacity-10",
236
+ )}
237
+ />
238
+ </TooltipTrigger>
239
+ <TooltipContent className="z-[1002]">
240
+ {inProject ? "Remover do projeto" : "Adicionar ao projeto"}
241
+ </TooltipContent>
242
+ </Tooltip>
243
+ </div>
244
+ </CommandItem>
245
+ );
246
+ })}
247
+ {isFetchingMore && (
248
+ <>
249
+ <UserItemSkeleton />
250
+ <UserItemSkeleton />
251
+ </>
252
+ )}
253
+ </>
254
+ )}
255
+ </CommandList>
256
+ </Command>
257
+ </div>
258
+ </PopoverContent>
259
+ </Popover>
260
+ );
261
+ }
@@ -0,0 +1,9 @@
1
+ 'use server';
2
+
3
+ import { safeServerAction } from '../../../utils/safeServerAction';
4
+ import { projectUsersService } from '../services/project-users.service';
5
+ import type { ListUsersParams } from '../types';
6
+
7
+ export async function listAllAccountUsersAction(params: ListUsersParams = {}) {
8
+ return safeServerAction(() => projectUsersService.listAllAccountUsers(params));
9
+ }
@@ -0,0 +1,34 @@
1
+ 'use client';
2
+
3
+ import { useInfiniteQuery } from '@tanstack/react-query';
4
+ import { listAllAccountUsersAction } from '../actions/list-all-account-users.action';
5
+ import { withAction } from '../../../utils/withAction';
6
+
7
+ const PAGE_SIZE = 20;
8
+
9
+ export const LIST_ALL_ACCOUNT_USERS_BASE_KEY = () => ['account-users', 'all'];
10
+ export const LIST_ALL_ACCOUNT_USERS_QUERY_KEY = (search: string = '', projectId?: number) => [
11
+ ...LIST_ALL_ACCOUNT_USERS_BASE_KEY(),
12
+ search,
13
+ projectId,
14
+ ];
15
+
16
+ export function useListAllAccountUsers(search: string = '', projectId?: number) {
17
+ return useInfiniteQuery({
18
+ queryKey: LIST_ALL_ACCOUNT_USERS_QUERY_KEY(search, projectId),
19
+ queryFn: ({ pageParam }) =>
20
+ withAction(() =>
21
+ listAllAccountUsersAction({
22
+ search: search || undefined,
23
+ page: pageParam as number,
24
+ limit: PAGE_SIZE,
25
+ inProject: projectId,
26
+ })
27
+ )(),
28
+ getNextPageParam: (lastPage, allPages) => {
29
+ const loaded = allPages.reduce((sum, p) => sum + p.data.length, 0);
30
+ return loaded < lastPage.total ? allPages.length + 1 : undefined;
31
+ },
32
+ initialPageParam: 1,
33
+ });
34
+ }
@@ -49,6 +49,28 @@ class ProjectUsersService {
49
49
  };
50
50
  }
51
51
 
52
+ async listAllAccountUsers(params: ListUsersParams = {}): Promise<UsersPage<AccountUser>> {
53
+ const { id_account } = await getUserContext();
54
+ const query = buildQueryParams(params as Record<string, string | number | boolean | undefined>);
55
+ const url = `/accounts/${id_account}/users${query ? `?${query}` : ''}`;
56
+
57
+ const response = await api.apps.get<ApiListResponse<AccountUser>>(url);
58
+
59
+ if (response.status === 0) {
60
+ throw new ApiError(
61
+ response.message || 'Erro ao listar usuários da conta',
62
+ 'LIST_ACCOUNT_USERS_FAILED',
63
+ 400
64
+ );
65
+ }
66
+
67
+ return {
68
+ data: response.data.map((item) => AccountUserSchema.parse(item)),
69
+ total: response.total,
70
+ page: params.page ?? 1,
71
+ };
72
+ }
73
+
52
74
  async listNotInProject(projectId: number, params: ListUsersParams = {}): Promise<UsersPage<AccountUser>> {
53
75
  const { id_account } = await getUserContext();
54
76
  const query = buildQueryParams({ notInProject: projectId, ...params } as Record<string, string | number | boolean | undefined>);
@@ -18,6 +18,7 @@ export const AccountUserSchema = z.object({
18
18
  email: z.string().nullable(),
19
19
  profile: z.string().nullable().optional().default("viewer"),
20
20
  photo: z.string().nullable().optional(),
21
+ in_project: z.boolean().optional(),
21
22
  });
22
23
 
23
24
  export type AccountUser = z.infer<typeof AccountUserSchema>;
@@ -31,6 +32,7 @@ export interface ListUsersParams {
31
32
  search?: string;
32
33
  page?: number;
33
34
  limit?: number;
35
+ inProject?: number;
34
36
  }
35
37
 
36
38
  export interface UsersPage<T> {