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,191 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface FDrawerHeader {
|
|
4
|
+
key: string;
|
|
5
|
+
name: string;
|
|
6
|
+
type?: string;
|
|
7
|
+
filterable?: boolean;
|
|
8
|
+
formule?: (item: any) => string;
|
|
9
|
+
search_name?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface FilterOption {
|
|
13
|
+
value: string;
|
|
14
|
+
label: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface Filter {
|
|
18
|
+
label: string;
|
|
19
|
+
type: string;
|
|
20
|
+
key: string;
|
|
21
|
+
filter_options?: FilterOption[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface BtnListItem {
|
|
25
|
+
type: string;
|
|
26
|
+
label?: string;
|
|
27
|
+
to?: string;
|
|
28
|
+
target?: string;
|
|
29
|
+
dropdown?: any[];
|
|
30
|
+
onclick?: () => void;
|
|
31
|
+
icon?: any[];
|
|
32
|
+
icon_type?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ActionItem {
|
|
36
|
+
label: string;
|
|
37
|
+
onclick?: (row: any) => void;
|
|
38
|
+
to?: (row: any) => string;
|
|
39
|
+
condition?: (row: any) => boolean;
|
|
40
|
+
permission?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ReponseDetail {
|
|
44
|
+
total_pages: number | null;
|
|
45
|
+
previous: string | null;
|
|
46
|
+
next: string | null;
|
|
47
|
+
current_page: number | null;
|
|
48
|
+
paginate?: {
|
|
49
|
+
next: string;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface ViewAction {
|
|
54
|
+
label: string;
|
|
55
|
+
onclick?: () => void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface SummaryRow {
|
|
59
|
+
label: string;
|
|
60
|
+
contents: Array<(param: any) => string | number>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface Summary {
|
|
64
|
+
colonnes: string[];
|
|
65
|
+
rows: SummaryRow[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface FDrawerProps {
|
|
69
|
+
id?: string;
|
|
70
|
+
selected?: any[];
|
|
71
|
+
maxText?: number;
|
|
72
|
+
type?: string;
|
|
73
|
+
noFilter?: boolean;
|
|
74
|
+
noSearch?: boolean;
|
|
75
|
+
canExport?: boolean;
|
|
76
|
+
table_type?: string;
|
|
77
|
+
toggle?: string;
|
|
78
|
+
headers?: FDrawerHeader[];
|
|
79
|
+
totalHeaders?: FDrawerHeader[];
|
|
80
|
+
totalMainHeaders?: FDrawerHeader[];
|
|
81
|
+
btnsList?: BtnListItem[];
|
|
82
|
+
infinite_scroll?: boolean;
|
|
83
|
+
title?: string | ReactNode;
|
|
84
|
+
title_?: string;
|
|
85
|
+
orderByDefault?: string;
|
|
86
|
+
api?: string;
|
|
87
|
+
additional_params?: string;
|
|
88
|
+
btn_add?: boolean;
|
|
89
|
+
inWorkspace?: boolean;
|
|
90
|
+
wTotalCard?: boolean;
|
|
91
|
+
wMenu?: boolean;
|
|
92
|
+
noTitle?: boolean;
|
|
93
|
+
sideTitle?: ReactNode;
|
|
94
|
+
wReturn?: string;
|
|
95
|
+
noPagination?: boolean;
|
|
96
|
+
summary?: Summary;
|
|
97
|
+
children?: ReactNode;
|
|
98
|
+
arch?: {
|
|
99
|
+
trigger: string;
|
|
100
|
+
elements: string;
|
|
101
|
+
};
|
|
102
|
+
secondElements?: Array<{
|
|
103
|
+
title: string;
|
|
104
|
+
content: (data: any) => string | number;
|
|
105
|
+
custom?: boolean;
|
|
106
|
+
style?: React.CSSProperties;
|
|
107
|
+
}>;
|
|
108
|
+
triggerActions?: ActionItem[];
|
|
109
|
+
actions?: ActionItem[];
|
|
110
|
+
hasAttach?: boolean;
|
|
111
|
+
globalCode?: string;
|
|
112
|
+
reloadAttachment?: () => void;
|
|
113
|
+
hasImportExcel?: boolean;
|
|
114
|
+
api_import?: string;
|
|
115
|
+
hasExportExcel?: boolean;
|
|
116
|
+
hasComHis?: boolean;
|
|
117
|
+
lead?: any;
|
|
118
|
+
update_func?: () => void;
|
|
119
|
+
hasExport?: boolean;
|
|
120
|
+
save?: () => void;
|
|
121
|
+
view?: boolean;
|
|
122
|
+
view_actions?: ViewAction[];
|
|
123
|
+
height?: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface FooterSummaryProps {
|
|
127
|
+
colonnes: string[];
|
|
128
|
+
rows: SummaryRow[];
|
|
129
|
+
param: any[];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface DropdownActionsMenuProps {
|
|
133
|
+
actions?: ActionItem[];
|
|
134
|
+
row: any;
|
|
135
|
+
setLineSelected: (line: string) => void;
|
|
136
|
+
center: any;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface TableCollapseSimpleProps {
|
|
140
|
+
dataFilters: any[];
|
|
141
|
+
arch: {
|
|
142
|
+
trigger: string;
|
|
143
|
+
elements: string;
|
|
144
|
+
};
|
|
145
|
+
loading: boolean;
|
|
146
|
+
loading2?: boolean;
|
|
147
|
+
actions?: ActionItem[];
|
|
148
|
+
secondElements?: Array<{
|
|
149
|
+
title: string;
|
|
150
|
+
content: (data: any) => string | number;
|
|
151
|
+
custom?: boolean;
|
|
152
|
+
style?: React.CSSProperties;
|
|
153
|
+
}>;
|
|
154
|
+
triggerActions?: ActionItem[];
|
|
155
|
+
maxText: number;
|
|
156
|
+
data: any[];
|
|
157
|
+
headers: FDrawerHeader[];
|
|
158
|
+
totalHeaders?: FDrawerHeader[];
|
|
159
|
+
totalMainHeaders?: FDrawerHeader[];
|
|
160
|
+
filters: Record<string, any>;
|
|
161
|
+
setFilters: React.Dispatch<React.SetStateAction<Record<string, any>>>;
|
|
162
|
+
setComment: React.Dispatch<React.SetStateAction<any>>;
|
|
163
|
+
setSecondInfoComment: React.Dispatch<React.SetStateAction<string>>;
|
|
164
|
+
setShowModalComment: React.Dispatch<React.SetStateAction<boolean>>;
|
|
165
|
+
showContent: Record<string, boolean>;
|
|
166
|
+
setShowContent: React.Dispatch<React.SetStateAction<Record<string, boolean>>>;
|
|
167
|
+
lineSelected: string;
|
|
168
|
+
setLineSelected: React.Dispatch<React.SetStateAction<string>>;
|
|
169
|
+
isSorting: string | undefined;
|
|
170
|
+
setIsSorting: React.Dispatch<React.SetStateAction<string | undefined>>;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface FTableProps extends Omit<FDrawerProps, 'type'> {
|
|
174
|
+
setSelected?: (items: any[]) => void;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface DynamicTableProps {
|
|
178
|
+
side_of_icon?: 'gauche' | 'droite';
|
|
179
|
+
arr?: Array<{
|
|
180
|
+
label: string;
|
|
181
|
+
name: string;
|
|
182
|
+
type: string;
|
|
183
|
+
list?: Array<{
|
|
184
|
+
id: string | number;
|
|
185
|
+
value: string;
|
|
186
|
+
label: string;
|
|
187
|
+
}>;
|
|
188
|
+
}>;
|
|
189
|
+
row?: any[];
|
|
190
|
+
new_row?: any;
|
|
191
|
+
}
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import React, { useRef, useState, useEffect, ChangeEvent, KeyboardEvent } from 'react';
|
|
2
|
+
import { Link } from 'react-router-dom';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
// Types
|
|
9
|
+
export interface InputFieldProps {
|
|
10
|
+
type?: string;
|
|
11
|
+
inline?: boolean;
|
|
12
|
+
label?: string;
|
|
13
|
+
title?: string;
|
|
14
|
+
formError?: string;
|
|
15
|
+
name: string;
|
|
16
|
+
value: string | number;
|
|
17
|
+
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
|
|
18
|
+
validationRules?: Record<string, any>;
|
|
19
|
+
onBlur?: (value: string | number) => void;
|
|
20
|
+
readOnly?: boolean;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
required?: boolean;
|
|
23
|
+
placeholder?: string;
|
|
24
|
+
min?: number;
|
|
25
|
+
max?: number;
|
|
26
|
+
minLength?: number;
|
|
27
|
+
maxLength?: number;
|
|
28
|
+
className?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
type InputProps = {
|
|
34
|
+
label?: string;
|
|
35
|
+
name: string;
|
|
36
|
+
type?: string;
|
|
37
|
+
value: string | number;
|
|
38
|
+
placeholder?: string;
|
|
39
|
+
required?: boolean;
|
|
40
|
+
disabled?: boolean;
|
|
41
|
+
error?: string;
|
|
42
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
43
|
+
onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const InputField: React.FC<InputProps> = ({
|
|
47
|
+
label,
|
|
48
|
+
name,
|
|
49
|
+
type = "text",
|
|
50
|
+
value,
|
|
51
|
+
placeholder,
|
|
52
|
+
required = false,
|
|
53
|
+
disabled = false,
|
|
54
|
+
error,
|
|
55
|
+
onChange,
|
|
56
|
+
onBlur,
|
|
57
|
+
}) => {
|
|
58
|
+
return (
|
|
59
|
+
<div className="flex flex-col gap-1 w-full">
|
|
60
|
+
{label && (
|
|
61
|
+
<label htmlFor={name} className="block text-gray-700 text-sm font-medium mb-2">
|
|
62
|
+
|
|
63
|
+
{label} {required && <span className="text-red-500">*</span>}
|
|
64
|
+
</label>
|
|
65
|
+
)}
|
|
66
|
+
|
|
67
|
+
<input
|
|
68
|
+
id={name}
|
|
69
|
+
name={name}
|
|
70
|
+
type={type}
|
|
71
|
+
value={value}
|
|
72
|
+
placeholder={placeholder}
|
|
73
|
+
required={required}
|
|
74
|
+
disabled={disabled}
|
|
75
|
+
onChange={onChange}
|
|
76
|
+
onBlur={onBlur}
|
|
77
|
+
className={`w-full px-3 py-2 border border-[#D9D9D9] focus:ring-2 focus:ring-[#6A8A82]/20
|
|
78
|
+
${error ? "border-red-500" : "border-gray-300"}
|
|
79
|
+
${disabled ? "bg-gray-100 cursor-not-allowed" : ""}
|
|
80
|
+
`}
|
|
81
|
+
/>
|
|
82
|
+
|
|
83
|
+
{error && <p className="text-xs text-red-500">{error}</p>}
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
// On reprend InputProps, mais on force `type="text"`
|
|
91
|
+
type TextInputProps = Omit<InputProps, "type">;
|
|
92
|
+
|
|
93
|
+
export const TextInput: React.FC<TextInputProps> = (props) => {
|
|
94
|
+
return <InputField {...props} type="text" />;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const NumberInput:React.FC<TextInputProps> = (props) => {
|
|
98
|
+
return <InputField type="number" {...props} />;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const DateInput: React.FC<TextInputProps> = (props) => {
|
|
102
|
+
return <InputField type="date" {...props} />;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
type Option = {
|
|
109
|
+
label: string;
|
|
110
|
+
value: string | number;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
type SelectInputProps = {
|
|
114
|
+
label?: string;
|
|
115
|
+
name: string;
|
|
116
|
+
value: string | number;
|
|
117
|
+
options: Option[];
|
|
118
|
+
defaultValue?: string | number;
|
|
119
|
+
required?: boolean;
|
|
120
|
+
disabled?: boolean;
|
|
121
|
+
error?: string;
|
|
122
|
+
className?: string;
|
|
123
|
+
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
|
|
124
|
+
onBlur?: (e: React.FocusEvent<HTMLSelectElement>) => void;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export const SelectInput: React.FC<SelectInputProps> = ({
|
|
128
|
+
label,
|
|
129
|
+
name,
|
|
130
|
+
value,
|
|
131
|
+
options,
|
|
132
|
+
defaultValue = "",
|
|
133
|
+
required = false,
|
|
134
|
+
disabled = false,
|
|
135
|
+
error,
|
|
136
|
+
className = "",
|
|
137
|
+
onChange,
|
|
138
|
+
onBlur,
|
|
139
|
+
}) => {
|
|
140
|
+
return (
|
|
141
|
+
<div className={`${className} flex flex-col gap-1 w-full`}>
|
|
142
|
+
{label && (
|
|
143
|
+
<label
|
|
144
|
+
htmlFor={name}
|
|
145
|
+
className="block text-gray-700 text-sm font-medium mb-2"
|
|
146
|
+
>
|
|
147
|
+
{label} {required && <span className="text-red-500">*</span>}
|
|
148
|
+
</label>
|
|
149
|
+
)}
|
|
150
|
+
|
|
151
|
+
<select
|
|
152
|
+
id={name}
|
|
153
|
+
name={name}
|
|
154
|
+
value={value ?? ""}
|
|
155
|
+
required={required}
|
|
156
|
+
disabled={disabled}
|
|
157
|
+
onChange={onChange}
|
|
158
|
+
onBlur={onBlur}
|
|
159
|
+
className={`w-full px-4 py-2 border rounded-lg text-sm
|
|
160
|
+
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent
|
|
161
|
+
${error ? "border-red-500" : "border-gray-300"}
|
|
162
|
+
${disabled ? "bg-gray-100 cursor-not-allowed" : ""}
|
|
163
|
+
`}
|
|
164
|
+
>
|
|
165
|
+
{defaultValue !== undefined && (
|
|
166
|
+
<option value="">{typeof defaultValue === "string" ? defaultValue : "Sélectionnez une option"}</option>
|
|
167
|
+
)}
|
|
168
|
+
{options.map(({ label, value: optionValue }) => (
|
|
169
|
+
<option key={optionValue} value={optionValue}>
|
|
170
|
+
{label}
|
|
171
|
+
</option>
|
|
172
|
+
))}
|
|
173
|
+
</select>
|
|
174
|
+
|
|
175
|
+
{error && <p className="text-xs text-red-500">{error}</p>}
|
|
176
|
+
</div>
|
|
177
|
+
);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
type FileInputProps = {
|
|
184
|
+
label?: string;
|
|
185
|
+
name: string;
|
|
186
|
+
file?: string | File; // peut être un chemin string ou un objet File
|
|
187
|
+
required?: boolean;
|
|
188
|
+
disabled?: boolean;
|
|
189
|
+
readOnly?: boolean;
|
|
190
|
+
error?: string;
|
|
191
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// ⚡ Adresse de base pour accéder aux fichiers
|
|
195
|
+
const addressIpformMedia = "http://localhost:8000/media/";
|
|
196
|
+
|
|
197
|
+
export const FileInput: React.FC<FileInputProps> = ({
|
|
198
|
+
label,
|
|
199
|
+
name,
|
|
200
|
+
file,
|
|
201
|
+
required = false,
|
|
202
|
+
disabled = false,
|
|
203
|
+
readOnly = false,
|
|
204
|
+
error,
|
|
205
|
+
onChange,
|
|
206
|
+
}) => {
|
|
207
|
+
return (
|
|
208
|
+
<div className="flex flex-col gap-1 w-full">
|
|
209
|
+
{label && (
|
|
210
|
+
<label
|
|
211
|
+
htmlFor={name}
|
|
212
|
+
className="block text-gray-700 text-sm font-medium mb-2"
|
|
213
|
+
>
|
|
214
|
+
{label} {required && <span className="text-red-500">*</span>}
|
|
215
|
+
{file && typeof file === "string" && (
|
|
216
|
+
<div>
|
|
217
|
+
<Link
|
|
218
|
+
to={addressIpformMedia + file}
|
|
219
|
+
target="_blank"
|
|
220
|
+
className="ml-2 text-blue-600 underline text-sm"
|
|
221
|
+
>
|
|
222
|
+
{file.split("/").pop()}
|
|
223
|
+
</Link>
|
|
224
|
+
</div>
|
|
225
|
+
)}
|
|
226
|
+
</label>
|
|
227
|
+
)}
|
|
228
|
+
|
|
229
|
+
<input
|
|
230
|
+
id={name}
|
|
231
|
+
type="file"
|
|
232
|
+
name={name}
|
|
233
|
+
onChange={onChange}
|
|
234
|
+
readOnly={readOnly}
|
|
235
|
+
disabled={disabled}
|
|
236
|
+
required={required}
|
|
237
|
+
className={`w-full px-4 py-2 border rounded-lg text-sm
|
|
238
|
+
focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent
|
|
239
|
+
${error ? "border-red-500" : "border-gray-300"}
|
|
240
|
+
${disabled ? "bg-gray-100 cursor-not-allowed" : ""}
|
|
241
|
+
`}
|
|
242
|
+
/>
|
|
243
|
+
|
|
244
|
+
{error && <p className="text-xs text-red-500">{error}</p>}
|
|
245
|
+
</div>
|
|
246
|
+
);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
export const ImageInput = (props) => {
|
|
359
|
+
const default_blur = () => { }
|
|
360
|
+
|
|
361
|
+
const fileInputRef = useRef(null);
|
|
362
|
+
const [selectedFilesV1, setSelectedFilesV1] = props.file;
|
|
363
|
+
const [file_url, setFileUrl] = useState(props.file_url ?? null);
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
const handleFileChange = (e, setfoo) => {
|
|
367
|
+
const newFiles = e.target.files;
|
|
368
|
+
setSelectedFilesV1(newFiles[0])
|
|
369
|
+
};
|
|
370
|
+
return (
|
|
371
|
+
<div className={`visu-div radius-05 col-md flex-col flex-center m-1 ${selectedFilesV1 !== null ? "bg-gray-bold" : "bg-gray-light-very"}`} style={{ height: props.height ?? 'inherit', maxHeight: props.height ?? 'inherit' }}>
|
|
372
|
+
{selectedFilesV1 !== null ? (
|
|
373
|
+
<label className='col flex-col flex-center w-100' role='button' >
|
|
374
|
+
<div className="over-hidden visu-filled">
|
|
375
|
+
<img src={URL.createObjectURL(selectedFilesV1)} alt='photo' className='mb-1' onClick={() => fileInputRef.current.click()} />
|
|
376
|
+
<FaMinusCircle role='button' onClick={() => setSelectedFilesV1(null)} className='icon rem-1' />
|
|
377
|
+
</div>
|
|
378
|
+
</label>
|
|
379
|
+
) : ((file_url !== null) ? (
|
|
380
|
+
<label className='col flex-col flex-center w-100' role='button' >
|
|
381
|
+
<div className="over-hidden visu-filled flex-center">
|
|
382
|
+
<img src={file_url} alt='photo' className='mb-1' onClick={() => fileInputRef.current.click()} />
|
|
383
|
+
<FaMinusCircle role='button' onClick={() => setFileUrl(null)} className='icon rem-1' />
|
|
384
|
+
</div>
|
|
385
|
+
</label>
|
|
386
|
+
) : (
|
|
387
|
+
<>
|
|
388
|
+
<label className='col flex-col w-100' role='button' onClick={() => fileInputRef.current.click()}>
|
|
389
|
+
<div className="col text-15rem flex-col flex-center">
|
|
390
|
+
<MdOutlineAddAPhoto className='mb-3 icon rem-25' />
|
|
391
|
+
Drop image here
|
|
392
|
+
</div>
|
|
393
|
+
</label>
|
|
394
|
+
|
|
395
|
+
</>
|
|
396
|
+
))}
|
|
397
|
+
<input
|
|
398
|
+
type="file"
|
|
399
|
+
name=""
|
|
400
|
+
className='d-none'
|
|
401
|
+
ref={fileInputRef}
|
|
402
|
+
onChange={(e) => handleFileChange(e, setSelectedFilesV1)}
|
|
403
|
+
/>
|
|
404
|
+
</div>
|
|
405
|
+
)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface ModalProps {
|
|
4
|
+
title: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
width?: string;
|
|
7
|
+
open: boolean;
|
|
8
|
+
onClose: () => void;
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const Modal: React.FC<ModalProps> = ({ title, description, width, open, onClose, children }) => {
|
|
13
|
+
if (!open) return null;
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|
17
|
+
<div className={`bg-white rounded-lg py-4 px-6 mx-4 w-[${width ? width : '60%'}]`}>
|
|
18
|
+
<div className="flex justify-between items-start mb-6">
|
|
19
|
+
<div>
|
|
20
|
+
<h3 className="text-xl font-semibold text-tuatara flex items-center space-x-2">
|
|
21
|
+
<span>{title}</span>
|
|
22
|
+
</h3>
|
|
23
|
+
{description && (
|
|
24
|
+
<p className="text-sm text-gray-600 mt-1">{description}</p>
|
|
25
|
+
)}
|
|
26
|
+
</div>
|
|
27
|
+
<button
|
|
28
|
+
onClick={onClose}
|
|
29
|
+
className="text-gray-400 hover:text-gray-600 text-xl"
|
|
30
|
+
aria-label="Close modal"
|
|
31
|
+
>
|
|
32
|
+
✕
|
|
33
|
+
</button>
|
|
34
|
+
</div>
|
|
35
|
+
{children}
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export default Modal;
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useToast } from '../../hooks/useToast';
|
|
3
|
+
|
|
4
|
+
const ToastDemo: React.FC = () => {
|
|
5
|
+
const { success, error, warning, info } = useToast();
|
|
6
|
+
|
|
7
|
+
const showSuccess = () => {
|
|
8
|
+
success('Opération réussie ! Tout s\'est bien passé.');
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const showError = () => {
|
|
12
|
+
error('Une erreur est survenue ! Veuillez réessayer.');
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const showWarning = () => {
|
|
16
|
+
warning('Attention ! Cette action nécessite votre confirmation.');
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const showInfo = () => {
|
|
20
|
+
info('Information : Nouvelle mise à jour disponible.');
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const showCustomToast = () => {
|
|
24
|
+
success('Toast personnalisé avec durée de 10 secondes', 10000);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className="p-8 max-w-md mx-auto bg-white rounded-lg shadow-lg">
|
|
29
|
+
<h2 className="text-2xl font-bold text-gray-800 mb-6 text-center">
|
|
30
|
+
Démonstration Toast
|
|
31
|
+
</h2>
|
|
32
|
+
|
|
33
|
+
<div className="space-y-4">
|
|
34
|
+
<button
|
|
35
|
+
onClick={showSuccess}
|
|
36
|
+
className="w-full bg-green-500 text-white py-2 px-4 rounded-lg hover:bg-green-600 transition-colors"
|
|
37
|
+
>
|
|
38
|
+
Toast Succès
|
|
39
|
+
</button>
|
|
40
|
+
|
|
41
|
+
<button
|
|
42
|
+
onClick={showError}
|
|
43
|
+
className="w-full bg-red-500 text-white py-2 px-4 rounded-lg hover:bg-red-600 transition-colors"
|
|
44
|
+
>
|
|
45
|
+
Toast Erreur
|
|
46
|
+
</button>
|
|
47
|
+
|
|
48
|
+
<button
|
|
49
|
+
onClick={showWarning}
|
|
50
|
+
className="w-full bg-yellow-500 text-white py-2 px-4 rounded-lg hover:bg-yellow-600 transition-colors"
|
|
51
|
+
>
|
|
52
|
+
Toast Avertissement
|
|
53
|
+
</button>
|
|
54
|
+
|
|
55
|
+
<button
|
|
56
|
+
onClick={showInfo}
|
|
57
|
+
className="w-full bg-blue-500 text-white py-2 px-4 rounded-lg hover:bg-blue-600 transition-colors"
|
|
58
|
+
>
|
|
59
|
+
Toast Information
|
|
60
|
+
</button>
|
|
61
|
+
|
|
62
|
+
<button
|
|
63
|
+
onClick={showCustomToast}
|
|
64
|
+
className="w-full bg-purple-500 text-white py-2 px-4 rounded-lg hover:bg-purple-600 transition-colors"
|
|
65
|
+
>
|
|
66
|
+
Toast Personnalisé (10s)
|
|
67
|
+
</button>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export default ToastDemo;
|