@greatapps/common 1.1.50 → 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.
- package/dist/components/layouts/UsersSelectorPopover.mjs +155 -139
- package/dist/components/layouts/UsersSelectorPopover.mjs.map +1 -1
- package/dist/index.mjs +8 -3
- package/dist/index.mjs.map +1 -1
- package/dist/modules/projects/actions/list-available-users.action.mjs +2 -2
- package/dist/modules/projects/actions/list-available-users.action.mjs.map +1 -1
- package/dist/modules/projects/actions/list-project-users.action.mjs +2 -2
- package/dist/modules/projects/actions/list-project-users.action.mjs.map +1 -1
- package/dist/modules/projects/hooks/list-available-users.hook.mjs +27 -0
- package/dist/modules/projects/hooks/list-available-users.hook.mjs.map +1 -0
- package/dist/modules/projects/hooks/list-project-users.hook.mjs +27 -0
- package/dist/modules/projects/hooks/list-project-users.hook.mjs.map +1 -0
- package/dist/modules/projects/services/project-users.service.mjs +21 -10
- package/dist/modules/projects/services/project-users.service.mjs.map +1 -1
- package/dist/modules/projects/types.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/layouts/UsersSelectorPopover.tsx +119 -126
- package/src/index.ts +4 -3
- package/src/modules/projects/actions/list-available-users.action.ts +3 -3
- package/src/modules/projects/actions/list-project-users.action.ts +3 -3
- package/src/modules/projects/hooks/list-available-users.hook.ts +26 -0
- package/src/modules/projects/hooks/list-project-users.hook.ts +26 -0
- package/src/modules/projects/services/project-users.service.ts +23 -10
- package/src/modules/projects/types.ts +13 -1
- package/dist/modules/projects/hooks/project-users.hook.mjs +0 -70
- package/dist/modules/projects/hooks/project-users.hook.mjs.map +0 -1
- 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,15 +1,17 @@
|
|
|
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(
|
|
11
|
+
async listInProject(projectId, params = {}) {
|
|
11
12
|
const { id_account, id_user } = await getUserContext();
|
|
12
|
-
const
|
|
13
|
+
const query = buildQueryParams(params);
|
|
14
|
+
const url = `/account/${id_account}/user/${id_user}/projects/${projectId}/users${query ? `?${query}` : ""}`;
|
|
13
15
|
const response = await api.apps.get(url);
|
|
14
16
|
if (response.status === 0) {
|
|
15
17
|
throw new ApiError(
|
|
@@ -18,11 +20,16 @@ class ProjectUsersService {
|
|
|
18
20
|
400
|
|
19
21
|
);
|
|
20
22
|
}
|
|
21
|
-
return
|
|
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(
|
|
29
|
+
async listNotInProject(projectId, params = {}) {
|
|
24
30
|
const { id_account } = await getUserContext();
|
|
25
|
-
const
|
|
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,11 +38,15 @@ class ProjectUsersService {
|
|
|
31
38
|
400
|
|
32
39
|
);
|
|
33
40
|
}
|
|
34
|
-
return
|
|
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({
|
|
47
|
+
async addToProject({ projectId, users }) {
|
|
37
48
|
const { id_account, id_user } = await getUserContext();
|
|
38
|
-
const url = `/account/${id_account}/user/${id_user}/projects/${
|
|
49
|
+
const url = `/account/${id_account}/user/${id_user}/projects/${projectId}/users`;
|
|
39
50
|
const response = await api.apps.post(url, { users });
|
|
40
51
|
if (response.status === 0) {
|
|
41
52
|
throw new ApiError(
|
|
@@ -45,9 +56,9 @@ class ProjectUsersService {
|
|
|
45
56
|
);
|
|
46
57
|
}
|
|
47
58
|
}
|
|
48
|
-
async removeFromProject({
|
|
59
|
+
async removeFromProject({ projectId, users }) {
|
|
49
60
|
const { id_account, id_user } = await getUserContext();
|
|
50
|
-
const url = `/account/${id_account}/user/${id_user}/projects/${
|
|
61
|
+
const url = `/account/${id_account}/user/${id_user}/projects/${projectId}/users`;
|
|
51
62
|
const response = await api.apps.delete(url, {
|
|
52
63
|
body: JSON.stringify({ users })
|
|
53
64
|
});
|
|
@@ -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(
|
|
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
|
|
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,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 {
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
35
|
-
projectId
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
}:
|
|
69
|
-
const
|
|
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">{
|
|
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={() =>
|
|
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={() =>
|
|
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
|
|
120
|
-
|
|
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
|
-
) :
|
|
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
|
-
{
|
|
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=
|
|
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
|
-
|
|
170
|
-
|
|
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 {
|
|
14
|
-
export
|
|
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
|
|
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(
|
|
7
|
-
return projectUsersService.listNotInProject(
|
|
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(
|
|
7
|
-
return projectUsersService.listInProject(
|
|
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
|
+
}
|