mobigrid-module 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vite").UserConfig;
2
+ export default _default;
package/index.tsx ADDED
@@ -0,0 +1,254 @@
1
+ import React from "react";
2
+ import { useEffect, useState } from "react";
3
+ import { format } from "date-fns";
4
+ import axios from "axios";
5
+ import { Alert, AlertTitle, AlertDescription } from "./components/ui/alert";
6
+ import { AlertCircle } from "lucide-react";
7
+ import { PageHeader } from "./components/Layout/PageHeader";
8
+ import { CustomTable } from "./components/CustomTable/CustomTable";
9
+ import Pagination from "./components/CustomTable/Pagination";
10
+
11
+ const ITEMS_PER_PAGE = 14;
12
+
13
+ interface ContainerProps {
14
+ configUrl: string;
15
+ preJsUrl: string;
16
+ itemsPerPage?: number;
17
+ children?: React.ReactNode;
18
+ }
19
+
20
+ const MobigridModule = ({
21
+ configUrl,
22
+ preJsUrl,
23
+ itemsPerPage = ITEMS_PER_PAGE,
24
+ children,
25
+ }: ContainerProps) {
26
+ const [config, setConfig] = useState<any>(null);
27
+ const [invalidConfig, setInvalidConfig] = useState(false);
28
+ const [errors, setErrors] = useState<
29
+ Array<{
30
+ title: string;
31
+ message: string;
32
+ }>
33
+ >([]);
34
+ const [isLoading, setIsLoading] = useState(false);
35
+ const [data, setData] = useState([]);
36
+ const [count, setCount] = useState(0);
37
+ const [currentPage, setCurrentPage] = useState(1);
38
+ const [totalPages, setTotalPages] = useState(1);
39
+ const [filters, setFilters] = useState({
40
+ fromDate: new Date(
41
+ new Date().getFullYear(),
42
+ new Date().getMonth(),
43
+ 1
44
+ ).toLocaleString(),
45
+ toDate: new Date(
46
+ new Date().getFullYear(),
47
+ new Date().getMonth() + 1,
48
+ 0,
49
+ 23,
50
+ 59,
51
+ 59
52
+ ).toLocaleString(),
53
+ });
54
+ const [filterOptions, setFilterOptions] = useState<any[]>([]);
55
+
56
+ useEffect(() => {
57
+ const loadConfig = async () => {
58
+ try {
59
+ const response = await fetch(configUrl);
60
+ const data = await response.json();
61
+ if (data && data.title && data.data_url && data.colomns) {
62
+ setConfig(data);
63
+ setFilterOptions(data.Filters?.flat() || []);
64
+ } else {
65
+ setInvalidConfig(true);
66
+ }
67
+ } catch (error) {
68
+ setInvalidConfig(true);
69
+ }
70
+ };
71
+
72
+ const loadFunctions = async () => {
73
+ const response = await fetch(preJsUrl);
74
+ const script = document.createElement("script");
75
+ script.textContent = await response.text();
76
+ document.head.appendChild(script);
77
+ };
78
+
79
+ if (configUrl && preJsUrl) {
80
+ loadConfig();
81
+ loadFunctions();
82
+ }
83
+ }, [configUrl, preJsUrl]);
84
+
85
+ useEffect(() => {
86
+ const controller = new AbortController();
87
+
88
+ const initializeFilters = async () => {
89
+ const filters = config?.Filters?.flat() || [];
90
+ for (const filter of filters) {
91
+ if (filter.type === "Select" && filter.urlSource) {
92
+ await fetchFilterOptions(filter, controller.signal);
93
+ }
94
+ }
95
+ };
96
+
97
+ if (config) initializeFilters();
98
+
99
+ return () => controller.abort();
100
+ }, [config]);
101
+
102
+ const getData = async () => {
103
+ setErrors([]);
104
+ const postBody = {
105
+ page: currentPage,
106
+ filters: {
107
+ ...filters,
108
+ fromDate: format(filters.fromDate, "MM-dd-yyyy HH:mm"),
109
+ toDate: format(filters.toDate, "MM-dd-yyyy HH:mm"),
110
+ },
111
+ };
112
+
113
+ setIsLoading(true);
114
+ try {
115
+ const response = await axios.post(config.data_url, postBody);
116
+ setIsLoading(false);
117
+
118
+ if (response?.data) {
119
+ setData(response.data?.DATA);
120
+ setCount(response.data?.PAGESNUM);
121
+ setTotalPages(Math.ceil(response.data?.PAGESNUM / itemsPerPage) || 1);
122
+ } else {
123
+ setInvalidConfig(true);
124
+ }
125
+ } catch (error) {
126
+ setIsLoading(false);
127
+ setErrors((prev) => [
128
+ ...prev,
129
+ {
130
+ title: "Erreur",
131
+ message: "Une erreur est survenue",
132
+ },
133
+ ]);
134
+ }
135
+ };
136
+
137
+ const fetchFilterOptions = async (filter: any, signal?: AbortSignal) => {
138
+ if (filter.type === "Select" && filter.urlSource) {
139
+ try {
140
+ const response = await axios.get(filter.urlSource, { signal });
141
+ if (response?.status === 200 && response?.data) {
142
+ const options = Array.isArray(response.data) ? response.data : [];
143
+ setFilterOptions((prev) =>
144
+ prev.map((f) => (f.name === filter.name ? { ...f, options } : f))
145
+ );
146
+ }
147
+ } catch (error) {
148
+ if (!axios.isCancel(error)) {
149
+ setErrors((prev) => [
150
+ ...prev,
151
+ {
152
+ title: "Erreur",
153
+ message: `Une erreur est survenue lors de la récupération des options pour ${filter.name}`,
154
+ },
155
+ ]);
156
+ console.error(`Error fetching options for ${filter.name}:`, error);
157
+ }
158
+ }
159
+ }
160
+ };
161
+
162
+ if (invalidConfig) {
163
+ return (
164
+ <div
165
+ style={{
166
+ display: "flex",
167
+ justifyContent: "center",
168
+ alignItems: "center",
169
+ height: "100vh",
170
+ }}
171
+ >
172
+ <div style={{ position: "absolute" }}>
173
+ <svg
174
+ className="h-5 w-5 mx-auto mb-2"
175
+ xmlns="http://www.w3.org/2000/svg"
176
+ viewBox="0 0 24 24"
177
+ fill="red"
178
+ >
179
+ <path d="M18.3 5.71a.996.996 0 00-1.41 0L12 10.59 7.11 5.7A.996.996 0 105.7 7.11L10.59 12 5.7 16.89a.996.996 0 101.41 1.41L12 13.41l4.89 4.89a.996.996 0 101.41-1.41L13.41 12l4.89-4.89c.38-.38.38-1.02 0-1.4z" />
180
+ </svg>
181
+ <div>Invalid configuration</div>
182
+ </div>
183
+ </div>
184
+ );
185
+ }
186
+
187
+ if (!config) {
188
+ return (
189
+ <div
190
+ style={{
191
+ display: "flex",
192
+ justifyContent: "center",
193
+ alignItems: "center",
194
+ height: "100vh",
195
+ }}
196
+ >
197
+ <div style={{ position: "absolute" }}>
198
+ <svg
199
+ className="animate-spin h-5 w-5 mx-auto mb-2"
200
+ xmlns="http://www.w3.org/2000/svg"
201
+ fill="none"
202
+ viewBox="0 0 24 24"
203
+ >
204
+ <circle
205
+ className="opacity-25"
206
+ cx="12"
207
+ cy="12"
208
+ r="10"
209
+ stroke="currentColor"
210
+ strokeWidth="4"
211
+ ></circle>
212
+ <path
213
+ className="opacity-75"
214
+ fill="currentColor"
215
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
216
+ ></path>
217
+ </svg>
218
+ <div>Loading configuration...</div>
219
+ </div>
220
+ </div>
221
+ );
222
+ }
223
+
224
+ return (
225
+ <div className="flex flex-col gap-4 p-4 max-w-[1300px] mx-auto mt-8 mb-">
226
+ <PageHeader
227
+ title={config.title}
228
+ configFilters={filterOptions}
229
+ filters={filters}
230
+ setFilters={setFilters}
231
+ onSearch={() => getData()}
232
+ count={count}
233
+ isLoading={isLoading}
234
+ setCurrentPage={setCurrentPage}
235
+ />
236
+ {errors.map((error, index) => (
237
+ <Alert key={index} variant="destructive">
238
+ <AlertCircle className="h-4 w-4" />
239
+ <AlertTitle>{error.title}</AlertTitle>
240
+ <AlertDescription>{error.message}</AlertDescription>
241
+ </Alert>
242
+ ))}
243
+ <CustomTable data={data} columns={config.colomns} isLoading={isLoading} />
244
+ <Pagination
245
+ currentPage={currentPage}
246
+ totalPages={totalPages}
247
+ onPageChange={setCurrentPage}
248
+ />
249
+ {children}
250
+ </div>
251
+ );
252
+ }
253
+
254
+ export {MobigridModule as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobigrid-module",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "",
5
5
  "main": "index.tsx",
6
6
  "type": "module",
@@ -64,9 +64,8 @@
64
64
  "typescript-eslint": "^8.11.0",
65
65
  "vite": "^5.4.10"
66
66
  },
67
- "types": "dist/types/index.d.ts",
67
+ "types": "dist/index.d.ts",
68
68
  "files": [
69
- "dist",
70
- "dist/types"
69
+ "dist"
71
70
  ]
72
71
  }