Skip to content

G. Integrasi Delete Bulk

1. Integrasi Delete Bulk

Membuat Interface

Alt text

request endpoint delete bulk
http://localhost:5002/book/delete/bulk
payload endpoint delete bulk
{
    "data" : [1,2,3]
}

response endpoint delete bulk
{
    "status": "Success",
    "message": "Berhasil meenghapus 3 dan gagal 0",
    "data": {}
}
book/interface/index.ts
...

export interface BookDeleteArrayPayload {
  data : number[]
}
...

Membuat Service Delete Bulk

book/lib/index.ts
...

const useDeleteBulkBook = () => {
    const { mutate, isLoading } = useMutation(
      (payload: BookDeleteArrayPayload) => {
        return axiosClient.post("/book/delete/bulk", payload);
      },
      {
        onSuccess: (response) => {
          Swal.fire({
            position: "top-end",
            icon: "success",
            title: response.data.message,
            showConfirmButton: false,
            timer: 1000,
          });

          queryClient.invalidateQueries(["/book/list"]);
        },
        onError: (error) => {
          Swal.fire({
            position: "top",
            icon: "error",
            title: "Ada Kesalahan",
            showConfirmButton: false,
            timer: 1000,
          });
        },
      }
    );
    return { mutate, isLoading };
  };


  return {
    useBookList,
    useCreateBook,
    useDetailBook,
    useUpdateBook,
    useDeleteBook,
    useCreateBulkBook,
    useDeleteBulkBook
  };

...

Membuat hook useConfirmDeleteBulk

hook/useConfirmBulkDelete
import Swal from "sweetalert2";
type SubmitFunction = (payload: number[]) => any;
export function useConfirmDeleteBulk({ onSubmit }: { onSubmit: SubmitFunction }) {
  const handleDeleteBulk = (payload: number[]) => {
    const swalWithBootstrapButtons = Swal.mixin({
      customClass: {
        confirmButton: "btn btn-success",
        cancelButton: "btn btn-danger",
      },
      buttonsStyling: true,
    });

    swalWithBootstrapButtons
      .fire({
        title: "Apakah Yakin?",
        text: "Data yang terhapus tidak bisa dikembalikan",
        icon: "warning",
        showCancelButton: true,
        confirmButtonText: "Hapus",
        confirmButtonColor: "red",

        cancelButtonText: "Batal",
        reverseButtons: true,
      })
      .then(async (result) => {
        if (result.isConfirmed) {
          await onSubmit(payload);
        }
      });
  };

  return handleDeleteBulk;
}

Membuat fitur delete bulk

app/book/page.tsx
"use client";
import Button from "@/components/Button";
import { Pagination } from "../../components/Pagination";
import { Table, Th, Thead, Tr, Tbody, Td } from "../../components/Table";
import useBookModule from "./lib";
import { Drawer } from "@/components/Drawer";
import Filter from "./module/Filter";
import { useDisclosure, useConfirmDelete, useConfirmDeleteBulk } from "@/hook";
import { useRouter } from "next/navigation";
import { DeleteButton, EditButton } from "@/components/ButtonAction";
import { useMemo, useState } from "react";

