@fctc/widget-logic 4.8.9 → 4.9.1

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/dist/index.js CHANGED
@@ -4415,6 +4415,28 @@ function useStorageState(key) {
4415
4415
  );
4416
4416
  return [state, setValue];
4417
4417
  }
4418
+ var guessTypeFromUrl = (url) => {
4419
+ const ext = url.split(".").pop()?.toLowerCase();
4420
+ if (!ext) return null;
4421
+ const map = {
4422
+ jpg: "image/jpeg",
4423
+ jpeg: "image/jpeg",
4424
+ png: "image/png",
4425
+ webp: "image/webp",
4426
+ gif: "image/gif",
4427
+ svg: "image/svg+xml",
4428
+ bmp: "image/bmp",
4429
+ tiff: "image/tiff",
4430
+ pdf: "application/pdf",
4431
+ zip: "application/zip",
4432
+ rar: "application/x-rar-compressed",
4433
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
4434
+ xls: "application/vnd.ms-excel",
4435
+ mp4: "video/mp4",
4436
+ mov: "video/quicktime"
4437
+ };
4438
+ return map[ext] || null;
4439
+ };
4418
4440
 
4419
4441
  // src/utils.ts
4420
4442
  __reExport(utils_exports, require("@fctc/interface-logic/utils"));
