gbs-add-block 1.0.4 → 1.0.6

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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # GBS Building Blocks 2.0 (v1.0.4)
1
+ # GBS Building Blocks 2.0 (v1.0.6)
2
2
 
3
3
  Latest and upgraded version of GBS building blocks with headless UI and removed dependencies.
4
4
 
@@ -6,13 +6,9 @@ Latest and upgraded version of GBS building blocks with headless UI and removed
6
6
 
7
7
  For detailed documentation on usage and props, Please visit: [Building Block Documentation v2.0](https://blackmax-designs.gitbook.io/building-block-v2.0)
8
8
 
9
- ## What's New 🎉 (Ver 1.0.4)
9
+ ## What's New 🎉 (Ver 1.0.6)
10
10
 
11
- - Major Update with more stable features and Data Grid.
12
- - Deprecated Grid Component, Use New Data Grid instead.
13
- - More UI/UX Optimization.
14
- - Animation improvements.
15
- - Custom Components in SideBar and Mobile Screen Support
11
+ - Uploader Enhancement
16
12
 
17
13
  ## Authors
18
14
 
package/index.cjs CHANGED
@@ -31,6 +31,7 @@ const CONFIG = {
31
31
  "BreadCrumb",
32
32
  "Bargraph",
33
33
  "UsePaginatedData",
34
+ "UseUploader"
34
35
  ],
35
36
  // Define component dependencies
36
37
  dependencies: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gbs-add-block",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "React Component Library",
5
5
  "type": "module",
6
6
  "files": [
@@ -0,0 +1,303 @@
1
+ import { useState, useCallback } from "react";
2
+
3
+ // Types
4
+ interface ChunkInfo {
5
+ file: Blob;
6
+ originalFile: File;
7
+ chunkIndex: number;
8
+ totalChunks: number;
9
+ }
10
+
11
+ interface UploadProgress {
12
+ [fileName: string]: number;
13
+ }
14
+
15
+ interface UploadResult {
16
+ fileName: string;
17
+ status: "complete" | "chunk_received" | "error";
18
+ message?: string;
19
+ }
20
+
21
+ interface UseChunkedUploadOptions {
22
+ chunkSize?: number;
23
+ maxConcurrentUploads?: number;
24
+ baseUrl?: string;
25
+ onProgress?: (fileName: string, progress: number) => void;
26
+ onComplete?: (fileName: string, result: UploadResult) => void;
27
+ onError?: (fileName: string, error: Error) => void;
28
+ additionalParams?: any;
29
+ }
30
+
31
+ interface UseChunkedUploadReturn {
32
+ uploadFiles: (files: File[]) => Promise<void>;
33
+ isUploading: boolean;
34
+ uploadProgress: UploadProgress;
35
+ uploadStatus: string;
36
+ cancelUpload: () => void;
37
+ reset: () => void;
38
+ }
39
+
40
+ // Helper function to process files into chunks
41
+ function processFiles(files: File[], chunkSize: number): ChunkInfo[] {
42
+ const finalFiles: ChunkInfo[] = [];
43
+
44
+ files.forEach((file) => {
45
+ if (file.size > chunkSize) {
46
+ const totalChunks = Math.ceil(file.size / chunkSize);
47
+ let offset = 0;
48
+ let chunkIndex = 0;
49
+
50
+ while (offset < file.size) {
51
+ const chunk = file.slice(offset, offset + chunkSize);
52
+ finalFiles.push({
53
+ file: chunk,
54
+ originalFile: file,
55
+ chunkIndex,
56
+ totalChunks,
57
+ });
58
+ offset += chunkSize;
59
+ chunkIndex++;
60
+ }
61
+ } else {
62
+ finalFiles.push({
63
+ file,
64
+ originalFile: file,
65
+ chunkIndex: 0,
66
+ totalChunks: 1,
67
+ });
68
+ }
69
+ });
70
+
71
+ return finalFiles;
72
+ }
73
+
74
+ // Helper function to upload a single chunk
75
+ async function uploadChunk(
76
+ chunk: Blob,
77
+ originalFile: File,
78
+ chunkIndex: number,
79
+ totalChunks: number,
80
+ baseUrl: string,
81
+ signal?: AbortSignal,
82
+ additionalParams: any = {}
83
+ ): Promise<UploadResult> {
84
+ const formData = new FormData();
85
+ formData.append("chunk", chunk);
86
+ formData.append("fileName", originalFile.name);
87
+ formData.append("chunkIndex", chunkIndex.toString());
88
+ formData.append("totalChunks", totalChunks.toString());
89
+ formData.append("fileSize", originalFile.size.toString());
90
+
91
+ Object.entries(additionalParams).forEach(([key, value]: any) => {
92
+ formData.append(key, value);
93
+ });
94
+
95
+ const response = await fetch(`${baseUrl}`, {
96
+ method: "POST",
97
+ body: formData,
98
+ signal,
99
+ });
100
+
101
+ if (!response.ok) {
102
+ throw new Error(`Upload failed: ${response.statusText}`);
103
+ }
104
+
105
+ return response.json();
106
+ }
107
+
108
+ // Main custom hook
109
+ export function useChunkedUpload(
110
+ options: UseChunkedUploadOptions = {}
111
+ ): UseChunkedUploadReturn {
112
+ const {
113
+ chunkSize = 1024 * 1024, // 1MB default
114
+ maxConcurrentUploads = 3,
115
+ baseUrl = "",
116
+ onProgress,
117
+ onComplete,
118
+ onError,
119
+ additionalParams = {},
120
+ } = options;
121
+
122
+ const [isUploading, setIsUploading] = useState(false);
123
+ const [uploadProgress, setUploadProgress] = useState<UploadProgress>({});
124
+ const [uploadStatus, setUploadStatus] = useState("");
125
+ const [abortController, setAbortController] =
126
+ useState<AbortController | null>(null);
127
+
128
+ const updateProgress = useCallback(
129
+ (fileName: string, progress: number) => {
130
+ setUploadProgress((prev) => ({
131
+ ...prev,
132
+ [fileName]: progress,
133
+ }));
134
+ onProgress?.(fileName, progress);
135
+ },
136
+ [onProgress]
137
+ );
138
+
139
+ const updateStatus = useCallback((status: string) => {
140
+ setUploadStatus(status);
141
+ }, []);
142
+
143
+ const uploadFiles = useCallback(
144
+ async (files: File[]) => {
145
+ if (files.length === 0) {
146
+ throw new Error("No files selected for upload.");
147
+ }
148
+
149
+ // Create new abort controller
150
+ const controller = new AbortController();
151
+ setAbortController(controller);
152
+
153
+ setIsUploading(true);
154
+ updateStatus("Processing files...");
155
+
156
+ try {
157
+ const processedFiles = processFiles(files, chunkSize);
158
+
159
+ // Group chunks by file
160
+ const fileChunks: { [key: string]: ChunkInfo[] } = {};
161
+ processedFiles.forEach((processedFile) => {
162
+ const fileName = processedFile.originalFile.name;
163
+ if (!fileChunks[fileName]) {
164
+ fileChunks[fileName] = [];
165
+ }
166
+ fileChunks[fileName].push(processedFile);
167
+ });
168
+
169
+ // Upload files with concurrency control
170
+ const fileNames = Object.keys(fileChunks);
171
+ const uploadPromises = fileNames.map(async (fileName) => {
172
+ const chunks = fileChunks[fileName];
173
+ updateStatus(`Uploading ${fileName}...`);
174
+
175
+ try {
176
+ // Create semaphore for concurrent chunk uploads
177
+ const semaphore = new Array(maxConcurrentUploads).fill(null);
178
+ let chunkIndex = 0;
179
+ const results: UploadResult[] = [];
180
+
181
+ const uploadNextChunk = async (): Promise<void> => {
182
+ if (chunkIndex >= chunks.length) return;
183
+
184
+ const currentChunk = chunks[chunkIndex++];
185
+ const result = await uploadChunk(
186
+ currentChunk.file,
187
+ currentChunk.originalFile,
188
+ currentChunk.chunkIndex,
189
+ currentChunk.totalChunks,
190
+ baseUrl,
191
+ controller.signal,
192
+ additionalParams
193
+ );
194
+
195
+ results.push(result);
196
+
197
+ // Update progress
198
+ const progress = (results.length / chunks.length) * 100;
199
+ updateProgress(fileName, progress);
200
+
201
+ // Continue uploading if there are more chunks
202
+ if (chunkIndex < chunks.length) {
203
+ await uploadNextChunk();
204
+ }
205
+ };
206
+
207
+ // Start concurrent uploads
208
+ await Promise.all(semaphore.map(() => uploadNextChunk()));
209
+
210
+ // Check if file is complete
211
+ const completeResult = results.find((r) => r.status === "complete");
212
+ if (completeResult) {
213
+ updateStatus(`${fileName} uploaded successfully!`);
214
+ onComplete?.(fileName, completeResult);
215
+ }
216
+
217
+ return { fileName, success: true };
218
+ } catch (error) {
219
+ const errorObj =
220
+ error instanceof Error ? error : new Error(String(error));
221
+ updateStatus(`Failed to upload ${fileName}: ${errorObj.message}`);
222
+ onError?.(fileName, errorObj);
223
+ return { fileName, success: false, error: errorObj };
224
+ }
225
+ });
226
+
227
+ const results = await Promise.all(uploadPromises);
228
+
229
+ const successCount = results.filter((r) => r.success).length;
230
+ const failCount = results.length - successCount;
231
+
232
+ if (failCount === 0) {
233
+ updateStatus("All files uploaded successfully!");
234
+ } else if (successCount === 0) {
235
+ updateStatus("All uploads failed!");
236
+ } else {
237
+ updateStatus(`${successCount} files uploaded, ${failCount} failed`);
238
+ }
239
+ } catch (error) {
240
+ if (error instanceof Error && error.name === "AbortError") {
241
+ updateStatus("Upload cancelled");
242
+ } else {
243
+ const errorMessage =
244
+ error instanceof Error ? error.message : String(error);
245
+ updateStatus(`Upload failed: ${errorMessage}`);
246
+ }
247
+ } finally {
248
+ setIsUploading(false);
249
+ setAbortController(null);
250
+ }
251
+ },
252
+ [
253
+ chunkSize,
254
+ maxConcurrentUploads,
255
+ baseUrl,
256
+ updateProgress,
257
+ updateStatus,
258
+ onComplete,
259
+ onError,
260
+ ]
261
+ );
262
+
263
+ const cancelUpload = useCallback(() => {
264
+ if (abortController) {
265
+ abortController.abort();
266
+ updateStatus("Cancelling upload...");
267
+ }
268
+ }, [abortController, updateStatus]);
269
+
270
+ const reset = useCallback(() => {
271
+ setUploadProgress({});
272
+ setUploadStatus("");
273
+ setIsUploading(false);
274
+ if (abortController) {
275
+ abortController.abort();
276
+ setAbortController(null);
277
+ }
278
+ }, [abortController]);
279
+
280
+ return {
281
+ uploadFiles,
282
+ isUploading,
283
+ uploadProgress,
284
+ uploadStatus,
285
+ cancelUpload,
286
+ reset,
287
+ };
288
+ }
289
+
290
+ // Alternative hook with simplified API for basic use cases
291
+ export function useSimpleUpload(baseUrl?: string) {
292
+ const { uploadFiles, isUploading, uploadStatus } = useChunkedUpload({
293
+ baseUrl,
294
+ chunkSize: 1024 * 1024, // 1MB
295
+ maxConcurrentUploads: 3,
296
+ });
297
+
298
+ return {
299
+ upload: uploadFiles,
300
+ isUploading,
301
+ status: uploadStatus,
302
+ };
303
+ }
@@ -8,7 +8,8 @@ export const usePaginatedData = (
8
8
  apiEndpoint: string,
9
9
  initialPageSize = 10,
10
10
  method: "GET" | "POST" = "GET",
11
- columns: any[] = []
11
+ columns: any[] = [],
12
+ additionalParams: any = {}
12
13
  ) => {
13
14
  const [data, setData] = useState<any[]>([]);
14
15
  const [loading, setLoading] = useState(false);
@@ -27,7 +28,6 @@ export const usePaginatedData = (
27
28
  pageSize: number,
28
29
  currentFilters: any[] = [],
29
30
  search = "",
30
- additionalParams: any = {},
31
31
  isExportCall: boolean | null = null,
32
32
  exportType: string | null = null
33
33
  ) => {
@@ -128,9 +128,13 @@ export const usePaginatedData = (
128
128
  // Handle search input
129
129
  const handleSearch = useCallback(
130
130
  (searchTerm: string) => {
131
- const updatedFilters = filters.filter((f) => {
132
- return !(f as any).searchTerm;
133
- });
131
+ const existingSearch = filters.find(
132
+ (f) => f.type === "search" && f.value === searchTerm
133
+ );
134
+
135
+ if (existingSearch) return;
136
+
137
+ const updatedFilters = filters.filter((f) => f.type !== "search");
134
138
 
135
139
  const finalFilters = [
136
140
  ...updatedFilters,
@@ -146,7 +150,7 @@ export const usePaginatedData = (
146
150
  // Handle exports
147
151
  const handleExports = useCallback(
148
152
  (exportType: string) => {
149
- fetchData(method, 0, pagination.pageSize, [], "", {}, true, exportType);
153
+ fetchData(method, 0, pagination.pageSize, [], "", true, exportType);
150
154
  },
151
155
  [fetchData, method, pagination.pageSize]
152
156
  );