lib
This commit is contained in:
parent
ed6f198fd7
commit
bb0e6d38f1
|
|
@ -1,168 +0,0 @@
|
||||||
import { FC } from "react";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogClose,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/lib/components/ui/dialog";
|
|
||||||
import { ButtonBetter, ButtonContainer } from "@/lib/components/ui/button";
|
|
||||||
import { IoEye } from "react-icons/io5";
|
|
||||||
import { HiPlus } from "react-icons/hi";
|
|
||||||
import { formatMoney } from "../form/field/TypeInput";
|
|
||||||
import { get_user } from "@/lib/utils/get_user";
|
|
||||||
import api from "@/lib/utils/axios";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { AlertTriangle, Check, Loader2 } from "lucide-react";
|
|
||||||
import { getNumber } from "@/lib/utils/getNumber";
|
|
||||||
import { events } from "@/lib/utils/event";
|
|
||||||
import get from "lodash.get";
|
|
||||||
export const AlertBatch: FC<any> = ({ local }) => {
|
|
||||||
// const fm = useLocal({
|
|
||||||
// // open:
|
|
||||||
// })
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Dialog>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<div className="flex flex-row flex-grow">
|
|
||||||
<ButtonContainer className="bg-primary">
|
|
||||||
<div className="flex items-center gap-x-0.5">
|
|
||||||
<HiPlus className="text-xl" />
|
|
||||||
<span className="capitalize">Create Batch</span>
|
|
||||||
</div>
|
|
||||||
</ButtonContainer>
|
|
||||||
</div>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className=" flex flex-col">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Create Batch</DialogTitle>
|
|
||||||
<DialogDescription className="hidden"></DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="flex items-center flex-row space-x-2 flex-grow">
|
|
||||||
<div className={cx(" flex flex-col flex-grow")}>
|
|
||||||
Are you sure to continue this action?
|
|
||||||
{local?.location_null
|
|
||||||
? ` There are ${formatMoney(
|
|
||||||
local?.location_null
|
|
||||||
)} locations NULL`
|
|
||||||
: ``}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<DialogFooter className="sm:justify-end">
|
|
||||||
<DialogClose asChild>
|
|
||||||
<ButtonBetter variant={"outline"}>
|
|
||||||
<div className="flex items-center gap-x-0.5">
|
|
||||||
<span className="capitalize">No</span>
|
|
||||||
</div>
|
|
||||||
</ButtonBetter>
|
|
||||||
</DialogClose>
|
|
||||||
<DialogClose
|
|
||||||
asChild
|
|
||||||
onClick={async () => {
|
|
||||||
try {
|
|
||||||
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..."}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
if (local.batch_lines?.length) {
|
|
||||||
const data = {
|
|
||||||
approver_id: get_user("employee.id"),
|
|
||||||
approver_name: get_user("employee.name"),
|
|
||||||
batch_lines: local.batch_lines?.length
|
|
||||||
? local.batch_lines.map((e: any) => {
|
|
||||||
return {
|
|
||||||
mp_planning_header_id: e,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
: [],
|
|
||||||
};
|
|
||||||
await api.post(
|
|
||||||
`${process.env.NEXT_PUBLIC_API_MPP}/api/batch/create`,
|
|
||||||
data
|
|
||||||
);
|
|
||||||
local.can_add = false;
|
|
||||||
local.render();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const batch: any = await api.get(
|
|
||||||
`${process.env.NEXT_PUBLIC_API_MPP}/api/batch/find-by-status/NEED APPROVAL`
|
|
||||||
);
|
|
||||||
local.batch = batch?.data?.data;
|
|
||||||
} catch (ex) {}
|
|
||||||
try {
|
|
||||||
const batch_ceo: any = await api.get(
|
|
||||||
`${process.env.NEXT_PUBLIC_API_MPP}/api/batch/find-by-status/APPROVED`
|
|
||||||
);
|
|
||||||
local.batch = batch_ceo?.data?.data;
|
|
||||||
} catch (ex) {}
|
|
||||||
local.render();
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
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" />
|
|
||||||
Create Batch Failed { get(ex, "response.data.meta.message") || ex.message}.
|
|
||||||
</div>
|
|
||||||
</div>,
|
|
||||||
{
|
|
||||||
dismissible: true,
|
|
||||||
className: css`
|
|
||||||
background: #ffecec;
|
|
||||||
border: 2px solid red;
|
|
||||||
`,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ButtonBetter>
|
|
||||||
<div className="flex items-center gap-x-0.5">
|
|
||||||
<span className="capitalize">Yes</span>
|
|
||||||
</div>
|
|
||||||
</ButtonBetter>
|
|
||||||
</DialogClose>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
import { FC } from "react";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogClose,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/lib/components/ui/dialog";
|
|
||||||
import { ButtonBetter, ButtonContainer } from "@/lib/components/ui/button";
|
|
||||||
import { IoEye } from "react-icons/io5";
|
|
||||||
import { HiPlus } from "react-icons/hi";
|
|
||||||
import api from "@/lib/utils/axios";
|
|
||||||
import { get_user } from "@/lib/utils/get_user";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { AlertTriangle, Check, Loader2 } from "lucide-react";
|
|
||||||
import get from "lodash.get";
|
|
||||||
export const AlertCeoApprove: FC<any> = ({ fm }) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Dialog>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<div className="flex flex-row flex-grow">
|
|
||||||
<ButtonBetter>Approve</ButtonBetter>
|
|
||||||
</div>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className=" flex flex-col">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Approve</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Are You Sure to Approve This Batch?
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<DialogFooter className="sm:justify-end">
|
|
||||||
<DialogClose asChild>
|
|
||||||
<ButtonBetter variant={"outline"}>
|
|
||||||
<div className="flex items-center gap-x-0.5">
|
|
||||||
<span className="capitalize">No</span>
|
|
||||||
</div>
|
|
||||||
</ButtonBetter>
|
|
||||||
</DialogClose>
|
|
||||||
<DialogClose asChild>
|
|
||||||
<ButtonBetter
|
|
||||||
onClick={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 {
|
|
||||||
const batch = await api.get(
|
|
||||||
`${process.env.NEXT_PUBLIC_API_MPP}/api/batch/find-by-status/NEED APPROVAL`
|
|
||||||
);
|
|
||||||
const id = batch?.data?.data?.id;
|
|
||||||
const param = {
|
|
||||||
id,
|
|
||||||
status: "APPROVED",
|
|
||||||
approved_by: get_user("employee.id"),
|
|
||||||
approver_name: get_user("employee.name"),
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await api.put(
|
|
||||||
`${process.env.NEXT_PUBLIC_API_MPP}/api/batch/update-status`,
|
|
||||||
param
|
|
||||||
);
|
|
||||||
|
|
||||||
fm.data = null;
|
|
||||||
fm.render();
|
|
||||||
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) {
|
|
||||||
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 { get(ex, "response.data.meta.message") || ex.message}.
|
|
||||||
</div>
|
|
||||||
</div>,
|
|
||||||
{
|
|
||||||
dismissible: true,
|
|
||||||
className: css`
|
|
||||||
background: #ffecec;
|
|
||||||
border: 2px solid red;
|
|
||||||
`,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-x-0.5">
|
|
||||||
<span className="capitalize">Yes</span>
|
|
||||||
</div>
|
|
||||||
</ButtonBetter>
|
|
||||||
</DialogClose>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,139 +0,0 @@
|
||||||
import { FC } from "react";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogClose,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/lib/components/ui/dialog";
|
|
||||||
import { ButtonBetter, ButtonContainer } from "@/lib/components/ui/button";
|
|
||||||
import { IoEye } from "react-icons/io5";
|
|
||||||
import { HiPlus } from "react-icons/hi";
|
|
||||||
import api from "@/lib/utils/axios";
|
|
||||||
import { get_user } from "@/lib/utils/get_user";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { AlertTriangle, Check, Loader2 } from "lucide-react";
|
|
||||||
import { getParams } from "@/lib/utils/get-params";
|
|
||||||
import get from "lodash.get";
|
|
||||||
export const AlertCeoApproveMPR: FC<any> = ({fm}) => {
|
|
||||||
const id = getParams("id");
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Dialog>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<div className="flex flex-row flex-grow">
|
|
||||||
<ButtonBetter>Approve</ButtonBetter>
|
|
||||||
</div>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className=" flex flex-col">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Approve</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Are You Sure to Approve This?
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<DialogFooter className="sm:justify-end">
|
|
||||||
<DialogClose asChild>
|
|
||||||
<ButtonBetter variant={"outline"}>
|
|
||||||
<div className="flex items-center gap-x-0.5">
|
|
||||||
<span className="capitalize">No</span>
|
|
||||||
</div>
|
|
||||||
</ButtonBetter>
|
|
||||||
</DialogClose>
|
|
||||||
<DialogClose asChild>
|
|
||||||
<ButtonBetter
|
|
||||||
onClick={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 {
|
|
||||||
|
|
||||||
const param = {
|
|
||||||
id,
|
|
||||||
status: "APPROVED",
|
|
||||||
level: "Level CEO",
|
|
||||||
approver_id: get_user("employee.id"),
|
|
||||||
approved_by: get_user("employee.name"),
|
|
||||||
};
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append("payload", JSON.stringify(param));
|
|
||||||
const res = await api.put(
|
|
||||||
`${process.env.NEXT_PUBLIC_API_MPP}/api/mp-requests/status`,
|
|
||||||
formData,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "multipart/form-data",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
setTimeout(() => {
|
|
||||||
fm.data.is_approve = false;
|
|
||||||
fm.render();
|
|
||||||
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) {
|
|
||||||
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 { get(ex, "response.data.meta.message") || ex.message}.
|
|
||||||
</div>
|
|
||||||
</div>,
|
|
||||||
{
|
|
||||||
dismissible: true,
|
|
||||||
className: css`
|
|
||||||
background: #ffecec;
|
|
||||||
border: 2px solid red;
|
|
||||||
`,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-x-0.5">
|
|
||||||
<span className="capitalize">Yes</span>
|
|
||||||
</div>
|
|
||||||
</ButtonBetter>
|
|
||||||
</DialogClose>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,317 +0,0 @@
|
||||||
import { FC, useEffect } from "react";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogClose,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/lib/components/ui/dialog";
|
|
||||||
import { ButtonBetter, ButtonContainer } from "@/lib/components/ui/button";
|
|
||||||
import { Checkbox } from "@/lib/components/ui/checkbox";
|
|
||||||
import { IoEye } from "react-icons/io5";
|
|
||||||
import { HiPlus } from "react-icons/hi";
|
|
||||||
import { useLocal } from "@/lib/utils/use-local";
|
|
||||||
import api from "@/lib/utils/axios";
|
|
||||||
import { Form } from "../form/Form";
|
|
||||||
import { Field } from "../form/Field";
|
|
||||||
import { cloneFM } from "@/lib/utils/cloneFm";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { AlertTriangle, Check, Loader2 } from "lucide-react";
|
|
||||||
import { get_user } from "@/lib/utils/get_user";
|
|
||||||
import { events } from "@/lib/utils/event";
|
|
||||||
import get from "lodash.get";
|
|
||||||
export const AlertCeoReject: FC<any> = ({ lc }) => {
|
|
||||||
const local = useLocal({
|
|
||||||
organization: [] as any[],
|
|
||||||
reject: "reject-all" as any,
|
|
||||||
fm: null as any,
|
|
||||||
org: [] as string[]
|
|
||||||
});
|
|
||||||
useEffect(() => {
|
|
||||||
const run = async () => {
|
|
||||||
const batch: any = await api.get(
|
|
||||||
`${process.env.NEXT_PUBLIC_API_MPP}/api/batch/find-by-status/NEED APPROVAL`
|
|
||||||
);
|
|
||||||
const btc = batch?.data?.data;
|
|
||||||
const addtional = {
|
|
||||||
status: "APPROVED",
|
|
||||||
paging: 1,
|
|
||||||
take: 500,
|
|
||||||
};
|
|
||||||
const params = await events("onload-param", addtional);
|
|
||||||
const res: any = await api.get(
|
|
||||||
`${process.env.NEXT_PUBLIC_API_MPP}/api/batch/organizations/${btc?.id}` +
|
|
||||||
params
|
|
||||||
);
|
|
||||||
const data: any[] = res.data.data;
|
|
||||||
const result = data?.length
|
|
||||||
? data.map((e) => {
|
|
||||||
return { id: e.id, label: e.name };
|
|
||||||
})
|
|
||||||
: [];
|
|
||||||
local.organization = result;
|
|
||||||
local.render();
|
|
||||||
};
|
|
||||||
run();
|
|
||||||
}, []);
|
|
||||||
const items = [
|
|
||||||
{
|
|
||||||
id: "reject-all",
|
|
||||||
label: "Reject All",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "reject-partially",
|
|
||||||
label: "Reject Partially",
|
|
||||||
},
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Dialog>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<div className="flex flex-row flex-grow">
|
|
||||||
<ButtonBetter variant={"reject"}>Reject</ButtonBetter>
|
|
||||||
</div>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className=" flex flex-col">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Reject</DialogTitle>
|
|
||||||
<DialogDescription className="hidden"></DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="flex flex-col flex-grow">
|
|
||||||
<div className="flex flex-col gap-y-2 mb-2">
|
|
||||||
{items.map((item) => (
|
|
||||||
<div
|
|
||||||
className="flex items-center space-x-2"
|
|
||||||
key={"checkbox_item_reject" + item.id}
|
|
||||||
>
|
|
||||||
<Checkbox
|
|
||||||
id={item.id}
|
|
||||||
checked={local?.reject === item.id}
|
|
||||||
onCheckedChange={(e) => {
|
|
||||||
local.reject = item.id;
|
|
||||||
local.render();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="terms"
|
|
||||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
||||||
>
|
|
||||||
{item.label}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{local?.reject === "reject-partially" &&
|
|
||||||
local?.organization?.length && (
|
|
||||||
<>
|
|
||||||
<Form
|
|
||||||
onSubmit={async (fm: any) => {}}
|
|
||||||
onLoad={async () => {
|
|
||||||
return {
|
|
||||||
organization: [],
|
|
||||||
};
|
|
||||||
}}
|
|
||||||
showResize={false}
|
|
||||||
header={(fm: any) => {
|
|
||||||
return <></>;
|
|
||||||
}}
|
|
||||||
onInit={(fm: any) => {
|
|
||||||
local.fm = fm;
|
|
||||||
local.render();
|
|
||||||
}}
|
|
||||||
children={(fm: any) => {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-y-2 flex-grow pl-6 max-h-[250px] overflow-y-scroll">
|
|
||||||
{local.organization.map((item) => {
|
|
||||||
const is_check = fm.data?.organization?.length
|
|
||||||
? fm.data.organization.find(
|
|
||||||
(org: any) => org?.id === item.id
|
|
||||||
)
|
|
||||||
: false;
|
|
||||||
const data = fm.data?.organization?.length
|
|
||||||
? fm.data.organization.find(
|
|
||||||
(org: any) => org?.id === item.id
|
|
||||||
)
|
|
||||||
: {};
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="flex flex-col"
|
|
||||||
key={"checkbox_item_reject" + item.id}
|
|
||||||
>
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<Checkbox
|
|
||||||
id={item.id}
|
|
||||||
onCheckedChange={(e) => {
|
|
||||||
if (e) {
|
|
||||||
if (
|
|
||||||
!Array.isArray(fm.data?.organization)
|
|
||||||
) {
|
|
||||||
fm.data["organization"] = [];
|
|
||||||
fm.render();
|
|
||||||
}
|
|
||||||
// Jika checkbox dicentang, tambahkan item ke array organization
|
|
||||||
fm.data.organization.push({
|
|
||||||
id: item.id,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Jika checkbox tidak dicentang, hapus item dari array organization
|
|
||||||
fm.data["organization"] = fm.data
|
|
||||||
?.organization?.length
|
|
||||||
? fm.data.organization.filter(
|
|
||||||
(org: any) => org?.id !== item.id
|
|
||||||
)
|
|
||||||
: [];
|
|
||||||
}
|
|
||||||
fm.render();
|
|
||||||
local.org = fm.data?.organization?.length ? fm.data.organization.map((e: any) => {
|
|
||||||
return {
|
|
||||||
id: e.id
|
|
||||||
}
|
|
||||||
}) : [];
|
|
||||||
local.render();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="terms"
|
|
||||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
||||||
>
|
|
||||||
{item.label}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{is_check ? (
|
|
||||||
<div className="pt-1 pb-3">
|
|
||||||
<Field
|
|
||||||
fm={cloneFM(fm, data)}
|
|
||||||
hidden_label={true}
|
|
||||||
name={"notes"}
|
|
||||||
label={"Organization"}
|
|
||||||
type={"text"}
|
|
||||||
placeholder="Notes"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<DialogFooter className="sm:justify-end">
|
|
||||||
<DialogClose asChild>
|
|
||||||
<ButtonBetter variant={"outline"}>
|
|
||||||
<div className="flex items-center gap-x-0.5">
|
|
||||||
<span className="capitalize">No</span>
|
|
||||||
</div>
|
|
||||||
</ButtonBetter>
|
|
||||||
</DialogClose>
|
|
||||||
<DialogClose
|
|
||||||
asChild
|
|
||||||
onClick={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 {
|
|
||||||
const isPartial = local.reject === "reject-partially";
|
|
||||||
if (isPartial) {
|
|
||||||
const partial = local?.org || [];
|
|
||||||
const res = await api.put(
|
|
||||||
`${process.env.NEXT_PUBLIC_API_MPP}/api/mp-plannings/lines/reject-partial-pt`,
|
|
||||||
{ approver_id: get_user("employee.id"), payload: partial }
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const batch = await api.get(
|
|
||||||
`${process.env.NEXT_PUBLIC_API_MPP}/api/batch/find-by-status/NEED APPROVAL`
|
|
||||||
);
|
|
||||||
const id = batch?.data?.data?.id;
|
|
||||||
const param = {
|
|
||||||
id,
|
|
||||||
status: "REJECTED",
|
|
||||||
approved_by: get_user("employee.id"),
|
|
||||||
approver_name: get_user("employee.name"),
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await api.put(
|
|
||||||
`${process.env.NEXT_PUBLIC_API_MPP}/api/batch/update-status`,
|
|
||||||
param
|
|
||||||
);
|
|
||||||
}
|
|
||||||
lc.data = null;
|
|
||||||
lc.render()
|
|
||||||
|
|
||||||
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) {
|
|
||||||
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 { get(ex, "response.data.meta.message") || ex.message}.
|
|
||||||
</div>
|
|
||||||
</div>,
|
|
||||||
{
|
|
||||||
dismissible: true,
|
|
||||||
className: css`
|
|
||||||
background: #ffecec;
|
|
||||||
border: 2px solid red;
|
|
||||||
`,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ButtonBetter>
|
|
||||||
<div className="flex items-center gap-x-0.5">
|
|
||||||
<span className="capitalize">Yes</span>
|
|
||||||
</div>
|
|
||||||
</ButtonBetter>
|
|
||||||
</DialogClose>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,203 +0,0 @@
|
||||||
import { FC, useEffect } from "react";
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogClose,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/lib/components/ui/dialog";
|
|
||||||
import { ButtonBetter, ButtonContainer } from "@/lib/components/ui/button";
|
|
||||||
import { Checkbox } from "@/lib/components/ui/checkbox";
|
|
||||||
import { IoEye } from "react-icons/io5";
|
|
||||||
import { HiPlus } from "react-icons/hi";
|
|
||||||
import { useLocal } from "@/lib/utils/use-local";
|
|
||||||
import api from "@/lib/utils/axios";
|
|
||||||
import { Form } from "../form/Form";
|
|
||||||
import { Field } from "../form/Field";
|
|
||||||
import { cloneFM } from "@/lib/utils/cloneFm";
|
|
||||||
import { toast } from "sonner";
|
|
||||||
import { AlertTriangle, Check, Loader2 } from "lucide-react";
|
|
||||||
import { get_user } from "@/lib/utils/get_user";
|
|
||||||
import { getParams } from "@/lib/utils/get-params";
|
|
||||||
import { Button } from "flowbite-react";
|
|
||||||
import get from "lodash.get";
|
|
||||||
export const AlertCeoRejectMPR: FC<any> = ({ lc }) => {
|
|
||||||
const id = getParams("id");
|
|
||||||
const local = useLocal({
|
|
||||||
organization: [] as any[],
|
|
||||||
reject: "reject-all" as any,
|
|
||||||
});
|
|
||||||
useEffect(() => {}, []);
|
|
||||||
const items = [
|
|
||||||
{
|
|
||||||
id: "reject-all",
|
|
||||||
label: "Reject All",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "reject-partially",
|
|
||||||
label: "Reject Partially",
|
|
||||||
},
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Dialog>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<div className="flex flex-row flex-grow">
|
|
||||||
<ButtonBetter variant={"reject"}>Reject</ButtonBetter>
|
|
||||||
</div>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className=" flex flex-col">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Reject</DialogTitle>
|
|
||||||
<DialogDescription>Are You Sure to Reject This?</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="flex flex-col flex-grow">
|
|
||||||
<Form
|
|
||||||
onSubmit={async (fm: any) => {}}
|
|
||||||
onLoad={async () => {
|
|
||||||
return {
|
|
||||||
organization: [],
|
|
||||||
};
|
|
||||||
}}
|
|
||||||
showResize={false}
|
|
||||||
header={(fm: any) => {
|
|
||||||
return <></>;
|
|
||||||
}}
|
|
||||||
children={(fm: any) => {
|
|
||||||
return (
|
|
||||||
<div className={cx("flex flex-col flex-wrap")}>
|
|
||||||
<div className="grid gap-4 mb-4 md:gap-6 md:grid-cols-2 sm:mb-8">
|
|
||||||
<div className="col-span-2">
|
|
||||||
<Field
|
|
||||||
fm={fm}
|
|
||||||
name={"notes"}
|
|
||||||
label={"Notes"}
|
|
||||||
type={"textarea"}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
onFooter={(fm: any) => {
|
|
||||||
return (
|
|
||||||
<DialogFooter className="sm:justify-end">
|
|
||||||
<DialogClose asChild>
|
|
||||||
<ButtonBetter variant={"outline"}>
|
|
||||||
<div className="flex items-center gap-x-0.5">
|
|
||||||
<span className="capitalize">No</span>
|
|
||||||
</div>
|
|
||||||
</ButtonBetter>
|
|
||||||
</DialogClose>
|
|
||||||
{!fm.data?.notes ? (
|
|
||||||
<ButtonBetter onClick={() => {
|
|
||||||
fm.error["notes"] = "Field is required"
|
|
||||||
fm.render();
|
|
||||||
}}>Yes</ButtonBetter>
|
|
||||||
) : (
|
|
||||||
<DialogClose
|
|
||||||
asChild
|
|
||||||
onClick={async () => {
|
|
||||||
fm.error = {};
|
|
||||||
fm.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{"Saving..."}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
const param = {
|
|
||||||
id,
|
|
||||||
status: "REJECTED",
|
|
||||||
level: "Level CEO",
|
|
||||||
approver_id: get_user("employee.id"),
|
|
||||||
approved_by: get_user("employee.name"),
|
|
||||||
notes: fm.data?.notes
|
|
||||||
};
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append("payload", JSON.stringify(param));
|
|
||||||
const res = await api.put(
|
|
||||||
`${process.env.NEXT_PUBLIC_API_MPP}/api/mp-requests/status`,
|
|
||||||
formData,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "multipart/form-data",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
setTimeout(() => {
|
|
||||||
lc.data.is_approve = false;
|
|
||||||
lc.render();
|
|
||||||
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) {
|
|
||||||
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 { get(ex, "response.data.meta.message") || ex.message}.
|
|
||||||
</div>
|
|
||||||
</div>,
|
|
||||||
{
|
|
||||||
dismissible: true,
|
|
||||||
className: css`
|
|
||||||
background: #ffecec;
|
|
||||||
border: 2px solid red;
|
|
||||||
`,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ButtonBetter>
|
|
||||||
<div className="flex items-center gap-x-0.5">
|
|
||||||
<span className="capitalize">Yes</span>
|
|
||||||
</div>
|
|
||||||
</ButtonBetter>
|
|
||||||
</DialogClose>
|
|
||||||
)}
|
|
||||||
</DialogFooter>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,18 +1,13 @@
|
||||||
"use client";
|
"use client";
|
||||||
import React, { FC } from "react";
|
import React, { FC } from "react";
|
||||||
import Link from "next/link";
|
import { Dropdown, Sidebar, Tooltip } from "flowbite-react";
|
||||||
import { Dropdown, Sidebar, TextInput, Tooltip } from "flowbite-react";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useSidebarContext } from "@/context/SidebarContext";
|
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { HiAdjustments, HiChartPie, HiCog } from "react-icons/hi";
|
import { HiAdjustments, HiCog } from "react-icons/hi";
|
||||||
import isSmallScreen from "@/lib/helpers/is-small-screen";
|
|
||||||
import { css } from "@emotion/css";
|
import { css } from "@emotion/css";
|
||||||
import { FaAngleUp, FaChevronDown, FaChevronUp } from "react-icons/fa";
|
import { FaAngleUp, FaChevronDown, FaChevronUp } from "react-icons/fa";
|
||||||
import { Minimize } from "lucide-react";
|
|
||||||
import { SidebarLinkBetter } from "../ui/link-better";
|
import { SidebarLinkBetter } from "../ui/link-better";
|
||||||
import { detectCase } from "@/lib/utils/detectCase";
|
import { detectCase } from "@/lib/utils/detectCase";
|
||||||
import { Skeleton } from "../ui/Skeleton";
|
|
||||||
import { useLocal } from "@/lib/utils/use-local";
|
import { useLocal } from "@/lib/utils/use-local";
|
||||||
interface TreeMenuItem {
|
interface TreeMenuItem {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
import * as React from "react"
|
||||||
|
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
|
||||||
|
import { cn } from "@/lib/utils/utils"
|
||||||
|
import { ButtonProps, buttonVariants } from "./button"
|
||||||
|
|
||||||
|
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
||||||
|
<nav
|
||||||
|
role="navigation"
|
||||||
|
aria-label="pagination"
|
||||||
|
className={cn("mx-auto flex w-full justify-center", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
Pagination.displayName = "Pagination"
|
||||||
|
|
||||||
|
const PaginationContent = React.forwardRef<
|
||||||
|
HTMLUListElement,
|
||||||
|
React.ComponentProps<"ul">
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<ul
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex flex-row items-center gap-1", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
PaginationContent.displayName = "PaginationContent"
|
||||||
|
|
||||||
|
const PaginationItem = React.forwardRef<
|
||||||
|
HTMLLIElement,
|
||||||
|
React.ComponentProps<"li">
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<li ref={ref} className={cn("", className)} {...props} />
|
||||||
|
))
|
||||||
|
PaginationItem.displayName = "PaginationItem"
|
||||||
|
|
||||||
|
type PaginationLinkProps = {
|
||||||
|
isActive?: boolean
|
||||||
|
} & Pick<ButtonProps, "size"> &
|
||||||
|
React.ComponentProps<"a">
|
||||||
|
|
||||||
|
const PaginationLink = ({
|
||||||
|
className,
|
||||||
|
isActive,
|
||||||
|
size = "icon",
|
||||||
|
...props
|
||||||
|
}: PaginationLinkProps) => (
|
||||||
|
<a
|
||||||
|
aria-current={isActive ? "page" : undefined}
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({
|
||||||
|
variant: isActive ? "outline" : "ghost",
|
||||||
|
size,
|
||||||
|
}),
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
PaginationLink.displayName = "PaginationLink"
|
||||||
|
|
||||||
|
const PaginationPrevious = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||||
|
<PaginationLink
|
||||||
|
aria-label="Go to previous page"
|
||||||
|
size="default"
|
||||||
|
className={cn("gap-1 pl-2.5", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronLeft className="h-4 w-4" />
|
||||||
|
<span>Previous</span>
|
||||||
|
</PaginationLink>
|
||||||
|
)
|
||||||
|
PaginationPrevious.displayName = "PaginationPrevious"
|
||||||
|
|
||||||
|
const PaginationNext = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||||
|
<PaginationLink
|
||||||
|
aria-label="Go to next page"
|
||||||
|
size="default"
|
||||||
|
className={cn("gap-1 pr-2.5", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span>Next</span>
|
||||||
|
<ChevronRight className="h-4 w-4" />
|
||||||
|
</PaginationLink>
|
||||||
|
)
|
||||||
|
PaginationNext.displayName = "PaginationNext"
|
||||||
|
|
||||||
|
const PaginationEllipsis = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"span">) => (
|
||||||
|
<span
|
||||||
|
aria-hidden
|
||||||
|
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<MoreHorizontal className="h-4 w-4" />
|
||||||
|
<span className="sr-only">More pages</span>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
PaginationEllipsis.displayName = "PaginationEllipsis"
|
||||||
|
|
||||||
|
export {
|
||||||
|
Pagination,
|
||||||
|
PaginationContent,
|
||||||
|
PaginationLink,
|
||||||
|
PaginationItem,
|
||||||
|
PaginationPrevious,
|
||||||
|
PaginationNext,
|
||||||
|
PaginationEllipsis,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { useLocal } from "@/lib/utils/use-local";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
export const PinterestLayout: React.FC<{
|
||||||
|
data: any[];
|
||||||
|
child: (item: any, index: any, data: any[], key?: string) => any;
|
||||||
|
col: number;
|
||||||
|
gap?: number;
|
||||||
|
}> = ({ data, child, col = 2, gap = 2 }) => {
|
||||||
|
function generateUUIDs(data: any[]): string[] {
|
||||||
|
return data.map(() => uuidv4());
|
||||||
|
}
|
||||||
|
const createColumns = (items: any[], numCols: number) => {
|
||||||
|
const columns: any = Array.from({ length: numCols }, () => []); // Membuat array kosong sebanyak jumlah kolom
|
||||||
|
items.forEach((item, index) => {
|
||||||
|
columns[index % numCols].push(item); // Memasukkan item ke dalam kolom secara berurutan
|
||||||
|
});
|
||||||
|
return columns;
|
||||||
|
};
|
||||||
|
const layout_grid = new Array(col);
|
||||||
|
const columns = createColumns(data, col); // Membagi data ke dalam kolom
|
||||||
|
const local = useLocal({
|
||||||
|
data: [] as any[],
|
||||||
|
ids: {
|
||||||
|
col: [] as string[],
|
||||||
|
data: [] as string[],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
const columns: any[] = Array.from({ length: col }, () => []); // Inisialisasi array kosong sebanyak 'col'
|
||||||
|
|
||||||
|
data.forEach((item, index) => {
|
||||||
|
const targetColumn = index % col; // Menentukan kolom target berdasarkan indeks
|
||||||
|
columns[targetColumn].push(item); // Memasukkan elemen ke kolom yang sesuai
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("Columns:", columns); // Debugging
|
||||||
|
local.data = columns;
|
||||||
|
local.render();
|
||||||
|
}, [data, col]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-grow flex-1 flex-col w-full h-full">
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
`grid gap-${gap}`,
|
||||||
|
css`
|
||||||
|
grid-template-columns: repeat(${col}, minmax(0, 1fr));
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{local.data.map((el, idx) => {
|
||||||
|
return (
|
||||||
|
<div className={`flex flex-col gap-${gap}`} key={"col_" + idx}>
|
||||||
|
{Array.isArray(el) && el.length ? (
|
||||||
|
<div
|
||||||
|
key={"col_" + idx + "_idx_" + idx}
|
||||||
|
className={`flex flex-col gap-${gap}`}
|
||||||
|
>
|
||||||
|
{el.map((item, ids, data) => {
|
||||||
|
return (
|
||||||
|
<div key={"row_" + idx + "_" + ids}>
|
||||||
|
{child(item, idx, data, "item_" + idx + "_" + ids)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -14,8 +14,7 @@ const buttonVariants = cva(
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
default: "bg-primary text-white shadow hover:bg-primary/90",
|
||||||
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
|
||||||
destructive:
|
destructive:
|
||||||
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
||||||
outline:
|
outline:
|
||||||
|
|
@ -24,6 +23,8 @@ const buttonVariants = cva(
|
||||||
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
||||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
|
noline:
|
||||||
|
"text-black border-none border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-9 px-4 py-2",
|
default: "h-9 px-4 py-2",
|
||||||
|
|
@ -57,13 +58,20 @@ const ButtonBetter = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const ButtonLink: FC<any> = ({ className, children, href }) => {
|
const ButtonLink: FC<any> = ({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
href,
|
||||||
|
variant = "default",
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Link href={href}>
|
<Link href={href}>
|
||||||
<ButtonBetter className={cx(className, "text-white")}>{children}</ButtonBetter>
|
<ButtonBetter className={cx(className)} variant={variant}>
|
||||||
|
{children}
|
||||||
|
</ButtonBetter>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
ButtonBetter.displayName = "Button";
|
ButtonBetter.displayName = "Button";
|
||||||
|
|
||||||
export {ButtonLink };
|
export { ButtonLink };
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue