@greatapps/common 1.1.49 → 1.1.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/components/layouts/UsersSelectorPopover.mjs +155 -139
  2. package/dist/components/layouts/UsersSelectorPopover.mjs.map +1 -1
  3. package/dist/index.mjs +8 -3
  4. package/dist/index.mjs.map +1 -1
  5. package/dist/modules/projects/actions/list-available-users.action.mjs +2 -2
  6. package/dist/modules/projects/actions/list-available-users.action.mjs.map +1 -1
  7. package/dist/modules/projects/actions/list-project-users.action.mjs +2 -2
  8. package/dist/modules/projects/actions/list-project-users.action.mjs.map +1 -1
  9. package/dist/modules/projects/hooks/list-available-users.hook.mjs +27 -0
  10. package/dist/modules/projects/hooks/list-available-users.hook.mjs.map +1 -0
  11. package/dist/modules/projects/hooks/list-project-users.hook.mjs +27 -0
  12. package/dist/modules/projects/hooks/list-project-users.hook.mjs.map +1 -0
  13. package/dist/modules/projects/services/project-users.service.mjs +24 -13
  14. package/dist/modules/projects/services/project-users.service.mjs.map +1 -1
  15. package/dist/modules/projects/types.mjs.map +1 -1
  16. package/package.json +1 -1
  17. package/src/components/layouts/UsersSelectorPopover.tsx +119 -126
  18. package/src/index.ts +4 -3
  19. package/src/modules/projects/actions/list-available-users.action.ts +3 -3
  20. package/src/modules/projects/actions/list-project-users.action.ts +3 -3
  21. package/src/modules/projects/hooks/list-available-users.hook.ts +26 -0
  22. package/src/modules/projects/hooks/list-project-users.hook.ts +26 -0
  23. package/src/modules/projects/services/project-users.service.ts +26 -13
  24. package/src/modules/projects/types.ts +13 -1
  25. package/dist/modules/projects/hooks/project-users.hook.mjs +0 -70
  26. package/dist/modules/projects/hooks/project-users.hook.mjs.map +0 -1
  27. package/src/modules/projects/hooks/project-users.hook.ts +0 -81
@@ -0,0 +1,27 @@
1
+ "use client";
2
+ import { useInfiniteQuery } from "@tanstack/react-query";
3
+ import { listProjectUsersAction } from "../actions/list-project-users.action";
4
+ const PAGE_SIZE = 20;
5
+ const LIST_PROJECT_USERS_BASE_KEY = (projectId) => ["project-users", projectId, "in"];
6
+ const LIST_PROJECT_USERS_QUERY_KEY = (projectId, search = "") => [
7
+ ...LIST_PROJECT_USERS_BASE_KEY(projectId),
8
+ search
9
+ ];
10
+ function useListProjectUsers(projectId, search = "") {
11
+ return useInfiniteQuery({
12
+ queryKey: LIST_PROJECT_USERS_QUERY_KEY(projectId, search),
13
+ queryFn: ({ pageParam }) => listProjectUsersAction(projectId, { search: search || void 0, page: pageParam, limit: PAGE_SIZE }),
14
+ getNextPageParam: (lastPage, allPages) => {
15
+ const loaded = allPages.reduce((sum, p) => sum + p.data.length, 0);
16
+ return loaded < lastPage.total ? allPages.length + 1 : void 0;
17
+ },
18
+ initialPageParam: 1,
19
+ enabled: !!projectId
20
+ });
21
+ }
22
+ export {
23
+ LIST_PROJECT_USERS_BASE_KEY,
24
+ LIST_PROJECT_USERS_QUERY_KEY,
25
+ useListProjectUsers
26
+ };
27
+ //# sourceMappingURL=list-project-users.hook.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/modules/projects/hooks/list-project-users.hook.ts"],"sourcesContent":["'use client';\r\n\r\nimport { useInfiniteQuery } from '@tanstack/react-query';\r\nimport { listProjectUsersAction } from '../actions/list-project-users.action';\r\n\r\nconst PAGE_SIZE = 20;\r\n\r\nexport const LIST_PROJECT_USERS_BASE_KEY = (projectId: number) => ['project-users', projectId, 'in'];\r\nexport const LIST_PROJECT_USERS_QUERY_KEY = (projectId: number, search: string = '') => [\r\n ...LIST_PROJECT_USERS_BASE_KEY(projectId),\r\n search,\r\n];\r\n\r\nexport function useListProjectUsers(projectId: number, search: string = '') {\r\n return useInfiniteQuery({\r\n queryKey: LIST_PROJECT_USERS_QUERY_KEY(projectId, search),\r\n queryFn: ({ pageParam }) =>\r\n listProjectUsersAction(projectId, { search: search || undefined, page: pageParam as number, limit: PAGE_SIZE }),\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 enabled: !!projectId,\r\n });\r\n}\r\n"],"mappings":";AAEA,SAAS,wBAAwB;AACjC,SAAS,8BAA8B;AAEvC,MAAM,YAAY;AAEX,MAAM,8BAA8B,CAAC,cAAsB,CAAC,iBAAiB,WAAW,IAAI;AAC5F,MAAM,+BAA+B,CAAC,WAAmB,SAAiB,OAAO;AAAA,EACtF,GAAG,4BAA4B,SAAS;AAAA,EACxC;AACF;AAEO,SAAS,oBAAoB,WAAmB,SAAiB,IAAI;AAC1E,SAAO,iBAAiB;AAAA,IACtB,UAAU,6BAA6B,WAAW,MAAM;AAAA,IACxD,SAAS,CAAC,EAAE,UAAU,MACpB,uBAAuB,WAAW,EAAE,QAAQ,UAAU,QAAW,MAAM,WAAqB,OAAO,UAAU,CAAC;AAAA,IAChH,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,IAClB,SAAS,CAAC,CAAC;AAAA,EACb,CAAC;AACH;","names":[]}
@@ -1,16 +1,18 @@
1
1
  import "server-only";
