diff --git a/comps/form/field/type/FilePreview.tsx b/comps/form/field/type/FilePreview.tsx
index 952f180..8d88d4b 100755
--- a/comps/form/field/type/FilePreview.tsx
+++ b/comps/form/field/type/FilePreview.tsx
@@ -1,12 +1,38 @@
import { ExternalLink } from "lucide-react";
-export const FilePreview = ({ url }: { url: string }) => {
+export const FilePreview = ({
+ url,
+ variant,
+}: {
+ url: string;
+ variant?: "thumb";
+}) => {
const file = getFileName(url);
+ if (typeof file === "string")
+ return (
+
+ {file}
+
+ );
const color = darkenColor(generateRandomColor(file.extension));
let content = (
{
{file.extension}
);
+
+ if (variant === "thumb") {
+ content = (
+
+ {file.extension}
+
+
+
+
+
+ );
+ }
+
if (url.startsWith("_file/")) {
if ([".png", ".jpeg", ".jpg", ".webp"].find((e) => url.endsWith(e))) {
content = (
);
}
@@ -36,16 +96,33 @@ export const FilePreview = ({ url }: { url: string }) => {
<>
{file.extension && (
{
let _url = siteurl(url || "");
window.open(_url, "_blank");
}}
>
{content}
-
-
-
+ {variant !== "thumb" && (
+
+
+
+ )}
)}
>
@@ -90,6 +167,17 @@ function generateRandomColor(str: string): string {
return color;
}
const getFileName = (url: string) => {
+ if (url.startsWith("[")) {
+ try {
+ const list = JSON.parse(url);
+ if (list.length === 0) return "Empty";
+ return `${list.length} File${list.length > 1 ? "s" : ""}`;
+ } catch (e) {
+ console.error(`Error parsing multi-file: ${url}`);
+ }
+ return "Unknown File";
+ }
+
const fileName = url.substring(url.lastIndexOf("/") + 1);
const dotIndex = fileName.lastIndexOf(".");
const fullname = fileName;
diff --git a/comps/form/field/type/TypeInput.tsx b/comps/form/field/type/TypeInput.tsx
index f84eb33..d0fd8bd 100755
--- a/comps/form/field/type/TypeInput.tsx
+++ b/comps/form/field/type/TypeInput.tsx
@@ -63,7 +63,6 @@ export const FieldTypeInput: FC<{
// let value: any = "2024-05-14T05:58:01.376Z" // case untuk date time
field.input = input;
- if (!field.prop) field.prop = prop;
if (["date", "datetime", "datetime-local", "time"].includes(type_field)) {
if (typeof value === "string" || value instanceof Date) {
let date = parse(value);
diff --git a/comps/form/field/type/TypeUpload.tsx b/comps/form/field/type/TypeUpload.tsx
index 2900b35..a1aedcc 100755
--- a/comps/form/field/type/TypeUpload.tsx
+++ b/comps/form/field/type/TypeUpload.tsx
@@ -1,14 +1,8 @@
-import { useLocal } from "@/utils/use-local";
-import get from "lodash.get";
-import { Loader2, Paperclip, Trash2, Upload } from "lucide-react";
import { FC } from "react";
-import * as XLSX from "xlsx";
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
-import { FilePreview } from "./FilePreview";
import { PropTypeInput } from "./TypeInput";
-const w = window as unknown as {
- serverurl: string;
-};
+import { FieldUploadMulti } from "./TypeUploadMulti";
+import { FieldUploadSingle } from "./TypeUploadSingle";
export const FieldUpload: FC<{
field: FieldLocal;
@@ -17,7 +11,11 @@ export const FieldUpload: FC<{
styling?: string;
arg: FieldProp;
on_change: (e: any) => void | Promise;
-}> = ({ field, fm, prop, on_change, arg }) => {
- console.log(field.prop.upload);
- return <>>;
+}> = (pass) => {
+ const { field, fm, prop, on_change, arg } = pass;
+ let mode = field.prop.upload?.mode || "single-file";
+ if (mode === "single-file") {
+ return ;
+ }
+ return ;
};
diff --git a/comps/form/field/type/TypeUploadMulti.tsx b/comps/form/field/type/TypeUploadMulti.tsx
index e69de29..d70df46 100755
--- a/comps/form/field/type/TypeUploadMulti.tsx
+++ b/comps/form/field/type/TypeUploadMulti.tsx
@@ -0,0 +1,223 @@
+import { useLocal } from "@/utils/use-local";
+import get from "lodash.get";
+import { Trash2, Upload } from "lucide-react";
+import { ChangeEvent, FC } from "react";
+import { FMLocal, FieldLocal, FieldProp } from "../../typings";
+import { PropTypeInput } from "./TypeInput";
+import { FilePreview } from "./FilePreview";
+import { Spinner } from "lib/comps/ui/field-loading";
+const w = window as unknown as {
+ serverurl: string;
+};
+
+export const FieldUploadMulti: FC<{
+ field: FieldLocal;
+ fm: FMLocal;
+ prop: PropTypeInput;
+ styling?: string;
+ arg: FieldProp;
+ on_change: (e: any) => void | Promise;
+}> = ({ field, fm, prop, on_change, arg }) => {
+ let value: string = (fm.data[field.name] || "").trim();
+ // let type_upload =
+ const input = useLocal({
+ value: 0 as any,
+ display: false as any,
+ ref: null as any,
+ drop: false as boolean,
+ uploading: new Set(),
+ fase: value ? "preview" : ("start" as "start" | "upload" | "preview"),
+ style: "inline" as "inline" | "full",
+ });
+
+ const parse_list = () => {
+ let list: string[] = [];
+ if (value.startsWith("[")) {
+ try {
+ list = JSON.parse(value);
+ } catch (e) {}
+ } else if (typeof value === "string" && value) {
+ list.push(value);
+ }
+ return list;
+ };
+
+ const on_upload = async (event: ChangeEvent) => {
+ const upload_single = async (file: File) => {
+ const formData = new FormData();
+ formData.append("file", file);
+
+ let url = siteurl("/_upload");
+ if (
+ location.hostname === "prasi.avolut.com" ||
+ location.host === "localhost:4550"
+ ) {
+ const newurl = new URL(location.href);
+ newurl.pathname = `/_proxy/${url}`;
+ url = newurl.toString();
+ }
+ const response = await fetch(url, {
+ method: "POST",
+ body: formData,
+ });
+
+ if (response.ok) {
+ const contentType: any = response.headers.get("content-type");
+ let result;
+ if (contentType.includes("application/json")) {
+ result = await response.json();
+ } else if (contentType.includes("text/plain")) {
+ result = await response.text();
+ } else {
+ result = await response.blob();
+ }
+ if (Array.isArray(result)) {
+ return `_file${get(result, "[0]")}`;
+ }
+ }
+ throw new Error("Upload Failed");
+ };
+
+ if (event.target.files) {
+ const list = parse_list();
+
+ for (let i = 0; i < event.target.files.length; i++) {
+ const file = event.target?.files?.item(i);
+ if (file) {
+ input.uploading.add(file);
+ upload_single(file).then((path) => {
+ input.uploading.delete(file);
+ list.push(path);
+ fm.data[field.name] = JSON.stringify(list);
+ fm.render();
+ });
+ }
+ }
+ input.render();
+ }
+ if (input.ref) {
+ input.ref.value = null;
+ }
+ };
+
+ if (isEditor) input.fase = "start";
+
+ const list = parse_list();
+
+ return (
+
+
+ {list.map((value, idx) => {
+ return (
+
{
+ e.stopPropagation();
+ e.preventDefault();
+ }}
+ >
+
+
+
{
+ e.preventDefault();
+ e.stopPropagation();
+ if (confirm("Remove this file ?")) {
+ list.splice(idx, 1);
+ fm.data[field.name] = JSON.stringify(list);
+ fm.render();
+ }
+ }}
+ className={cx(
+ "c-flex c-flex-row c-items-center c-px-1 c-rounded c-bg-white c-cursor-pointer hover:c-bg-red-100 c-absolute c-top-0 c-right-0 del transition-all",
+ css`
+ border: 1px solid red;
+ width: 25px;
+ height: 25px;
+ margin: 5px;
+ `
+ )}
+ >
+
+
+
+
+ );
+ })}
+ {input.uploading.size > 0 && (
+
+ )}
+
+
+
+
+ {!isEditor && (
+
{
+ if (!input.ref) {
+ input.ref = ref;
+ }
+ }}
+ type="file"
+ multiple={true}
+ accept={field.prop.upload?.accept}
+ onChange={on_upload}
+ className={cx(
+ "c-absolute c-w-full c-h-full c-cursor-pointer c-top-0 c-left-0 c-opacity-0"
+ )}
+ />
+ )}
+
+
+
+
+
+ Upload File
+
+
+
+
+
{
+ e.stopPropagation();
+ e.preventDefault();
+ }}
+ >
+
+
+ );
+};
diff --git a/comps/form/field/type/TypeUploadSingle.tsx b/comps/form/field/type/TypeUploadSingle.tsx
index a3012eb..8279db2 100755
--- a/comps/form/field/type/TypeUploadSingle.tsx
+++ b/comps/form/field/type/TypeUploadSingle.tsx
@@ -1,7 +1,7 @@
import { useLocal } from "@/utils/use-local";
import get from "lodash.get";
import { Loader2, Paperclip, Trash2, Upload } from "lucide-react";
-import { FC } from "react";
+import { ChangeEvent, FC } from "react";
import * as XLSX from "xlsx";
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
import { FilePreview } from "./FilePreview";
@@ -19,7 +19,6 @@ export const FieldUploadSingle: FC<{
on_change: (e: any) => void | Promise;
}> = ({ field, fm, prop, on_change, arg }) => {
const styling = arg.upload_style ? arg.upload_style : "full";
- let type_field = prop.sub_type;
let value: any = fm.data[field.name];
// let type_upload =
const input = useLocal({
@@ -31,30 +30,46 @@ export const FieldUploadSingle: FC<{
style: "inline" as "inline" | "full",
});
- const on_upload = async (event: any) => {
+ const on_upload = async (event: ChangeEvent) => {
let file = null;
try {
- file = event.target.files[0];
+ file = event.target?.files?.[0];
} catch (ex) {}
- if (type_field === "import") {
+ if (prop.model_upload === "import") {
const reader = new FileReader();
- reader.onload = (e: any) => {
- const binaryStr = e.target.result;
- const workbook = XLSX.read(binaryStr, { type: "binary" });
+ function arrayBufferToBinaryString(buffer: ArrayBuffer): string {
+ const bytes = new Uint8Array(buffer);
+ return String.fromCharCode.apply(null, Array.from(bytes));
+ }
- const worksheet = workbook.Sheets[workbook.SheetNames[0]];
- const jsonData = XLSX.utils.sheet_to_json(worksheet);
- if (typeof on_change === "function") {
- const res = on_change({
- value: jsonData,
- file: file,
- binnary: e.target.result,
- });
+ reader.onload = (e: ProgressEvent) => {
+ if (e.target && e.target.result) {
+ const binaryStr =
+ typeof e.target.result === "string"
+ ? e.target.result
+ : arrayBufferToBinaryString(e.target.result);
+ const workbook = XLSX.read(binaryStr, { type: "binary" });
+
+ const worksheet = workbook.Sheets[workbook.SheetNames[0]];
+ const jsonData = XLSX.utils.sheet_to_json(worksheet);
+ if (typeof on_change === "function") {
+ on_change({
+ value: jsonData,
+ file: file,
+ binnary: e.target.result,
+ });
+ }
}
};
- reader.readAsBinaryString(file);
- } else {
+ if (file) {
+ if (typeof reader.readAsArrayBuffer === "function") {
+ reader.readAsArrayBuffer(file);
+ } else {
+ reader.readAsBinaryString(file);
+ }
+ }
+ } else if (file) {
const formData = new FormData();
formData.append("file", file);
@@ -132,6 +147,7 @@ export const FieldUploadSingle: FC<{
ref={(ref) => (input.ref = ref)}
type="file"
multiple={false}
+ accept={field.prop.upload?.accept}
onChange={on_upload}
className={cx(
"c-absolute c-w-full c-h-full c-cursor-pointer c-top-0 c-left-0 c-opacity-0"
@@ -152,7 +168,7 @@ export const FieldUploadSingle: FC<{
-
@@ -173,7 +189,7 @@ export const FieldUploadSingle: FC<{
}}
className={cx(
input.drop ? "c-bg-gray-100" : "",
- "hover:c-bg-gray-100 c-flex-grow c-m-1 c-relative c-flex-grow c-p-4 c-items-center c-flex c-flex-row c-text-gray-400 c-border c-border-gray-200 c-border-dashed c-rounded c-cursor-pointer"
+ "hover:c-bg-gray-100 c-m-1 c-relative c-flex-grow c-p-4 c-items-center c-flex c-flex-row c-text-gray-400 c-border c-border-gray-200 c-border-dashed c-rounded c-cursor-pointer"
)}
>
@@ -217,19 +233,19 @@ export const FieldUploadSingle: FC<{
) : input.fase === "preview" ? (
-
-
{
- e.preventDefault();
- e.stopPropagation();
- if (confirm("Clear this file ?")) {
- input.fase = "start";
- fm.data[field.name] = null;
- fm.render();
- }
- }}
- />
+ {
+ e.preventDefault();
+ e.stopPropagation();
+ if (confirm("Clear this file ?")) {
+ input.fase = "start";
+ fm.data[field.name] = null;
+ fm.render();
+ }
+ }}
+ className="c-flex c-flex-row c-items-center c-border c-px-2 c-rounded c-cursor-pointer hover:c-bg-red-100"
+ >
+
) : (
diff --git a/comps/form/typings.ts b/comps/form/typings.ts
index 1a1d81d..e1dd6c3 100755
--- a/comps/form/typings.ts
+++ b/comps/form/typings.ts
@@ -48,7 +48,7 @@ export type FieldProp = {
label: string;
desc?: string;
props?: any;
- upload?: { mode: "single-file" | "multi-file" };
+ upload?: { mode: "single-file" | "multi-file"; accept: string };
link: {
text:
| string
@@ -187,7 +187,7 @@ export type FieldInternal
= {
name: string;
fm: FMLocal;
}) => void | Promise;
- prop?: FieldProp;
+ prop: FieldProp;
max_date?: FieldProp["max_date"];
min_date?: FieldProp["min_date"];
error?: any;
diff --git a/comps/form/utils/init.tsx b/comps/form/utils/init.tsx
index 8afacad..c749f79 100755
--- a/comps/form/utils/init.tsx
+++ b/comps/form/utils/init.tsx
@@ -56,7 +56,12 @@ export const formInit = (fm: FMLocal, props: FMProps) => {