const Book = () => {
  const { useBookList, useDeleteBook, useDeleteBulkBook } = useBookModule();
  const [deletePayload, setDeletePayload] = useState<number[]>([]);

  const { mutate, isLoading } = useDeleteBook();
  const { mutate: mutateDeleteBulk, isLoading: isLoadingDeleteBulk } =
    useDeleteBulkBook();
  const router = useRouter();
  const handleDelete = useConfirmDelete({
    onSubmit: (id) => {
      mutate(id);
    },
  });
  const handleDeleteBulk = useConfirmDeleteBulk({
    onSubmit: (payload) => {
      console.log("payload", payload);
      mutateDeleteBulk({ data: payload }, {
        onSuccess : ()=> {
          setDeletePayload([])
        }
      });
    },
  });

  const {
    data,
    isFetching,
    isError,
    params,
    setParams,
    handleFilter,
    handleClear,
    handlePageSize,
    handlePage,
  } = useBookList();

  const checked = useMemo(() => {
    if (!data) {
      return { isAllCheced: false };
    }
    const isAllChecked = data.data.every((n) => deletePayload.includes(n.id));

    return { isAllCheced: isAllChecked };
  }, [deletePayload, data]);

  const { isOpen, onOpen, onClose } = useDisclosure();

  return (
    <>
      <Drawer
        onClose={onClose}
        onClear={handleClear}
        onSubmit={handleFilter}
        title="Filter Buku"
        isOpen={isOpen}
      >
        <Filter params={params} setParams={setParams} />
      </Drawer>
      <section className=" py-10 overflow-auto ">
        <section className="flex items-center justify-between ">
          <div>
            <Button
              width="sm"
              onClick={onOpen}
              colorSchema="blue"
              title="Filter"
            />
            <Button
              width="sm"
              onClick={() => {
                handleDeleteBulk(deletePayload);
              }}
              isLoading={isLoadingDeleteBulk}
              colorSchema="red"
              isDisabled={deletePayload.length === 0}
              title="Hapus "
            />
          </div>
          <div className="space-x-5">
            <Button
              onClick={() => {
                router.push("/book/tambah");
              }}
              width="sm"
              colorSchema="red"
              title="tambah"
            />
            <Button
              onClick={() => {
                router.push("/book/tambah-bulk");
              }}
              width="sm"
              colorSchema="green"
              title="tambah bulk"
            />
          </div>
        </section>

        <section className="h-full w-full mt-5 ">
          {JSON.stringify(deletePayload)}
          <Table
            isFetching={isFetching}
            isEmpty={data?.data?.length === 0}
            isError={isError}
          >
            <Thead>
              <Tr>
                <Th scope="col">
                  <div className="flex items-center gap-x-3">
                    <input
                      checked={checked.isAllCheced}
                      onChange={() => {
                        if (checked.isAllCheced) {
                          setDeletePayload([]);
                        } else {
                          setDeletePayload((state) => {
                            if (!data) {
                              return [];
                            }

                            const selected: number[] = Array.from(
                              new Set([
                                ...state,
                                ...data?.data?.map((n) => Number(n.id)),
                              ])
                            );

                            return [...selected];
                          });
                        }
                      }}
                      type="checkbox"
                      className="text-blue-500 border-gray-300 rounded dark:bg-gray-900 dark:ring-offset-gray-900 dark:border-gray-700"
                    />
                  </div>
                </Th>
                <Th scope="col">No</Th>
                <Th scope="col">Title</Th>
                <Th scope="col">Author</Th>
                <Th scope="col">Year</Th>
                <Th scope="col">Created At</Th>
                <Th scope="col">Updated At</Th>
                <Th scope="col">Aksi</Th>
              </Tr>
            </Thead>
            <Tbody>
              {data &&
                data.data.map((item, index) => (
                  <Tr key={index}>
                    <Td>
                      <input
                        checked={deletePayload.includes(item.id)}
                        onChange={(e) => {
                          if (e.target.checked) {
                            setDeletePayload((state) => [...state, item.id]);
                          } else {
                            const filtered = deletePayload.filter(
                              (n) => n !== item.id
                            );
                            setDeletePayload(filtered);
                          }
                        }}
                        type="checkbox"
                        className="text-blue-500 border-gray-300 rounded dark:bg-gray-900 dark:ring-offset-gray-900 dark:border-gray-700"
                      />
                    </Td>
                    <Td>{(params.page - 1) * params.pageSize + index + 1}</Td>
                    <Td>
                      <span>{item.title}</span>
                    </Td>
                    <Td>
                      <span>{item.author}</span>
                    </Td>
                    <Td>
                      <span>{item.year}</span>
                    </Td>
                    <Td>
                      <span>{item.created_at}</span>
                    </Td>
                    <Td>
                      <span>{item.updated_at}</span>
                    </Td>
                    <Td>
                      <span className="flex items-center space-x-2">
                        <DeleteButton
                          isLoading={isLoading}
                          onClick={() => {
                            handleDelete(item.id || 0);
                          }}
                        />
                        <EditButton
                          onClick={() => {
                            router.push(`book/${item.id}/edit`);
                          }}
                        />
                      </span>
                    </Td>
                  </Tr>
                ))}
            </Tbody>
          </Table>

          <Pagination
            page={params.page}
            pageSize={params.pageSize}
            handlePageSize={handlePageSize}
            handlePage={handlePage}
            pagination={data?.pagination}
          />
        </section>
      </section>
    </>
  );
};

