mobigrid-module 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mobigrid-module",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "",
5
5
  "main": "index.tsx",
6
6
  "type": "module",
@@ -63,5 +63,10 @@
63
63
  "typescript": "^5.7.2",
64
64
  "typescript-eslint": "^8.11.0",
65
65
  "vite": "^5.4.10"
66
- }
66
+ },
67
+ "types": "dist/types/index.d.ts",
68
+ "files": [
69
+ "dist",
70
+ "dist/types"
71
+ ]
67
72
  }
package/rollup.config.js DELETED
@@ -1,35 +0,0 @@
1
- import resolve from "@rollup/plugin-node-resolve";
2
- import commonjs from "@rollup/plugin-commonjs";
3
- import peerDepsExternal from "rollup-plugin-peer-deps-external";
4
- import typescript from "@rollup/plugin-typescript";
5
- import { terser } from "rollup-plugin-terser";
6
- import json from '@rollup/plugin-json';
7
-
8
- export default {
9
- input: "src/index.tsx",
10
- output: [
11
- {
12
- file: "dist/index.tsx",
13
- format: "cjs",
14
- sourcemap: true,
15
- },
16
- {
17
- file: "dist/index.esm.js",
18
- format: "esm",
19
- sourcemap: true,
20
- },
21
- ],
22
- plugins: [
23
- peerDepsExternal(),
24
- resolve(),
25
- commonjs(),
26
- typescript({
27
- tsconfig: "./tsconfig.json",
28
- outDir: "./dist",
29
- declarationDir: "./dist"
30
- }),
31
- terser(),
32
- json(),
33
- ],
34
- external: ["react", "react-dom"],
35
- };
@@ -1,182 +0,0 @@
1
- import React from "react";
2
- import {
3
- flexRender,
4
- getCoreRowModel,
5
- useReactTable,
6
- } from "@tanstack/react-table";
7
- import {
8
- Table,
9
- TableBody,
10
- TableCell,
11
- TableHead,
12
- TableHeader,
13
- TableRow,
14
- } from "../../components/ui/table";
15
- import { format } from "date-fns";
16
- import Icon from "../Icon";
17
-
18
- interface TableProps {
19
- data: any[];
20
- columns: any[];
21
- isLoading: boolean;
22
- }
23
-
24
- export function CustomTable({ data, columns, isLoading }: TableProps) {
25
- const table = useReactTable({
26
- data: isLoading ? [] : data,
27
- columns: columns.map((col) => ({
28
- accessorKey: col.key,
29
- header: col.title,
30
- cell: (info) => {
31
- if (col.key === "ACTIONS_BUTTONS") {
32
- return (
33
- <div className="flex gap-2 justify-end">
34
- {col.actions?.map((action: any, index: number) => (
35
- <button
36
- key={index}
37
- className={`inline-flex items-center px-2 py-1 text-sm rounded-full transition-colors duration-200 ${
38
- index === 0
39
- ? "text-blue-700 bg-blue-50 border border-blue-200 hover:bg-blue-100"
40
- : index === 1
41
- ? "text-green-700 bg-green-50 border border-green-200 hover:bg-green-100"
42
- : index === 2
43
- ? "text-purple-700 bg-purple-50 border border-purple-200 hover:bg-purple-100"
44
- : index === 3
45
- ? "text-orange-700 bg-orange-50 border border-orange-200 hover:bg-orange-100"
46
- : "text-gray-700 bg-gray-50 border border-gray-200 hover:bg-gray-100"
47
- }`}
48
- onClick={() => {
49
- console.log(action.action, info.row.original);
50
- }}
51
- >
52
- {action.icon && (
53
- <svg
54
- xmlns="http://www.w3.org/2000/svg"
55
- width="16"
56
- height="16"
57
- viewBox="0 0 24 24"
58
- fill="none"
59
- stroke="currentColor"
60
- strokeWidth="2"
61
- strokeLinecap="round"
62
- strokeLinejoin="round"
63
- className="mr-2"
64
- >
65
- <Icon name={action.icon.replace(/^icon-/, '')} className="mr-2" />
66
- </svg>
67
- )}
68
- {action.label}
69
- </button>
70
- ))}
71
- </div>
72
- );
73
- }
74
- if (col.type === "status") {
75
- const statusColors: { [key: string]: string } = {
76
- PENDING: "bg-yellow-100 text-yellow-800",
77
- PAID: "bg-green-100 text-green-800",
78
- CANCELLED: "bg-red-100 text-red-800",
79
- CANCEL_STARTED: "bg-orange-100 text-orange-800",
80
- ECHEC: "bg-gray-100 text-gray-800",
81
- };
82
- const status = info.getValue() as string;
83
- return (
84
- <div className="flex items-center justify-center">
85
- <span
86
- className={`px-2 py-1 rounded-full text-xs font-medium text-center ${
87
- statusColors[status] || "bg-gray-100 text-gray-800"
88
- }`}
89
- >
90
- {status || "-"}
91
- </span>
92
- </div>
93
- );
94
- }
95
- if (col.type === "money") {
96
- return `${info.getValue()} ${col.currency}`;
97
- }
98
- if (col.type === "date") {
99
- return format(new Date(info.getValue() as string), "dd-MM-yyyy");
100
- }
101
- if (col.scroll) {
102
- return (
103
- <div className="max-h-24 overflow-y-auto">
104
- {info.getValue()}
105
- </div>
106
- );
107
- }
108
- return info.getValue();
109
- },
110
- })),
111
- getCoreRowModel: getCoreRowModel(),
112
- enableMultiRowSelection: true,
113
- enableRowSelection: true,
114
- state: {
115
- rowSelection: {},
116
- },
117
- });
118
-
119
- return (
120
- <Table className="border border-gray-200 rounded-md">
121
- <TableHeader className="bg-gray-50">
122
- {table.getHeaderGroups().map((headerGroup) => (
123
- <TableRow key={headerGroup.id}>
124
- {headerGroup.headers.map((header) => (
125
- <TableHead key={header.id} className="font-medium text-gray-800 py-2">
126
- {flexRender(
127
- header.column.columnDef.header,
128
- header.getContext()
129
- )}
130
- </TableHead>
131
- ))}
132
- </TableRow>
133
- ))}
134
- </TableHeader>
135
- <TableBody>
136
- {isLoading ? (
137
- <TableRow>
138
- <TableCell colSpan={columns.length} className="text-center py-4">
139
- <svg
140
- className="animate-spin h-5 w-5 mx-auto mb-2"
141
- xmlns="http://www.w3.org/2000/svg"
142
- fill="none"
143
- viewBox="0 0 24 24"
144
- >
145
- <circle
146
- className="opacity-25"
147
- cx="12"
148
- cy="12"
149
- r="10"
150
- stroke="currentColor"
151
- strokeWidth="4"
152
- ></circle>
153
- <path
154
- className="opacity-75"
155
- fill="currentColor"
156
- 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"
157
- ></path>
158
- </svg>
159
- Chargement des données...
160
- </TableCell>
161
- </TableRow>
162
- ) : !data || data.length === 0 ? (
163
- <TableRow>
164
- <TableCell colSpan={columns.length} className="text-center py-4">
165
- Aucune donnée disponible
166
- </TableCell>
167
- </TableRow>
168
- ) : (
169
- table.getRowModel().rows.map((row) => (
170
- <TableRow key={row.id} className="bg-white hover:bg-gray-100">
171
- {row.getVisibleCells().map((cell) => (
172
- <TableCell key={cell.id}>
173
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
174
- </TableCell>
175
- ))}
176
- </TableRow>
177
- ))
178
- )}
179
- </TableBody>
180
- </Table>
181
- );
182
- }
@@ -1,136 +0,0 @@
1
- import React from "react";
2
- import {
3
- Pagination,
4
- PaginationContent,
5
- PaginationEllipsis,
6
- PaginationItem,
7
- PaginationLink,
8
- PaginationNext,
9
- PaginationPrevious,
10
- } from "../../components/ui/pagination";
11
-
12
- interface PaginationProps {
13
- currentPage: number;
14
- totalPages: number;
15
- onPageChange: (page: number) => void;
16
- }
17
-
18
- export default function ({
19
- currentPage,
20
- totalPages,
21
- onPageChange,
22
- }: PaginationProps) {
23
- const renderPaginationItems = () => {
24
- const items = [];
25
-
26
- // Previous button
27
- items.push(
28
- <PaginationItem key="prev">
29
- <PaginationPrevious
30
- href="#"
31
- onClick={(e) => {
32
- e.preventDefault();
33
- if (currentPage > 1) onPageChange(currentPage - 1);
34
- }}
35
- />
36
- </PaginationItem>
37
- );
38
-
39
- // First page
40
- items.push(
41
- <PaginationItem key={1}>
42
- <PaginationLink
43
- href="#"
44
- isActive={currentPage === 1}
45
- onClick={(e) => {
46
- e.preventDefault();
47
- onPageChange(1);
48
- }}
49
- >
50
- 1
51
- </PaginationLink>
52
- </PaginationItem>
53
- );
54
-
55
- // Calculate visible page range
56
- let startPage = Math.max(2, currentPage - 1);
57
- let endPage = Math.min(totalPages - 1, currentPage + 1);
58
-
59
- // Add ellipsis after first page if needed
60
- if (startPage > 2) {
61
- items.push(
62
- <PaginationItem key="ellipsis1">
63
- <PaginationEllipsis />
64
- </PaginationItem>
65
- );
66
- }
67
-
68
- // Add middle pages
69
- for (let i = startPage; i <= endPage; i++) {
70
- items.push(
71
- <PaginationItem key={i}>
72
- <PaginationLink
73
- href="#"
74
- isActive={currentPage === i}
75
- onClick={(e) => {
76
- e.preventDefault();
77
- onPageChange(i);
78
- }}
79
- >
80
- {i}
81
- </PaginationLink>
82
- </PaginationItem>
83
- );
84
- }
85
-
86
- // Add ellipsis before last page if needed
87
- if (endPage < totalPages - 1) {
88
- items.push(
89
- <PaginationItem key="ellipsis2">
90
- <PaginationEllipsis />
91
- </PaginationItem>
92
- );
93
- }
94
-
95
- // Last page (if not already included)
96
- if (totalPages > 1) {
97
- items.push(
98
- <PaginationItem key={totalPages}>
99
- <PaginationLink
100
- href="#"
101
- isActive={currentPage === totalPages}
102
- onClick={(e) => {
103
- e.preventDefault();
104
- onPageChange(totalPages);
105
- }}
106
- >
107
- {totalPages}
108
- </PaginationLink>
109
- </PaginationItem>
110
- );
111
- }
112
-
113
- // Next button
114
- items.push(
115
- <PaginationItem key="next">
116
- <PaginationNext
117
- href="#"
118
- onClick={(e) => {
119
- e.preventDefault();
120
- if (currentPage < totalPages) onPageChange(currentPage + 1);
121
- }}
122
- />
123
- </PaginationItem>
124
- );
125
-
126
- return items;
127
- };
128
-
129
- return (
130
- <Pagination>
131
- <PaginationContent>
132
- {renderPaginationItems()}
133
- </PaginationContent>
134
- </Pagination>
135
- );
136
- }
@@ -1,27 +0,0 @@
1
- import React from "react";
2
- import * as FeatherIcons from 'react-feather';
3
- import { IconProps as FeatherIconProps } from 'react-feather';
4
-
5
- interface IconProps extends Omit<FeatherIconProps, 'ref'> {
6
- name: string;
7
- }
8
-
9
- const Icon = ({ name, ...props }: IconProps): JSX.Element | null => {
10
- const formatIconName = (iconName: string): string => {
11
- return iconName
12
- .split(/[-_]/)
13
- .map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
14
- .join('');
15
- };
16
-
17
- const IconComponent = FeatherIcons[formatIconName(name) as keyof typeof FeatherIcons];
18
-
19
- if (!IconComponent) {
20
- console.warn(`Icon "${name}" not found in react-feather library`);
21
- return null;
22
- }
23
-
24
- return <IconComponent {...props} />;
25
- };
26
-
27
- export default Icon;
@@ -1,270 +0,0 @@
1
- import * as React from "react";
2
- import { DatePickerWithRange } from "../../components/ui/date-picker-with-range";
3
- import { Button } from "../../components/ui/button";
4
- import { DateRange } from "react-day-picker";
5
- import { Input } from "../../components/ui/input";
6
- import {
7
- Select,
8
- SelectItem,
9
- SelectContent,
10
- SelectValue,
11
- SelectTrigger,
12
- } from "../../components/ui/select";
13
- import { useState, useMemo } from "react";
14
-
15
- interface PageHeaderProps {
16
- title: string;
17
- filters: any;
18
- configFilters: any;
19
- setFilters: (filters: any) => void;
20
- onSearch: () => void;
21
- count: number;
22
- isLoading: boolean;
23
- setCurrentPage: (page: number) => void;
24
- }
25
-
26
- export function PageHeader({
27
- title,
28
- filters,
29
- setFilters,
30
- configFilters,
31
- onSearch,
32
- count,
33
- isLoading,
34
- setCurrentPage,
35
- }: PageHeaderProps) {
36
- const [initialFilters, setInitialFilters] = React.useState(filters);
37
-
38
- const [exportDisabled, setExportDisabled] = useState(false);
39
- const filtersChanged = React.useMemo(() => {
40
- return JSON.stringify(initialFilters) !== JSON.stringify(filters);
41
- }, [initialFilters, filters]);
42
-
43
- const handleSearch = async () => {
44
- setInitialFilters(filters);
45
- if (filtersChanged) await setCurrentPage(1);
46
- onSearch();
47
- };
48
-
49
- const handleDateChange = (date: DateRange | undefined) => {
50
- setFilters({ ...filters, fromDate: date?.from, toDate: date?.to });
51
- };
52
-
53
- const handleExport = async (ctx: string) => {
54
- try {
55
- const response = await fetch(
56
- (() => {
57
- let url = `/cashplus/newfront/templates/extract-action.cfm?ctx=${ctx}`;
58
- for (const [key, value] of Object.entries(filters)) {
59
- if (value && typeof value !== "object") {
60
- if (key === "fromDate" || key === "toDate") {
61
- const date = new Date(value as string);
62
- url =
63
- url +
64
- `&${key}=${date.getFullYear()}-${String(
65
- date.getMonth() + 1
66
- ).padStart(2, "0")}-${String(date.getDate()).padStart(
67
- 2,
68
- "0"
69
- )}`;
70
- continue;
71
- }
72
-
73
- url = url + `&${key}=${value}`;
74
- }
75
- }
76
- return url;
77
- })()
78
- );
79
-
80
- if (!response.ok) {
81
- throw new Error("Export failed");
82
- }
83
-
84
- const blob = await response.blob();
85
- const url = window.URL.createObjectURL(blob);
86
- const a = document.createElement("a");
87
- a.href = url;
88
- const fileName = `${title.replace(/\s+/g, "_")}_${filters.fromDate}_${
89
- filters.toDate
90
- }_.csv`;
91
- a.download = fileName;
92
- document.body.appendChild(a);
93
- a.click();
94
- window.URL.revokeObjectURL(url);
95
- document.body.removeChild(a);
96
- } catch (error) {
97
- console.error("Export error:", error);
98
- }
99
- };
100
-
101
- return (
102
- <div className="flex flex-col gap-2">
103
- <h1 className="text-2xl font-bold text-[rgb(0,137,149)]">
104
- <svg
105
- xmlns="http://www.w3.org/2000/svg"
106
- width="24"
107
- height="24"
108
- viewBox="0 0 24 24"
109
- fill="none"
110
- stroke="rgb(0, 137, 149)"
111
- strokeWidth="2"
112
- strokeLinecap="round"
113
- strokeLinejoin="round"
114
- className="inline-block mr-2"
115
- >
116
- <line x1="8" y1="6" x2="21" y2="6" />
117
- <line x1="8" y1="12" x2="21" y2="12" />
118
- <line x1="8" y1="18" x2="21" y2="18" />
119
- <line x1="3" y1="6" x2="3.01" y2="6" />
120
- <line x1="3" y1="12" x2="3.01" y2="12" />
121
- <line x1="3" y1="18" x2="3.01" y2="18" />
122
- </svg>
123
-
124
- {title}
125
- <span className="ml-2 px-2 py-1 text-sm bg-gray-100 text-[rgb(0,137,149)] rounded-full">
126
- {count}
127
- </span>
128
- </h1>
129
- <div className="border-b border-gray-100 mb-4"></div>
130
- <div className="flex gap-4 flex-wrap justify-between">
131
- <DatePickerWithRange
132
- dateFrom={filters.fromDate}
133
- dateTo={filters.toDate}
134
- handleDateChange={handleDateChange}
135
- />
136
- {configFilters &&
137
- configFilters.map((filter: any, index: number) => (
138
- <div key={`${filter.name}-${index}`}>
139
- {filter.type === "Text" && (
140
- <Input
141
- type="text"
142
- placeholder={filter.placeholder}
143
- value={filters[filter.name]}
144
- onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
145
- setFilters({ ...filters, [filter.name]: e.target.value })
146
- }
147
- onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
148
- if (e.key === "Enter") {
149
- handleSearch();
150
- }
151
- }}
152
- />
153
- )}
154
- {filter.type === "Select" && (
155
- <Select
156
- disabled={filter.disabled || filter.options.length === 0}
157
- value={filters[filter.name]}
158
- onValueChange={(value: string) => {
159
- setFilters({
160
- ...filters,
161
- [filter.name]: value === "_empty" ? undefined : value,
162
- });
163
- }}
164
- >
165
- <SelectTrigger>
166
- <SelectValue placeholder={filter.placeholder} />
167
- </SelectTrigger>
168
- <SelectContent>
169
- {useMemo(
170
- () =>
171
- filter.options.map(
172
- (
173
- option: { VALUE: string; NAME: string },
174
- optionIndex: number
175
- ) => (
176
- <SelectItem
177
- key={`${option.VALUE}-${optionIndex}`}
178
- value={option.VALUE || "_empty"}
179
- >
180
- {option.NAME}
181
- </SelectItem>
182
- )
183
- ),
184
- [filter.options]
185
- )}
186
- </SelectContent>
187
- </Select>
188
- )}
189
- </div>
190
- ))}
191
- <div className="flex gap-2 ml-auto">
192
- {configFilters.some((filter: any) => filter.type === "Export") && (
193
- <Button
194
- disabled={exportDisabled}
195
- onClick={async () => {
196
- setExportDisabled(true);
197
- const exportFilter = configFilters.find(
198
- (filter: any) => filter.type === "Export"
199
- );
200
- await handleExport(exportFilter?.ctx);
201
- const timer = setInterval(() => {
202
- setExportDisabled(false);
203
- clearInterval(timer);
204
- }, 30000);
205
- }}
206
- size="sm"
207
- >
208
- <svg
209
- xmlns="http://www.w3.org/2000/svg"
210
- width="16"
211
- height="16"
212
- viewBox="0 0 24 24"
213
- fill="none"
214
- stroke="currentColor"
215
- strokeWidth="2"
216
- strokeLinecap="round"
217
- strokeLinejoin="round"
218
- >
219
- <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
220
- <polyline points="7 10 12 15 17 10" />
221
- <line x1="12" y1="15" x2="12" y2="3" />
222
- </svg>
223
- CSV
224
- </Button>
225
- )}
226
- <Button size="sm" onClick={handleSearch} disabled={isLoading}>
227
- {filtersChanged ? (
228
- <>
229
- <svg
230
- xmlns="http://www.w3.org/2000/svg"
231
- width="16"
232
- height="16"
233
- viewBox="0 0 24 24"
234
- fill="none"
235
- stroke="currentColor"
236
- strokeWidth="2"
237
- strokeLinecap="round"
238
- strokeLinejoin="round"
239
- >
240
- <circle cx="11" cy="11" r="8" />
241
- <path d="m21 21-4.3-4.3" />
242
- </svg>
243
- Recherche
244
- </>
245
- ) : (
246
- <>
247
- <svg
248
- width="16"
249
- height="16"
250
- viewBox="0 0 24 24"
251
- fill="none"
252
- stroke={"currentColor"}
253
- strokeWidth="2"
254
- strokeLinecap="round"
255
- strokeLinejoin="round"
256
- className="reload-icon"
257
- aria-hidden="true"
258
- >
259
- <path d="M21 12a9 9 0 11-3-6.74" />
260
- <path d="M21 3v6h-6" />
261
- </svg>
262
- Actualiser
263
- </>
264
- )}
265
- </Button>
266
- </div>
267
- </div>
268
- </div>
269
- );
270
- }