@titus-system/syncdesk 0.3.1 → 0.5.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,29 @@
1
+ # CHANGELOG
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Added
10
+
11
+ ### Changed
12
+
13
+ ### Deprecated
14
+
15
+ ### Removed
16
+
17
+ ### Fixed
18
+
19
+ ### Security
20
+
21
+ ### Dev Notes
22
+
23
+ ---
24
+
25
+ # 0.5.0 - 2026-04-04
26
+
27
+ ### Added
28
+
29
+ - Get a specific client's conversations.
package/README.md CHANGED
@@ -14,6 +14,8 @@ npm install @titus-system/syncdesk
14
14
 
15
15
  ### Install locally
16
16
 
17
+ Alternatively, you can install locally bypassing the npm registry.
18
+
17
19
  ```sh
18
20
  npm run build
19
21
  npm pack
@@ -25,13 +27,44 @@ It creates a compressed tarball file in your folder. You can install it in your
25
27
  npm install /path/to/your/library-1.0.0.tgz
26
28
  ```
27
29
 
30
+ ### Configure your project to use the library
31
+
32
+ This lib requires some configuration to work properly, such as the API base URL.
33
+ To do so, you should call the `configLibrary` function in your project, passing the necessary configuration options. For example:
34
+
35
+ ```ts
36
+ import { configLibrary } from "@titus-system/syncdesk";
37
+ configureLibrary({
38
+ baseURL: 'http://localhost:8000/api', // should not be hardcoded, but in a .env file or similar
39
+
40
+ getAccessToken: () => localStorage.getItem('access_token'),
41
+ getRefreshToken: () => localStorage.getItem('refresh_token'),
42
+
43
+ onTokensRefreshed: (newAccess: string, newRefresh: string) => {
44
+ localStorage.setItem('access_token', newAccess)
45
+ localStorage.setItem('refresh_token', newRefresh)
46
+ },
47
+
48
+ onUnauthorized: () => {
49
+ localStorage.removeItem('access_token')
50
+ localStorage.removeItem('refresh_token')
51
+ window.location.href = '/login'
52
+
53
+ // // Mobile-specific logic to clear secure storage and navigate
54
+ // await SecureStore.deleteItemAsync('access_token')
55
+ // router.replace('/login')
56
+ }
57
+ })
58
+ ```
59
+
28
60
  ## Publish new version to npm registry
29
61
 
30
62
  ```sh
31
63
  # You may have to login to npm registry first with `npm login`
32
64
  npm login
33
65
 
34
- npm run build && npm publish
66
+ npm run build
67
+ npm publish --access public
35
68
  ```
36
69
 
37
70
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@titus-system/syncdesk",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "author": "",
@@ -1,17 +1,24 @@
1
1
  import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
2
2
  import { apiClient, ApiResponse } from "../../api";
3
3
  import {
4
+ AdminRegisterUserRequest,
5
+ ChangePasswordRequest,
6
+ ForgotPasswordRequest,
7
+ ForgotPasswordResponse,
4
8
  LoginResponse,
5
9
  RegisterUserRequest,
10
+ ResetPasswordRequest,
6
11
  UserCreatedResponse,
7
12
  UserLoginRequest,
8
13
  UserWithRoles,
9
14
  } from "../types/auth";
15
+ import { User } from "../../users/types/user";
10
16
 
11
17
  const PATH = "auth";
12
18
 
13
19
  /**
14
20
  * Get the currently authenticated user's profile.
21
+ * @returns {UseQueryResult<UserWithRoles, Error>} The query result.
15
22
  */
