ptechcore_ui 0.0.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.
Files changed (57) hide show
  1. package/eslint.config.js +28 -0
  2. package/index.html +78 -0
  3. package/package.json +42 -0
  4. package/postcss.config.js +6 -0
  5. package/src/App.tsx +156 -0
  6. package/src/assets/imgs/login_illustration.png +0 -0
  7. package/src/components/common/Buttons.tsx +39 -0
  8. package/src/components/common/Cards.tsx +18 -0
  9. package/src/components/common/FDrawer.tsx +2448 -0
  10. package/src/components/common/FDrawer.types.ts +191 -0
  11. package/src/components/common/Inputs.tsx +409 -0
  12. package/src/components/common/Modals.tsx +41 -0
  13. package/src/components/common/Navigations.tsx +0 -0
  14. package/src/components/common/Toast.tsx +0 -0
  15. package/src/components/demo/ToastDemo.tsx +73 -0
  16. package/src/components/layout/Header.tsx +202 -0
  17. package/src/components/layout/ModernDoubleSidebarLayout.tsx +727 -0
  18. package/src/components/layout/PrivateLayout.tsx +52 -0
  19. package/src/components/layout/Sidebar.tsx +182 -0
  20. package/src/components/ui/Toast.tsx +93 -0
  21. package/src/contexts/SessionContext.tsx +77 -0
  22. package/src/contexts/ThemeContext.tsx +58 -0
  23. package/src/contexts/ToastContext.tsx +94 -0
  24. package/src/index.css +3 -0
  25. package/src/main.tsx +10 -0
  26. package/src/models/Organization.ts +47 -0
  27. package/src/models/Plan.ts +42 -0
  28. package/src/models/User.ts +23 -0
  29. package/src/pages/Analytics.tsx +101 -0
  30. package/src/pages/CreateOrganization.tsx +215 -0
  31. package/src/pages/Dashboard.tsx +15 -0
  32. package/src/pages/Home.tsx +12 -0
  33. package/src/pages/Profile.tsx +313 -0
  34. package/src/pages/Settings.tsx +382 -0
  35. package/src/pages/Team.tsx +180 -0
  36. package/src/pages/auth/Login.tsx +140 -0
  37. package/src/pages/auth/Register.tsx +302 -0
  38. package/src/pages/organizations/DetailEntity.tsx +1002 -0
  39. package/src/pages/organizations/DetailOrganizations.tsx +1629 -0
  40. package/src/pages/organizations/ListOrganizations.tsx +270 -0
  41. package/src/pages/pricings/CartPlan.tsx +486 -0
  42. package/src/pages/pricings/ListPricing.tsx +321 -0
  43. package/src/pages/users/CreateUser.tsx +450 -0
  44. package/src/pages/users/ListUsers.tsx +0 -0
  45. package/src/services/AuthServices.ts +94 -0
  46. package/src/services/OrganizationServices.ts +61 -0
  47. package/src/services/PlanSubscriptionServices.tsx +137 -0
  48. package/src/services/UserServices.ts +36 -0
  49. package/src/services/api.ts +64 -0
  50. package/src/styles/theme.ts +383 -0
  51. package/src/utils/utils.ts +48 -0
  52. package/src/vite-env.d.ts +1 -0
  53. package/tailwind.config.js +158 -0
  54. package/tsconfig.app.json +24 -0
  55. package/tsconfig.json +7 -0
  56. package/tsconfig.node.json +22 -0
  57. package/vite.config.ts +10 -0