2
2
  import { api } from "../../../infra/api/client";
3
3
  import { ApiError } from "../../../infra/api/types";
4
+ import { buildQueryParams } from "../../../infra/utils/params";
4
5
  import { getUserContext } from "../../auth/utils/get-user-context";
5
6
  import {
6
7
  AccountUserSchema,
7
8
  ProjectUserSchema
8
9
  } from "../types";
9
10
  class ProjectUsersService {
10
- async listInProject(id_project) {
11
+ async listInProject(projectId, params = {}) {
11
12
  const { id_account, id_user } = await getUserContext();
12
- const url = `/account/${id_account}/user/${id_user}/projects/${id_project}/users`;
13
- const response = await api.pages.get(url);
13
+ const query = buildQueryParams(params);
14
+ const url = `/account/${id_account}/user/${id_user}/projects/${projectId}/users${query ? `?${query}` : ""}`;
15
+ const response = await api.apps.get(url);
14
16
  if (response.status === 0) {
15
17
  throw new ApiError(
16
18
  response.message || "Erro ao listar usu\xE1rios do projeto",
@@ -18,11 +20,16 @@ class ProjectUsersService {
18
20
  400
19
21
  );
20
22
  }
21
- return response.data.map((item) => ProjectUserSchema.parse(item));
23
+ return {
24
+ data: response.data.map((item) => ProjectUserSchema.parse(item)),
25
+ total: response.total,
26
+ page: params.page ?? 1
27
+ };
22
28
  }
23
- async listNotInProject(id_project) {
29
+ async listNotInProject(projectId, params = {}) {
24
30
  const { id_account } = await getUserContext();
25
- const url = `/accounts/${id_account}/users?notInProject=${id_project}`;
31
+ const query = buildQueryParams({ notInProject: projectId, ...params });
32
+ const url = `/accounts/${id_account}/users${query ? `?${query}` : ""}`;
26
33
  const response = await api.apps.get(url);
27
34
  if (response.status === 0) {
28
35
  throw new ApiError(
@@ -31,12 +38,16 @@ class ProjectUsersService {
31
38
  400
32
39
  );
33
40
  }
34
- return response.data.map((item) => AccountUserSchema.parse(item));
41
+ return {
42
+ data: response.data.map((item) => AccountUserSchema.parse(item)),
43
+ total: response.total,
44
+ page: params.page ?? 1
45
+ };
35
46
  }
36
- async addToProject({ id_project, users }) {
47
+ async addToProject({ projectId, users }) {
37
48
  const { id_account, id_user } = await getUserContext();
38
- const url = `/account/${id_account}/user/${id_user}/projects/${id_project}/users`;
39
- const response = await api.pages.post(url, { users });
49
+ const url = `/account/${id_account}/user/${id_user}/projects/${projectId}/users`;
50
+ const response = await api.apps.post(url, { users });
40
51
  if (response.status === 0) {
41
52
  throw new ApiError(
42
53
  response.message || "Erro ao adicionar usu\xE1rio ao projeto",
@@ -45,10 +56,10 @@ class ProjectUsersService {
45
56
  );
46
57
  }
47
58
  }
48
- async removeFromProject({ id_project, users }) {
59
+ async removeFromProject({ projectId, users }) {
49
60
  const { id_account, id_user } = await getUserContext();
50
- const url = `/account/${id_account}/user/${id_user}/projects/${id_project}/users`;
51
- const response = await api.pages.delete(url, {
61
+ const url = `/account/${id_account}/user/${id_user}/projects/${projectId}/users`;
62
+ const response = await api.apps.delete(url, {
52
63
  body: JSON.stringify({ users })
53
64
  });
54
65
  if (response.status === 0) {
@@ -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 { getUserContext } from '../../auth/utils/get-user-context';\r\nimport {\r\n AccountUser,\r\n AccountUserSchema,\r\n AddRemoveProjectUsersParams,\r\n ProjectUser,\r\n ProjectUserSchema,\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(id_project: number): Promise<ProjectUser[]> {\r\n const { id_account, id_user } = await getUserContext();\r\n const url = `/account/${id_account}/user/${id_user}/projects/${id_project}/users`;\r\n\r\n const response = await api.pages.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 response.data.map((item) => ProjectUserSchema.parse(item));\r\n }\r\n\r\n async listNotInProject(id_project: number): Promise<AccountUser[]> {\r\n const { id_account } = await getUserContext();\r\n const url = `/accounts/${id_account}/users?notInProject=${id_project}`;\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 response.data.map((item) => AccountUserSchema.parse(item));\r\n }\r\n\r\n async addToProject({ id_project, users }: AddRemoveProjectUsersParams): Promise<void> {\r\n const { id_account, id_user } = await getUserContext();\r\n const url = `/account/${id_account}/user/${id_user}/projects/${id_project}/users`;\r\n\r\n const response = await api.pages.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({ id_project, users }: AddRemoveProjectUsersParams): Promise<void> {\r\n const { id_account, id_user } = await getUserContext();\r\n const url = `/account/${id_account}/user/${id_user}/projects/${id_project}/users`;\r\n\r\n const response = await api.pages.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,sBAAsB;AAC/B;AAAA,EAEE;AAAA,EAGA;AAAA,OACK;AAcP,MAAM,oBAAoB;AAAA,EACxB,MAAM,cAAc,YAA4C;AAC9D,UAAM,EAAE,YAAY,QAAQ,IAAI,MAAM,eAAe;AACrD,UAAM,MAAM,YAAY,UAAU,SAAS,OAAO,aAAa,UAAU;AAEzE,UAAM,WAAW,MAAM,IAAI,MAAM,IAAkC,GAAG;AAEtE,QAAI,SAAS,WAAW,GAAG;AACzB,YAAM,IAAI;AAAA,QACR,SAAS,WAAW;AAAA,QACpB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,WAAO,SAAS,KAAK,IAAI,CAAC,SAAS,kBAAkB,MAAM,IAAI,CAAC;AAAA,EAClE;AAAA,EAEA,MAAM,iBAAiB,YAA4C;AACjE,UAAM,EAAE,WAAW,IAAI,MAAM,eAAe;AAC5C,UAAM,MAAM,aAAa,UAAU,uBAAuB,UAAU;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,SAAS,KAAK,IAAI,CAAC,SAAS,kBAAkB,MAAM,IAAI,CAAC;AAAA,EAClE;AAAA,EAEA,MAAM,aAAa,EAAE,YAAY,MAAM,GAA+C;AACpF,UAAM,EAAE,YAAY,QAAQ,IAAI,MAAM,eAAe;AACrD,UAAM,MAAM,YAAY,UAAU,SAAS,OAAO,aAAa,UAAU;AAEzE,UAAM,WAAW,MAAM,IAAI,MAAM,KAAwB,KAAK,EAAE,MAAM,CAAC;AAEvE,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,YAAY,MAAM,GAA+C;AACzF,UAAM,EAAE,YAAY,QAAQ,IAAI,MAAM,eAAe;AACrD,UAAM,MAAM,YAAY,UAAU,SAAS,OAAO,aAAa,UAAU;AAEzE,UAAM,WAAW,MAAM,IAAI,MAAM,OAA0B,KAAK;AAAA,MAC9D,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 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 +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(),\r\n email: z.string(),\r\n profile: z.string().optional(),\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(),\r\n email: z.string(),\r\n profile: z.string().optional(),\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 id_project: number;\r\n users: 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;AAAA,EACf,OAAO,EAAE,OAAO;AAAA,EAChB,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,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;AAAA,EACf,OAAO,EAAE,OAAO;AAAA,EAChB,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,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(),\r\n email: z.string(),\r\n profile: z.string().optional(),\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(),\r\n email: z.string(),\r\n profile: z.string().optional(),\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;AAAA,EACf,OAAO,EAAE,OAAO;AAAA,EAChB,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,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;AAAA,EACf,OAAO,EAAE,OAAO;AAAA,EAChB,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AACxC,CAAC;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greatapps/common",
3
- "version": "1.1.49",
3
+ "version": "1.1.51",
4
4
  "description": "Shared library for GreatApps frontend applications",
5
5
  "main": "./dist/index.mjs",
6
6
  "types": "./src/index.ts",
@@ -1,22 +1,30 @@
1
1
  "use client"
2
2
 
3
- import { useState } from 'react';
3
+ import { useEffect, useState } from 'react';
4
4
  import { Plus, Settings, User, Users } from 'lucide-react';
5
5
  import Image from 'next/image';
6
6
  import { useRouter } from 'next/navigation';
7
+ import { useQueryClient, useMutation } from '@tanstack/react-query';
7
8
  import { Popover, PopoverContent, PopoverTrigger } from '../ui/overlay/Popover';
8
9
  import { Button } from '../ui/buttons/Button';
9
10
  import { Command, CommandEmpty, CommandInput, CommandItem, CommandList } from '../ui/overlay/Command';
10
11
  import { Checkbox } from '../ui/form/Checkbox';
11
12
  import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/overlay/Tooltip';
12
13
  import { cn } from '../../infra/utils/clsx';
13
- import { useProjectUsers } from '../../modules/projects/hooks/project-users.hook';
14
+ import { useListProjectUsers, LIST_PROJECT_USERS_BASE_KEY } from '../../modules/projects/hooks/list-project-users.hook';
15
+ import { useListAvailableUsers, LIST_AVAILABLE_USERS_BASE_KEY } from '../../modules/projects/hooks/list-available-users.hook';
16
+ import { addProjectUserAction } from '../../modules/projects/actions/add-project-user.action';
17
+ import { removeProjectUserAction } from '../../modules/projects/actions/remove-project-user.action';
14
18
 
15
- export interface UserSelectorItem {
16
- id: number;
17
- name: string;
18
- avatar: string;
19
- checked: boolean;
19
+ const DEFAULT_AVATAR = '/images/Logomark.png';
20
+
21
+ export interface UsersSelectorPopoverProps {
22
+ projectId?: number;
23
+ addRoute?: string;
24
+ manageRoute?: string;
25
+ side?: 'top' | 'bottom' | 'left' | 'right';
26
+ align?: 'start' | 'center' | 'end';
27
+ contentClassName?: string;
20
28
  }
21
29
 
22
30
  function UserItemSkeleton() {
@@ -31,50 +39,94 @@ function UserItemSkeleton() {
31
39
  );
32
40
  }
33
41
 
34
- export interface UsersSelectorPopoverProps {
35
- projectId?: number;
36
- users?: UserSelectorItem[];
37
- onToggleUser?: (id: number) => void;
38
- addRoute?: string;
39
- manageRoute?: string;
40
- userCount?: number;
41
- side?: 'top' | 'bottom' | 'left' | 'right';
42
- align?: 'start' | 'center' | 'end';
43
- contentClassName?: string;
44
- }
45
-
46
- interface ContentProps {
47
- users: UserSelectorItem[];
48
- isLoading: boolean;
49
- userCount: number;
50
- toggleUser: (id: number) => void;
51
- addRoute: string;
52
- manageRoute: string;
53
- side?: UsersSelectorPopoverProps['side'];
54
- align?: UsersSelectorPopoverProps['align'];
55
- contentClassName?: string;
56
- }
57
-
58
- function UsersSelectorPopoverContent({
59
- users,
60
- isLoading,
61
- userCount,
62
- toggleUser,
63
- addRoute,
64
- manageRoute,
42
+ export function UsersSelectorPopover({
43
+ projectId,
44
+ addRoute = '/my-teams?createNew=true',
45
+ manageRoute = '/my-teams',
65
46
  side,
66
47
  align,
67
48
  contentClassName,
68
- }: ContentProps) {
69
- const route = useRouter();
49
+ }: UsersSelectorPopoverProps) {
50
+ const router = useRouter();
51
+ const queryClient = useQueryClient();
70
52
  const [open, setOpen] = useState(false);
71
53
  const [hoveredId, setHoveredId] = useState<number | null>(null);
54
+ const [searchInput, setSearchInput] = useState('');
55
+ const [search, setSearch] = useState('');
56
+
57
+ const id = projectId ?? 0;
58
+
59
+ useEffect(() => {
60
+ const timer = setTimeout(() => setSearch(searchInput), 300);
61
+ return () => clearTimeout(timer);
62
+ }, [searchInput]);
63
+
64
+ const projectUsers = useListProjectUsers(id, search);
65
+ const availableUsers = useListAvailableUsers(id, search);
66
+
67
+ const inProject = projectUsers.data?.pages.flatMap((p) => p.data) ?? [];
68
+ const notInProject = availableUsers.data?.pages.flatMap((p) => p.data) ?? [];
69
+ const isLoading = projectUsers.isLoading || availableUsers.isLoading;
70
+ const isFetchingMore = projectUsers.isFetchingNextPage || availableUsers.isFetchingNextPage;
71
+ const totalInProject = projectUsers.data?.pages[0]?.total ?? inProject.length;
72
+
73
+ const invalidate = () => {
74
+ queryClient.invalidateQueries({ queryKey: LIST_PROJECT_USERS_BASE_KEY(id) });
75
+ queryClient.invalidateQueries({ queryKey: LIST_AVAILABLE_USERS_BASE_KEY(id) });
76
+ };
77
+
78
+ const addMutation = useMutation({
79
+ mutationFn: (userId: number) => addProjectUserAction({ projectId: id, users: [userId] }),
80
+ onSuccess: invalidate,
81
+ });
82
+
83
+ const removeMutation = useMutation({
84
+ mutationFn: (userId: number) => removeProjectUserAction({ projectId: id, users: [userId] }),
85
+ onSuccess: invalidate,
86
+ });
87
+
88
+ const allUsers = [
89
+ ...inProject.map((u) => ({
90
+ id: u.id_user,
91
+ name: u.name,
92
+ avatar: u.photo ?? DEFAULT_AVATAR,
93
+ checked: true as const,
94
+ removable: u.profile !== 'owner' && u.profile !== 'admin',
95
+ })),
96
+ ...notInProject.map((u) => ({
97
+ id: u.id,
98
+ name: u.name,
99
+ avatar: u.photo ?? DEFAULT_AVATAR,
100
+ checked: false as const,
101
+ removable: true,
102
+ })),
103
+ ];
104
+
105
+ const toggleUser = (id: number, checked: boolean, removable: boolean) => {
106
+ if (checked) {
107
+ if (!removable) return;
108
+ removeMutation.mutate(id);
109
+ } else {
110
+ addMutation.mutate(id);
111
+ }
112
+ };
113
+
114
+ const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
115
+ const target = e.currentTarget;
116
+ const nearBottom = target.scrollTop + target.clientHeight >= target.scrollHeight - 50;
117
+ if (!nearBottom) return;
118
+ if (projectUsers.hasNextPage && !projectUsers.isFetchingNextPage) {
119
+ projectUsers.fetchNextPage();
120
+ } else if (availableUsers.hasNextPage && !availableUsers.isFetchingNextPage) {
121
+ availableUsers.fetchNextPage();
122
+ }
123
+ };
72
124
 
73
125
  return (
74
126
  <Popover open={open} onOpenChange={setOpen}>
75
127
  <PopoverTrigger className="cursor-pointer">
76
128
  <div className="flex items-center gap-2 text-gray-950 px-3 py-2 border border-gray-200 rounded-full hover:border-gray-950">
77
- <span className="paragraph-small-semibold">{userCount}</span>
129
+ <span className="paragraph-small-semibold">{totalInProject}</span>
78
130
  <User size={16} />
79
131
  </div>
80
132
  </PopoverTrigger>
@@ -95,7 +147,7 @@ function UsersSelectorPopoverContent({
95
147
  <Button
96
148
  variant="secondary"
97
149
  className="h-8 paragraph-small-semibold text-gray-950"
98
- onClick={() => route.push(addRoute)}
150
+ onClick={() => router.push(addRoute)}
99
151
  >
100
152
  <Plus size={16} />
101
153
  Adicionar
@@ -105,7 +157,7 @@ function UsersSelectorPopoverContent({
105
157
  <Button
106
158
  variant="secondary"
107
159
  className="p-0 size-8"
108
- onClick={() => route.push(manageRoute)}
160
+ onClick={() => router.push(manageRoute)}
109
161
  >
110
162
  <Settings size={18} />
111
163
  </Button>
@@ -115,16 +167,23 @@ function UsersSelectorPopoverContent({
115
167
  </div>
116
168
  </div>
117
169
 
118
- <Command className="gap-2" value="">
119
- <CommandInput placeholder="Busque aqui" />
120
- <CommandList className="custom-scrollbar paragraph-small-medium text-gray-600 h-[162px]">
170
+ <Command shouldFilter={false} className="gap-2" value="">
171
+ <CommandInput
172
+ placeholder="Busque aqui"
173
+ value={searchInput}
174
+ onValueChange={setSearchInput}
175
+ />
176
+ <CommandList
177
+ className="custom-scrollbar paragraph-small-medium text-gray-600 h-[162px]"
178
+ onScroll={handleScroll}
179
+ >
121
180
  {isLoading ? (
122
181
  <>
123
182
  <UserItemSkeleton />
124
183
  <UserItemSkeleton />
125
184
  <UserItemSkeleton />
126
185
  </>
127
- ) : users.length === 0 ? (
186
+ ) : allUsers.length === 0 ? (
128
187
  <div className="flex flex-col items-center justify-center gap-2 h-full text-gray-400 py-4">
129
188
  <Users size={28} className="text-gray-300" />
130
189
  <span className="paragraph-small-medium text-center text-gray-500">
@@ -136,7 +195,7 @@ function UsersSelectorPopoverContent({
136
195
  ) : (
137
196
  <>
138
197
  <CommandEmpty>Nenhum resultado encontrado.</CommandEmpty>
139
- {users.map((user) => (
198
+ {allUsers.map((user) => (
140
199
  <CommandItem
141
200
  key={user.id}
142
201
  value={user.name}
@@ -145,8 +204,8 @@ function UsersSelectorPopoverContent({
145
204
  onPointerLeave={() => setHoveredId(null)}
146
205
  onFocus={() => setHoveredId(user.id)}
147
206
  onBlur={() => setHoveredId(null)}
148
- onSelect={() => toggleUser(user.id)}
149
- className="cursor-pointer"
207
+ onSelect={() => toggleUser(user.id, user.checked, user.removable)}
208
+ className={user.removable ? 'cursor-pointer' : 'cursor-default'}
150
209
  >
151
210
  <div className="flex items-center justify-between w-full">
152
211
  <div className="flex items-center gap-2">
@@ -159,15 +218,16 @@ function UsersSelectorPopoverContent({
159
218
  />
160
219
  {user.name}
161
220
  </div>
162
- <Tooltip open={hoveredId === user.id}>
221
+ <Tooltip open={user.removable && hoveredId === user.id}>
163
222
  <TooltipTrigger
164
223
  onClick={(e) => e.stopPropagation()}
165
224
  onPointerDown={(e) => e.stopPropagation()}
166
225
  >
167
226
  <Checkbox
168
227
  checked={user.checked}
169
- onCheckedChange={() => toggleUser(user.id)}
170
- className={hoveredId === user.id ? 'border-gray-950' : ''}
228
+ disabled={!user.removable}
229
+ onCheckedChange={() => toggleUser(user.id, user.checked, user.removable)}
230
+ className={user.removable && hoveredId === user.id ? 'border-gray-950' : ''}
171
231
  />
172
232
  </TooltipTrigger>
173
233
  <TooltipContent>
@@ -177,6 +237,12 @@ function UsersSelectorPopoverContent({
177
237
  </div>
178
238
  </CommandItem>
179
239
  ))}
240
+ {isFetchingMore && (
241
+ <>
242
+ <UserItemSkeleton />
243
+ <UserItemSkeleton />
244
+ </>
245
+ )}
180
246
  </>
181
247
  )}
182
248
  </CommandList>
@@ -186,76 +252,3 @@ function UsersSelectorPopoverContent({
186
252
  </Popover>
187
253
  );
188
254
  }
189
-
190
- function ConnectedUsersSelectorPopover({
191
- projectId,
192
- addRoute,
193
- manageRoute,
194
- side,
195
- align,
196
- contentClassName,
197
- }: Required<Pick<UsersSelectorPopoverProps, 'projectId'>> &
198
- Pick<UsersSelectorPopoverProps, 'addRoute' | 'manageRoute' | 'side' | 'align' | 'contentClassName'>) {
199
- const { users, isLoading, userCount, toggleUser } = useProjectUsers(projectId);
200
- return (
201
- <UsersSelectorPopoverContent
202
- users={users}
203
- isLoading={isLoading}
204
- userCount={userCount}
205
- toggleUser={toggleUser}
206
- addRoute={addRoute ?? '/my-teams?createNew=true'}
207
- manageRoute={manageRoute ?? '/my-teams'}
208
- side={side}
209
- align={align}
210
- contentClassName={contentClassName}
211
- />
212
- );
213
- }
214
-
215
- function StaticUsersSelectorPopover({
216
- users: externalUsers = [],
217
- onToggleUser,
218
- userCount = 5,
219
- addRoute = '/my-teams?createNew=true',
220
- manageRoute = '/my-teams',
221
- side,
222
- align,
223
- contentClassName,
224
- }: Omit<UsersSelectorPopoverProps, 'projectId'>) {
225
- const [internalUsers, setInternalUsers] = useState<UserSelectorItem[]>(externalUsers);
226
-
227
- const users = externalUsers.length > 0 ? externalUsers : internalUsers;
228
-
229
- const toggleUser = (id: number) => {
230
- if (onToggleUser) {
231
- onToggleUser(id);
232
- } else {
233
- setInternalUsers((prev) =>
234
- prev.map((u) => (u.id === id ? { ...u, checked: !u.checked } : u))
235
- );
236
- }
237
- };
238
-
239
- return (
240
- <UsersSelectorPopoverContent
241
- users={users}
242
- isLoading={false}
243
- userCount={userCount}
244
- toggleUser={toggleUser}
245
- addRoute={addRoute}
246
- manageRoute={manageRoute}
247
- side={side}
248
- align={align}
249
- contentClassName={contentClassName}
250
- />
251
- );
252
- }
253
-
254
- function UsersSelectorPopover(props: UsersSelectorPopoverProps) {
255
- if (props.projectId != null) {
256
- return <ConnectedUsersSelectorPopover {...props} projectId={props.projectId} />;
257
- }
258
- return <StaticUsersSelectorPopover {...props} />;
259
- }
260
-
261
- export { UsersSelectorPopover };
package/src/index.ts CHANGED
@@ -10,8 +10,9 @@ export * from './providers/auth.provider';
10
10
  export * from './providers/whitelabel.provider';
11
11
 
12
12
  // Hooks
13
- export { useProjectUsers, projectUsersQueryKey } from './modules/projects/hooks/project-users.hook';
14
- export type { ProjectUser, AccountUser, AddRemoveProjectUsersParams } from './modules/projects/types';
13
+ export { useListProjectUsers, LIST_PROJECT_USERS_QUERY_KEY, LIST_PROJECT_USERS_BASE_KEY } from './modules/projects/hooks/list-project-users.hook';
14
+ export { useListAvailableUsers, LIST_AVAILABLE_USERS_QUERY_KEY, LIST_AVAILABLE_USERS_BASE_KEY } from './modules/projects/hooks/list-available-users.hook';
15
+ export type { ProjectUser, AccountUser, AddRemoveProjectUsersParams, ListUsersParams, UsersPage } from './modules/projects/types';
15
16
  export { useUserQuery, useUserValidateSession, useInvalidateUser, useSetUserData, USER_QUERY_KEY } from './modules/auth/hooks/useUserQuery';
16
17
  export { useSubscriptions, SUBSCRIPTIONS_QUERY_KEY } from './modules/subscriptions/hooks/list-subscriptions.hook';
17
18
  export { useActiveSubscription } from './modules/subscriptions/hooks/find-active-subscription.hook';
@@ -51,7 +52,7 @@ export type { NotificationItemProps } from './components/layouts/NotificationIte
51
52
  export { NotificationsPopover } from './components/layouts/NotificationsPopover';
52
53
  export type { NotificationsPopoverProps, NotificationData } from './components/layouts/NotificationsPopover';
53
54
  export { UsersSelectorPopover } from './components/layouts/UsersSelectorPopover';
54
- export type { UsersSelectorPopoverProps, UserSelectorItem } from './components/layouts/UsersSelectorPopover';
55
+ export type { UsersSelectorPopoverProps } from './components/layouts/UsersSelectorPopover';
55
56
  export { ProfilePopover } from './components/layouts/ProfilePopover';
56
57
  export type { ProfilePopoverProps, ProfileMenuItem } from './components/layouts/ProfilePopover';
57
58
  export { NavBarItem } from './components/layouts/NavBarItem';
@@ -1,8 +1,8 @@
1
1
  'use server';
2
2
 
3
3
  import { projectUsersService } from '../services/project-users.service';
4
- import type { AccountUser } from '../types';
4
+ import type { AccountUser, ListUsersParams, UsersPage } from '../types';
5
5
 
6
- export async function listAvailableUsersAction(id_project: number): Promise<AccountUser[]> {
7
- return projectUsersService.listNotInProject(id_project);
6
+ export async function listAvailableUsersAction(projectId: number, params: ListUsersParams = {}): Promise<UsersPage<AccountUser>> {
7
+ return projectUsersService.listNotInProject(projectId, params);
8
8
  }
@@ -1,8 +1,8 @@
1
1
  'use server';
2
2
 
3
3
  import { projectUsersService } from '../services/project-users.service';
4
- import type { ProjectUser } from '../types';
4
+ import type { ListUsersParams, ProjectUser, UsersPage } from '../types';
5
5
 
6
- export async function listProjectUsersAction(id_project: number): Promise<ProjectUser[]> {
7
- return projectUsersService.listInProject(id_project);
6
+ export async function listProjectUsersAction(projectId: number, params: ListUsersParams = {}): Promise<UsersPage<ProjectUser>> {
7
+ return projectUsersService.listInProject(projectId, params);
8
8
  }
@@ -0,0 +1,26 @@
1
+ 'use client';
2
+
3
+ import { useInfiniteQuery } from '@tanstack/react-query';
4
+ import { listAvailableUsersAction } from '../actions/list-available-users.action';
5
+
6
+ const PAGE_SIZE = 20;
7
+
8
+ export const LIST_AVAILABLE_USERS_BASE_KEY = (projectId: number) => ['project-users', projectId, 'out'];
9
+ export const LIST_AVAILABLE_USERS_QUERY_KEY = (projectId: number, search: string = '') => [
10
+ ...LIST_AVAILABLE_USERS_BASE_KEY(projectId),
11
+ search,
12
+ ];
13
+
14
+ export function useListAvailableUsers(projectId: number, search: string = '') {
15
+ return useInfiniteQuery({
16
+ queryKey: LIST_AVAILABLE_USERS_QUERY_KEY(projectId, search),
17
+ queryFn: ({ pageParam }) =>
18
+ listAvailableUsersAction(projectId, { search: search || undefined, page: pageParam as number, limit: PAGE_SIZE }),
19
+ getNextPageParam: (lastPage, allPages) => {
20
+ const loaded = allPages.reduce((sum, p) => sum + p.data.length, 0);
21
+ return loaded < lastPage.total ? allPages.length + 1 : undefined;
22
+ },
23
+ initialPageParam: 1,
24
+ enabled: !!projectId,
25
+ });
26
+ }
@@ -0,0 +1,26 @@
1
+ 'use client';
2
+
3
+ import { useInfiniteQuery } from '@tanstack/react-query';
4
+ import { listProjectUsersAction } from '../actions/list-project-users.action';
5
+
6
+ const PAGE_SIZE = 20;
7
+
8
+ export const LIST_PROJECT_USERS_BASE_KEY = (projectId: number) => ['project-users', projectId, 'in'];
9
+ export const LIST_PROJECT_USERS_QUERY_KEY = (projectId: number, search: string = '') => [
10
+ ...LIST_PROJECT_USERS_BASE_KEY(projectId),
11
+ search,
12
+ ];
13
+
14
+ export function useListProjectUsers(projectId: number, search: string = '') {
15
+ return useInfiniteQuery({
16
+ queryKey: LIST_PROJECT_USERS_QUERY_KEY(projectId, search),
17
+ queryFn: ({ pageParam }) =>
18
+ listProjectUsersAction(projectId, { search: search || undefined, page: pageParam as number, limit: PAGE_SIZE }),
19
+ getNextPageParam: (lastPage, allPages) => {
20
+ const loaded = allPages.reduce((sum, p) => sum + p.data.length, 0);
21
+ return loaded < lastPage.total ? allPages.length + 1 : undefined;
22
+ },
23
+ initialPageParam: 1,
24
+ enabled: !!projectId,
25
+ });
26
+ }