export default Book;
Alt text Alt text

2. refactory kode

Refactor useConfirBulkDelete

hook/useConfirmBulkDelete
import { useMemo, useState } from "react";
import Swal from "sweetalert2";
type SubmitFunction = (payload: number[]) => any;
type Data = {
  data: {
    id: number;
  }[];
};
export function useConfirmDeleteBulk({
  onSubmit,
  data,
}: {
  onSubmit: SubmitFunction;
  data: Data | undefined;
}) {
  const [deletePayload, setDeletePayload] = useState<number[]>([]);

  const checked = useMemo(() => {
    if (!data) {
      return { isAllCheced: false };
    }
    const isAllChecked = data.data.every((n) => deletePayload.includes(n.id));

    return { isAllCheced: isAllChecked };
  }, [deletePayload, data]);

  const handleDeleteBulk = (payload: number[]) => {
    const swalWithBootstrapButtons = Swal.mixin({
      customClass: {
        confirmButton: "btn btn-success",
        cancelButton: "btn btn-danger",
      },
      buttonsStyling: true,
    });

    swalWithBootstrapButtons
      .fire({
        title: "Apakah Yakin?",
        text: "Data yang terhapus tidak bisa dikembalikan",
        icon: "warning",
        showCancelButton: true,
        confirmButtonText: "Hapus",
        confirmButtonColor: "red",

        cancelButtonText: "Batal",
        reverseButtons: true,
      })
      .then(async (result) => {
        if (result.isConfirmed) {
          await onSubmit(payload);
        }
      });
  };

  return {handleDeleteBulk, deletePayload, setDeletePayload, checked}
}

Refactor halaman list book

book/page.tsx
"use client";
import Button from "@/components/Button";
import { Pagination } from "../../components/Pagination";
import { Table, Th, Thead, Tr, Tbody, Td } from "../../components/Table";
import useBookModule from "./lib";
import { Drawer } from "@/components/Drawer";
import Filter from "./module/Filter";
import { useDisclosure, useConfirmDelete, useConfirmDeleteBulk } from "@/hook";
import { useRouter } from "next/navigation";
import { DeleteButton, EditButton } from "@/components/ButtonAction";
import { useMemo, useState } from "react";

