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.
- package/eslint.config.js +28 -0
- package/index.html +78 -0
- package/package.json +42 -0
- package/postcss.config.js +6 -0
- package/src/App.tsx +156 -0
- package/src/assets/imgs/login_illustration.png +0 -0
- package/src/components/common/Buttons.tsx +39 -0
- package/src/components/common/Cards.tsx +18 -0
- package/src/components/common/FDrawer.tsx +2448 -0
- package/src/components/common/FDrawer.types.ts +191 -0
- package/src/components/common/Inputs.tsx +409 -0
- package/src/components/common/Modals.tsx +41 -0
- package/src/components/common/Navigations.tsx +0 -0
- package/src/components/common/Toast.tsx +0 -0
- package/src/components/demo/ToastDemo.tsx +73 -0
- package/src/components/layout/Header.tsx +202 -0
- package/src/components/layout/ModernDoubleSidebarLayout.tsx +727 -0
- package/src/components/layout/PrivateLayout.tsx +52 -0
- package/src/components/layout/Sidebar.tsx +182 -0
- package/src/components/ui/Toast.tsx +93 -0
- package/src/contexts/SessionContext.tsx +77 -0
- package/src/contexts/ThemeContext.tsx +58 -0
- package/src/contexts/ToastContext.tsx +94 -0
- package/src/index.css +3 -0
- package/src/main.tsx +10 -0
- package/src/models/Organization.ts +47 -0
- package/src/models/Plan.ts +42 -0
- package/src/models/User.ts +23 -0
- package/src/pages/Analytics.tsx +101 -0
- package/src/pages/CreateOrganization.tsx +215 -0
- package/src/pages/Dashboard.tsx +15 -0
- package/src/pages/Home.tsx +12 -0
- package/src/pages/Profile.tsx +313 -0
- package/src/pages/Settings.tsx +382 -0
- package/src/pages/Team.tsx +180 -0
- package/src/pages/auth/Login.tsx +140 -0
- package/src/pages/auth/Register.tsx +302 -0
- package/src/pages/organizations/DetailEntity.tsx +1002 -0
- package/src/pages/organizations/DetailOrganizations.tsx +1629 -0
- package/src/pages/organizations/ListOrganizations.tsx +270 -0
- package/src/pages/pricings/CartPlan.tsx +486 -0
- package/src/pages/pricings/ListPricing.tsx +321 -0
- package/src/pages/users/CreateUser.tsx +450 -0
- package/src/pages/users/ListUsers.tsx +0 -0
- package/src/services/AuthServices.ts +94 -0
- package/src/services/OrganizationServices.ts +61 -0
- package/src/services/PlanSubscriptionServices.tsx +137 -0
- package/src/services/UserServices.ts +36 -0
- package/src/services/api.ts +64 -0
- package/src/styles/theme.ts +383 -0
- package/src/utils/utils.ts +48 -0
- package/src/vite-env.d.ts +1 -0
- package/tailwind.config.js +158 -0
- package/tsconfig.app.json +24 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +22 -0
- package/vite.config.ts +10 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { useNavigate } from 'react-router-dom';
|
|
3
|
+
import {
|
|
4
|
+
Building2,
|
|
5
|
+
Plus,
|
|
6
|
+
Search,
|
|
7
|
+
MoreVertical,
|
|
8
|
+
Edit,
|
|
9
|
+
Trash2,
|
|
10
|
+
Eye,
|
|
11
|
+
Phone,
|
|
12
|
+
Mail,
|
|
13
|
+
MapPin,
|
|
14
|
+
Calendar
|
|
15
|
+
} from 'lucide-react';
|
|
16
|
+
import { useToast } from '../../contexts/ToastContext';
|
|
17
|
+
import { useSession } from '../../contexts/SessionContext';
|
|
18
|
+
import { OrganizationServices } from '../../services/OrganizationServices';
|
|
19
|
+
import { Organization } from '../../models/Organization';
|
|
20
|
+
import PrimaryButton from '../../components/common/Buttons';
|
|
21
|
+
|
|
22
|
+
const ListOrganizations: React.FC = () => {
|
|
23
|
+
const [organizations, setOrganizations] = useState<Organization[]>([]);
|
|
24
|
+
const [loading, setLoading] = useState(true);
|
|
25
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
26
|
+
const [dropdownOpen, setDropdownOpen] = useState<number | null>(null);
|
|
27
|
+
|
|
28
|
+
const navigate = useNavigate();
|
|
29
|
+
const { success, error: showError } = useToast();
|
|
30
|
+
const { token } = useSession();
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
loadOrganizations();
|
|
34
|
+
}, []);
|
|
35
|
+
|
|
36
|
+
const loadOrganizations = async () => {
|
|
37
|
+
if (!token) {
|
|
38
|
+
showError('Vous devez etre connecté pour voir les organisations');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
setLoading(true);
|
|
44
|
+
const result = await OrganizationServices.getUserOrganizations(token) as {success: boolean, data: Organization[] };
|
|
45
|
+
setOrganizations(result.data);
|
|
46
|
+
} catch (error: any) {
|
|
47
|
+
showError('Erreur lors du chargement des organisations');
|
|
48
|
+
console.error(error);
|
|
49
|
+
} finally {
|
|
50
|
+
setLoading(false);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const handleDeleteOrganization = async (id: number, name: string) => {
|
|
55
|
+
if (!token) return;
|
|
56
|
+
|
|
57
|
+
if (!window.confirm(`�tes-vous s�r de vouloir supprimer l'organisation "${name}" ?`)) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
await OrganizationServices.deleteOrganization(id, token);
|
|
63
|
+
success('Organisation supprimée avec succès');
|
|
64
|
+
loadOrganizations();
|
|
65
|
+
} catch (error: any) {
|
|
66
|
+
showError('Erreur lors de la suppression de l\'organisation');
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const filteredOrganizations = organizations.filter(org =>
|
|
71
|
+
org.legal_name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
72
|
+
(org.trading_name && org.trading_name.toLowerCase().includes(searchTerm.toLowerCase()))
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const formatDate = (dateString: string) => {
|
|
76
|
+
return new Date(dateString).toLocaleDateString('fr-FR', {
|
|
77
|
+
year: 'numeric',
|
|
78
|
+
month: 'long',
|
|
79
|
+
day: 'numeric'
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
if (loading) {
|
|
84
|
+
return (
|
|
85
|
+
<div className="space-y-6">
|
|
86
|
+
<div className="flex items-center justify-center min-h-[400px]">
|
|
87
|
+
<div className="text-center">
|
|
88
|
+
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-[#8290A9] mx-auto mb-4"></div>
|
|
89
|
+
<p className="text-gray-600">Chargement des organisations...</p>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div className="space-y-6">
|
|
98
|
+
{/* Header */}
|
|
99
|
+
<div className="flex items-center justify-between">
|
|
100
|
+
<div>
|
|
101
|
+
<h1 className="text-3xl font-bold text-gray-900">Mes Organisations</h1>
|
|
102
|
+
<p className="text-gray-600 mt-2">
|
|
103
|
+
Gérez vos organisations et leurs entités
|
|
104
|
+
</p>
|
|
105
|
+
</div>
|
|
106
|
+
<PrimaryButton onClick={() => navigate('/organizations/new')} >
|
|
107
|
+
<Plus className="w-4 h-4" />
|
|
108
|
+
<span>Nouvelle Organisation</span>
|
|
109
|
+
</PrimaryButton>
|
|
110
|
+
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
{/* Search */}
|
|
114
|
+
<div className="relative max-w-md">
|
|
115
|
+
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
|
|
116
|
+
<input
|
|
117
|
+
type="text"
|
|
118
|
+
placeholder="Rechercher une organisation..."
|
|
119
|
+
value={searchTerm}
|
|
120
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
121
|
+
className="w-full pl-10 pr-4 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#8290A9] focus:border-transparent"
|
|
122
|
+
/>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
{/* Organizations Grid */}
|
|
126
|
+
{filteredOrganizations.length === 0 ? (
|
|
127
|
+
<div className="text-center py-12">
|
|
128
|
+
<Building2 className="w-16 h-16 text-gray-300 mx-auto mb-4" />
|
|
129
|
+
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
|
130
|
+
{searchTerm ? 'Aucune organisation trouv�e' : 'Aucune organisation'}
|
|
131
|
+
</h3>
|
|
132
|
+
<p className="text-gray-600 mb-6">
|
|
133
|
+
{searchTerm
|
|
134
|
+
? 'Essayez de modifier votre recherche'
|
|
135
|
+
: 'Commencez par cr�er votre premi�re organisation'
|
|
136
|
+
}
|
|
137
|
+
</p>
|
|
138
|
+
{!searchTerm && (
|
|
139
|
+
<button
|
|
140
|
+
onClick={() => navigate('/create-organization')}
|
|
141
|
+
className="bg-[#8290A9] text-white px-6 py-2 rounded-lg hover:bg-[#6B7C92] transition-colors"
|
|
142
|
+
>
|
|
143
|
+
Créer une organisation
|
|
144
|
+
</button>
|
|
145
|
+
)}
|
|
146
|
+
</div>
|
|
147
|
+
) : (
|
|
148
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
149
|
+
{filteredOrganizations.map((organization) => (
|
|
150
|
+
<div
|
|
151
|
+
key={organization.id}
|
|
152
|
+
className="bg-white rounded-lg shadow-sm border border-gray-200 p-6 hover:shadow-md transition-shadow"
|
|
153
|
+
>
|
|
154
|
+
{/* Card Header */}
|
|
155
|
+
<div className="flex items-start justify-between mb-4">
|
|
156
|
+
<div className="flex items-center space-x-3">
|
|
157
|
+
<div className="w-12 h-12 bg-[#8290A9] rounded-full flex items-center justify-center">
|
|
158
|
+
<Building2 className="w-6 h-6 text-white" />
|
|
159
|
+
</div>
|
|
160
|
+
<div className="flex-1 min-w-0">
|
|
161
|
+
<h3 className="text-lg font-semibold text-gray-900 truncate">
|
|
162
|
+
{organization.legal_name}
|
|
163
|
+
</h3>
|
|
164
|
+
{organization.trading_name && (
|
|
165
|
+
<p className="text-sm text-gray-600 truncate">
|
|
166
|
+
{organization.trading_name}
|
|
167
|
+
</p>
|
|
168
|
+
)}
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<div className="relative">
|
|
173
|
+
<button
|
|
174
|
+
onClick={() => setDropdownOpen(dropdownOpen === organization.id ? null : organization.id)}
|
|
175
|
+
className="p-1 rounded-full hover:bg-gray-100 transition-colors"
|
|
176
|
+
>
|
|
177
|
+
<MoreVertical className="w-4 h-4 text-gray-400" />
|
|
178
|
+
</button>
|
|
179
|
+
|
|
180
|
+
{dropdownOpen === organization.id && (
|
|
181
|
+
<div className="absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg border border-gray-200 py-2 z-10">
|
|
182
|
+
<button
|
|
183
|
+
onClick={() => {
|
|
184
|
+
navigate(`/organizations/${organization.id}`);
|
|
185
|
+
setDropdownOpen(null);
|
|
186
|
+
}}
|
|
187
|
+
className="w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 flex items-center space-x-2"
|
|
188
|
+
>
|
|
189
|
+
<Eye className="w-4 h-4" />
|
|
190
|
+
<span>Voir les détails</span>
|
|
191
|
+
</button>
|
|
192
|
+
<button
|
|
193
|
+
onClick={() => {
|
|
194
|
+
navigate(`/organizations/${organization.id}/edit`);
|
|
195
|
+
setDropdownOpen(null);
|
|
196
|
+
}}
|
|
197
|
+
className="w-full px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-50 flex items-center space-x-2"
|
|
198
|
+
>
|
|
199
|
+
<Edit className="w-4 h-4" />
|
|
200
|
+
<span>Modifier</span>
|
|
201
|
+
</button>
|
|
202
|
+
<button
|
|
203
|
+
onClick={() => {
|
|
204
|
+
handleDeleteOrganization(organization.id, organization.legal_name);
|
|
205
|
+
setDropdownOpen(null);
|
|
206
|
+
}}
|
|
207
|
+
className="w-full px-4 py-2 text-left text-sm text-red-600 hover:bg-red-50 flex items-center space-x-2"
|
|
208
|
+
>
|
|
209
|
+
<Trash2 className="w-4 h-4" />
|
|
210
|
+
<span>Supprimer</span>
|
|
211
|
+
</button>
|
|
212
|
+
</div>
|
|
213
|
+
)}
|
|
214
|
+
</div>
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
{/* Contact Info */}
|
|
218
|
+
<div className="space-y-2 mb-4">
|
|
219
|
+
{organization.email && (
|
|
220
|
+
<div className="flex items-center space-x-2 text-sm text-gray-600">
|
|
221
|
+
<Mail className="w-4 h-4" />
|
|
222
|
+
<span className="truncate">{organization.email}</span>
|
|
223
|
+
</div>
|
|
224
|
+
)}
|
|
225
|
+
{organization.phone && (
|
|
226
|
+
<div className="flex items-center space-x-2 text-sm text-gray-600">
|
|
227
|
+
<Phone className="w-4 h-4" />
|
|
228
|
+
<span>{organization.phone}</span>
|
|
229
|
+
</div>
|
|
230
|
+
)}
|
|
231
|
+
{organization.address && (
|
|
232
|
+
<div className="flex items-start space-x-2 text-sm text-gray-600">
|
|
233
|
+
<MapPin className="w-4 h-4 mt-0.5 flex-shrink-0" />
|
|
234
|
+
<span className="line-clamp-2">{organization.address}</span>
|
|
235
|
+
</div>
|
|
236
|
+
)}
|
|
237
|
+
</div>
|
|
238
|
+
|
|
239
|
+
{/* Footer */}
|
|
240
|
+
<div className="pt-4 border-t border-gray-100">
|
|
241
|
+
<div className="flex items-center justify-between text-xs text-gray-500">
|
|
242
|
+
<div className="flex items-center space-x-1">
|
|
243
|
+
<Calendar className="w-3 h-3" />
|
|
244
|
+
<span>Créé le {formatDate(organization.created_at)}</span>
|
|
245
|
+
</div>
|
|
246
|
+
<button
|
|
247
|
+
onClick={() => navigate(`/organizations/${organization.id}`)}
|
|
248
|
+
className="text-[#8290A9] hover:text-[#6B7C92] font-medium"
|
|
249
|
+
>
|
|
250
|
+
Voir
|
|
251
|
+
</button>
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
))}
|
|
256
|
+
</div>
|
|
257
|
+
)}
|
|
258
|
+
|
|
259
|
+
{/* Click outside to close dropdown */}
|
|
260
|
+
{dropdownOpen && (
|
|
261
|
+
<div
|
|
262
|
+
className="fixed inset-0 z-5"
|
|
263
|
+
onClick={() => setDropdownOpen(null)}
|
|
264
|
+
/>
|
|
265
|
+
)}
|
|
266
|
+
</div>
|
|
267
|
+
);
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
export default ListOrganizations;
|