16
23
  export const useGetMe = () => {
17
24
  return useQuery({
@@ -32,6 +39,8 @@ export const useGetMe = () => {
32
39
  * Log in a user.
33
40
  *
34
41
  * Refresh tokens are handled automatically using Interceptors.
42
+ * @param {UserLoginRequest} credentials The user login credentials.
43
+ * @returns {UseMutationResult<LoginResponse, Error, UserLoginRequest>} The mutation result.
35
44
  */
36
45
  export const useLogin = () => {
37
46
  const queryClient = useQueryClient();
@@ -55,6 +64,8 @@ export const useLogin = () => {
55
64
 
56
65
  /**
57
66
  * Register a new user.
67
+ * @param {RegisterUserRequest} userData The user registration details.
68
+ * @returns {UseMutationResult<UserCreatedResponse, Error, RegisterUserRequest>} The mutation result.
58
69
  */
59
70
  export const useRegister = () => {
60
71
  const queryClient = useQueryClient();
@@ -78,6 +89,7 @@ export const useRegister = () => {
78
89
 
79
90
  /**
80
91
  * Log out the current user.
92
+ * @returns {UseMutationResult<void, Error, void>} The mutation result.
81
93
  */
82
94
  export const useLogout = () => {
83
95
  const queryClient = useQueryClient();
@@ -93,3 +105,69 @@ export const useLogout = () => {
93
105
  },
94
106
  });
95
107
  };
108
+
109
+ /**
110
+ * Register a new user as admin.
111
+ * @param {AdminRegisterUserRequest} userData The admin user registration details.
112
+ * @returns {UseMutationResult<User, Error, AdminRegisterUserRequest>} The mutation result.
113
+ */
114
+ export const useAdminRegister = () => {
115
+ const queryClient = useQueryClient();
116
+
117
+ return useMutation({
118
+ mutationFn: async (userData: AdminRegisterUserRequest): Promise<User> => {
119
+ const response = await apiClient.post<ApiResponse<User>>(
120
+ `${PATH}/admin/register`,
121
+ userData,
122
+ );
123
+ return response.data.data;
124
+ },
125
+ onSuccess: () => {
126
+ queryClient.invalidateQueries({ queryKey: ["users"] });
127
+ },
128
+ });
129
+ };
130
+
131
+ /**
132
+ * Change the current user's password.
133
+ * @param {ChangePasswordRequest} data The password change request details.
134
+ * @returns {UseMutationResult<void, Error, ChangePasswordRequest>} The mutation result.
135
+ */
136
+ export const useChangePassword = () => {
137
+ return useMutation({
138
+ mutationFn: async (data: ChangePasswordRequest): Promise<void> => {
139
+ await apiClient.post(`${PATH}/change-password`, data);
140
+ },
141
+ });
142
+ };
143
+
144
+ /**
145
+ * Request a password reset.
146
+ * @param {ForgotPasswordRequest} data The forgot password request details.
147
+ * @returns {UseMutationResult<ForgotPasswordResponse, Error, ForgotPasswordRequest>} The mutation result.
148
+ */
149
+ export const useForgotPassword = () => {
150
+ return useMutation({
151
+ mutationFn: async (
152
+ data: ForgotPasswordRequest,
153
+ ): Promise<ForgotPasswordResponse> => {
154
+ const response = await apiClient.post<
155
+ ApiResponse<ForgotPasswordResponse>
156
+ >(`${PATH}/forgot-password`, data);
157
+ return response.data.data;
158
+ },
159
+ });
160
+ };
161
+
162
+ /**
163
+ * Reset the current user's password using a valid reset token.
164
+ * @param {ResetPasswordRequest} data The reset password request details.
165
+ * @returns {UseMutationResult<void, Error, ResetPasswordRequest>} The mutation result.
166
+ */
167
+ export const useResetPassword = () => {
168
+ return useMutation({
169
+ mutationFn: async (data: ResetPasswordRequest): Promise<void> => {
170
+ await apiClient.post(`${PATH}/reset-password`, data);
171
+ },
172
+ });
173
+ };
@@ -3,6 +3,8 @@ export type OAuthProvider = "local" | "google" | "microsoft";
3
3
  export interface LoginResponse {
4
4
  access_token: string;
5
5
  refresh_token: string;
6
+ must_change_password?: boolean;
7
+ must_accept_terms?: boolean;
6
8
  }
7
9
 
8
10
  export interface UserCreatedResponse {
@@ -20,11 +22,35 @@ export interface UserLoginRequest {
20
22
 
21
23
  export interface RegisterUserRequest {
22
24
  email: string;
23
- name: string;
24
- username: string;
25
+ name?: string;
26
+ username?: string;
25
27
  password: string;
26
28
  }
27
29
 
30
+ export interface AdminRegisterUserRequest {
31
+ email: string;
32
+ name?: string | null;
33
+ role_ids?: number[];
34
+ }
35
+
36
+ export interface ChangePasswordRequest {
37
+ current_password: string;
38
+ new_password: string;
39
+ }
40
+
41
+ export interface ForgotPasswordRequest {
42
+ email: string;
43
+ }
44
+
45
+ export interface ResetPasswordRequest {
46
+ token: string;
47
+ new_password: string;
48
+ }
49
+
50
+ export interface ForgotPasswordResponse {
51
+ message: string;
52
+ }
53
+
28
54
  // I am making a safe assumption for your /me route based on "user_with_roles"
29
55
  export interface UserWithRoles {
30
56
  id: string;
@@ -6,14 +6,19 @@ import type {
6
6
  ReadyResponse,
7
7
  } from "../types/health";
8
8
 
9
+ const PATH = "";
10
+
9
11
  /**
10
12
  * Ping the server to check for basic connectivity.
13
+ * @returns {UseQueryResult<PingResponse, Error>} The result.
11
14
  */
12
15
  export const usePing = () => {
13
16
  return useQuery({
14
17
  queryKey: ["health", "ping"],
15
18
  queryFn: async (): Promise<PingResponse> => {
16
- const response = await apiClient.get<ApiResponse<PingResponse>>("/ping");
19
+ const response = await apiClient.get<ApiResponse<PingResponse>>(
20
+ `${PATH}/ping`,
21
+ );
17
22
  return response.data.data;
18
23
  },
19
24
  });
@@ -21,13 +26,15 @@ export const usePing = () => {
21
26
 
22
27
  /**
23
28
  * Check the detailed health of the server and its dependencies (e.g. databases).
29
+ * @returns {UseQueryResult<HealthResponse, Error>} The result.
24
30
  */
25
31
  export const useHealth = () => {
26
32
  return useQuery({
27
33
  queryKey: ["health", "status"],
28
34
  queryFn: async (): Promise<HealthResponse> => {
29
- const response =
30
- await apiClient.get<ApiResponse<HealthResponse>>("/health");
35
+ const response = await apiClient.get<ApiResponse<HealthResponse>>(
36
+ `${PATH}/health`,
37
+ );
31
38
  return response.data.data;
32
39
  },
33
40
  });
@@ -35,13 +42,15 @@ export const useHealth = () => {
35
42
 
36
43
  /**
37
44
  * Check if the server is ready to accept traffic.
45
+ * @returns {UseQueryResult<ReadyResponse, Error>} The result.
38
46
  */
39
47
  export const useReady = () => {
40
48
  return useQuery({
41
49
  queryKey: ["health", "ready"],
42
50
  queryFn: async (): Promise<ReadyResponse> => {
43
- const response =
44
- await apiClient.get<ApiResponse<ReadyResponse>>("/ready");
51
+ const response = await apiClient.get<ApiResponse<ReadyResponse>>(
52
+ `${PATH}/ready`,
53
+ );
45
54
  return response.data.data;
46
55
  },
47
56
  });
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ export * from "./roles";
5
5
  export * from "./permissions";
6
6
  export * from "./live_chat";
7
7
  export * from "./health";
8
+ export * from "./ticket";
8
9
 
9
10
  export { config } from "./config";
10
11
  export type { LibraryConfig } from "./config";
@@ -9,8 +9,9 @@ import {
9
9
  const PATH = "/conversations";
10
10
 
11
11
  /**
12
- * Get all conversations recursively mapping to a ticket.
13
- * @param ticket_id
12
+ * Get all conversations of a ticket.
13
+ * @param {string} ticket_id ticket_id parameter.
14
+ * @returns {UseQueryResult<Conversation[]>} The query result.
14
15
  */
15
16
  export const useGetConversations = (ticket_id: string) => {
16
17
  return useQuery({
@@ -26,10 +27,29 @@ export const useGetConversations = (ticket_id: string) => {
26
27
  };
27
28
 
28
29
  /**
29
- * Get paginated messages for a ticket.
30
- * @param ticket_id
31
- * @param page
32
- * @param limit
30
+ * Get a specific client's conversations.
31
+ * @param {string} client_id client_id parameter.
32
+ * @returns {UseQueryResult<Conversation[]>} The query result.
33
+ */
34
+ export const useGetClientConversations = (client_id: string) => {
35
+ return useQuery({
36
+ queryKey: ["conversations", "client", client_id],
37
+ queryFn: async (): Promise<Conversation[]> => {
38
+ const response = await apiClient.get<ApiResponse<Conversation[]>>(
39
+ `${PATH}/client/${client_id}`,
40
+ );
41
+ return response.data.data;
42
+ },
43
+ enabled: !!client_id,
44
+ });
45
+ };
46
+
47
+ /**
48
+ * Get ticket messages with pagination.
49
+ * @param {string} ticket_id ticket_id parameter.
50
+ * @param {number} page page parameter.
51
+ * @param {number} limit limit parameter.
52
+ * @returns {UseQueryResult<PaginatedMessages>} The query result.
33
53
  */
34
54
  export const useGetPaginatedMessages = (
35
55
  ticket_id: string,
@@ -56,7 +76,9 @@ export const useGetPaginatedMessages = (
56
76
  };
57
77
 
58
78
  /**
59
- * Create a new conversation mapping to a ticket.
79
+ * Create a new conversation.
80
+ * @param {CreateConversationDTO} dto The conversation creation details.
81
+ * @returns {UseMutationResult<Conversation, Error, CreateConversationDTO>} The mutation result.
60
82
  */
61
83
  export const useCreateConversation = () => {
62
84
  const queryClient = useQueryClient();
@@ -79,6 +101,8 @@ export const useCreateConversation = () => {
79
101
 
80
102
  /**
81
103
  * Assign an agent to a conversation.
104
+ * @param {{ chat_id: string; agent_id: string }} params The chat ID and agent ID payload.
105
+ * @returns {UseMutationResult<void, Error, { chat_id: string; agent_id: string }>} The mutation result.
82
106
  */
83
107
  export const useSetConversationAgent = () => {
84
108
  const queryClient = useQueryClient();
@@ -93,9 +117,6 @@ export const useSetConversationAgent = () => {
93
117
  }): Promise<void> => {
94
118
  await apiClient.patch(`${PATH}/${chat_id}/set-agent/${agent_id}`);
95
119
  },
96
- // We invalidate the ticket conversations here.
97
- // To do so effectively, you may need the ticket_id, but the mutation only receives the chat_id.
98
- // If ticket list invalidation is required, invalidate the whole 'conversations' key.
99
120
  onSuccess: () => {
100
121
  queryClient.invalidateQueries({ queryKey: ["conversations"] });
101
122
  },
@@ -9,23 +9,35 @@ import type {
9
9
  AddPermissionRolesDTO,
10
10
  } from "../types/permission";
11
11
 
12
+ const PATH = "/permissions";
13
+
14
+ /**
15
+ * List all permissions.
16
+ * @returns {UseQueryResult<Permission[]>} The query result.
17
+ */
12
18
  export function usePermissions() {
13
19
  return useQuery<Permission[]>({
14
20
  queryKey: ["permissions"],
15
21
  queryFn: async () => {
16
- const response =
17
- await apiClient.get<ApiResponse<Permission[]>>("/permissions/");
22
+ const response = await apiClient.get<ApiResponse<Permission[]>>(
23
+ `${PATH}/`,
24
+ );
18
25
  return response.data.data;
19
26
  },
20
27
  });
21
28
  }
22
29
 
30
+ /**
31
+ * Get a permission by ID.
32
+ * @param {number} id id parameter.
33
+ * @returns {UseQueryResult<Permission>} The query result.
34
+ */
23
35
  export function usePermission(id: number) {
24
36
  return useQuery<Permission>({
25
37
  queryKey: ["permissions", id],
26
38
  queryFn: async () => {
27
39
  const response = await apiClient.get<ApiResponse<Permission>>(
28
- `/permissions/${id}`,
40
+ `${PATH}/${id}`,
29
41
  );
30
42
  return response.data.data;
31
43
  },
@@ -33,12 +45,17 @@ export function usePermission(id: number) {
33
45
  });
34
46
  }
35
47
 
48
+ /**
49
+ * Create a new permission.
50
+ * @param {CreatePermissionDTO} dto DTO containing details.
51
+ * @returns {UseMutationResult<Permission, Error, CreatePermissionDTO>} The mutation result.
52
+ */
36
53
  export function useCreatePermission() {
37
54
  const queryClient = useQueryClient();
38
55
  return useMutation<Permission, Error, CreatePermissionDTO>({
39
56
  mutationFn: async (dto) => {
40
57
  const response = await apiClient.post<ApiResponse<Permission>>(
41
- "/permissions/",
58
+ `${PATH}/`,
42
59
  dto,
43
60
  );
44
61
  return response.data.data;
@@ -49,6 +66,14 @@ export function useCreatePermission() {
49
66
  });
50
67
  }
51
68
 
69
+ /**
70
+ * Replace a permission by ID.
71
+ * @param {number} id ID.
72
+ * @param {ReplacePermissionDTO} dto DTO containing details.
73
+ * @returns {UseMutationResult<Permission,
74
+ Error,
75
+ { id: number; dto: ReplacePermissionDTO }>} The mutation result.
76
+ */
52
77
  export function useReplacePermission() {
53
78
  const queryClient = useQueryClient();
54
79
  return useMutation<
@@ -58,7 +83,7 @@ export function useReplacePermission() {
58
83
  >({
59
84
  mutationFn: async ({ id, dto }) => {
60
85
  const response = await apiClient.put<ApiResponse<Permission>>(
61
- `/permissions/${id}`,
86
+ `${PATH}/${id}`,
62
87
  dto,
63
88
  );
64
89
  return response.data.data;
@@ -70,6 +95,14 @@ export function useReplacePermission() {
70
95
  });
71
96
  }
72
97
 
98
+ /**
99
+ * Update a permission by ID.
100
+ * @param {number} id ID.
101
+ * @param {UpdatePermissionDTO} dto DTO containing details.
102
+ * @returns {UseMutationResult<Permission,
103
+ Error,
104
+ { id: number; dto: UpdatePermissionDTO }>} The mutation result.
105
+ */
73
106
  export function useUpdatePermission() {
74
107
  const queryClient = useQueryClient();
75
108
  return useMutation<
@@ -79,7 +112,7 @@ export function useUpdatePermission() {
79
112
  >({
80
113
  mutationFn: async ({ id, dto }) => {
81
114
  const response = await apiClient.patch<ApiResponse<Permission>>(
82
- `/permissions/${id}`,
115
+ `${PATH}/${id}`,
83
116
  dto,
84
117
  );
85
118
  return response.data.data;
@@ -91,12 +124,17 @@ export function useUpdatePermission() {
91
124
  });
92
125
  }
93
126
 
127
+ /**
128
+ * Delete a permission by ID.
129
+ * @param {number} dto DTO containing details.
130
+ * @returns {UseMutationResult<Permission, Error, number>} The mutation result.
131
+ */
94
132
  export function useDeletePermission() {
95
133
  const queryClient = useQueryClient();
96
134
  return useMutation<Permission, Error, number>({
97
135
  mutationFn: async (id) => {
98
136
  const response = await apiClient.delete<ApiResponse<Permission>>(
99
- `/permissions/${id}`,
137
+ `${PATH}/${id}`,
100
138
  );
101
139
  return response.data.data;
102
140
  },
@@ -107,12 +145,21 @@ export function useDeletePermission() {
107
145
  });
108
146
  }
109
147
 
148
+ /**
149
+ * Get permission with associated roles.
150
+ *
151
+ * All roles that have this permission will be included in the
152
+ * response under a `roles` field.
153
+ *
154
+ * @param {number} id id parameter.
155
+ * @returns {UseQueryResult<Permission>} The query result.
156
+ */
110
157
  export function usePermissionRoles(id: number) {
111
158
  return useQuery<Permission>({
112
159
  queryKey: ["permissions", id, "roles"],
113
160
  queryFn: async () => {
114
161
  const response = await apiClient.get<ApiResponse<Permission>>(
115
- `/permissions/${id}/roles`,
162
+ `${PATH}/${id}/roles`,
116
163
  );
117
164
  return response.data.data;
118
165
  },
@@ -120,6 +167,14 @@ export function usePermissionRoles(id: number) {
120
167
  });
121
168
  }
122
169
 
170
+ /**
171
+ * Add a permission to a list of roles.
172
+ * @param {number} id ID.
173
+ * @param {AddPermissionRolesDTO} dto DTO containing details.
174
+ * @returns {UseMutationResult<Permission,
175
+ Error,
176
+ { id: number; dto: AddPermissionRolesDTO }>} The mutation result.
177
+ */
123
178
  export function useAddPermissionRoles() {
124
179
  const queryClient = useQueryClient();
125
180
  return useMutation<
@@ -129,7 +184,7 @@ export function useAddPermissionRoles() {
129
184
  >({
130
185
  mutationFn: async ({ id, dto }) => {
131
186
  const response = await apiClient.post<ApiResponse<Permission>>(
132
- `/permissions/${id}/roles`,
187
+ `${PATH}/${id}/roles`,
133
188
  dto,
134
189
  );
135
190
  return response.data.data;
@@ -1,33 +1,55 @@
1
1
  import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
2
2
  import { apiClient, ApiResponse } from "../../api";
3
- import type { Role, CreateRoleDTO, ReplaceRoleDTO, UpdateRoleDTO, AddRolePermissionsDTO } from "../types/role";
3
+ import type {
4
+ Role,
5
+ CreateRoleDTO,
6
+ ReplaceRoleDTO,
7
+ UpdateRoleDTO,
8
+ AddRolePermissionsDTO,
9
+ } from "../types/role";
4
10
 
11
+ const PATH = "/roles";
12
+
13
+ /**
14
+ * List all roles.
15
+ * @returns {UseQueryResult<Role[]>} The query result.
16
+ */
5
17
  export function useRoles() {
6
18
  return useQuery<Role[]>({
7
19
  queryKey: ["roles"],
8
20
  queryFn: async () => {
9
- const response = await apiClient.get<ApiResponse<Role[]>>("/roles/");
21
+ const response = await apiClient.get<ApiResponse<Role[]>>(`${PATH}/`);
10
22
  return response.data.data;
11
23
  },
12
24
  });
13
25
  }
14
26
 
27
+ /**
28
+ * Get a role by ID.
29
+ * @param {number} id id parameter.
30
+ * @returns {UseQueryResult<Role>} The query result.
31
+ */
15
32
  export function useRole(id: number) {
16
33
  return useQuery<Role>({
17
34
  queryKey: ["roles", id],
18
35
  queryFn: async () => {
19
- const response = await apiClient.get<ApiResponse<Role>>(`/roles/${id}`);
36
+ const response = await apiClient.get<ApiResponse<Role>>(`${PATH}/${id}`);
20
37
  return response.data.data;
21
38
  },
22
39
  enabled: !!id,
23
40
  });
24
41
  }
25
42
 
43
+ /**
44
+ * Create a new role.
45
+ * @param {CreateRoleDTO} dto DTO containing details.
46
+ * @returns {UseMutationResult<Role, Error, CreateRoleDTO>} The mutation result.
47
+ */
26
48
  export function useCreateRole() {
27
49
  const queryClient = useQueryClient();
28
50
  return useMutation<Role, Error, CreateRoleDTO>({
29
51
  mutationFn: async (dto) => {
30
- const response = await apiClient.post<ApiResponse<Role>>("/roles/", dto);
52
+ const response = await apiClient.post<ApiResponse<Role>>(`${PATH}/`, dto);
31
53
  return response.data.data;
32
54
  },
33
55
  onSuccess: () => {
@@ -36,11 +58,20 @@ export function useCreateRole() {
36
58
  });
37
59
  }
38
60
 
61
+ /**
62
+ * Replace a role by ID.
63
+ * @param {number} id ID.
64
+ * @param {ReplaceRoleDTO} dto DTO containing details.
65
+ * @returns {UseMutationResult<Role, Error, { id: number; dto: ReplaceRoleDTO }>} The mutation result.
66
+ */
39
67
  export function useReplaceRole() {
40
68
  const queryClient = useQueryClient();
41
69
  return useMutation<Role, Error, { id: number; dto: ReplaceRoleDTO }>({
42
70
  mutationFn: async ({ id, dto }) => {
43
- const response = await apiClient.put<ApiResponse<Role>>(`/roles/${id}`, dto);
71
+ const response = await apiClient.put<ApiResponse<Role>>(
72
+ `${PATH}/${id}`,
73
+ dto,
74
+ );
44
75
  return response.data.data;
45
76
  },
46
77
  onSuccess: (_, { id }) => {
@@ -50,11 +81,20 @@ export function useReplaceRole() {
50
81
  });
51
82
  }
52
83
 
84
+ /**
85
+ * Update a role by ID.
86
+ * @param {number} id ID.
87
+ * @param {UpdateRoleDTO} dto DTO containing details.
88
+ * @returns {UseMutationResult<Role, Error, { id: number; dto: UpdateRoleDTO }>} The mutation result.
89
+ */
53
90
  export function useUpdateRole() {
54
91
  const queryClient = useQueryClient();
55
92
  return useMutation<Role, Error, { id: number; dto: UpdateRoleDTO }>({
56
93
  mutationFn: async ({ id, dto }) => {
57
- const response = await apiClient.patch<ApiResponse<Role>>(`/roles/${id}`, dto);
94
+ const response = await apiClient.patch<ApiResponse<Role>>(
95
+ `${PATH}/${id}`,
96
+ dto,
97
+ );
58
98
  return response.data.data;
59
99
  },
60
100
  onSuccess: (_, { id }) => {
@@ -64,11 +104,18 @@ export function useUpdateRole() {
64
104
  });
65
105
  }
66
106
 
107
+ /**
108
+ * Delete a role by ID.
109
+ * @param {number} dto DTO containing details.
110
+ * @returns {UseMutationResult<Role, Error, number>} The mutation result.
111
+ */
67
112
  export function useDeleteRole() {
68
113
  const queryClient = useQueryClient();
69
114
  return useMutation<Role, Error, number>({
70
115
  mutationFn: async (id) => {
71
- const response = await apiClient.delete<ApiResponse<Role>>(`/roles/${id}`);
116
+ const response = await apiClient.delete<ApiResponse<Role>>(
117
+ `${PATH}/${id}`,
118
+ );
72
119
  return response.data.data;
73
120
  },
74
121
  onSuccess: (_, id) => {
@@ -78,22 +125,39 @@ export function useDeleteRole() {
78
125
  });
79
126
  }
80
127
 
128
+ /**
129
+ * Get the permissions for a role by ID.
130
+ * @param {number} id id parameter.
131
+ * @returns {UseQueryResult<Role>} The query result.
132
+ */
81
133
  export function useRolePermissions(id: number) {
82
134
  return useQuery<Role>({
83
135
  queryKey: ["roles", id, "permissions"],
84
136
  queryFn: async () => {
85
- const response = await apiClient.get<ApiResponse<Role>>(`/roles/${id}/permissions`);
137
+ const response = await apiClient.get<ApiResponse<Role>>(
138
+ `${PATH}/${id}/permissions`,
139
+ );
86
140
  return response.data.data;
87
141
  },
88
142
  enabled: !!id,
89
143
  });
90
144
  }
91
145
 
146
+ /**
147
+ * Add permissions to a role.
148
+ *
149
+ * @param {number} id ID.
150
+ * @param {AddRolePermissionsDTO} dto DTO containing details.
151
+ * @returns {UseMutationResult<Role, Error, { id: number; dto: AddRolePermissionsDTO }>} The mutation result.
152
+ */
92
153
  export function useAddRolePermissions() {
93
154
  const queryClient = useQueryClient();
94
155
  return useMutation<Role, Error, { id: number; dto: AddRolePermissionsDTO }>({
95
156
  mutationFn: async ({ id, dto }) => {
96
- const response = await apiClient.post<ApiResponse<Role>>(`/roles/${id}/permissions`, dto);
157
+ const response = await apiClient.post<ApiResponse<Role>>(
158
+ `${PATH}/${id}/permissions`,
159
+ dto,
160
+ );
97
161
  return response.data.data;
98
162
  },
99
163
  onSuccess: (_, { id }) => {
@@ -0,0 +1,89 @@
1
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
2
+ import { apiClient, ApiResponse } from "../../api";
3
+ import {
4
+ CreateTicketRequest,
5
+ CreateTicketResponse,
6
+ TicketResponse,
7
+ TicketSearchFilters,
8
+ UpdateTicketStatusRequest,
9
+ UpdateTicketStatusResponse,
10
+ } from "../types/ticket";
11
+
12
+ const PATH = "/tickets";
13
+
14
+ // Query keys
15
+ export const TICKET_KEYS = {
16
+ all: ["tickets"] as const,
17
+ list: (filters: TicketSearchFilters) =>
18
+ [...TICKET_KEYS.all, "list", filters] as const,
19
+ };
20
+
21
+ /**
22
+ * Get all tickets.
23
+ * @param {TicketSearchFilters} filters filters parameter.
24
+ * @returns {UseQueryResult<TicketResponse[]>} The query result.
25
+ */
26
+ export const useTickets = (filters: TicketSearchFilters = {}) => {
27
+ return useQuery({
28
+ queryKey: TICKET_KEYS.list(filters),
29
+ queryFn: async (): Promise<TicketResponse[]> => {
30
+ const response = await apiClient.get<ApiResponse<TicketResponse[]>>(
31
+ `${PATH}/`,
32
+ { params: filters },
33
+ );
34
+ return response.data.data;
35
+ },
36
+ });
37
+ };
38
+
39
+ /**
40
+ * Create a new ticket.
41
+ * @param {CreateTicketRequest} payload The ticket creation details.
42
+ * @returns {UseMutationResult<CreateTicketResponse, Error, CreateTicketRequest>} The mutation result.
43
+ */
44
+ export const useCreateTicket = () => {
45
+ const queryClient = useQueryClient();
46
+
47
+ return useMutation({
48
+ mutationFn: async (
49
+ payload: CreateTicketRequest,
50
+ ): Promise<CreateTicketResponse> => {
51
+ const response = await apiClient.post<ApiResponse<CreateTicketResponse>>(
52
+ `${PATH}/`,
53
+ payload,
54
+ );
55
+ return response.data.data;
56
+ },
57
+ onSuccess: () => {
58
+ // Invalidate tickets list queries on success
59
+ queryClient.invalidateQueries({ queryKey: TICKET_KEYS.all });
60
+ },
61
+ });
62
+ };
63
+
64
+ /**
65
+ * Update a ticket's status.
66
+ * @param {{ ticketId: string; payload: UpdateTicketStatusRequest }} params The ticket ID and status update payload.
67
+ * @returns {UseMutationResult<UpdateTicketStatusResponse, Error, { ticketId: string; payload: UpdateTicketStatusRequest }>} The mutation result.
68
+ */
69
+ export const useUpdateTicketStatus = () => {
70
+ const queryClient = useQueryClient();
71
+
72
+ return useMutation({
73
+ mutationFn: async ({
74
+ ticketId,
75
+ payload,
76
+ }: {
77
+ ticketId: string;
78
+ payload: UpdateTicketStatusRequest;
79
+ }): Promise<UpdateTicketStatusResponse> => {
80
+ const response = await apiClient.patch<
81
+ ApiResponse<UpdateTicketStatusResponse>
82
+ >(`${PATH}/${ticketId}/status`, payload);
83
+ return response.data.data;
84
+ },
85
+ onSuccess: () => {
86
+ queryClient.invalidateQueries({ queryKey: TICKET_KEYS.all });
87
+ },
88
+ });
89
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./types/ticket";
2
+ export * from "./hooks/useTickets";
@@ -0,0 +1,88 @@
1
+ export type TicketType = "issue" | "access" | "new_feature";
2
+ export type TicketCriticality = "high" | "medium" | "low";
3
+ export type TicketStatus =
4
+ | "open"
5
+ | "in_progress"
6
+ | "waiting_for_provider"
7
+ | "waiting_for_validation"
8
+ | "finished";
9
+
10
+ export interface CreateTicketRequest {
11
+ triage_id: string; // PydanticObjectId as string
12
+ type: TicketType;
13
+ criticality: TicketCriticality;
14
+ product: string;
15
+ description: string;
16
+ chat_ids: string[]; // List of PydanticObjectId as string
17
+ client_id: string; // UUID as string
18
+ }
19
+
20
+ export interface CreateTicketResponse {
21
+ id: string;
22
+ status: TicketStatus;
23
+ creation_date: string; // ISO datetime
24
+ }
25
+
26
+ export interface TicketSearchFilters {
27
+ ticket_id?: string;
28
+ client_id?: string;
29
+ triage_id?: string;
30
+ status?: TicketStatus;
31
+ criticality?: TicketCriticality;
32
+ type?: TicketType;
33
+ product?: string;
34
+ }
35
+
36
+ export interface TicketCompanyResponse {
37
+ id: string;
38
+ name: string;
39
+ }
40
+
41
+ export interface TicketClientResponse {
42
+ id: string;
43
+ name: string;
44
+ email: string;
45
+ company: TicketCompanyResponse;
46
+ }
47
+
48
+ export interface TicketHistoryResponse {
49
+ agent_id: string;
50
+ name: string;
51
+ level: string;
52
+ assignment_date: string;
53
+ exit_date: string;
54
+ transfer_reason: string;
55
+ }
56
+
57
+ export interface TicketCommentResponse {
58
+ comment_id: string;
59
+ author: string;
60
+ text: string;
61
+ date: string;
62
+ internal: boolean;
63
+ }
64
+
65
+ export interface TicketResponse {
66
+ id: string;
67
+ triage_id: string;
68
+ type: TicketType;
69
+ criticality: TicketCriticality;
70
+ product: string;
71
+ status: TicketStatus;
72
+ creation_date: string;
73
+ description: string;
74
+ chat_ids: string[];
75
+ agent_history: TicketHistoryResponse[];
76
+ client: TicketClientResponse;
77
+ comments: TicketCommentResponse[];
78
+ }
79
+
80
+ export interface UpdateTicketStatusRequest {
81
+ status: TicketStatus;
82
+ }
83
+
84
+ export interface UpdateTicketStatusResponse {
85
+ id: string;
86
+ previous_status: TicketStatus;
87
+ current_status: TicketStatus;
88
+ }
@@ -12,7 +12,7 @@ const PATH = "/users";
12
12
 
13
13
  /**
14
14
  * Get all users.
15
- * @returns
15
+ * @returns {UseQueryResult<User[]>} The query result.
16
16
  */
17
17
  export const useGetUsers = () => {
18
18
  return useQuery({
@@ -28,8 +28,8 @@ export const useGetUsers = () => {
28
28
 
29
29
  /**
30
30
  * Get one user.
31
- * @param id
32
- * @returns
31
+ * @param {string} id id parameter.
32
+ * @returns {UseQueryResult<User>} The query result.
33
33
  */
34
34
  export const useGetUser = (id: string) => {
35
35
  return useQuery({
@@ -44,7 +44,8 @@ export const useGetUser = (id: string) => {
44
44
 
45
45
  /**
46
46
  * Create a user.
47
- * @returns
47
+ * @param {CreateUserDTO} user The user creation details.
48
+ * @returns {UseMutationResult<User, Error, CreateUserDTO>} The mutation result.
48
49
  */
49
50
  export const useCreateUser = () => {
50
51
  const queryClient = useQueryClient();
@@ -63,7 +64,8 @@ export const useCreateUser = () => {
63
64
 
64
65
  /**
65
66
  * Replace an entire user.
66
- * @returns
67
+ * @param {{ id: string; data: ReplaceUserDTO }} params The ID and user replacement data.
68
+ * @returns {UseMutationResult<User, Error, { id: string; data: ReplaceUserDTO }>} The mutation result.
67
69
  */
68
70
  export const useUpdateUser = () => {
69
71
  const queryClient = useQueryClient();
@@ -91,7 +93,8 @@ export const useUpdateUser = () => {
91
93
 
92
94
  /**
93
95
  * Update specific fields of a user.
94
- * @returns
96
+ * @param {{ id: string; data: UpdateUserDTO }} params The ID and user update data.
97
+ * @returns {UseMutationResult<User, Error, { id: string; data: UpdateUserDTO }>} The mutation result.
95
98
  */
96
99
  export const usePatchUser = () => {
97
100
  const queryClient = useQueryClient();
@@ -119,7 +122,8 @@ export const usePatchUser = () => {
119
122
 
120
123
  /**
121
124
  * Add roles to a user.
122
- * @returns
125
+ * @param {{ id: string; data: AddUserRolesDTO }} params The ID and user roles data.
126
+ * @returns {UseMutationResult<User, Error, { id: string; data: AddUserRolesDTO }>} The mutation result.
123
127
  */
124
128
  export const useAddUserRoles = () => {
125
129
  const queryClient = useQueryClient();
@@ -14,9 +14,3 @@ export type {
14
14
  ReplaceUserDTO,
15
15
  AddUserRolesDTO,
16
16
  } from "./types/user";
17
-
18
- // # syncdesk-api/app/api/api_router
19
- // - /auth/ -- /app/domains/auth/routers/auth_router
20
- // | `/api/users/` | User management (CRUD) |
21
- // | `/api/roles/` | Role management (CRUD) |
22
- // | `/api/permissions/` | Permission management (CRUD) |