255 lines
6.9 KiB
TypeScript
255 lines
6.9 KiB
TypeScript
"use client";
|
|
import { useLocal } from "@/lib/utils/use-local";
|
|
import { AlertTriangle, Check, Loader2 } from "lucide-react";
|
|
import { ReactNode, useEffect, useState } from "react";
|
|
import { toast } from "sonner";
|
|
import {
|
|
ResizableHandle,
|
|
ResizablePanel,
|
|
ResizablePanelGroup,
|
|
} from "../ui/resize";
|
|
import get from "lodash.get";
|
|
import { Skeleton } from "../ui/Skeleton";
|
|
|
|
type Local<T> = {
|
|
data: T | null;
|
|
submit: () => Promise<void>;
|
|
render: () => void;
|
|
};
|
|
|
|
export const Form: React.FC<any> = ({
|
|
children,
|
|
header,
|
|
onLoad,
|
|
onSubmit,
|
|
onFooter,
|
|
showResize,
|
|
mode,
|
|
className,
|
|
onInit,
|
|
}) => {
|
|
const local = useLocal({
|
|
ready: false,
|
|
data: null as any | null,
|
|
submit: async () => {
|
|
toast.info(
|
|
<>
|
|
<Loader2
|
|
className={cx(
|
|
"h-4 w-4 animate-spin-important",
|
|
css`
|
|
animation: spin 1s linear infinite !important;
|
|
@keyframes spin {
|
|
0% {
|
|
transform: rotate(0deg);
|
|
}
|
|
100% {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
`
|
|
)}
|
|
/>
|
|
{"Saving..."}
|
|
</>
|
|
);
|
|
try {
|
|
await onSubmit(local);
|
|
setTimeout(() => {
|
|
toast.success(
|
|
<div
|
|
className={cx(
|
|
"cursor-pointer flex flex-col select-none items-stretch flex-1 w-full"
|
|
)}
|
|
onClick={() => {
|
|
toast.dismiss();
|
|
}}
|
|
>
|
|
<div className="flex text-green-700 items-center success-title font-semibold">
|
|
<Check className="h-6 w-6 mr-1 " />
|
|
Record Saved
|
|
</div>
|
|
</div>
|
|
);
|
|
}, 1000);
|
|
} catch (ex: any) {
|
|
const msg = get(ex, "response.data.meta.message") || ex.message;
|
|
toast.error(
|
|
<div className="flex flex-col w-full">
|
|
<div className="flex text-red-600 items-center">
|
|
<AlertTriangle className="h-4 w-4 mr-1" />
|
|
Submit Failed {msg}.
|
|
</div>
|
|
</div>,
|
|
{
|
|
dismissible: true,
|
|
className: css`
|
|
background: #ffecec;
|
|
border: 2px solid red;
|
|
`,
|
|
}
|
|
);
|
|
}
|
|
},
|
|
reload: async () => {
|
|
local.ready = false;
|
|
local.render();
|
|
toast.info(
|
|
<>
|
|
<Loader2
|
|
className={cx(
|
|
"h-4 w-4 animate-spin-important",
|
|
css`
|
|
animation: spin 1s linear infinite !important;
|
|
@keyframes spin {
|
|
0% {
|
|
transform: rotate(0deg);
|
|
}
|
|
100% {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
`
|
|
)}
|
|
/>
|
|
{"Loading..."}
|
|
</>
|
|
);
|
|
local.data = null;
|
|
local.render();
|
|
const res = onLoad();
|
|
if (res instanceof Promise) {
|
|
res.then((data) => {
|
|
local.ready = true;
|
|
local.data = data;
|
|
local.render(); // Panggil render setelah data diperbarui
|
|
// toast.dismiss();
|
|
// toast.success("Data Loaded Successfully!");
|
|
});
|
|
} else {
|
|
local.ready = true;
|
|
local.data = res;
|
|
local.render(); // Panggil render untuk memicu re-render
|
|
toast.dismiss();
|
|
toast.success("Data Loaded Successfully!");
|
|
}
|
|
},
|
|
fields: {} as any,
|
|
render: () => {},
|
|
error: {} as any,
|
|
mode,
|
|
});
|
|
useEffect(() => {
|
|
if (typeof onInit === "function") {
|
|
onInit(local);
|
|
}
|
|
local.ready = false;
|
|
local.render();
|
|
toast.info(
|
|
<>
|
|
<Loader2
|
|
className={cx(
|
|
"h-4 w-4 animate-spin-important",
|
|
css`
|
|
animation: spin 1s linear infinite !important;
|
|
@keyframes spin {
|
|
0% {
|
|
transform: rotate(0deg);
|
|
}
|
|
100% {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
`
|
|
)}
|
|
/>
|
|
{"Loading..."}
|
|
</>
|
|
);
|
|
const res = onLoad();
|
|
if (res instanceof Promise) {
|
|
res.then((data) => {
|
|
local.ready = true;
|
|
local.data = data;
|
|
local.render(); // Panggil render setelah data diperbarui
|
|
// toast.dismiss();
|
|
// toast.success("Data Loaded Successfully!");
|
|
});
|
|
} else {
|
|
local.ready = true;
|
|
local.data = res;
|
|
local.render(); // Panggil render untuk memicu re-render
|
|
toast.dismiss();
|
|
toast.success("Data Loaded Successfully!");
|
|
}
|
|
}, []);
|
|
|
|
// Tambahkan dependency ke header agar reaktif
|
|
const HeaderComponent = header(local);
|
|
if (!local.ready)
|
|
return (
|
|
<div className="flex flex-grow flex-row items-center justify-center">
|
|
<div className="space-y-2">
|
|
<Skeleton className="h-4 w-[250px]" />
|
|
<Skeleton className="h-16 w-[250px]" />
|
|
</div>
|
|
</div>
|
|
);
|
|
return (
|
|
<div className={`flex-grow flex-col flex ${className}`}>
|
|
<div className="flex flex-row">{header(local)}</div>
|
|
{showResize ? (
|
|
// Resize panels...
|
|
<ResizablePanelGroup direction="vertical" className="rounded-lg border">
|
|
<ResizablePanel className="border-none flex flex-col">
|
|
<form
|
|
className="flex flex-grow flex-col"
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
local.submit();
|
|
}}
|
|
>
|
|
{local.ready ? (
|
|
children(local)
|
|
) : (
|
|
<div>
|
|
<div className="space-y-2">
|
|
<Skeleton className="h-4 w-[250px]" />
|
|
<Skeleton className="h-4 w-[200px]" />
|
|
</div>
|
|
</div>
|
|
)}
|
|
</form>
|
|
</ResizablePanel>
|
|
<ResizableHandle className="border-none" />
|
|
<ResizablePanel className="border-t-2 flex flex-row flex-grow">
|
|
{typeof onFooter === "function" ? onFooter(local) : null}
|
|
</ResizablePanel>
|
|
</ResizablePanelGroup>
|
|
) : (
|
|
<>
|
|
<form
|
|
className="flex flex-grow flex-col flex-grow"
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
local.submit();
|
|
}}
|
|
>
|
|
{local.ready ? (
|
|
children(local)
|
|
) : (
|
|
<div className="flex flex-grow flex-row items-center justify-center">
|
|
<div className="space-y-2">
|
|
<Skeleton className="h-4 w-[250px]" />
|
|
<Skeleton className="h-16 w-[200px]" />
|
|
</div>
|
|
</div>
|
|
)}
|
|
</form>
|
|
{typeof onFooter === "function" ? onFooter(local) : null}
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|