This commit is contained in:
rizky 2024-08-09 19:54:39 -07:00
parent 3d810d5d41
commit 2122f74cc3
8 changed files with 302 additions and 287 deletions

View File

@ -1,11 +1,11 @@
import { useLocal } from "@/utils/use-local"; import { useLocal } from "@/utils/use-local";
import { call_prasi_events } from "lib/exports";
import { FC, useEffect } from "react"; import { FC, useEffect } from "react";
import { FieldProp } from "../typings"; import { FieldProp } from "../typings";
import { useField } from "../utils/use-field"; import { useField } from "../utils/use-field";
import { validate } from "../utils/validate";
import { FieldInput } from "./FieldInput"; import { FieldInput } from "./FieldInput";
import { Label } from "./Label"; import { Label } from "./Label";
import { validate } from "../utils/validate";
import { call_prasi_events } from "lib/exports";
export const Field: FC<FieldProp> = (arg) => { export const Field: FC<FieldProp> = (arg) => {
const showlabel = arg.show_label || "y"; const showlabel = arg.show_label || "y";

View File

@ -63,7 +63,7 @@ export const FieldTypeInput: FC<{
// let value: any = "2024-05-14T05:58:01.376Z" // case untuk date time // let value: any = "2024-05-14T05:58:01.376Z" // case untuk date time
field.input = input; field.input = input;
field.prop = prop; if (!field.prop) field.prop = prop;
if (["date", "datetime", "datetime-local", "time"].includes(type_field)) { if (["date", "datetime", "datetime-local", "time"].includes(type_field)) {
if (typeof value === "string" || value instanceof Date) { if (typeof value === "string" || value instanceof Date) {
let date = parse(value); let date = parse(value);

View File

@ -1,20 +1,11 @@
import { useLocal } from "@/utils/use-local"; import { useLocal } from "@/utils/use-local";
import get from "lodash.get"; import get from "lodash.get";
import { Loader2, Paperclip, Trash2, Upload } from "lucide-react";
import { FC } from "react"; import { FC } from "react";
import { FMLocal, FieldLocal, FieldProp } from "../../typings";
import { PropTypeInput } from "./TypeInput";
import * as XLSX from "xlsx"; import * as XLSX from "xlsx";
import { import { FMLocal, FieldLocal, FieldProp } from "../../typings";
ArrowDownToLine,
ExternalLink,
Loader2,
Paperclip,
SquareArrowOutUpRight,
Trash2,
Upload,
} from "lucide-react";
import { Spinner } from "lib/comps/ui/field-loading";
import { FilePreview } from "./FilePreview"; import { FilePreview } from "./FilePreview";
import { PropTypeInput } from "./TypeInput";
const w = window as unknown as { const w = window as unknown as {
serverurl: string; serverurl: string;
}; };
@ -27,264 +18,6 @@ export const FieldUpload: FC<{
arg: FieldProp; arg: FieldProp;
on_change: (e: any) => void | Promise<void>; on_change: (e: any) => void | Promise<void>;
}> = ({ field, fm, prop, on_change, arg }) => { }> = ({ field, fm, prop, on_change, arg }) => {
const styling = arg.upload_style ? arg.upload_style : "full"; console.log(field.prop.upload);
let type_field = prop.sub_type; return <></>;
let value: any = fm.data[field.name];
// let type_upload =
const input = useLocal({
value: 0 as any,
display: false as any,
ref: null as any,
drop: false as boolean,
fase: value ? "preview" : ("start" as "start" | "upload" | "preview"),
style: "inline" as "inline" | "full",
});
const on_upload = async (event: any) => {
let file = null;
try {
file = event.target.files[0];
} catch (ex) {}
if (type_field === "import") {
const reader = new FileReader();
reader.onload = (e: any) => {
const binaryStr = 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") {
const res = on_change({
value: jsonData,
file: file,
binnary: e.target.result,
});
}
};
reader.readAsBinaryString(file);
} else {
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();
}
input.fase = "upload";
input.render();
try {
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)) {
fm.data[field.name] = `_file${get(result, "[0]")}`;
fm.render();
setTimeout(() => {
input.fase = "preview";
input.render();
}, 1000);
} else {
input.fase = "start";
input.render();
alert("Error upload");
}
} else {
}
} catch (ex) {
input.fase = "start";
input.render();
alert("Error upload");
}
}
if (input.ref) {
input.ref.value = null;
}
};
if (isEditor) input.fase = "start";
return (
<div className="c-flex-grow c-flex-row c-flex c-w-full c-h-full c-items-stretch">
{input.fase === "start" ? (
<>
<div
className={cx(
"c-flex c-flex-row c-relative c-flex-grow c-items-center c-cursor-pointer hover:c-bg-blue-50",
css`
input[type="file"],
input[type="file"]::-webkit-file-upload-button {
cursor: pointer;
}
`
)}
>
{!isEditor && (
<input
ref={(ref) => (input.ref = ref)}
type="file"
multiple={false}
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"
)}
/>
)}
{styling !== "full" ? (
<>
<div
onClick={() => {
if (input.ref) {
console.log(input.ref);
input.ref.click();
}
}}
className="c-items-center c-flex c-text-base c-px-3 c-outline-none c-rounded c-cursor-pointer "
>
<div className="c-flex c-flex-row c-items-center c-px-2">
<Upload className="c-h-4 c-w-4" />
</div>
<div className="c-flex c-flex-row c-items-center">
Upload File
</div>
</div>
</>
) : (
<>
<div
onDrop={(e: any) => {
e.preventDefault();
input.drop = false;
input.render();
}}
onDragOver={(e: any) => {
// Prevent default behavior (Prevent file from being opened)
e.preventDefault();
input.drop = true;
input.render();
}}
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"
)}
>
<div className="c-flex-row c-flex c-flex-grow c-space-x-2">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 14 14"
>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M3.5 13.5h-2a1 1 0 0 1-1-1v-8h13v8a1 1 0 0 1-1 1h-2" />
<path d="M4.5 10L7 7.5L9.5 10M7 7.5v6M11.29 1a1 1 0 0 0-.84-.5h-6.9a1 1 0 0 0-.84.5L.5 4.5h13zM7 .5v4" />
</g>
</svg>
<div className="c-flex c-flex-col">
<span className="c-font-medium">
Drop Your File or{" "}
<span className="c-underline c-text-blue-500">
Browse
</span>
</span>
</div>
</div>
</div>
</>
)}
</div>
</>
) : input.fase === "upload" ? (
<div className="c-flex c-items-center">
<div className="c-px-2">
<Loader2 className={cx("c-h-5 c-w-5 c-animate-spin")} />
</div>
<div className="">Uploading</div>
</div>
) : input.fase === "preview" ? (
<div className="c-flex c-justify-between c-flex-1 c-p-1">
<FilePreview url={value || ""} />
<div className="c-flex c-flex-row c-items-center c-border c-px-2 c-rounded c-cursor-pointer hover:c-bg-red-100">
<Trash2
className="c-text-red-500 c-h-4 c-w-4"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
if (confirm("Clear this file ?")) {
input.fase = "start";
fm.data[field.name] = null;
fm.render();
}
}}
/>
</div>
</div>
) : (
<></>
)}
</div>
);
};
const IconFile: FC<{ type: string }> = ({ type }) => {
if (["xlsx"].includes(type)) {
return (
<div className="c-flex c-flex-row c-text-[#2a801d]">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="m2.859 2.877l12.57-1.795a.5.5 0 0 1 .571.494v20.848a.5.5 0 0 1-.57.494L2.858 21.123a1 1 0 0 1-.859-.99V3.867a1 1 0 0 1 .859-.99M17 3h4a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1h-4zm-6.8 9L13 8h-2.4L9 10.286L7.4 8H5l2.8 4L5 16h2.4L9 13.714L10.6 16H13z"
/>
</svg>
</div>
);
} else {
return (
<div className="c-flex c-flex-row ">
<Paperclip className="c-h-5 c-w-5" />
</div>
);
}
return (
<div className="c-flex c-flex-row c-p-2">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="m2.859 2.877l12.57-1.795a.5.5 0 0 1 .571.494v20.848a.5.5 0 0 1-.57.494L2.858 21.123a1 1 0 0 1-.859-.99V3.867a1 1 0 0 1 .859-.99M17 3h4a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1h-4zm-6.8 9L13 8h-2.4L9 10.286L7.4 8H5l2.8 4L5 16h2.4L9 13.714L10.6 16H13z"
/>
</svg>
</div>
);
}; };

View File

View File

@ -0,0 +1,281 @@
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;
};
export const FieldUploadSingle: FC<{
field: FieldLocal;
fm: FMLocal;
prop: PropTypeInput;
styling?: string;
arg: FieldProp;
on_change: (e: any) => void | Promise<void>;
}> = ({ 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({
value: 0 as any,
display: false as any,
ref: null as any,
drop: false as boolean,
fase: value ? "preview" : ("start" as "start" | "upload" | "preview"),
style: "inline" as "inline" | "full",
});
const on_upload = async (event: any) => {
let file = null;
try {
file = event.target.files[0];
} catch (ex) {}
if (type_field === "import") {
const reader = new FileReader();
reader.onload = (e: any) => {
const binaryStr = 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") {
const res = on_change({
value: jsonData,
file: file,
binnary: e.target.result,
});
}
};
reader.readAsBinaryString(file);
} else {
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();
}
input.fase = "upload";
input.render();
try {
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)) {
fm.data[field.name] = `_file${get(result, "[0]")}`;
fm.render();
setTimeout(() => {
input.fase = "preview";
input.render();
}, 1000);
} else {
input.fase = "start";
input.render();
alert("Error upload");
}
} else {
}
} catch (ex) {
input.fase = "start";
input.render();
alert("Error upload");
}
}
if (input.ref) {
input.ref.value = null;
}
};
if (isEditor) input.fase = "start";
return (
<div className="c-flex-grow c-flex-row c-flex c-w-full c-h-full c-items-stretch">
{input.fase === "start" ? (
<>
<div
className={cx(
"c-flex c-flex-row c-relative c-flex-grow c-items-center c-cursor-pointer hover:c-bg-blue-50",
css`
input[type="file"],
input[type="file"]::-webkit-file-upload-button {
cursor: pointer;
}
`
)}
>
{!isEditor && (
<input
ref={(ref) => (input.ref = ref)}
type="file"
multiple={false}
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"
)}
/>
)}
{styling !== "full" ? (
<>
<div
onClick={() => {
if (input.ref) {
console.log(input.ref);
input.ref.click();
}
}}
className="c-items-center c-flex c-text-base c-px-1 c-outline-none c-rounded c-cursor-pointer "
>
<div className="c-flex c-flex-row c-items-center c-px-2">
<Upload className="c-h-4 c-w-4" />
</div>
<div className="c-flex c-flex-row c-items-center">
Upload File
</div>
</div>
</>
) : (
<>
<div
onDrop={(e: any) => {
e.preventDefault();
input.drop = false;
input.render();
}}
onDragOver={(e: any) => {
// Prevent default behavior (Prevent file from being opened)
e.preventDefault();
input.drop = true;
input.render();
}}
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"
)}
>
<div className="c-flex-row c-flex c-flex-grow c-space-x-2">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 14 14"
>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M3.5 13.5h-2a1 1 0 0 1-1-1v-8h13v8a1 1 0 0 1-1 1h-2" />
<path d="M4.5 10L7 7.5L9.5 10M7 7.5v6M11.29 1a1 1 0 0 0-.84-.5h-6.9a1 1 0 0 0-.84.5L.5 4.5h13zM7 .5v4" />
</g>
</svg>
<div className="c-flex c-flex-col">
<span className="c-font-medium">
Drop Your File or{" "}
<span className="c-underline c-text-blue-500">
Browse
</span>
</span>
</div>
</div>
</div>
</>
)}
</div>
</>
) : input.fase === "upload" ? (
<div className="c-flex c-items-center">
<div className="c-px-2">
<Loader2 className={cx("c-h-5 c-w-5 c-animate-spin")} />
</div>
<div className="">Uploading</div>
</div>
) : input.fase === "preview" ? (
<div className="c-flex c-justify-between c-flex-1 c-p-1">
<FilePreview url={value || ""} />
<div className="c-flex c-flex-row c-items-center c-border c-px-2 c-rounded c-cursor-pointer hover:c-bg-red-100">
<Trash2
className="c-text-red-500 c-h-4 c-w-4"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
if (confirm("Clear this file ?")) {
input.fase = "start";
fm.data[field.name] = null;
fm.render();
}
}}
/>
</div>
</div>
) : (
<></>
)}
</div>
);
};
const IconFile: FC<{ type: string }> = ({ type }) => {
if (["xlsx"].includes(type)) {
return (
<div className="c-flex c-flex-row c-text-[#2a801d]">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="m2.859 2.877l12.57-1.795a.5.5 0 0 1 .571.494v20.848a.5.5 0 0 1-.57.494L2.858 21.123a1 1 0 0 1-.859-.99V3.867a1 1 0 0 1 .859-.99M17 3h4a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1h-4zm-6.8 9L13 8h-2.4L9 10.286L7.4 8H5l2.8 4L5 16h2.4L9 13.714L10.6 16H13z"
/>
</svg>
</div>
);
} else {
return (
<div className="c-flex c-flex-row ">
<Paperclip className="c-h-5 c-w-5" />
</div>
);
}
return (
<div className="c-flex c-flex-row c-p-2">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="m2.859 2.877l12.57-1.795a.5.5 0 0 1 .571.494v20.848a.5.5 0 0 1-.57.494L2.858 21.123a1 1 0 0 1-.859-.99V3.867a1 1 0 0 1 .859-.99M17 3h4a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1h-4zm-6.8 9L13 8h-2.4L9 10.286L7.4 8H5l2.8 4L5 16h2.4L9 13.714L10.6 16H13z"
/>
</svg>
</div>
);
};

