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,101 @@
1
+ import React from 'react';
2
+ import { BarChart3, TrendingUp, Users, Clock } from 'lucide-react';
3
+
4
+ const Analytics: React.FC = () => {
5
+ const stats = [
6
+ {
7
+ title: 'Performances équipe',
8
+ value: '94%',
9
+ change: '+12%',
10
+ trend: 'up',
11
+ icon: TrendingUp,
12
+ color: 'text-green-600'
13
+ },
14
+ {
15
+ title: 'Membres actifs',
16
+ value: '24',
17
+ change: '+3',
18
+ trend: 'up',
19
+ icon: Users,
20
+ color: 'text-blue-600'
21
+ },
22
+ {
23
+ title: 'Temps moyen',
24
+ value: '2.4h',
25
+ change: '-0.3h',
26
+ trend: 'down',
27
+ icon: Clock,
28
+ color: 'text-purple-600'
29
+ },
30
+ {
31
+ title: 'Tâches complétées',
32
+ value: '187',
33
+ change: '+23',
34
+ trend: 'up',
35
+ icon: BarChart3,
36
+ color: 'text-orange-600'
37
+ }
38
+ ];
39
+
40
+ return (
41
+ <div className="space-y-6">
42
+ {/* Header */}
43
+ <div>
44
+ <h1 className="text-3xl font-bold text-gray-900">Analytics</h1>
45
+ <p className="text-gray-600 mt-2">
46
+ Analysez les performances de votre équipe et suivez les métriques importantes
47
+ </p>
48
+ </div>
49
+
50
+ {/* Stats Grid */}
51
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
52
+ {stats.map((stat, index) => {
53
+ const Icon = stat.icon;
54
+ return (
55
+ <div key={index} className="bg-white rounded-lg p-6 shadow-sm border border-gray-200">
56
+ <div className="flex items-center justify-between">
57
+ <div>
58
+ <p className="text-sm font-medium text-gray-600">{stat.title}</p>
59
+ <p className="text-2xl font-bold text-gray-900 mt-2">{stat.value}</p>
60
+ <p className={`text-sm mt-2 ${
61
+ stat.trend === 'up' ? 'text-green-600' : 'text-red-600'
62
+ }`}>
63
+ {stat.change} vs mois dernier
64
+ </p>
65
+ </div>
66
+ <div className={`p-3 rounded-full bg-gray-50 ${stat.color}`}>
67
+ <Icon className="w-6 h-6" />
68
+ </div>
69
+ </div>
70
+ </div>
71
+ );
72
+ })}
73
+ </div>
74
+
75
+ {/* Charts Section */}
76
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
77
+ {/* Chart 1 */}
78
+ <div className="bg-white rounded-lg p-6 shadow-sm border border-gray-200">
79
+ <h3 className="text-lg font-semibold text-gray-900 mb-4">
80
+ Tendance des performances
81
+ </h3>
82
+ <div className="h-64 flex items-center justify-center bg-gray-50 rounded-lg">
83
+ <p className="text-gray-500">Graphique des performances</p>
84
+ </div>
85
+ </div>
86
+
87
+ {/* Chart 2 */}
88
+ <div className="bg-white rounded-lg p-6 shadow-sm border border-gray-200">
89
+ <h3 className="text-lg font-semibold text-gray-900 mb-4">
90
+ Répartition des tâches
91
+ </h3>
92
+ <div className="h-64 flex items-center justify-center bg-gray-50 rounded-lg">
93
+ <p className="text-gray-500">Graphique en secteurs</p>
94
+ </div>
95
+ </div>
96
+ </div>
97
+ </div>
98
+ );
99
+ };
100
+
101
+ export default Analytics;
@@ -0,0 +1,215 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import { ArrowLeft } from 'lucide-react';
4
+
5
+
6
+ import { Organization, OrganizationServices } from '../services/OrganizationServices';
7
+ import { useSession } from '../contexts/SessionContext';
8
+ import { useToast } from '../contexts/ToastContext';
9
+ import { InputField, TextInput } from '../components/common/Inputs';
10
+ import PrimaryButton from '../components/common/Buttons';
11
+
12
+
13
+ type Step = 'organization' | 'entity' | 'confirmation';
14
+
15
+
16
+ const CreateOrganization: React.FC = () => {
17
+ const [loading, setLoading] = useState(false);
18
+ const { loggedUser, token } = useSession();
19
+ const { success, error: showError } = useToast();
20
+
21
+ const [formData, setFormData] = useState<Partial<Organization>>({
22
+ legal_name: '',
23
+ trading_name: '',
24
+ phone: '',
25
+ email: '',
26
+ address: '',
27
+ owner: loggedUser?.id || 0,
28
+ created_at: '',
29
+ updated_at: '',
30
+ is_deleted: false,
31
+ });
32
+
33
+ const [errors, setErrors] = useState<Partial<Record<keyof Organization, string>>>({});
34
+
35
+ const navigate = useNavigate();
36
+
37
+
38
+ useEffect(() => {
39
+ if (loggedUser) {
40
+ setFormData(prev => ({ ...prev, owner: loggedUser.id }));
41
+ }
42
+ }, [loggedUser]);
43
+
44
+ const validateForm = (): boolean => {
45
+ const newErrors: Partial<Record<keyof Organization, string>> = {};
46
+
47
+ if (!formData.legal_name?.trim()) {
48
+ newErrors.legal_name = 'La raison sociale est obligatoire';
49
+ }
50
+
51
+ if (formData.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
52
+ newErrors.email = 'Format d\'email invalide';
53
+ }
54
+
55
+ setErrors(newErrors);
56
+ return Object.keys(newErrors).length === 0;
57
+ };
58
+
59
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
60
+ const { name, value } = e.target;
61
+ setFormData(prev => ({ ...prev, [name]: value }));
62
+ if (errors[name as keyof Organization]) {
63
+ setErrors(prev => ({ ...prev, [name]: undefined }));
64
+ }
65
+ };
66
+
67
+ const handleTextareaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
68
+ const { name, value } = e.target;
69
+ setFormData(prev => ({ ...prev, [name]: value }));
70
+ if (errors[name as keyof Organization]) {
71
+ setErrors(prev => ({ ...prev, [name]: undefined }));
72
+ }
73
+ };
74
+
75
+
76
+
77
+
78
+ const handleSubmit = async (e: React.FormEvent) => {
79
+ e.preventDefault();
80
+ if (!validateForm() || !token) {
81
+ if (!token) {
82
+ showError('Vous devez être connecté pour créer une organisation');
83
+ }
84
+ return;
85
+ }
86
+
87
+ setLoading(true);
88
+ try {
89
+ await OrganizationServices.createOrganization(formData as Partial<Organization>, token);
90
+ success('Organisation créée avec succès !');
91
+ navigate('/organizations');
92
+ } catch (err: any) {
93
+ showError(err.message || 'Erreur lors de la création de l\'organisation');
94
+ } finally {
95
+ setLoading(false);
96
+ }
97
+ };
98
+
99
+
100
+
101
+
102
+
103
+ return (
104
+ <div className="min-h-screen bg-gray-50 py-8">
105
+ <div className=" mx-auto px-4">
106
+ {/* Header */}
107
+ <div className="flex items-center mb-8">
108
+ <button
109
+ onClick={() => navigate('/dashboard')}
110
+ className="flex items-center text-gray-600 hover:text-gray-900 transition-colors"
111
+ >
112
+ <ArrowLeft className="w-4 h-4 mr-2" />
113
+ Retour au tableau de bord
114
+ </button>
115
+ </div>
116
+
117
+ {/* Title */}
118
+ <div className=" mb-8">
119
+ <h1 className="text-3xl font-bold text-gray-900 mb-2">
120
+ Création d'organisation
121
+ </h1>
122
+ <p className="text-gray-600">
123
+ Configurez votre organisation et créez votre première entité
124
+ </p>
125
+ </div>
126
+
127
+ {/* Step Indicator */}
128
+
129
+
130
+ {/* Current Step Content */}
131
+ <div className="mx-auto">
132
+ <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
133
+ <form onSubmit={handleSubmit} className="space-y-6">
134
+ {/* Raison sociale */}
135
+ <TextInput
136
+ label="Raison sociale"
137
+ name="legal_name"
138
+ value={formData.legal_name || ''}
139
+ placeholder="Nom légal de l'organisation"
140
+ required
141
+ error={errors.legal_name}
142
+ onChange={handleInputChange}
143
+ />
144
+
145
+ {/* Nom commercial */}
146
+ <TextInput
147
+ label="Nom commercial"
148
+ name="trading_name"
149
+ value={formData.trading_name || ''}
150
+ placeholder="Nom commercial (optionnel)"
151
+ onChange={handleInputChange}
152
+ />
153
+
154
+ {/* Contact */}
155
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
156
+ <InputField
157
+ label="Téléphone"
158
+ name="phone"
159
+ type="tel"
160
+ value={formData.phone || ''}
161
+ placeholder="+225 XX XX XXX XXX"
162
+ onChange={handleInputChange}
163
+ />
164
+
165
+ <InputField
166
+ label="Email"
167
+ name="email"
168
+ type="email"
169
+ value={formData.email || ''}
170
+ placeholder="contact@organisation.com"
171
+ error={errors.email}
172
+ onChange={handleInputChange}
173
+ />
174
+ </div>
175
+
176
+ {/* Adresse */}
177
+ <div>
178
+ <label className="block text-sm font-medium text-gray-700 mb-2">
179
+ Adresse
180
+ </label>
181
+ <textarea
182
+ name="address"
183
+ value={formData.address || ''}
184
+ onChange={handleTextareaChange}
185
+ rows={3}
186
+ className="w-full px-4 py-3 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#6B7C92] focus:border-transparent placeholder-gray-400"
187
+ placeholder="Adresse complète de l'organisation"
188
+ />
189
+ </div>
190
+
191
+ {/* Actions */}
192
+ <div className="flex justify-between pt-6 border-t border-gray-200">
193
+ <button
194
+ type="button"
195
+ onClick={() => navigate('/organizations')}
196
+ className="px-6 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
197
+ >
198
+ Annuler
199
+ </button>
200
+
201
+ <PrimaryButton onClick={handleSubmit} loading={loading}>
202
+ {loading ? 'Création...' : 'Créer l\'organisation'}
203
+ </PrimaryButton>
204
+
205
+
206
+ </div>
207
+ </form>
208
+ </div>
209
+ </div>
210
+ </div>
211
+ </div>
212
+ );
213
+ };
214
+
215
+ export default CreateOrganization;
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+
3
+ const Dashboard: React.FC = () => {
4
+ return (
5
+ <div style={{ padding: '2rem' }}>
6
+ <h1>Dashboard</h1>
7
+ <p>Welcome to your dashboard. Here you can view your stats and manage your account.</p>
8
+ {/* Add dashboard widgets/components here */}
9
+
10
+
11
+ </div>
12
+ );
13
+ };
14
+
15
+ export default Dashboard;
@@ -0,0 +1,12 @@
1
+ import React, { useState } from 'react';
2
+
3
+ const Home: React.FC = () => {
4
+
5
+ return (
6
+ <div className="min-h-screen bg-gray-50">
7
+ Bienvenue sur Rewise !
8
+ </div>
9
+ );
10
+ };
11
+
12
+ export default Home;
@@ -0,0 +1,313 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Camera, Mail, Phone, MapPin, Calendar, Edit3, Save, X, User as UserIcon } from 'lucide-react';
3
+ import { useToast } from '../contexts/ToastContext';
4
+ import { useSession, User } from '../contexts/SessionContext';
5
+
6
+ const Profile: React.FC = () => {
7
+ const [isEditing, setIsEditing] = useState(false);
8
+ const [loading, setLoading] = useState(false);
9
+ const { success, error: showError } = useToast();
10
+
11
+ const { loggedUser } = useSession();
12
+
13
+ const [profileData, setProfileData] = useState<User | null>(loggedUser);
14
+ const [editData, setEditData] = useState<Partial<User>>({});
15
+
16
+ useEffect(() => {
17
+ if (loggedUser) {
18
+ setProfileData(loggedUser);
19
+ setEditData(loggedUser);
20
+ }
21
+ }, [loggedUser]);
22
+
23
+ const handleEdit = () => {
24
+ setEditData({ ...profileData });
25
+ setIsEditing(true);
26
+ };
27
+
28
+ const handleCancel = () => {
29
+ setEditData({ ...profileData });
30
+ setIsEditing(false);
31
+ };
32
+
33
+ const handleSave = async () => {
34
+ setLoading(true);
35
+ try {
36
+ // Simuler un appel API
37
+ await new Promise(resolve => setTimeout(resolve, 1000));
38
+ setProfileData({ ...editData });
39
+ setIsEditing(false);
40
+ success('Profil mis à jour avec succès !');
41
+ } catch (error) {
42
+ showError('Erreur lors de la mise à jour du profil');
43
+ } finally {
44
+ setLoading(false);
45
+ }
46
+ };
47
+
48
+ const handleInputChange = (field: string, value: string) => {
49
+ setEditData(prev => ({ ...prev, [field]: value }));
50
+ };
51
+
52
+ const formatDate = (dateString: string) => {
53
+ return new Date(dateString).toLocaleDateString('fr-FR', {
54
+ year: 'numeric',
55
+ month: 'long',
56
+ day: 'numeric'
57
+ });
58
+ };
59
+
60
+ const getInitials = (firstName: string, lastName: string) => {
61
+ return `${firstName?.[0] || ''}${lastName?.[0] || ''}`.toUpperCase();
62
+ };
63
+
64
+ const activities = [
65
+ { id: 1, action: 'Connexion', time: 'Il y a 2 heures', type: 'login' },
66
+ { id: 2, action: 'Mise à jour du profil', time: 'Hier', type: 'update' },
67
+ { id: 3, action: 'Nouveau projet créé', time: 'Il y a 3 jours', type: 'create' },
68
+ { id: 4, action: 'Tâche complétée', time: 'Il y a 5 jours', type: 'complete' },
69
+ ];
70
+
71
+ if (!profileData) {
72
+ return (
73
+ <div className="space-y-6">
74
+ <div className="flex items-center justify-center min-h-[400px]">
75
+ <div className="text-center">
76
+ <h2 className="text-xl font-semibold text-gray-900 mb-2">Chargement du profil...</h2>
77
+ <p className="text-gray-600">Veuillez patienter pendant le chargement de vos informations.</p>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ );
82
+ }
83
+
84
+ return (
85
+ <div className="space-y-6">
86
+ {/* Header */}
87
+ <div className="flex items-center justify-between">
88
+ <div>
89
+ <h1 className="text-3xl font-bold text-gray-900">Mon Profil</h1>
90
+ <p className="text-gray-600 mt-2">
91
+ Gérez vos informations personnelles et vos préférences
92
+ </p>
93
+ </div>
94
+ {!isEditing ? (
95
+ <button
96
+ onClick={handleEdit}
97
+ className="bg-[#8290A9] text-white px-4 py-2 rounded-lg hover:bg-[#6B7C92] transition-colors flex items-center space-x-2"
98
+ >
99
+ <Edit3 className="w-4 h-4" />
100
+ <span>Modifier</span>
101
+ </button>
102
+ ) : (
103
+ <div className="flex space-x-2">
104
+ <button
105
+ onClick={handleCancel}
106
+ className="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600 transition-colors flex items-center space-x-2"
107
+ >
108
+ <X className="w-4 h-4" />
109
+ <span>Annuler</span>
110
+ </button>
111
+ <button
112
+ onClick={handleSave}
113
+ disabled={loading}
114
+ className="bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 transition-colors flex items-center space-x-2 disabled:opacity-50"
115
+ >
116
+ <Save className="w-4 h-4" />
117
+ <span>{loading ? 'Sauvegarde...' : 'Sauvegarder'}</span>
118
+ </button>
119
+ </div>
120
+ )}
121
+ </div>
122
+
123
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
124
+ {/* Profile Card */}
125
+ <div className="lg:col-span-2">
126
+ <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
127
+ <div className="flex items-start space-x-6">
128
+ {/* Avatar */}
129
+ <div className="relative">
130
+ <div className="w-24 h-24 bg-[#8290A9] rounded-full flex items-center justify-center">
131
+ <span className="text-white text-2xl font-bold">
132
+ {getInitials(profileData?.first_name || '', profileData?.last_name || '')}
133
+ </span>
134
+ </div>
135
+ {isEditing && (
136
+ <button className="absolute bottom-0 right-0 bg-[#8290A9] text-white p-2 rounded-full hover:bg-[#6B7C92] transition-colors">
137
+ <Camera className="w-4 h-4" />
138
+ </button>
139
+ )}
140
+ </div>
141
+
142
+ {/* Basic Info */}
143
+ <div className="flex-1 space-y-4">
144
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
145
+ <div>
146
+ <label className="block text-sm font-medium text-gray-700 mb-1">
147
+ Prénom
148
+ </label>
149
+ {isEditing ? (
150
+ <input
151
+ type="text"
152
+ value={editData.first_name || ''}
153
+ onChange={(e) => handleInputChange('first_name', e.target.value)}
154
+ className="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#8290A9] focus:border-transparent"
155
+ />
156
+ ) : (
157
+ <p className="text-gray-900 font-medium">{profileData?.first_name}</p>
158
+ )}
159
+ </div>
160
+ <div>
161
+ <label className="block text-sm font-medium text-gray-700 mb-1">
162
+ Nom
163
+ </label>
164
+ {isEditing ? (
165
+ <input
166
+ type="text"
167
+ value={editData.last_name || ''}
168
+ onChange={(e) => handleInputChange('last_name', e.target.value)}
169
+ className="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#8290A9] focus:border-transparent"
170
+ />
171
+ ) : (
172
+ <p className="text-gray-900 font-medium">{profileData?.last_name}</p>
173
+ )}
174
+ </div>
175
+ </div>
176
+
177
+ <div>
178
+ <label className="block text-sm font-medium text-gray-700 mb-1">
179
+ Nom d'utilisateur
180
+ </label>
181
+ {isEditing ? (
182
+ <input
183
+ type="text"
184
+ value={editData.username || ''}
185
+ onChange={(e) => handleInputChange('username', e.target.value)}
186
+ className="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#8290A9] focus:border-transparent"
187
+ />
188
+ ) : (
189
+ <p className="text-gray-900 font-medium">{profileData?.username}</p>
190
+ )}
191
+ </div>
192
+ </div>
193
+ </div>
194
+ </div>
195
+
196
+ {/* Contact Information */}
197
+ <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mt-6">
198
+ <h3 className="text-lg font-semibold text-gray-900 mb-4">
199
+ Informations de contact
200
+ </h3>
201
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
202
+ <div>
203
+ <label className="block text-sm font-medium text-gray-700 mb-1">
204
+ Email
205
+ </label>
206
+ {isEditing ? (
207
+ <input
208
+ type="email"
209
+ value={editData.email || ''}
210
+ onChange={(e) => handleInputChange('email', e.target.value)}
211
+ className="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#8290A9] focus:border-transparent"
212
+ />
213
+ ) : (
214
+ <div className="flex items-center space-x-2">
215
+ <Mail className="w-4 h-4 text-gray-400" />
216
+ <span className="text-gray-900">{profileData?.email}</span>
217
+ </div>
218
+ )}
219
+ </div>
220
+ <div>
221
+ <label className="block text-sm font-medium text-gray-700 mb-1">
222
+ Téléphone
223
+ </label>
224
+ {isEditing ? (
225
+ <input
226
+ type="tel"
227
+ value={editData.phonenumber || ''}
228
+ onChange={(e) => handleInputChange('phonenumber', e.target.value)}
229
+ className="w-full px-3 py-2 border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-[#8290A9] focus:border-transparent"
230
+ />
231
+ ) : (
232
+ <div className="flex items-center space-x-2">
233
+ <Phone className="w-4 h-4 text-gray-400" />
234
+ <span className="text-gray-900">{profileData?.phonenumber}</span>
235
+ </div>
236
+ )}
237
+ </div>
238
+ <div>
239
+ <label className="block text-sm font-medium text-gray-700 mb-1">
240
+ Statut du compte
241
+ </label>
242
+ <div className="flex items-center space-x-2">
243
+ <div className={`w-2 h-2 rounded-full ${profileData?.is_active ? 'bg-green-500' : 'bg-red-500'}`}></div>
244
+ <span className="text-gray-900">{profileData?.is_active ? 'Actif' : 'Inactif'}</span>
245
+ </div>
246
+ </div>
247
+ <div>
248
+ <label className="block text-sm font-medium text-gray-700 mb-1">
249
+ Rôle
250
+ </label>
251
+ <div className="flex items-center space-x-2">
252
+ <UserIcon className="w-4 h-4 text-gray-400" />
253
+ <span className="text-gray-900">
254
+ {profileData?.is_superuser ? 'Super Administrateur' :
255
+ profileData?.is_staff ? 'Staff' : 'Utilisateur'}
256
+ </span>
257
+ </div>
258
+ </div>
259
+ </div>
260
+ </div>
261
+ </div>
262
+
263
+ {/* Sidebar */}
264
+ <div className="space-y-6">
265
+ {/* Stats */}
266
+ <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
267
+ <h3 className="text-lg font-semibold text-gray-900 mb-4">
268
+ Informations générales
269
+ </h3>
270
+ <div className="space-y-3">
271
+ <div className="flex items-center space-x-2">
272
+ <Calendar className="w-4 h-4 text-gray-400" />
273
+ <div>
274
+ <p className="text-sm text-gray-600">Date d'inscription</p>
275
+ <p className="font-medium">{profileData?.date_joined ? formatDate(profileData.date_joined) : 'Non disponible'}</p>
276
+ </div>
277
+ </div>
278
+ {profileData?.last_login && (
279
+ <div className="flex items-center space-x-2">
280
+ <Calendar className="w-4 h-4 text-gray-400" />
281
+ <div>
282
+ <p className="text-sm text-gray-600">Dernière connexion</p>
283
+ <p className="font-medium">{formatDate(profileData.last_login)}</p>
284
+ </div>
285
+ </div>
286
+ )}
287
+ </div>
288
+ </div>
289
+
290
+ {/* Recent Activity */}
291
+ <div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
292
+ <h3 className="text-lg font-semibold text-gray-900 mb-4">
293
+ Activité récente
294
+ </h3>
295
+ <div className="space-y-3">
296
+ {activities.map((activity) => (
297
+ <div key={activity.id} className="flex items-start space-x-3">
298
+ <div className="w-2 h-2 bg-[#8290A9] rounded-full mt-2"></div>
299
+ <div>
300
+ <p className="text-sm font-medium text-gray-900">{activity.action}</p>
301
+ <p className="text-xs text-gray-500">{activity.time}</p>
302
+ </div>
303
+ </div>
304
+ ))}
305
+ </div>
306
+ </div>
307
+ </div>
308
+ </div>
309
+ </div>
310
+ );
311
+ };
312
+
313
+ export default Profile;