const Book = () => {
  const { useBookList, useDeleteBook, useDeleteBulkBook } = useBookModule();

  const { mutate, isLoading } = useDeleteBook();
  const { mutate: mutateDeleteBulk, isLoading: isLoadingDeleteBulk } =
    useDeleteBulkBook();
  const router = useRouter();
  const handleDelete = useConfirmDelete({
    onSubmit: (id) => {
      mutate(id);
    },
  });

  const {
    data,
    isFetching,
    isError,
    params,
    setParams,
    handleFilter,
    handleClear,
    handlePageSize,
    handlePage,
  } = useBookList();
  const { handleDeleteBulk, deletePayload, setDeletePayload, checked } =
    useConfirmDeleteBulk({
      data: data,
      onSubmit: (payload) => {
        mutateDeleteBulk(
          { data: payload },
          {
            onSuccess: () => {
              setDeletePayload([]);
            },
          }
        );
      },
    });

  const { isOpen, onOpen, onClose } = useDisclosure();

  return (
    <>
      <Drawer
        onClose={onClose}
        onClear={handleClear}
        onSubmit={handleFilter}
        title="Filter Buku"
        isOpen={isOpen}
      >
        <Filter params={params} setParams={setParams} />
      </Drawer>
      <section className=" py-10 overflow-auto ">
        <section className="flex items-center justify-between ">
          <div>
            <Button
              width="sm"
              onClick={onOpen}
              colorSchema="blue"
              title="Filter"
            />
            <Button
              width="sm"
              onClick={() => {
                handleDeleteBulk(deletePayload);
              }}
              isLoading={isLoadingDeleteBulk}
              colorSchema="red"
              isDisabled={deletePayload.length === 0}
              title="Hapus "
            />
          </div>
          <div className="space-x-5">
            <Button
              onClick={() => {
                router.push("/book/tambah");
              }}
              width="sm"
              colorSchema="red"
              title="tambah"
            />
            <Button
              onClick={() => {
                router.push("/book/tambah-bulk");
              }}
              width="sm"
              colorSchema="green"
              title="tambah bulk"
            />
          </div>
        </section>

        <section className="h-full w-full mt-5 ">
          {JSON.stringify(deletePayload)}
          <Table
            isFetching={isFetching}
            isEmpty={data?.data?.length === 0}
            isError={isError}
          >
            <Thead>
              <Tr>
                <Th scope="col">
                  <div className="flex items-center gap-x-3">
                    <input
                      checked={checked.isAllCheced}
                      onChange={() => {
                        if (checked.isAllCheced) {
                          setDeletePayload([]);
                        } else {
                          setDeletePayload((state) => {
                            if (!data) {
                              return [];
                            }

                            const selected: number[] = Array.from(
                              new Set([
                                ...state,
                                ...data?.data?.map((n) => Number(n.id)),
                              ])
                            );

                            return [...selected];
                          });
                        }
                      }}
                      type="checkbox"
                      className="text-blue-500 border-gray-300 rounded dark:bg-gray-900 dark:ring-offset-gray-900 dark:border-gray-700"
                    />
                  </div>
                </Th>
                <Th scope="col">No</Th>
                <Th scope="col">Title</Th>
                <Th scope="col">Author</Th>
                <Th scope="col">Year</Th>
                <Th scope="col">Created At</Th>
                <Th scope="col">Updated At</Th>
                <Th scope="col">Aksi</Th>
              </Tr>
            </Thead>
            <Tbody>
              {data &&
                data.data.map((item, index) => (
                  <Tr key={index}>
                    <Td>
                      <input
                        checked={deletePayload.includes(item.id)}
                        onChange={(e) => {
                          if (e.target.checked) {
                            setDeletePayload((state) => [...state, item.id]);
                          } else {
                            const filtered = deletePayload.filter(
                              (n) => n !== item.id
                            );
                            setDeletePayload(filtered);
                          }
                        }}
                        type="checkbox"
                        className="text-blue-500 border-gray-300 rounded dark:bg-gray-900 dark:ring-offset-gray-900 dark:border-gray-700"
                      />
                    </Td>
                    <Td>{(params.page - 1) * params.pageSize + index + 1}</Td>
                    <Td>
                      <span>{item.title}</span>
                    </Td>
                    <Td>
                      <span>{item.author}</span>
                    </Td>
                    <Td>
                      <span>{item.year}</span>
                    </Td>
                    <Td>
                      <span>{item.created_at}</span>
                    </Td>
                    <Td>
                      <span>{item.updated_at}</span>
                    </Td>
                    <Td>
                      <span className="flex items-center space-x-2">
                        <DeleteButton
                          isLoading={isLoading}
                          onClick={() => {
                            handleDelete(item.id || 0);
                          }}
                        />
                        <EditButton
                          onClick={() => {
                            router.push(`book/${item.id}/edit`);
                          }}
                        />
                      </span>
                    </Td>
                  </Tr>
                ))}
            </Tbody>
          </Table>

          <Pagination
            page={params.page}
            pageSize={params.pageSize}
            handlePageSize={handlePageSize}
            handlePage={handlePage}
            pagination={data?.pagination}
          />
        </section>
      </section>
    </>
  );
};

export default Book;

Alt text Alt text