View File

@ -48,6 +48,7 @@ export type FieldProp = {
label: string; label: string;
desc?: string; desc?: string;
props?: any; props?: any;
upload?: { mode: "single-file" | "multi-file" };
link: { link: {
text: text:
| string | string
@ -107,10 +108,7 @@ export type FieldProp = {
current: any; current: any;
options: { value: string; label: string; item?: any }[]; options: { value: string; label: string; item?: any }[];
}) => boolean; }) => boolean;
on_init: (arg: { on_init: (arg: { field: any; name: string }) => void;
field: any,
name: string
}) => void;
pk: string; pk: string;
sub_type: string; sub_type: string;
placeholder: string; placeholder: string;
@ -189,11 +187,11 @@ export type FieldInternal<T extends FieldProp["type"]> = {
name: string; name: string;
fm: FMLocal; fm: FMLocal;
}) => void | Promise<void>; }) => void | Promise<void>;
prop?: any; prop?: FieldProp;
max_date?: FieldProp["max_date"]; max_date?: FieldProp["max_date"];
min_date?: FieldProp["min_date"]; min_date?: FieldProp["min_date"];
error?: any; error?: any;
table_fields?: any[] table_fields?: any[];
}; };
export type FieldLocal = FieldInternal<any> & { export type FieldLocal = FieldInternal<any> & {
render: () => void; render: () => void;

View File

@ -16,9 +16,9 @@ export const useField = (
input: {}, input: {},
ref: null as any, ref: null as any,
} as any); } as any);
const ref = useRef(null as any) const ref = useRef(null as any);
field.ref = ref; field.ref = ref;
const name = typeof arg.name === "string" ? arg.name : arg.name(); const name = typeof arg.name === "string" ? arg.name : arg.name();
const label = typeof arg.label === "string" ? arg.label : arg.label(); const label = typeof arg.label === "string" ? arg.label : arg.label();
const required = const required =
@ -34,11 +34,12 @@ export const useField = (
custom: arg.custom, custom: arg.custom,
required: required === "y", required: required === "y",
required_msg: arg.required_msg, required_msg: arg.required_msg,
disabled: typeof arg.disabled === "function" ? arg.disabled : arg.disabled === "y", disabled:
typeof arg.disabled === "function" ? arg.disabled : arg.disabled === "y",
on_change: arg.on_change, on_change: arg.on_change,
max_date: arg.max_date, max_date: arg.max_date,
min_date: arg.min_date, min_date: arg.min_date,
table_fields: [] table_fields: [],
}; };
if (field.status === "init" || isEditor) { if (field.status === "init" || isEditor) {
@ -52,13 +53,14 @@ export const useField = (
useEffect(() => { useEffect(() => {
if (field.status === "init" || !fm.fields[name]) { if (field.status === "init" || !fm.fields[name]) {
field.status = "ready"; field.status = "ready";
if(!fm.fields){ if (!fm.fields) {
fm.fields = {} fm.fields = {};
} }
fm.fields[name] = field; fm.fields[name] = field;
field.render(); field.render();
} }
}, []); }, []);
field.prop = arg;
return field; return field;
}; };

View File

@ -54,6 +54,7 @@ const form = lazifyMany({
Form: async () => (await import("@/comps/form/Form")).Form, Form: async () => (await import("@/comps/form/Form")).Form,
Field: async () => (await import("@/comps/form/field/Field")).Field, Field: async () => (await import("@/comps/form/field/Field")).Field,
}); });
export const Form = form.Form; export const Form = form.Form;
export const Field = form.Field; export const Field = form.Field;