@@ -0,0 +1,450 @@
1
+ import React, { useState } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import {
4
+ ArrowLeft,
5
+ User as UserIcon,
6
+ Mail,
7
+ Phone,
8
+ Shield,
9
+ Users,
10
+ Save,
11
+ X
12
+ } from 'lucide-react';
13
+ import { useToast } from '../../contexts/ToastContext';
14
+ import { useSession } from '../../contexts/SessionContext';
15
+ import { UserServices } from '../../services/UserServices';
16
+ import { User } from '../../models/User';
17
+ import { RewiseBasicCard } from '../../components/common/Cards';
18
+ import { TextInput, InputField, SelectInput } from '../../components/common/Inputs';
19
+
20
+ const CreateUser: React.FC = () => {
21
+ const [formData, setFormData] = useState<Partial<User>>({
22
+ username: '',
23
+ first_name: '',
24
+ last_name: '',
25
+ email: '',
26
+ phonenumber: '',
27
+ is_active: true,
28
+ is_staff: false,
29
+ is_superuser: false,
30
+ groups: [],
31
+ user_permissions: [],
32
+ centers_access: []
33
+ });
34
+
35
+ const [errors, setErrors] = useState<Partial<Record<keyof User, string>>>({});
36
+ const [loading, setLoading] = useState(false);
37
+ const [activeTab, setActiveTab] = useState<'general' | 'permissions' | 'access'>('general');
38
+
39
+ const navigate = useNavigate();
40
+ const { success, error: showError } = useToast();
41
+ const { token } = useSession();
42
+
43
+ const tabs = [
44
+ { id: 'general', label: 'Informations générales', icon: UserIcon },
45
+ { id: 'permissions', label: 'Permissions', icon: Shield },
46
+ { id: 'permissions', label: 'Permissions', icon: Shield },
47
+ { id: 'access', label: 'Accès aux centres', icon: Users }
48
+
49
+ ];
50
+
51
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
52
+ const { name, value, type, checked } = e.target;
53
+ setFormData(prev => ({
54
+ ...prev,
55
+ [name]: type === 'checkbox' ? checked : value
56
+ }));
57
+
58
+ if (errors[name as keyof User]) {
59
+ setErrors(prev => ({ ...prev, [name]: undefined }));
60
+ }
61
+ };
62
+
63
+ const handleSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
64
+ const { name, value } = e.target;
65
+ setFormData(prev => ({ ...prev, [name]: value === 'true' }));
66
+ };
67
+
68
+ const validateForm = (): boolean => {
69
+ const newErrors: Partial<Record<keyof User, string>> = {};
70
+
71
+ if (!formData.username?.trim()) {
72
+ newErrors.username = 'Le nom d\'utilisateur est obligatoire';
73
+ }
74
+
75
+ if (!formData.first_name?.trim()) {
76
+ newErrors.first_name = 'Le pr�nom est obligatoire';
77
+ }
78
+
79
+ if (!formData.last_name?.trim()) {
80
+ newErrors.last_name = 'Le nom de famille est obligatoire';
81
+ }
82
+
83
+ if (!formData.email?.trim()) {
84
+ newErrors.email = 'L\'email est obligatoire';
85
+ } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
86
+ newErrors.email = 'Format d\'email invalide';
87
+ }
88
+
89
+ if (formData.phonenumber && !/^[\+]?[0-9\s\-\(\)]+$/.test(formData.phonenumber)) {
90
+ newErrors.phonenumber = 'Format de t�l�phone invalide';
91
+ }
92
+
93
+ setErrors(newErrors);
94
+ return Object.keys(newErrors).length === 0;
95
+ };
96
+
97
+ const handleSubmit = async (e: React.FormEvent) => {
98
+ e.preventDefault();
99
+ if (!validateForm()) return;
100
+
101
+ if (!token) {
102
+ showError('Vous devez �tre connect� pour cr�er un utilisateur');
103
+ return;
104
+ }
105
+
106
+ setLoading(true);
107
+ try {
108
+ await UserServices.addUser(formData, token);
109
+ success('Utilisateur cr�� avec succ�s !');
110
+ navigate('/users');
111
+ } catch (error: any) {
112
+ console.error(error);
113
+ showError('Erreur lors de la cr�ation de l\'utilisateur');
114
+ } finally {
115
+ setLoading(false);
116
+ }
117
+ };
118
+
119
+ const addCenterAccess = () => {
120
+ setFormData(prev => ({
121
+ ...prev,
122
+ centers_access: [
123
+ ...(prev.centers_access || []),
124
+ { id: 0, permissions: [] }
125
+ ]
126
+ }));
127
+ };
128
+
129
+ const removeCenterAccess = (index: number) => {
130
+ setFormData(prev => ({
131
+ ...prev,
132
+ centers_access: prev.centers_access?.filter((_, i) => i !== index) || []
133
+ }));
134
+ };
135
+
136
+ const updateCenterAccess = (index: number, field: 'id' | 'permissions', value: any) => {
137
+ setFormData(prev => ({
138
+ ...prev,
139
+ centers_access: prev.centers_access?.map((center, i) =>
140
+ i === index ? { ...center, [field]: value } : center
141
+ ) || []
142
+ }));
143
+ };
144
+
145
+ const renderTabContent = () => {
146
+ switch (activeTab) {
147
+ case 'general':
148
+ return (
149
+ <div className="space-y-6">
150
+ {/* Informations de base */}
151
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
152
+ <TextInput
153
+ label="Nom d'utilisateur"
154
+ name="username"
155
+ value={formData.username || ''}
156
+ placeholder="nom utilisateur"
157
+ required
158
+ error={errors.username}
159
+ onChange={handleInputChange}
160
+ />
161
+
162
+ <InputField
163
+ label="Email"
164
+ name="email"
165
+ type="email"
166
+ value={formData.email || ''}
167
+ placeholder="utilisateur@example.com"
168
+ required
169
+ error={errors.email}
170
+ onChange={handleInputChange}
171
+ />
172
+ </div>
173
+
174
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
175
+ <TextInput
176
+ label="Pr�nom"
177
+ name="first_name"
178
+ value={formData.first_name || ''}
179
+ placeholder="Pr�nom"
180
+ required
181
+ error={errors.first_name}
182
+ onChange={handleInputChange}
183
+ />
184
+
185
+ <TextInput
186
+ label="Nom de famille"
187
+ name="last_name"
188
+ value={formData.last_name || ''}
189
+ placeholder="Nom de famille"
190
+ required
191
+ error={errors.last_name}
192
+ onChange={handleInputChange}
193
+ />
194
+ </div>
195
+
196
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
197
+ <InputField
198
+ label="T�l�phone"
199
+ name="phonenumber"
200
+ type="tel"
201
+ value={formData.phonenumber || ''}
202
+ placeholder="+225 XX XX XXX XXX"
203
+ error={errors.phonenumber}
204
+ onChange={handleInputChange}
205
+ />
206
+
207
+ <SelectInput
208
+ label="Statut"
209
+ name="is_active"
210
+ value={formData.is_active?.toString() || 'true'}
211
+ options={[
212
+ { value: 'true', label: 'Actif' },
213
+ { value: 'false', label: 'Inactif' }
214
+ ]}
215
+ onChange={handleSelectChange}
216
+ />
217
+ </div>
218
+ </div>
219
+ );
220
+
221
+ case 'permissions':
222
+ return (
223
+ <div className="space-y-6">
224
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
225
+ <div className="space-y-4">
226
+ <label className="flex items-center space-x-3">
227
+ <input
228
+ type="checkbox"
229
+ name="is_staff"
230
+ checked={formData.is_staff || false}
231
+ onChange={handleInputChange}
232
+ className="rounded border-gray-300 text-[#8290A9] focus:ring-[#8290A9]"
233
+ />
234
+ <div>
235
+ <span className="text-sm font-medium text-gray-900">Membre du personnel</span>
236
+ <p className="text-xs text-gray-500">
237
+ Permet d'acc�der � l'interface d'administration
238
+ </p>
239
+ </div>
240
+ </label>
241
+
242
+ <label className="flex items-center space-x-3">
243
+ <input
244
+ type="checkbox"
245
+ name="is_superuser"
246
+ checked={formData.is_superuser || false}
247
+ onChange={handleInputChange}
248
+ className="rounded border-gray-300 text-[#8290A9] focus:ring-[#8290A9]"
249
+ />
250
+ <div>
251
+ <span className="text-sm font-medium text-gray-900">Super utilisateur</span>
252
+ <p className="text-xs text-gray-500">
253
+ Acc�s complet � toutes les fonctionnalit�s
254
+ </p>
255
+ </div>
256
+ </label>
257
+ </div>
258
+ </div>
259
+
260
+ <div className="bg-gray-50 rounded-lg p-4">
261
+ <h4 className="text-sm font-medium text-gray-700 mb-2">Groupes d'utilisateurs</h4>
262
+ <p className="text-xs text-gray-500 mb-4">
263
+ Les groupes seront configurables apr�s la cr�ation de l'utilisateur
264
+ </p>
265
+ <div className="text-sm text-gray-600">
266
+ Groupes assign�s : {formData.groups?.length || 0}
267
+ </div>
268
+ </div>
269
+ </div>
270
+ );
271
+
272
+ case 'access':
273
+ return (
274
+ <div className="space-y-6">
275
+ <div className="flex justify-between items-center">
276
+ <div>
277
+ <h4 className="text-sm font-medium text-gray-700">Acc�s aux centres</h4>
278
+ <p className="text-xs text-gray-500 mt-1">
279
+ Configurez l'acc�s de l'utilisateur aux diff�rents centres
280
+ </p>
281
+ </div>
282
+ <button
283
+ type="button"
284
+ onClick={addCenterAccess}
285
+ className="bg-[#8290A9] text-white px-3 py-1 rounded text-sm hover:bg-[#6B7C92] transition-colors"
286
+ >
287
+ Ajouter un centre
288
+ </button>
289
+ </div>
290
+
291
+ {formData.centers_access && formData.centers_access.length > 0 ? (
292
+ <div className="space-y-4">
293
+ {formData.centers_access.map((center, index) => (
294
+ <div key={index} className="border border-gray-200 rounded-lg p-4">
295
+ <div className="flex justify-between items-start mb-4">
296
+ <h5 className="text-sm font-medium text-gray-700">
297
+ Centre d'acc�s #{index + 1}
298
+ </h5>
299
+ <button
300
+ type="button"
301
+ onClick={() => removeCenterAccess(index)}
302
+ className="text-red-600 hover:text-red-800"
303
+ >
304
+ <X className="w-4 h-4" />
305
+ </button>
306
+ </div>
307
+
308
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
309
+ <div>
310
+ <label className="block text-xs font-medium text-gray-700 mb-1">
311
+ ID du centre
312
+ </label>
313
+ <input
314
+ type="number"
315
+ value={center.id}
316
+ onChange={(e) => updateCenterAccess(index, 'id', parseInt(e.target.value) || 0)}
317
+ className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-[#8290A9] focus:border-transparent"
318
+ placeholder="ID du centre"
319
+ />
320
+ </div>
321
+
322
+ <div>
323
+ <label className="block text-xs font-medium text-gray-700 mb-1">
324
+ Permissions (IDs s�par�s par des virgules)
325
+ </label>
326
+ <input
327
+ type="text"
328
+ value={center.permissions?.join(', ') || ''}
329
+ onChange={(e) => {
330
+ const permissions = e.target.value
331
+ .split(',')
332
+ .map(p => parseInt(p.trim()))
333
+ .filter(p => !isNaN(p));
334
+ updateCenterAccess(index, 'permissions', permissions);
335
+ }}
336
+ className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-[#8290A9] focus:border-transparent"
337
+ placeholder="1, 2, 3"
338
+ />
339
+ </div>
340
+ </div>
341
+ </div>
342
+ ))}
343
+ </div>
344
+ ) : (
345
+ <div className="text-center py-8 bg-gray-50 rounded-lg">
346
+ <Users className="w-12 h-12 text-gray-300 mx-auto mb-4" />
347
+ <p className="text-gray-500 text-sm">Aucun acc�s aux centres configur�</p>
348
+ <p className="text-gray-400 text-xs mt-1">
349
+ Cliquez sur "Ajouter un centre" pour commencer
350
+ </p>
351
+ </div>
352
+ )}
353
+ </div>
354
+ );
355
+
356
+ default:
357
+ return null;
358
+ }
359
+ };
360
+
361
+ return (
362
+ <div className="space-y-6">
363
+ {/* Header */}
364
+ <div className="flex items-center justify-between">
365
+ <div className="flex items-center space-x-4">
366
+ <button
367
+ onClick={() => navigate('/users')}
368
+ className="flex items-center text-gray-600 hover:text-gray-900 transition-colors"
369
+ >
370
+ <ArrowLeft className="w-4 h-4 mr-2" />
371
+ Retour aux utilisateurs
372
+ </button>
373
+ </div>
374
+ </div>
375
+
376
+ {/* Form */}
377
+ <RewiseBasicCard title={
378
+ <div className="flex items-center space-x-3">
379
+ <div className="w-10 h-10 bg-[#8290A9] rounded-full flex items-center justify-center">
380
+ <UserIcon className="w-5 h-5 text-white" />
381
+ </div>
382
+ <div>
383
+ <h1 className="text-xl font-semibold text-gray-900">Cr�er un utilisateur</h1>
384
+ <p className="text-sm text-gray-600">Ajoutez un nouveau membre � votre �quipe</p>
385
+ </div>
386
+ </div>
387
+ }>
388
+ <form onSubmit={handleSubmit}>
389
+ {/* Tabs */}
390
+ <div className="border-b border-gray-200 mb-6">
391
+ <nav className="flex space-x-8">
392
+ {tabs.map((tab) => {
393
+ const Icon = tab.icon;
394
+ return (
395
+ <button
396
+ key={tab.id}
397
+ type="button"
398
+ onClick={() => setActiveTab(tab.id as any)}
399
+ className={`py-4 px-1 border-b-2 font-medium text-sm transition-colors ${
400
+ activeTab === tab.id
401
+ ? 'border-[#8290A9] text-[#8290A9]'
402
+ : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
403
+ }`}
404
+ >
405
+ <Icon className="w-4 h-4 inline mr-2" />
406
+ {tab.label}
407
+ </button>
408
+ );
409
+ })}
410
+ </nav>
411
+ </div>
412
+
413
+ {/* Tab Content */}
414
+ {renderTabContent()}
415
+
416
+ {/* Actions */}
417
+ <div className="flex justify-between pt-6 border-t border-gray-200 mt-8">
418
+ <button
419
+ type="button"
420
+ onClick={() => navigate('/users')}
421
+ className="px-6 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
422
+ >
423
+ Annuler
424
+ </button>
425
+
426
+ <button
427
+ type="submit"
428
+ disabled={loading}
429
+ className="px-6 py-2 bg-[#8290A9] text-white rounded-lg hover:bg-[#6B7C92] transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center space-x-2"
430
+ >
431
+ {loading ? (
432
+ <>
433
+ <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
434
+ <span>Cr�ation...</span>
435
+ </>
436
+ ) : (
437
+ <>
438
+ <Save className="w-4 h-4" />
439
+ <span>Cr�er l'utilisateur</span>
440
+ </>
441
+ )}
442
+ </button>
443
+ </div>
444
+ </form>
445
+ </RewiseBasicCard>
446
+ </div>
447
+ );
448
+ };
449
+
450
+ export default CreateUser;
File without changes
@@ -0,0 +1,94 @@
1
+ import { ThermometerSnowflake } from "lucide-react";
2
+ import { API_URL } from "./api";
3
+ import { User } from "../models/User";
4
+
5
+
6
+ const API_BASE_URL = `${API_URL}/core/auth/`;
7
+
8
+ export interface SendOtpPayload {
9
+ email: string;
10
+ }
11
+
12
+ export interface VerifyOtpPayload {
13
+ email: string;
14
+ otp: string;
15
+ }
16
+
17
+ export interface CompleteRegistrationPayload {
18
+ email: string;
19
+ password: string;
20
+ confirmed_password: string;
21
+ firstname?: string;
22
+ lastname?: string;
23
+ phonenumber?: string;
24
+ }
25
+
26
+ export interface AddUserPayload {
27
+ email: string;
28
+ first_name: string;
29
+ last_name: string;
30
+ phonenumber?: string;
31
+ centers_access?: Array<{
32
+ id: number;
33
+ permissions?: number[];
34
+ }>;
35
+ }
36
+
37
+ export interface LoginPayload {
38
+ username: string;
39
+ password: string;
40
+ }
41
+
42
+ class FetchApi {
43
+ static async post<T>(url: string, payload?: any, token?: string): Promise<T> {
44
+ const headers: Record<string, string> = {
45
+ "Content-Type": "application/json",
46
+ };
47
+ if (token) {
48
+ headers["Authorization"] = `Token ${token}`;
49
+ }
50
+ const res = await fetch(url, {
51
+ method: "POST",
52
+ headers,
53
+ body: payload ? JSON.stringify(payload) : undefined,
54
+ });
55
+ if (!res.ok) throw new Error(await res.text());
56
+ return res.json();
57
+ }
58
+
59
+ static async get<T>(url: string, token?: string): Promise<T> {
60
+ const headers: Record<string, string> = {};
61
+ if (token) {
62
+ headers["Authorization"] = `Token ${token}`;
63
+ }
64
+ const res = await fetch(url, {
65
+ method: "GET",
66
+ headers,
67
+ });
68
+ if (!res.ok) throw new Error(await res.text());
69
+ return res.json();
70
+ }
71
+ }
72
+
73
+ export const AuthServices = {
74
+ sendOtp: (payload: SendOtpPayload) =>
75
+ FetchApi.post(`${API_BASE_URL}send-otp/`, payload),
76
+
77
+ verifyOtp: (payload: VerifyOtpPayload) =>
78
+ FetchApi.post(`${API_BASE_URL}verify-otp/`, payload),
79
+
80
+ completeRegistration: (payload: CompleteRegistrationPayload) =>
81
+ FetchApi.post(`${API_BASE_URL}complete-registration/`, payload),
82
+
83
+ addUser: (payload: User) =>
84
+ FetchApi.post(`${API_BASE_URL}add-user/`, payload),
85
+
86
+ login: (payload: LoginPayload) =>
87
+ FetchApi.post(`${API_BASE_URL}login/`, payload),
88
+
89
+ getUserInformations: (token: string) =>
90
+ FetchApi.get(`${API_BASE_URL}user-informations/`, token),
91
+
92
+ logout: () =>
93
+ FetchApi.post(`${API_BASE_URL}logout/`),
94
+ };
@@ -0,0 +1,61 @@
1
+
2
+
3
+ import { sub } from 'date-fns';
4
+ import { API_URL, FetchApi } from './api';
5
+ import { Entity, Organization } from '../models/Organization';
6
+
7
+ const ORGANIZATIONS_API_URL = `${API_URL}/core/organizations/`;
8
+ const ENTITIES_API_URL = `${API_URL}/core/entities/`;
9
+
10
+
11
+
12
+ interface SubscriptionData {
13
+ plan: number;
14
+ billing_cycle: 'monthly' | 'yearly';
15
+ extra_modules?: number[];
16
+ }
17
+
18
+ export const OrganizationServices = {
19
+ createOrganization: (data: Partial<Organization>, token: string) =>
20
+ FetchApi.post(`${ORGANIZATIONS_API_URL}`, data, token),
21
+
22
+ getUserOrganizations: (token: string) =>
23
+ FetchApi.get(`${API_URL}/core/users/organizations/`, token),
24
+
25
+ getOrganization: (id: number, token: string) =>
26
+ FetchApi.get(`${ORGANIZATIONS_API_URL}${id}/`, token),
27
+
28
+ updateOrganization: (id: number, data: Partial<Organization>, token: string) =>
29
+ FetchApi.put(`${ORGANIZATIONS_API_URL}${id}/`, data, token),
30
+
31
+
32
+ subscribe: (id: number, data: SubscriptionData, token: string) =>
33
+ FetchApi.put(`${ORGANIZATIONS_API_URL}${id}/subscribe/`, data, token),
34
+
35
+ deleteOrganization: (id: number, token: string) =>
36
+ FetchApi.delete(`${ORGANIZATIONS_API_URL}${id}/`, token),
37
+ };
38
+
39
+ export const EntityServices = {
40
+ createEntity: (organizationId: number, data: Entity, token: string) =>
41
+ FetchApi.post(`${ENTITIES_API_URL}`, { ...data, organization: organizationId }, token),
42
+
43
+ getOrganizationEntities: (organizationId: number, token: string) =>
44
+ FetchApi.get(`${ORGANIZATIONS_API_URL}${organizationId}/entities/`, token),
45
+
46
+ getEntity: (id: number, token: string) =>
47
+ FetchApi.get(`${ENTITIES_API_URL}${id}/`, token),
48
+
49
+ updateEntity: (id: number, data: Partial<Entity>, token: string) =>
50
+ FetchApi.put(`${ENTITIES_API_URL}${id}/`, data, token),
51
+
52
+
53
+ getEntityUsers: (id: number, token: string) =>
54
+ FetchApi.get(`${ENTITIES_API_URL}${id}/users/`, token),
55
+
56
+ addUserToEntity: (entityId: number, userId: number, token: string) =>
57
+ FetchApi.post(`${ENTITIES_API_URL}${entityId}/users/`, { user_id: userId }, token),
58
+
59
+ deleteEntity: (id: number, token: string) =>
60
+ FetchApi.delete(`${ENTITIES_API_URL}${id}/`, token),
61
+ };