mobigrid-module 1.0.7 → 1.0.8

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.
@@ -0,0 +1,157 @@
1
+ import * as React from "react"
2
+ import * as SelectPrimitive from "@radix-ui/react-select"
3
+ import { Check, ChevronDown, ChevronUp } from "lucide-react"
4
+
5
+ import { cn } from "../../lib/utils"
6
+
7
+ const Select = SelectPrimitive.Root
8
+
9
+ const SelectGroup = SelectPrimitive.Group
10
+
11
+ const SelectValue = SelectPrimitive.Value
12
+
13
+ const SelectTrigger = React.forwardRef<
14
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
15
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
16
+ >(({ className, children, ...props }, ref) => (
17
+ <SelectPrimitive.Trigger
18
+ ref={ref}
19
+ className={cn(
20
+ "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
21
+ className
22
+ )}
23
+ {...props}
24
+ >
25
+ {children}
26
+ <SelectPrimitive.Icon asChild>
27
+ <ChevronDown className="h-4 w-4 opacity-50" />
28
+ </SelectPrimitive.Icon>
29
+ </SelectPrimitive.Trigger>
30
+ ))
31
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
32
+
33
+ const SelectScrollUpButton = React.forwardRef<
34
+ React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
35
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
36
+ >(({ className, ...props }, ref) => (
37
+ <SelectPrimitive.ScrollUpButton
38
+ ref={ref}
39
+ className={cn(
40
+ "flex cursor-default items-center justify-center py-1",
41
+ className
42
+ )}
43
+ {...props}
44
+ >
45
+ <ChevronUp className="h-4 w-4" />
46
+ </SelectPrimitive.ScrollUpButton>
47
+ ))
48
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
49
+
50
+ const SelectScrollDownButton = React.forwardRef<
51
+ React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
52
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
53
+ >(({ className, ...props }, ref) => (
54
+ <SelectPrimitive.ScrollDownButton
55
+ ref={ref}
56
+ className={cn(
57
+ "flex cursor-default items-center justify-center py-1",
58
+ className
59
+ )}
60
+ {...props}
61
+ >
62
+ <ChevronDown className="h-4 w-4" />
63
+ </SelectPrimitive.ScrollDownButton>
64
+ ))
65
+ SelectScrollDownButton.displayName =
66
+ SelectPrimitive.ScrollDownButton.displayName
67
+
68
+ const SelectContent = React.forwardRef<
69
+ React.ElementRef<typeof SelectPrimitive.Content>,
70
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
71
+ >(({ className, children, position = "popper", ...props }, ref) => (
72
+ <SelectPrimitive.Portal>
73
+ <SelectPrimitive.Content
74
+ ref={ref}
75
+ className={cn(
76
+ "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
77
+ position === "popper" &&
78
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
79
+ className
80
+ )}
81
+ position={position}
82
+ {...props}
83
+ >
84
+ <SelectScrollUpButton />
85
+ <SelectPrimitive.Viewport
86
+ className={cn(
87
+ "p-1",
88
+ position === "popper" &&
89
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
90
+ )}
91
+ >
92
+ {children}
93
+ </SelectPrimitive.Viewport>
94
+ <SelectScrollDownButton />
95
+ </SelectPrimitive.Content>
96
+ </SelectPrimitive.Portal>
97
+ ))
98
+ SelectContent.displayName = SelectPrimitive.Content.displayName
99
+
100
+ const SelectLabel = React.forwardRef<
101
+ React.ElementRef<typeof SelectPrimitive.Label>,
102
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
103
+ >(({ className, ...props }, ref) => (
104
+ <SelectPrimitive.Label
105
+ ref={ref}
106
+ className={cn("px-2 py-1.5 text-sm font-semibold", className)}
107
+ {...props}
108
+ />
109
+ ))
110
+ SelectLabel.displayName = SelectPrimitive.Label.displayName
111
+
112
+ const SelectItem = React.forwardRef<
113
+ React.ElementRef<typeof SelectPrimitive.Item>,
114
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
115
+ >(({ className, children, ...props }, ref) => (
116
+ <SelectPrimitive.Item
117
+ ref={ref}
118
+ className={cn(
119
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
120
+ className
121
+ )}
122
+ {...props}
123
+ >
124
+ <span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
125
+ <SelectPrimitive.ItemIndicator>
126
+ <Check className="h-4 w-4" />
127
+ </SelectPrimitive.ItemIndicator>
128
+ </span>
129
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
130
+ </SelectPrimitive.Item>
131
+ ))
132
+ SelectItem.displayName = SelectPrimitive.Item.displayName
133
+
134
+ const SelectSeparator = React.forwardRef<
135
+ React.ElementRef<typeof SelectPrimitive.Separator>,
136
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
137
+ >(({ className, ...props }, ref) => (
138
+ <SelectPrimitive.Separator
139
+ ref={ref}
140
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
141
+ {...props}
142
+ />
143
+ ))
144
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName
145
+
146
+ export {
147
+ Select,
148
+ SelectGroup,
149
+ SelectValue,
150
+ SelectTrigger,
151
+ SelectContent,
152
+ SelectLabel,
153
+ SelectItem,
154
+ SelectSeparator,
155
+ SelectScrollUpButton,
156
+ SelectScrollDownButton,
157
+ }
@@ -0,0 +1,30 @@
1
+ import React from "react"
2
+ import { useTheme } from "next-themes"
3
+ import { Toaster as Sonner } from "sonner"
4
+
5
+ type ToasterProps = React.ComponentProps<typeof Sonner>
6
+
7
+ const Toaster = ({ ...props }: ToasterProps) => {
8
+ const { theme = "system" } = useTheme()
9
+
10
+ return (
11
+ <Sonner
12
+ theme={theme as ToasterProps["theme"]}
13
+ className="toaster group"
14
+ toastOptions={{
15
+ classNames: {
16
+ toast:
17
+ "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
18
+ description: "group-[.toast]:text-muted-foreground",
19
+ actionButton:
20
+ "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
21
+ cancelButton:
22
+ "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
23
+ },
24
+ }}
25
+ {...props}
26
+ />
27
+ )
28
+ }
29
+
30
+ export { Toaster }
@@ -0,0 +1,120 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "../../lib/utils"
4
+
5
+ const Table = React.forwardRef<
6
+ HTMLTableElement,
7
+ React.HTMLAttributes<HTMLTableElement>
8
+ >(({ className, ...props }, ref) => (
9
+ <div className="relative w-full overflow-auto">
10
+ <table
11
+ ref={ref}
12
+ className={cn("w-full caption-bottom text-sm", className)}
13
+ {...props}
14
+ />
15
+ </div>
16
+ ))
17
+ Table.displayName = "Table"
18
+
19
+ const TableHeader = React.forwardRef<
20
+ HTMLTableSectionElement,
21
+ React.HTMLAttributes<HTMLTableSectionElement>
22
+ >(({ className, ...props }, ref) => (
23
+ <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
24
+ ))
25
+ TableHeader.displayName = "TableHeader"
26
+
27
+ const TableBody = React.forwardRef<
28
+ HTMLTableSectionElement,
29
+ React.HTMLAttributes<HTMLTableSectionElement>
30
+ >(({ className, ...props }, ref) => (
31
+ <tbody
32
+ ref={ref}
33
+ className={cn("[&_tr:last-child]:border-0", className)}
34
+ {...props}
35
+ />
36
+ ))
37
+ TableBody.displayName = "TableBody"
38
+
39
+ const TableFooter = React.forwardRef<
40
+ HTMLTableSectionElement,
41
+ React.HTMLAttributes<HTMLTableSectionElement>
42
+ >(({ className, ...props }, ref) => (
43
+ <tfoot
44
+ ref={ref}
45
+ className={cn(
46
+ "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
47
+ className
48
+ )}
49
+ {...props}
50
+ />
51
+ ))
52
+ TableFooter.displayName = "TableFooter"
53
+
54
+ const TableRow = React.forwardRef<
55
+ HTMLTableRowElement,
56
+ React.HTMLAttributes<HTMLTableRowElement>
57
+ >(({ className, ...props }, ref) => (
58
+ <tr
59
+ ref={ref}
60
+ className={cn(
61
+ "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
62
+ className
63
+ )}
64
+ {...props}
65
+ />
66
+ ))
67
+ TableRow.displayName = "TableRow"
68
+
69
+ const TableHead = React.forwardRef<
70
+ HTMLTableCellElement,
71
+ React.ThHTMLAttributes<HTMLTableCellElement>
72
+ >(({ className, ...props }, ref) => (
73
+ <th
74
+ ref={ref}
75
+ className={cn(
76
+ "h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
77
+ className
78
+ )}
79
+ {...props}
80
+ />
81
+ ))
82
+ TableHead.displayName = "TableHead"
83
+
84
+ const TableCell = React.forwardRef<
85
+ HTMLTableCellElement,
86
+ React.TdHTMLAttributes<HTMLTableCellElement>
87
+ >(({ className, ...props }, ref) => (
88
+ <td
89
+ ref={ref}
90
+ className={cn(
91
+ "p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
92
+ className
93
+ )}
94
+ {...props}
95
+ />
96
+ ))
97
+ TableCell.displayName = "TableCell"
98
+
99
+ const TableCaption = React.forwardRef<
100
+ HTMLTableCaptionElement,
101
+ React.HTMLAttributes<HTMLTableCaptionElement>
102
+ >(({ className, ...props }, ref) => (
103
+ <caption
104
+ ref={ref}
105
+ className={cn("mt-4 text-sm text-muted-foreground", className)}
106
+ {...props}
107
+ />
108
+ ))
109
+ TableCaption.displayName = "TableCaption"
110
+
111
+ export {
112
+ Table,
113
+ TableHeader,
114
+ TableBody,
115
+ TableFooter,
116
+ TableHead,
117
+ TableRow,
118
+ TableCell,
119
+ TableCaption,
120
+ }
package/index.d.ts ADDED
@@ -0,0 +1,55 @@
1
+ declare module "mobigrid-module" {
2
+ import { ReactNode } from "react";
3
+
4
+ export interface FilterOption {
5
+ name: string;
6
+ type: string;
7
+ urlSource?: string;
8
+ options?: any[];
9
+ label?: string;
10
+ placeholder?: string;
11
+ required?: boolean;
12
+ }
13
+
14
+ export interface Column {
15
+ field: string;
16
+ headerName: string;
17
+ width?: number;
18
+ sortable?: boolean;
19
+ renderCell?: (params: any) => ReactNode;
20
+ }
21
+
22
+ export interface Config {
23
+ title: string;
24
+ data_url: string;
25
+ colomns: Column[];
26
+ Filters?: FilterOption[][];
27
+ }
28
+
29
+ export interface FilterState {
30
+ fromDate: string;
31
+ toDate: string;
32
+ [key: string]: any;
33
+ }
34
+
35
+ export interface ApiResponse {
36
+ DATA: any[];
37
+ PAGESNUM: number;
38
+ }
39
+
40
+ export interface Error {
41
+ title: string;
42
+ message: string;
43
+ }
44
+
45
+ export interface MobigridModuleProps {
46
+ configUrl: string;
47
+ preJsUrl: string;
48
+ itemsPerPage?: number;
49
+ children?: ReactNode;
50
+ }
51
+
52
+ const MobigridModule: React.FC<MobigridModuleProps>;
53
+
54
+ export default MobigridModule;
55
+ }
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/lib/utils.ts ADDED
@@ -0,0 +1,6 @@
1
+ import { type ClassValue, clsx } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
package/package.json CHANGED
@@ -1,8 +1,7 @@
1
1
  {
2
2
  "name": "mobigrid-module",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "",
5
- "main": "dist/index.tsx",
6
5
  "type": "module",
7
6
  "scripts": {
8
7
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -65,6 +64,10 @@
65
64
  },
66
65
  "types": "dist/index.d",
67
66
  "files": [
68
- "dist"
67
+ "dist",
68
+ "index.tsx",
69
+ "index.d.ts",
70
+ "components",
71
+ "lib"
69
72
  ]
70
73
  }