@@ -6208,126 +6230,101 @@ var colorFieldController = (props) => {
6208
6230
 
6209
6231
  // src/widget/basic/binary-field/controller.ts
6210
6232
  var import_react22 = require("react");
6211
- var import_utils16 = require("@fctc/interface-logic/utils");
6212
- var ALLOWED_TYPES = [
6213
- "image/jpeg",
6214
- "image/png",
6215
- "application/pdf",
6216
- "video/mp4",
6217
- "application/zip",
6218
- "application/x-zip-compressed",
6219
- "application/vnd.ms-excel",
6220
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
6221
- "application/json"
6222
- ];
6223
- var MAX_FILE_SIZE = 10 * 1024 * 1024;
6224
- var MAX_TOTAL_SIZE = 50 * 1024 * 1024;
6225
6233
  var binaryFieldController = (props) => {
6226
6234
  const {
6227
6235
  name,
6228
6236
  methods,
6229
6237
  readonly = false,
6230
- value,
6231
6238
  filename,
6239
+ onChange: handleOnchange,
6240
+ service,
6241
+ xNode,
6242
+ path,
6232
6243
  rootField,
6233
6244
  index,
6234
- onChange: handleOnchange
6245
+ value
6235
6246
  } = props;
6236
- const inputId = (0, import_react22.useId)();
6237
- const [selectedFile, setSelectedFile] = (0, import_react22.useState)(null);
6238
- const [selectedPreview, setSelectedPreview] = (0, import_react22.useState)(null);
6239
- const [initialFile, setInitialFile] = (0, import_react22.useState)(value || null);
6240
- const binaryRef = (0, import_react22.useRef)(null);
6241
- const convertUrlToBase64 = async (url) => {
6247
+ const { useUploadFile: useUploadFile2 } = (0, provider_exports.useService)();
6248
+ const { mutateAsync } = useUploadFile2();
6249
+ const [url, setUrl] = (0, import_react22.useState)(value || null);
6250
+ const [fileInfo, setFileInfo] = (0, import_react22.useState)(null);
6251
+ (0, import_react22.useEffect)(() => {
6252
+ if (!value) return;
6253
+ fetchFileMeta(value);
6254
+ }, [value]);
6255
+ const formatSize = (bytes) => {
6256
+ if (bytes < 1024) return bytes + " B";
6257
+ let kb = bytes / 1024;
6258
+ if (kb < 1024) return kb.toFixed(2) + " KB";
6259
+ let mb = kb / 1024;
6260
+ if (mb < 1024) return mb.toFixed(2) + " MB";
6261
+ return (mb / 1024).toFixed(2) + " GB";
6262
+ };
6263
+ const fetchFileMeta = async (url2) => {
6242
6264
  try {
6243
- const response = await fetch(url);
6244
- const blob = await response.blob();
6245
- return new Promise((resolve, reject) => {
6246
- const reader = new FileReader();
6247
- reader.onloadend = () => resolve(reader.result);
6248
- reader.onerror = reject;
6249
- reader.readAsDataURL(blob);
6265
+ const res = await fetch(url2, { method: "HEAD" });
6266
+ const size = res.headers.get("content-length") || "";
6267
+ let type = res.headers.get("content-type") || "";
6268
+ const date = res.headers.get("last-modified") || "";
6269
+ const guessed = guessTypeFromUrl(url2);
6270
+ if (guessed) type = guessed;
6271
+ setFileInfo({
6272
+ size: size ? formatSize(Number(size)) : "--",
6273
+ type,
6274
+ date: date ? new Date(date).toLocaleString() : "--"
6250
6275
  });
6251
- } catch (error) {
6252
- console.error("Error converting URL to Base64:", error);
6253
- throw error;
6276
+ setUrl(url2);
6277
+ } catch (e) {
6278
+ console.error(e);
6254
6279
  }
6255
6280
  };
6256
- const extractBase64Data = (base64Url) => base64Url.includes("base64,") ? base64Url.split("base64,")[1] : base64Url;
6257
- const isBlobUrl = (url) => /^blob:/.test(url);
6258
- const checkIsImageLink = (url) => {
6259
- const imageExtensions = /\.(jpg|jpeg|png|gif|bmp|webp|svg|tiff|ico)$/i;
6260
- return imageExtensions.test(url) || (0, import_utils16.isBase64Image)(url) || isBlobUrl(url);
6261
- };
6262
- const getBase64WithMimeType = (base64) => {
6263
- if (typeof base64 !== "string" || base64.length < 10) return null;
6264
- if ((0, import_utils16.isBase64Image)(base64)) return base64;
6265
- let mimeType = null;
6266
- if (base64.startsWith("iVBORw0KGgo")) mimeType = "image/png";
6267
- else if (base64.startsWith("/9j/")) mimeType = "image/jpeg";
6268
- else if (base64.startsWith("R0lGOD")) mimeType = "image/gif";
6269
- else if (base64.startsWith("Qk")) mimeType = "image/bmp";
6270
- else if (base64.startsWith("UklGR")) mimeType = "image/webp";
6271
- return mimeType ? `data:${mimeType};base64,${base64}` : null;
6272
- };
6273
- const handleFileChange = async (e, onChange) => {
6274
- if (readonly) return;
6275
- const file = e?.target?.files?.[0];
6276
- if (!file) return;
6277
- if (!ALLOWED_TYPES.includes(file.type)) {
6278
- alert(`File type not allowed: ${file.type}`);
6279
- return;
6280
- }
6281
- if (file.size > MAX_FILE_SIZE) {
6282
- alert(`File exceeds 10MB limit.`);
6283
- return;
6284
- }
6285
- const fileUrl = URL.createObjectURL(file);
6286
- setSelectedFile(file);
6287
- setSelectedPreview(fileUrl);
6288
- const base64 = await convertUrlToBase64(fileUrl);
6289
- const base64Data = extractBase64Data(base64);
6290
- methods?.setValue(name, base64Data, { shouldDirty: true });
6291
- onChange(base64Data);
6292
- handleOnchange && handleOnchange(name ?? "", base64Data);
6281
+ const onUploadFile = async (formData) => {
6282
+ const res = await mutateAsync({
6283
+ formData,
6284
+ service,
6285
+ xNode,
6286
+ path
6287
+ });
6288
+ const url2 = res?.url;
6289
+ methods?.setValue(name, url2, { shouldDirty: true });
6290
+ handleOnchange && handleOnchange(name ?? "", url2);
6293
6291
  if (filename) {
6294
6292
  methods?.setValue(
6295
6293
  rootField ? `${rootField?.name}.${index}.${filename}` : filename,
6296
- file?.name,
6294
+ url2?.split("/").pop(),
6297
6295
  { shouldDirty: true }
6298
6296
  );
6299
6297
  handleOnchange && handleOnchange(
6300
6298
  rootField ? `${rootField?.name}.${index}.${filename}` : filename,
6301
- file?.name
6299
+ url2?.split("/").pop()
6302
6300
  );
6303
6301
  }
6302
+ setUrl(url2);
6303
+ fetchFileMeta(url2);
6304
+ return url2;
6304
6305
  };
6305
- const handleRemoveFile = (onChange) => {
6306
- if (selectedPreview) URL.revokeObjectURL(selectedPreview);
6307
- setSelectedFile(null);
6308
- setSelectedPreview(null);
6309
- setInitialFile(null);
6310
- onChange(null);
6306
+ const onDeleteFile = () => {
6307
+ if (filename) {
6308
+ methods?.setValue(
6309
+ rootField ? `${rootField?.name}.${index}.${filename}` : filename,
6310
+ null,
6311
+ { shouldDirty: true }
6312
+ );
6313
+ handleOnchange && handleOnchange(
6314
+ rootField ? `${rootField?.name}.${index}.${filename}` : filename,
6315
+ null
6316
+ );
6317
+ }
6318
+ setUrl(null);
6319
+ setFileInfo(null);
6320
+ methods?.setValue(name, null, { shouldDirty: true });
6311
6321
  handleOnchange && handleOnchange(name ?? "", null);
6312
6322
  };
6313
- (0, import_react22.useEffect)(() => {
6314
- return () => {
6315
- if (selectedPreview) {
6316
- URL.revokeObjectURL(selectedPreview);
6317
- }
6318
- };
6319
- }, [selectedPreview]);
6320
6323
  return {
6321
- inputId,
6322
- selectedFile,
6323
- selectedPreview,
6324
- initialFile,
6325
- binaryRef,
6326
- handleFileChange,
6327
- handleRemoveFile,
6328
- checkIsImageLink,
6329
- getBase64WithMimeType,
6330
- setInitialFile
6324
+ onUploadFile,
6325
+ onDeleteFile,
6326
+ fileInfo,
6327
+ url
6331
6328
  };
6332
6329
  };
6333
6330
 
@@ -6399,7 +6396,7 @@ var tableHeadController = (props) => {
6399
6396
 
6400
6397
  // src/widget/advance/table/table-view/controller.ts
6401
6398
  var import_react24 = require("react");
6402
- var import_utils18 = require("@fctc/interface-logic/utils");
6399
+ var import_utils17 = require("@fctc/interface-logic/utils");
6403
6400
  var tableController = ({ data }) => {
6404
6401
  const [rows, setRows] = (0, import_react24.useState)([]);
6405
6402
  const [columnVisibility, setColumnVisibility] = (0, import_react24.useState)({});
@@ -6442,10 +6439,10 @@ var tableController = ({ data }) => {
6442
6439
  const columns = (0, import_react24.useMemo)(() => {
6443
6440
  try {
6444
6441
  return mergeFields?.filter((item) => {
6445
- return item?.widget !== "details_Receive_money" && !(item?.column_invisible ? import_utils18.domainHelper.matchDomains(
6442
+ return item?.widget !== "details_Receive_money" && !(item?.column_invisible ? import_utils17.domainHelper.matchDomains(
6446
6443
  data.context,
6447
6444
  item?.column_invisible
6448
- ) : item?.invisible ? import_utils18.domainHelper.matchDomains(data.context, item?.invisible) : false);
6445
+ ) : item?.invisible ? import_utils17.domainHelper.matchDomains(data.context, item?.invisible) : false);
6449
6446
  })?.map((field) => {
6450
6447
  const overridden = columnVisibility[field?.name];
6451
6448
  return {
package/dist/index.mjs CHANGED
@@ -4504,6 +4504,28 @@ function useStorageState(key) {
4504
4504
  );
4505
4505
  return [state, setValue];
4506
4506
  }
4507
+ var guessTypeFromUrl = (url) => {
4508
+ const ext = url.split(".").pop()?.toLowerCase();
4509
+ if (!ext) return null;
4510
+ const map = {
4511
+ jpg: "image/jpeg",
4512
+ jpeg: "image/jpeg",
4513
+ png: "image/png",
4514
+ webp: "image/webp",
4515
+ gif: "image/gif",
4516
+ svg: "image/svg+xml",
4517
+ bmp: "image/bmp",
4518
+ tiff: "image/tiff",
4519
+ pdf: "application/pdf",
4520
+ zip: "application/zip",
4521
+ rar: "application/x-rar-compressed",
4522
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
4523
+ xls: "application/vnd.ms-excel",
4524
+ mp4: "video/mp4",
4525
+ mov: "video/quicktime"
4526
+ };
4527
+ return map[ext] || null;
4528
+ };
4507
4529
 
4508
4530
  // src/utils.ts
4509
4531
  __reExport(utils_exports, utils_star);
@@ -6302,132 +6324,107 @@ var colorFieldController = (props) => {
6302
6324
  };
6303
6325
 
6304
6326
  // src/widget/basic/binary-field/controller.ts
6305
- import { useEffect as useEffect13, useId as useId2, useRef as useRef4, useState as useState12 } from "react";
6306
- import { isBase64Image } from "@fctc/interface-logic/utils";
6307
- var ALLOWED_TYPES = [
6308
- "image/jpeg",
6309
- "image/png",
6310
- "application/pdf",
6311
- "video/mp4",
6312
- "application/zip",
6313
- "application/x-zip-compressed",
6314
- "application/vnd.ms-excel",
6315
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
6316
- "application/json"
6317
- ];
6318
- var MAX_FILE_SIZE = 10 * 1024 * 1024;
6319
- var MAX_TOTAL_SIZE = 50 * 1024 * 1024;
6327
+ import { useEffect as useEffect13, useState as useState12 } from "react";
6320
6328
  var binaryFieldController = (props) => {
6321
6329
  const {
6322
6330
  name,
6323
6331
  methods,
6324
6332
  readonly = false,
6325
- value,
6326
6333
  filename,
6334
+ onChange: handleOnchange,
6335
+ service,
6336
+ xNode,
6337
+ path,
6327
6338
  rootField,
6328
6339
  index,
6329
- onChange: handleOnchange
6340
+ value
6330
6341
  } = props;
6331
- const inputId = useId2();
6332
- const [selectedFile, setSelectedFile] = useState12(null);
6333
- const [selectedPreview, setSelectedPreview] = useState12(null);
6334
- const [initialFile, setInitialFile] = useState12(value || null);
6335
- const binaryRef = useRef4(null);
6336
- const convertUrlToBase64 = async (url) => {
6342
+ const { useUploadFile: useUploadFile2 } = (0, provider_exports.useService)();
6343
+ const { mutateAsync } = useUploadFile2();
6344
+ const [url, setUrl] = useState12(value || null);
6345
+ const [fileInfo, setFileInfo] = useState12(null);
6346
+ useEffect13(() => {
6347
+ if (!value) return;
6348
+ fetchFileMeta(value);
6349
+ }, [value]);
6350
+ const formatSize = (bytes) => {
6351
+ if (bytes < 1024) return bytes + " B";
6352
+ let kb = bytes / 1024;
6353
+ if (kb < 1024) return kb.toFixed(2) + " KB";
6354
+ let mb = kb / 1024;
6355
+ if (mb < 1024) return mb.toFixed(2) + " MB";
6356
+ return (mb / 1024).toFixed(2) + " GB";
6357
+ };
6358
+ const fetchFileMeta = async (url2) => {
6337
6359
  try {
6338
- const response = await fetch(url);
6339
- const blob = await response.blob();
6340
- return new Promise((resolve, reject) => {
6341
- const reader = new FileReader();
6342
- reader.onloadend = () => resolve(reader.result);
6343
- reader.onerror = reject;
6344
- reader.readAsDataURL(blob);
6360
+ const res = await fetch(url2, { method: "HEAD" });
6361
+ const size = res.headers.get("content-length") || "";
6362
+ let type = res.headers.get("content-type") || "";
6363
+ const date = res.headers.get("last-modified") || "";
6364
+ const guessed = guessTypeFromUrl(url2);
6365
+ if (guessed) type = guessed;
6366
+ setFileInfo({
6367
+ size: size ? formatSize(Number(size)) : "--",
6368
+ type,
6369
+ date: date ? new Date(date).toLocaleString() : "--"
6345
6370
  });
6346
- } catch (error) {
6347
- console.error("Error converting URL to Base64:", error);
6348
- throw error;
6371
+ setUrl(url2);
6372
+ } catch (e) {
6373
+ console.error(e);
6349
6374
  }
6350
6375
  };
6351
- const extractBase64Data = (base64Url) => base64Url.includes("base64,") ? base64Url.split("base64,")[1] : base64Url;
6352
- const isBlobUrl = (url) => /^blob:/.test(url);
6353
- const checkIsImageLink = (url) => {
6354
- const imageExtensions = /\.(jpg|jpeg|png|gif|bmp|webp|svg|tiff|ico)$/i;
6355
- return imageExtensions.test(url) || isBase64Image(url) || isBlobUrl(url);
6356
- };
6357
- const getBase64WithMimeType = (base64) => {
6358
- if (typeof base64 !== "string" || base64.length < 10) return null;
6359
- if (isBase64Image(base64)) return base64;
6360
- let mimeType = null;
6361
- if (base64.startsWith("iVBORw0KGgo")) mimeType = "image/png";
6362
- else if (base64.startsWith("/9j/")) mimeType = "image/jpeg";
6363
- else if (base64.startsWith("R0lGOD")) mimeType = "image/gif";
6364
- else if (base64.startsWith("Qk")) mimeType = "image/bmp";
6365
- else if (base64.startsWith("UklGR")) mimeType = "image/webp";
6366
- return mimeType ? `data:${mimeType};base64,${base64}` : null;
6367
- };
6368
- const handleFileChange = async (e, onChange) => {
6369
- if (readonly) return;
6370
- const file = e?.target?.files?.[0];
6371
- if (!file) return;
6372
- if (!ALLOWED_TYPES.includes(file.type)) {
6373
- alert(`File type not allowed: ${file.type}`);
6374
- return;
6375
- }
6376
- if (file.size > MAX_FILE_SIZE) {
6377
- alert(`File exceeds 10MB limit.`);
6378
- return;
6379
- }
6380
- const fileUrl = URL.createObjectURL(file);
6381
- setSelectedFile(file);
6382
- setSelectedPreview(fileUrl);
6383
- const base64 = await convertUrlToBase64(fileUrl);
6384
- const base64Data = extractBase64Data(base64);
6385
- methods?.setValue(name, base64Data, { shouldDirty: true });
6386
- onChange(base64Data);
6387
- handleOnchange && handleOnchange(name ?? "", base64Data);
6376
+ const onUploadFile = async (formData) => {
6377
+ const res = await mutateAsync({
6378
+ formData,
6379
+ service,
6380
+ xNode,
6381
+ path
6382
+ });
6383
+ const url2 = res?.url;
6384
+ methods?.setValue(name, url2, { shouldDirty: true });
6385
+ handleOnchange && handleOnchange(name ?? "", url2);
6388
6386
  if (filename) {
6389
6387
  methods?.setValue(
6390
6388
  rootField ? `${rootField?.name}.${index}.${filename}` : filename,
6391
- file?.name,
6389
+ url2?.split("/").pop(),
6392
6390
  { shouldDirty: true }
6393
6391
  );
6394
6392
  handleOnchange && handleOnchange(
6395
6393
  rootField ? `${rootField?.name}.${index}.${filename}` : filename,
6396
- file?.name
6394
+ url2?.split("/").pop()
6397
6395
  );
6398
6396
  }
6397
+ setUrl(url2);
6398
+ fetchFileMeta(url2);
6399
+ return url2;
6399
6400
  };
6400
- const handleRemoveFile = (onChange) => {
6401
- if (selectedPreview) URL.revokeObjectURL(selectedPreview);
6402
- setSelectedFile(null);
6403
- setSelectedPreview(null);
6404
- setInitialFile(null);
6405
- onChange(null);
6401
+ const onDeleteFile = () => {
6402
+ if (filename) {
6403
+ methods?.setValue(
6404
+ rootField ? `${rootField?.name}.${index}.${filename}` : filename,
6405
+ null,
6406
+ { shouldDirty: true }
6407
+ );
6408
+ handleOnchange && handleOnchange(
6409
+ rootField ? `${rootField?.name}.${index}.${filename}` : filename,
6410
+ null
6411
+ );
6412
+ }
6413
+ setUrl(null);
6414
+ setFileInfo(null);
6415
+ methods?.setValue(name, null, { shouldDirty: true });
6406
6416
  handleOnchange && handleOnchange(name ?? "", null);
6407
6417
  };
6408
- useEffect13(() => {
6409
- return () => {
6410
- if (selectedPreview) {
6411
- URL.revokeObjectURL(selectedPreview);
6412
- }
6413
- };
6414
- }, [selectedPreview]);
6415
6418
  return {
6416
- inputId,
6417
- selectedFile,
6418
- selectedPreview,
6419
- initialFile,
6420
- binaryRef,
6421
- handleFileChange,
6422
- handleRemoveFile,
6423
- checkIsImageLink,
6424
- getBase64WithMimeType,
6425
- setInitialFile
6419
+ onUploadFile,
6420
+ onDeleteFile,
6421
+ fileInfo,
6422
+ url
6426
6423
  };
6427
6424
  };
6428
6425
 
6429
6426
  // src/widget/advance/table/table-head/controller.ts
6430
- import { useMemo as useMemo9, useRef as useRef5 } from "react";
6427
+ import { useMemo as useMemo9, useRef as useRef4 } from "react";
6431
6428
  var tableHeadController = (props) => {
6432
6429
  const {
6433
6430
  typeTable,
@@ -6438,7 +6435,7 @@ var tableHeadController = (props) => {
6438
6435
  setSelectedRowKeys
6439
6436
  } = props;
6440
6437
  const { rowIds: recordIds } = useGetRowIds(tableRef);
6441
- const selectedRowKeysRef = useRef5(recordIds);
6438
+ const selectedRowKeysRef = useRef4(recordIds);
6442
6439
  const isGroupTable = typeTable === "group";
6443
6440
  const recordsCheckedGroup = useMemo9(() => {
6444
6441
  if (!rows || !groupByList) return 0;
package/dist/widget.d.mts CHANGED
@@ -1,5 +1,4 @@
1
1
  import * as react from 'react';
2
- import { ChangeEvent } from 'react';
3
2
  import { IInputFieldProps } from './types.mjs';
4
3
  import moment from 'moment';
5
4
  import '@fctc/interface-logic/types';
@@ -213,20 +212,24 @@ declare const colorFieldController: (props: IColorFieldProps) => {
213
212
  savePickColor: (colorObject: any) => Promise<void>;
214
213
  };
215
214
 
216
- declare const binaryFieldController: (props: IInputFieldProps & {
215
+ interface IBinaryFieldProps extends IInputFieldProps {
216
+ service?: string;
217
+ xNode?: string;
218
+ path?: string;
217
219
  rootField?: any;
218
220
  index?: number;
219
- }) => {
220
- inputId: string;
221
- selectedFile: File | null;
222
- selectedPreview: string | null;
223
- initialFile: any;
224
- binaryRef: react.RefObject<HTMLDivElement>;
225
- handleFileChange: (e: ChangeEvent<HTMLInputElement>, onChange: (value: string | null) => void) => Promise<void>;
226
- handleRemoveFile: (onChange: (value: string | null) => void) => void;
227
- checkIsImageLink: (url: string) => boolean;
228
- getBase64WithMimeType: (base64: string) => string | null;
229
- setInitialFile: react.Dispatch<any>;
221
+ }
222
+
223
+ type FileInfor = {
224
+ type?: string;
225
+ date?: string;
226
+ size?: string;
227
+ };
228
+ declare const binaryFieldController: (props: IBinaryFieldProps) => {
229
+ onUploadFile: (formData: FormData) => Promise<any>;
230
+ onDeleteFile: () => void;
231
+ fileInfo: FileInfor | null;
232
+ url: any;
230
233
  };
231
234
 
232
235
  declare const tableHeadController: (props: any) => {
package/dist/widget.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import * as react from 'react';
2
- import { ChangeEvent } from 'react';
3
2
  import { IInputFieldProps } from './types.js';
4
3
  import moment from 'moment';
5
4
  import '@fctc/interface-logic/types';
@@ -213,20 +212,24 @@ declare const colorFieldController: (props: IColorFieldProps) => {
213
212
  savePickColor: (colorObject: any) => Promise<void>;
214
213
  };
215
214
 
216
- declare const binaryFieldController: (props: IInputFieldProps & {
215
+ interface IBinaryFieldProps extends IInputFieldProps {
216
+ service?: string;
217
+ xNode?: string;
218
+ path?: string;
217
219
  rootField?: any;
218
220
  index?: number;
219
- }) => {
220
- inputId: string;
221
- selectedFile: File | null;
222
- selectedPreview: string | null;
223
- initialFile: any;
224
- binaryRef: react.RefObject<HTMLDivElement>;
225
- handleFileChange: (e: ChangeEvent<HTMLInputElement>, onChange: (value: string | null) => void) => Promise<void>;
226
- handleRemoveFile: (onChange: (value: string | null) => void) => void;
227
- checkIsImageLink: (url: string) => boolean;
228
- getBase64WithMimeType: (base64: string) => string | null;
229
- setInitialFile: react.Dispatch<any>;
221
+ }
222
+
223
+ type FileInfor = {
224
+ type?: string;
225
+ date?: string;
226
+ size?: string;
227
+ };
228
+ declare const binaryFieldController: (props: IBinaryFieldProps) => {
229
+ onUploadFile: (formData: FormData) => Promise<any>;
230
+ onDeleteFile: () => void;
231
+ fileInfo: FileInfor | null;
232
+ url: any;
230
233
  };
231
234
 
232
235
  declare const tableHeadController: (props: any) => {
package/dist/widget.js CHANGED
@@ -4305,6 +4305,28 @@ function useStorageState(key) {
4305
4305
  );
4306
4306
  return [state, setValue];
4307
4307
  }
4308
+ var guessTypeFromUrl = (url) => {
4309
+ const ext = url.split(".").pop()?.toLowerCase();
4310
+ if (!ext) return null;
4311
+ const map = {
4312
+ jpg: "image/jpeg",
4313
+ jpeg: "image/jpeg",
4314
+ png: "image/png",
4315
+ webp: "image/webp",
4316
+ gif: "image/gif",
4317
+ svg: "image/svg+xml",
4318
+ bmp: "image/bmp",
4319
+ tiff: "image/tiff",
4320
+ pdf: "application/pdf",
4321
+ zip: "application/zip",
4322
+ rar: "application/x-rar-compressed",
4323
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
4324
+ xls: "application/vnd.ms-excel",
4325
+ mp4: "video/mp4",
4326
+ mov: "video/quicktime"
4327
+ };
4328
+ return map[ext] || null;
4329
+ };
4308
4330
 
4309
4331
  // src/utils.ts
4310
4332
  __reExport(utils_exports, require("@fctc/interface-logic/utils"));
@@ -5448,126 +5470,101 @@ var colorFieldController = (props) => {
5448
5470
 
5449
5471
  // src/widget/basic/binary-field/controller.ts
5450
5472
  var import_react22 = require("react");
5451
- var import_utils16 = require("@fctc/interface-logic/utils");
5452
- var ALLOWED_TYPES = [
5453
- "image/jpeg",
5454
- "image/png",
5455
- "application/pdf",
5456
- "video/mp4",
5457
- "application/zip",
5458
- "application/x-zip-compressed",
5459
- "application/vnd.ms-excel",
5460
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
5461
- "application/json"
5462
- ];
5463
- var MAX_FILE_SIZE = 10 * 1024 * 1024;
5464
- var MAX_TOTAL_SIZE = 50 * 1024 * 1024;
5465
5473
  var binaryFieldController = (props) => {
5466
5474
  const {
5467
5475
  name,
5468
5476
  methods,
5469
5477
  readonly = false,
5470
- value,
5471
5478
  filename,
5479
+ onChange: handleOnchange,
5480
+ service,
5481
+ xNode,
5482
+ path,
5472
5483
  rootField,
5473
5484
  index,
5474
- onChange: handleOnchange
5485
+ value
5475
5486
  } = props;
5476
- const inputId = (0, import_react22.useId)();
5477
- const [selectedFile, setSelectedFile] = (0, import_react22.useState)(null);
5478
- const [selectedPreview, setSelectedPreview] = (0, import_react22.useState)(null);
5479
- const [initialFile, setInitialFile] = (0, import_react22.useState)(value || null);
5480
- const binaryRef = (0, import_react22.useRef)(null);
5481
- const convertUrlToBase64 = async (url) => {
5487
+ const { useUploadFile: useUploadFile2 } = (0, provider_exports.useService)();
5488
+ const { mutateAsync } = useUploadFile2();
5489
+ const [url, setUrl] = (0, import_react22.useState)(value || null);
5490
+ const [fileInfo, setFileInfo] = (0, import_react22.useState)(null);
5491
+ (0, import_react22.useEffect)(() => {
5492
+ if (!value) return;
5493
+ fetchFileMeta(value);
5494
+ }, [value]);
5495
+ const formatSize = (bytes) => {
5496
+ if (bytes < 1024) return bytes + " B";
5497
+ let kb = bytes / 1024;
5498
+ if (kb < 1024) return kb.toFixed(2) + " KB";
5499
+ let mb = kb / 1024;
5500
+ if (mb < 1024) return mb.toFixed(2) + " MB";
5501
+ return (mb / 1024).toFixed(2) + " GB";
5502
+ };
5503
+ const fetchFileMeta = async (url2) => {
5482
5504
  try {
5483
- const response = await fetch(url);
5484
- const blob = await response.blob();
5485
- return new Promise((resolve, reject) => {
5486
- const reader = new FileReader();
5487
- reader.onloadend = () => resolve(reader.result);
5488
- reader.onerror = reject;
5489
- reader.readAsDataURL(blob);
5505
+ const res = await fetch(url2, { method: "HEAD" });
5506
+ const size = res.headers.get("content-length") || "";
5507
+ let type = res.headers.get("content-type") || "";
5508
+ const date = res.headers.get("last-modified") || "";
5509
+ const guessed = guessTypeFromUrl(url2);
5510
+ if (guessed) type = guessed;
5511
+ setFileInfo({
5512
+ size: size ? formatSize(Number(size)) : "--",
5513
+ type,
5514
+ date: date ? new Date(date).toLocaleString() : "--"
5490
5515
  });
5491
- } catch (error) {
5492
- console.error("Error converting URL to Base64:", error);
5493
- throw error;
5516
+ setUrl(url2);
5517
+ } catch (e) {
5518
+ console.error(e);
5494
5519
  }
5495
5520
  };
5496
- const extractBase64Data = (base64Url) => base64Url.includes("base64,") ? base64Url.split("base64,")[1] : base64Url;
5497
- const isBlobUrl = (url) => /^blob:/.test(url);
5498
- const checkIsImageLink = (url) => {
5499
- const imageExtensions = /\.(jpg|jpeg|png|gif|bmp|webp|svg|tiff|ico)$/i;
5500
- return imageExtensions.test(url) || (0, import_utils16.isBase64Image)(url) || isBlobUrl(url);
5501
- };
5502
- const getBase64WithMimeType = (base64) => {
5503
- if (typeof base64 !== "string" || base64.length < 10) return null;
5504
- if ((0, import_utils16.isBase64Image)(base64)) return base64;
5505
- let mimeType = null;
5506
- if (base64.startsWith("iVBORw0KGgo")) mimeType = "image/png";
5507
- else if (base64.startsWith("/9j/")) mimeType = "image/jpeg";
5508
- else if (base64.startsWith("R0lGOD")) mimeType = "image/gif";
5509
- else if (base64.startsWith("Qk")) mimeType = "image/bmp";
5510
- else if (base64.startsWith("UklGR")) mimeType = "image/webp";
5511
- return mimeType ? `data:${mimeType};base64,${base64}` : null;
5512
- };
5513
- const handleFileChange = async (e, onChange) => {
5514
- if (readonly) return;
5515
- const file = e?.target?.files?.[0];
5516
- if (!file) return;
5517
- if (!ALLOWED_TYPES.includes(file.type)) {
5518
- alert(`File type not allowed: ${file.type}`);
5519
- return;
5520
- }
5521
- if (file.size > MAX_FILE_SIZE) {
5522
- alert(`File exceeds 10MB limit.`);
5523
- return;
5524
- }
5525
- const fileUrl = URL.createObjectURL(file);
5526
- setSelectedFile(file);
5527
- setSelectedPreview(fileUrl);
5528
- const base64 = await convertUrlToBase64(fileUrl);
5529
- const base64Data = extractBase64Data(base64);
5530
- methods?.setValue(name, base64Data, { shouldDirty: true });
5531
- onChange(base64Data);
5532
- handleOnchange && handleOnchange(name ?? "", base64Data);
5521
+ const onUploadFile = async (formData) => {
5522
+ const res = await mutateAsync({
5523
+ formData,
5524
+ service,
5525
+ xNode,
5526
+ path
5527
+ });
5528
+ const url2 = res?.url;
5529
+ methods?.setValue(name, url2, { shouldDirty: true });
5530
+ handleOnchange && handleOnchange(name ?? "", url2);
5533
5531
  if (filename) {
5534
5532
  methods?.setValue(
5535
5533
  rootField ? `${rootField?.name}.${index}.${filename}` : filename,
5536
- file?.name,
5534
+ url2?.split("/").pop(),
5537
5535
  { shouldDirty: true }
5538
5536
  );
5539
5537
  handleOnchange && handleOnchange(
5540
5538
  rootField ? `${rootField?.name}.${index}.${filename}` : filename,
5541
- file?.name
5539
+ url2?.split("/").pop()
5542
5540
  );
5543
5541
  }
5542
+ setUrl(url2);
5543
+ fetchFileMeta(url2);
5544
+ return url2;
5544
5545
  };
5545
- const handleRemoveFile = (onChange) => {
5546
- if (selectedPreview) URL.revokeObjectURL(selectedPreview);
5547
- setSelectedFile(null);
5548
- setSelectedPreview(null);
5549
- setInitialFile(null);
5550
- onChange(null);
5546
+ const onDeleteFile = () => {
5547
+ if (filename) {
5548
+ methods?.setValue(
5549
+ rootField ? `${rootField?.name}.${index}.${filename}` : filename,
5550
+ null,
5551
+ { shouldDirty: true }
5552
+ );
5553
+ handleOnchange && handleOnchange(
5554
+ rootField ? `${rootField?.name}.${index}.${filename}` : filename,
5555
+ null
5556
+ );
5557
+ }
5558
+ setUrl(null);
5559
+ setFileInfo(null);
5560
+ methods?.setValue(name, null, { shouldDirty: true });
5551
5561
  handleOnchange && handleOnchange(name ?? "", null);
5552
5562
  };
5553
- (0, import_react22.useEffect)(() => {
5554
- return () => {
5555
- if (selectedPreview) {
5556
- URL.revokeObjectURL(selectedPreview);
5557
- }
5558
- };
5559
- }, [selectedPreview]);
5560
5563
  return {
5561
- inputId,
5562
- selectedFile,
5563
- selectedPreview,
5564
- initialFile,
5565
- binaryRef,
5566
- handleFileChange,
5567
- handleRemoveFile,
5568
- checkIsImageLink,
5569
- getBase64WithMimeType,
5570
- setInitialFile
5564
+ onUploadFile,
5565
+ onDeleteFile,
5566
+ fileInfo,
5567
+ url
5571
5568
  };
5572
5569
  };
5573
5570
 
@@ -5639,7 +5636,7 @@ var tableHeadController = (props) => {
5639
5636
 
5640
5637
  // src/widget/advance/table/table-view/controller.ts
5641
5638
  var import_react24 = require("react");
5642
- var import_utils18 = require("@fctc/interface-logic/utils");
5639
+ var import_utils17 = require("@fctc/interface-logic/utils");
5643
5640
  var tableController = ({ data }) => {
5644
5641
  const [rows, setRows] = (0, import_react24.useState)([]);
5645
5642
  const [columnVisibility, setColumnVisibility] = (0, import_react24.useState)({});
@@ -5682,10 +5679,10 @@ var tableController = ({ data }) => {
5682
5679
  const columns = (0, import_react24.useMemo)(() => {
5683
5680
  try {
5684
5681
  return mergeFields?.filter((item) => {
5685
- return item?.widget !== "details_Receive_money" && !(item?.column_invisible ? import_utils18.domainHelper.matchDomains(
5682
+ return item?.widget !== "details_Receive_money" && !(item?.column_invisible ? import_utils17.domainHelper.matchDomains(
5686
5683
  data.context,
5687
5684
  item?.column_invisible
5688
- ) : item?.invisible ? import_utils18.domainHelper.matchDomains(data.context, item?.invisible) : false);
5685
+ ) : item?.invisible ? import_utils17.domainHelper.matchDomains(data.context, item?.invisible) : false);
5689
5686
  })?.map((field) => {
5690
5687
  const overridden = columnVisibility[field?.name];
5691
5688
  return {
package/dist/widget.mjs CHANGED
@@ -4372,6 +4372,28 @@ function useStorageState(key) {
4372
4372
  );
4373
4373
  return [state, setValue];
4374
4374
  }
4375
+ var guessTypeFromUrl = (url) => {
4376
+ const ext = url.split(".").pop()?.toLowerCase();
4377
+ if (!ext) return null;
4378
+ const map = {
4379
+ jpg: "image/jpeg",
4380
+ jpeg: "image/jpeg",
4381
+ png: "image/png",
4382
+ webp: "image/webp",
4383
+ gif: "image/gif",
4384
+ svg: "image/svg+xml",
4385
+ bmp: "image/bmp",
4386
+ tiff: "image/tiff",
4387
+ pdf: "application/pdf",
4388
+ zip: "application/zip",
4389
+ rar: "application/x-rar-compressed",
4390
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
4391
+ xls: "application/vnd.ms-excel",
4392
+ mp4: "video/mp4",
4393
+ mov: "video/quicktime"
4394
+ };
4395
+ return map[ext] || null;
4396
+ };
4375
4397
 
4376
4398
  // src/utils.ts
4377
4399
  __reExport(utils_exports, utils_star);
@@ -5519,132 +5541,107 @@ var colorFieldController = (props) => {
5519
5541
  };
5520
5542
 
5521
5543
  // src/widget/basic/binary-field/controller.ts
5522
- import { useEffect as useEffect13, useId as useId2, useRef as useRef4, useState as useState12 } from "react";
5523
- import { isBase64Image } from "@fctc/interface-logic/utils";
5524
- var ALLOWED_TYPES = [
5525
- "image/jpeg",
5526
- "image/png",
5527
- "application/pdf",
5528
- "video/mp4",
5529
- "application/zip",
5530
- "application/x-zip-compressed",
5531
- "application/vnd.ms-excel",
5532
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
5533
- "application/json"
5534
- ];
5535
- var MAX_FILE_SIZE = 10 * 1024 * 1024;
5536
- var MAX_TOTAL_SIZE = 50 * 1024 * 1024;
5544
+ import { useEffect as useEffect13, useState as useState12 } from "react";
5537
5545
  var binaryFieldController = (props) => {
5538
5546
  const {
5539
5547
  name,
5540
5548
  methods,
5541
5549
  readonly = false,
5542
- value,
5543
5550
  filename,
5551
+ onChange: handleOnchange,
5552
+ service,
5553
+ xNode,
5554
+ path,
5544
5555
  rootField,
5545
5556
  index,
5546
- onChange: handleOnchange
5557
+ value
5547
5558
  } = props;
5548
- const inputId = useId2();
5549
- const [selectedFile, setSelectedFile] = useState12(null);
5550
- const [selectedPreview, setSelectedPreview] = useState12(null);
5551
- const [initialFile, setInitialFile] = useState12(value || null);
5552
- const binaryRef = useRef4(null);
5553
- const convertUrlToBase64 = async (url) => {
5559
+ const { useUploadFile: useUploadFile2 } = (0, provider_exports.useService)();
5560
+ const { mutateAsync } = useUploadFile2();
5561
+ const [url, setUrl] = useState12(value || null);
5562
+ const [fileInfo, setFileInfo] = useState12(null);
5563
+ useEffect13(() => {
5564
+ if (!value) return;
5565
+ fetchFileMeta(value);
5566
+ }, [value]);
5567
+ const formatSize = (bytes) => {
5568
+ if (bytes < 1024) return bytes + " B";
5569
+ let kb = bytes / 1024;
5570
+ if (kb < 1024) return kb.toFixed(2) + " KB";
5571
+ let mb = kb / 1024;
5572
+ if (mb < 1024) return mb.toFixed(2) + " MB";
5573
+ return (mb / 1024).toFixed(2) + " GB";
5574
+ };
5575
+ const fetchFileMeta = async (url2) => {
5554
5576
  try {
5555
- const response = await fetch(url);
5556
- const blob = await response.blob();
5557
- return new Promise((resolve, reject) => {
5558
- const reader = new FileReader();
5559
- reader.onloadend = () => resolve(reader.result);
5560
- reader.onerror = reject;
5561
- reader.readAsDataURL(blob);
5577
+ const res = await fetch(url2, { method: "HEAD" });
5578
+ const size = res.headers.get("content-length") || "";
5579
+ let type = res.headers.get("content-type") || "";
5580
+ const date = res.headers.get("last-modified") || "";
5581
+ const guessed = guessTypeFromUrl(url2);
5582
+ if (guessed) type = guessed;
5583
+ setFileInfo({
5584
+ size: size ? formatSize(Number(size)) : "--",
5585
+ type,
5586
+ date: date ? new Date(date).toLocaleString() : "--"
5562
5587
  });
5563
- } catch (error) {
5564
- console.error("Error converting URL to Base64:", error);
5565
- throw error;
5588
+ setUrl(url2);
5589
+ } catch (e) {
5590
+ console.error(e);
5566
5591
  }
5567
5592
  };
5568
- const extractBase64Data = (base64Url) => base64Url.includes("base64,") ? base64Url.split("base64,")[1] : base64Url;
5569
- const isBlobUrl = (url) => /^blob:/.test(url);
5570
- const checkIsImageLink = (url) => {
5571
- const imageExtensions = /\.(jpg|jpeg|png|gif|bmp|webp|svg|tiff|ico)$/i;
5572
- return imageExtensions.test(url) || isBase64Image(url) || isBlobUrl(url);
5573
- };
5574
- const getBase64WithMimeType = (base64) => {
5575
- if (typeof base64 !== "string" || base64.length < 10) return null;
5576
- if (isBase64Image(base64)) return base64;
5577
- let mimeType = null;
5578
- if (base64.startsWith("iVBORw0KGgo")) mimeType = "image/png";
5579
- else if (base64.startsWith("/9j/")) mimeType = "image/jpeg";
5580
- else if (base64.startsWith("R0lGOD")) mimeType = "image/gif";
5581
- else if (base64.startsWith("Qk")) mimeType = "image/bmp";
5582
- else if (base64.startsWith("UklGR")) mimeType = "image/webp";
5583
- return mimeType ? `data:${mimeType};base64,${base64}` : null;
5584
- };
5585
- const handleFileChange = async (e, onChange) => {
5586
- if (readonly) return;
5587
- const file = e?.target?.files?.[0];
5588
- if (!file) return;
5589
- if (!ALLOWED_TYPES.includes(file.type)) {
5590
- alert(`File type not allowed: ${file.type}`);
5591
- return;
5592
- }
5593
- if (file.size > MAX_FILE_SIZE) {
5594
- alert(`File exceeds 10MB limit.`);
5595
- return;
5596
- }
5597
- const fileUrl = URL.createObjectURL(file);
5598
- setSelectedFile(file);
5599
- setSelectedPreview(fileUrl);
5600
- const base64 = await convertUrlToBase64(fileUrl);
5601
- const base64Data = extractBase64Data(base64);
5602
- methods?.setValue(name, base64Data, { shouldDirty: true });
5603
- onChange(base64Data);
5604
- handleOnchange && handleOnchange(name ?? "", base64Data);
5593
+ const onUploadFile = async (formData) => {
5594
+ const res = await mutateAsync({
5595
+ formData,
5596
+ service,
5597
+ xNode,
5598
+ path
5599
+ });
5600
+ const url2 = res?.url;
5601
+ methods?.setValue(name, url2, { shouldDirty: true });
5602
+ handleOnchange && handleOnchange(name ?? "", url2);
5605
5603
  if (filename) {
5606
5604
  methods?.setValue(
5607
5605
  rootField ? `${rootField?.name}.${index}.${filename}` : filename,
5608
- file?.name,
5606
+ url2?.split("/").pop(),
5609
5607
  { shouldDirty: true }
5610
5608
  );
5611
5609
  handleOnchange && handleOnchange(
5612
5610
  rootField ? `${rootField?.name}.${index}.${filename}` : filename,
5613
- file?.name
5611
+ url2?.split("/").pop()
5614
5612
  );
5615
5613
  }
5614
+ setUrl(url2);
5615
+ fetchFileMeta(url2);
5616
+ return url2;
5616
5617
  };
5617
- const handleRemoveFile = (onChange) => {
5618
- if (selectedPreview) URL.revokeObjectURL(selectedPreview);
5619
- setSelectedFile(null);
5620
- setSelectedPreview(null);
5621
- setInitialFile(null);
5622
- onChange(null);
5618
+ const onDeleteFile = () => {
5619
+ if (filename) {
5620
+ methods?.setValue(
5621
+ rootField ? `${rootField?.name}.${index}.${filename}` : filename,
5622
+ null,
5623
+ { shouldDirty: true }
5624
+ );
5625
+ handleOnchange && handleOnchange(
5626
+ rootField ? `${rootField?.name}.${index}.${filename}` : filename,
5627
+ null
5628
+ );
5629
+ }
5630
+ setUrl(null);
5631
+ setFileInfo(null);
5632
+ methods?.setValue(name, null, { shouldDirty: true });
5623
5633
  handleOnchange && handleOnchange(name ?? "", null);
5624
5634
  };
5625
- useEffect13(() => {
5626
- return () => {
5627
- if (selectedPreview) {
5628
- URL.revokeObjectURL(selectedPreview);
5629
- }
5630
- };
5631
- }, [selectedPreview]);
5632
5635
  return {
5633
- inputId,
5634
- selectedFile,
5635
- selectedPreview,
5636
- initialFile,
5637
- binaryRef,
5638
- handleFileChange,
5639
- handleRemoveFile,
5640
- checkIsImageLink,
5641
- getBase64WithMimeType,
5642
- setInitialFile
5636
+ onUploadFile,
5637
+ onDeleteFile,
5638
+ fileInfo,
5639
+ url
5643
5640
  };
5644
5641
  };
5645
5642
 
5646
5643
  // src/widget/advance/table/table-head/controller.ts
5647
- import { useMemo as useMemo9, useRef as useRef5 } from "react";
5644
+ import { useMemo as useMemo9, useRef as useRef4 } from "react";
5648
5645
  var tableHeadController = (props) => {
5649
5646
  const {
5650
5647
  typeTable,
@@ -5655,7 +5652,7 @@ var tableHeadController = (props) => {
5655
5652
  setSelectedRowKeys
5656
5653
  } = props;
5657
5654
  const { rowIds: recordIds } = useGetRowIds(tableRef);
5658
- const selectedRowKeysRef = useRef5(recordIds);
5655
+ const selectedRowKeysRef = useRef4(recordIds);
5659
5656
  const isGroupTable = typeTable === "group";
5660
5657
  const recordsCheckedGroup = useMemo9(() => {
5661
5658
  if (!rows || !groupByList) return 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fctc/widget-logic",
3
- "version": "4.8.9",
3
+ "version": "4.9.1",
4
4
  "types": "dist/index.d.ts",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.mjs",
@@ -74,7 +74,7 @@
74
74
  "test": "jest"
75
75
  },
76
76
  "dependencies": {
77
- "@fctc/interface-logic": "^4.1.10",
77
+ "@fctc/interface-logic": "^4.2.1",
78
78
  "@headlessui/react": "^2.2.6",
79
79
  "@tanstack/react-query": "^5.84.0",
80
80
  "i18next": "^25.3.2",