Skip to content

D. Integrasi Detail dan Update Book

1. Integrasi Detail Book

Membuat Routing Halaman Update

Alt text

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 } from "@/hook";
import { useRouter } from "next/navigation";
import { TrashIcon, PencilSquareIcon } from "@heroicons/react/20/solid";
import { DeleteButton, EditButton } from "@/components/ButtonAction";


const Book = () => {
  const { useBookList } = useBookModule();
  const router = useRouter();

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

  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=" p-10 overflow-auto ">
        <section className="flex items-center justify-between ">
          <Button
            width="sm"
            onClick={onOpen}
            colorSchema="blue"
            title="Filter"
          />
          <Button
            onClick={() => {
              router.push("/book/tambah");
            }}
            width="sm"
            colorSchema="red"
            title="tambah"
          />
        </section>

        <section className="h-full w-full mt-5 ">
          <Table
            isFetching={isFetching}
            isEmpty={data?.data?.length === 0}
            isError={isError}
          >
            <Thead>
              <Tr>
                <Th scope="col">
                  <div className="flex items-center gap-x-3">
                    <input
                      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
                        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>
                      <DeleteButton
                        onClick={() => {
                          console.log("ok");
                        }}
                      />
                      <EditButton
                        onClick={() => {
                         router.push(`book/${item.id}/edit`)
                        }}
                      />
                    </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

Membuat Halaman Update

app/book/[id]/update/page.tsx
"use client";
import Button from "@/components/Button";
import InputText from "@/components/InputText";
import Label from "@/components/Label";
import Select from "@/components/Select";
import { useFormik, Form, FormikProvider } from "formik";
import * as yup from "yup";
import { BookUpdatePayload } from "../../interface";
import useBookModule from "../../lib";
import Link from "next/link";
import { ArrowLongLeftIcon } from "@heroicons/react/20/solid";
import { option } from "../../tambah/page";

const createBookSchema = yup.object().shape({
  title: yup.string().nullable().default("").required("Wajib isi"),
  author: yup.string().nullable().default("").required("Wajib isi"),
  year: yup.number().nullable().default(undefined).required("Wajib pilih"),
});

const UpdateBook = ({ params }: { params: { id: string } }) => {


  const formik = useFormik<BookUpdatePayload>({
    initialValues: {
      title:  "",
      year: "",
      author:  "",
      id: 0,
    },
    validationSchema: createBookSchema,
    enableReinitialize: true,
    onSubmit: ()=> console.log('update'),
  });

  const {
    handleChange,
    handleSubmit,
    setFieldValue,
    handleBlur,
    values,
    errors,
    resetForm,
    setValues,
  } = formik;


  return (
    <section className="flex items-center  justify-center w-full h-full">
      <section className="w-1/2">
        <Link href={"/book"}>
          <span className="flex items-center">
            {" "}
            <ArrowLongLeftIcon className="h-5 w-5 mr-2" />
            Kembali
          </span>
        </Link>
        <h2 className="text-xl font-bold text-gray-500">Perbaharui Buku</h2>

        <FormikProvider value={formik}>
          <Form className="space-y-5" onSubmit={handleSubmit}>
            <section>
              <Label htmlFor="title" title="Title" />
              <InputText
                value={values.title}
                placeholder="Judul Buku"
                id="title"
                name="title"
                onChange={handleChange}
                onBlur={handleBlur}
                isError={!!errors.title}
                messageError={errors.title}
              />
            </section>
            <section>
              <Label htmlFor="author" title="Auhtor" />
              <InputText
                value={values.author}
                placeholder="Penulis Buku"
                id="author"
                name="author"
                onChange={handleChange}
                onBlur={handleBlur}
                isError={!!errors.author}
                messageError={errors.author}
              />
            </section>
            <section>
              <Label htmlFor="year" title="Year" />
              <Select
                value={values.year}
                id="year"
                name="year"
                onChange={handleChange}
                onBlur={handleBlur}
                options={option}
                isError={!!errors.year}
                messageError={errors.year}
              />
            </section>
            <section>
              <Button
                height="md"
                title="Perbarui"
                colorSchema="blue"

              />
            </section>
          </Form>
        </FormikProvider>
      </section>
    </section>
  );
};

export default UpdateBook;

Alt text

Membuat interface Detail Book

Alt text

request endpoint detail
http://localhost:5002/book/detail/1
response endpoint detail
{
    "status": "Success",
    "message": "OK",
    "data": {
        "id": 1,
        "title": "nestjs testing update 1",
        "author": "ihsan santana",
        "year": 2021,
        "created_at": "2023-09-22T06:05:48.000Z",
        "updated_at": "2023-09-22T06:05:48.000Z"
    }
}
app/book/interface/index.ts
import {
  BaseResponsePagination,
} from "@/lib/axiosClient";

interface Book {
  id: number | undefined;
  title: string;
  author: string;
  year: number | undefined | string;
  created_at: string;
  updated_at: string;
}

export interface BookListResponse extends BaseResponsePagination {
  data: Book[];
}

export interface BookListFilter extends Partial<Book> {
  from_year?: string;
  to_year?: string;
  page : number ,
  pageSize : number
}


export interface BookCreatePayload extends Pick<Book, "author" | "title" | "year"> {}
export interface BookDetail extends Book{}

Membuat Service Detail Book

app/book/lib/index.ts
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { axiosClient } from "@/lib/axiosClient";
import Swal from "sweetalert2";
import {
  BookCreatePayload,
  BookDetail,
  BookListFilter,
  BookListResponse,
} from "../interface";
import { usePagination } from "@/hook/usePagination";

const useBookModule = () => {

  const queryClient = useQueryClient();
  const defaultParams: BookListFilter = {
    title: "",
    author: "",
    from_year: "",
    to_year: "",
    page: 1,
    pageSize: 10,
  };


 ....




  const getDetailBook = async (
    id:string
  ): Promise<BookDetail> => {
    return axiosClient.get(`/book/detail/${id}`).then((res) => res.data.data);
  };

  const useDetailBook = (id:string) => {
    const { data, isLoading, isFetching } = useQuery(
      ["/book/detail", { id }],
      () => getDetailBook(id),
      {
        select: (response) => response,


      }
    );

    return { data, isFetching, isLoading };

  }

  return { useBookList, useCreateBook, useDetailBook };
};

export default useBookModule;

Memanggil Service Detail di Halaman update

book/[id]/update/page.tsx
"use client";
import Button from "@/components/Button";
import InputText from "@/components/InputText";
import Label from "@/components/Label";
import Select from "@/components/Select";
import { useFormik, Form, FormikProvider } from "formik";
import * as yup from "yup";
import { BookUpdatePayload } from "../../interface";
import useBookModule from "../../lib";
import Link from "next/link";
import { ArrowLongLeftIcon } from "@heroicons/react/20/solid";
import { option } from "../../tambah/page";

const createBookSchema = yup.object().shape({
  title: yup.string().nullable().default("").required("Wajib isi"),
  author: yup.string().nullable().default("").required("Wajib isi"),
  year: yup.number().nullable().default(undefined).required("Wajib pilih"),
});

const UpdateBook = ({ params }: { params: { id: string } }) => {
  const { useDetailBook, useUpdateBook } = useBookModule();
  const { mutate, isLoading } = useUpdateBook(params.id);
  const { data, isFetching } = useDetailBook(params.id);



  const formik = useFormik<BookUpdatePayload>({
    initialValues: {
      title: data?.title || "",
      year: data?.year,
      author: data?.author || "",
      id: data?.id,
    },
    validationSchema: createBookSchema,
    enableReinitialize: true,
    onSubmit: ()=> console.log('update'),
  });

  const {
    handleChange,
    handleSubmit,
    setFieldValue,
    handleBlur,
    values,
    errors,
    resetForm,
    setValues,
  } = formik;

  if(isFetching) {

    return (
        <p>Loading</p>
    )
  }
  return (
    <section className="flex items-center  justify-center w-full h-full">
      <section className="w-1/2">
        <Link href={"/book"}>
          <span className="flex items-center">
            {" "}
            <ArrowLongLeftIcon className="h-5 w-5 mr-2" />
            Kembali
          </span>
        </Link>
        <h2 className="text-xl font-bold text-gray-500">Perbaharui Buku</h2>

        <FormikProvider value={formik}>
          <Form className="space-y-5" onSubmit={handleSubmit}>
            <section>
              <Label htmlFor="title" title="Title" />
              <InputText
                value={values.title}
                placeholder="Judul Buku"
                id="title"
                name="title"
                onChange={handleChange}
                onBlur={handleBlur}
                isError={!!errors.title}
                messageError={errors.title}
              />
            </section>
            <section>
              <Label htmlFor="author" title="Auhtor" />
              <InputText
                value={values.author}
                placeholder="Penulis Buku"
                id="author"
                name="author"
                onChange={handleChange}
                onBlur={handleBlur}
                isError={!!errors.author}
                messageError={errors.author}
              />
            </section>
            <section>
              <Label htmlFor="year" title="Year" />
              <Select
                value={values.year}
                id="year"
                name="year"
                onChange={handleChange}
                onBlur={handleBlur}
                options={option}
                isError={!!errors.year}
                messageError={errors.year}
              />
            </section>
            <section>
              <Button
                height="md"
                title="Perbarui"
                colorSchema="blue"
                isLoading={isLoading}
                isDisabled={isLoading}
              />
            </section>
          </Form>
        </FormikProvider>
      </section>
    </section>
  );
};

export default UpdateBook;

Alt text

2. Integrasi Update Book

Membuat interface update book

Alt text

request endpoint update
http://localhost:5002/book/update/1
payload endpoint update
{
    "title" : "NestJS Up",
    "author" : "Ihsan Update",
    "year" : 2023
}

response endpoint update
{
    "status": "Success ",
    "message": "Buku berhasil di update",
    "data": {
        "title": "NestJS Up",
        "author": "Ihsan Update",
        "year": 2023,
        "id": 1
    }
}
book/interface/index.ts
import {
  BaseResponsePagination,
} from "@/lib/axiosClient";

interface Book {
  id: number | undefined;
  title: string;
  author: string;
  year: number | undefined | string;
  created_at: string;
  updated_at: string;
}

export interface BookListResponse extends BaseResponsePagination {
  data: Book[];
}

export interface BookListFilter extends Partial<Book> {
  from_year?: string;
  to_year?: string;
  page : number ,
  pageSize : number 
}


export interface BookCreatePayload extends Pick<Book, "author" | "title" | "year"> {}
export interface BookUpdatePayload extends Pick<Book, "author" | "title" | "year" | "id"> {}
export interface BookDetail extends Book{}

Membuat Service Update Book

book/lib/index.ts
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { axiosClient } from "@/lib/axiosClient";
import Swal from "sweetalert2";
import {
  BookCreatePayload,
  BookDetail,
  BookListFilter,
  BookListResponse,
  BookUpdatePayload,
} from "../interface";
import { usePagination } from "@/hook/usePagination";

const useBookModule = () => {

  const queryClient = useQueryClient();
  const defaultParams: BookListFilter = {
    title: "",
    author: "",
    from_year: "",
    to_year: "",
    page: 1,
    pageSize: 10,
  };

  ...


  const useUpdateBook = (id:string) => {
    const { mutate, isLoading } = useMutation(
      (payload: BookUpdatePayload) => {
        return axiosClient.put(`/book/update/${id}`, payload);
      },
      {
        onSuccess: (response) => {
          Swal.fire({
            position: "top-end",
            icon: "success",
            title: response.data.message,
            showConfirmButton: false,
            timer: 1000,
          });
          queryClient.invalidateQueries(["/book/detail"]);
        },

        onError: (error) => {
          alert("ok");
        },
      }
    );
    return { mutate, isLoading };
  };

  return { useBookList, useCreateBook, useDetailBook, useUpdateBook };
};

export default useBookModule;

Memanggil Service Update Book di Halaman update

book/[id]/update/page.tsx
"use client";
import Button from "@/components/Button";
import InputText from "@/components/InputText";
import Label from "@/components/Label";
import Select from "@/components/Select";
import { useFormik, Form, FormikProvider } from "formik";
import * as yup from "yup";
import { BookUpdatePayload } from "../../interface";
import useBookModule from "../../lib";
import Link from "next/link";
import { ArrowLongLeftIcon } from "@heroicons/react/20/solid";
import { option } from "../../tambah/page";

const createBookSchema = yup.object().shape({
  title: yup.string().nullable().default("").required("Wajib isi"),
  author: yup.string().nullable().default("").required("Wajib isi"),
  year: yup.number().nullable().default(undefined).required("Wajib pilih"),
});

const UpdateBook = ({ params }: { params: { id: string } }) => {
  const { useDetailBook, useUpdateBook } = useBookModule();
  const { mutate, isLoading } = useUpdateBook(params.id);
  const { data, isFetching } = useDetailBook(params.id);

  const onSubmit = async (values: BookUpdatePayload) => {
    mutate(values);
  };

  const formik = useFormik<BookUpdatePayload>({
    initialValues: {
      title: data?.title || "",
      year: data?.year,
      author: data?.author || "",
      id: data?.id,
    },
    validationSchema: createBookSchema,
    enableReinitialize: true,
    onSubmit: onSubmit,
  });

  const {
    handleChange,
    handleSubmit,
    setFieldValue,
    handleBlur,
    values,
    errors,
    resetForm,
    setValues,
  } = formik;

  if(isFetching) {

    return (
        <p>Loading</p>
    )
  }
  return (
    <section className="flex items-center  justify-center w-full h-full">
      <section className="w-1/2">
        <Link href={"/book"}>
          <span className="flex items-center">
            {" "}
            <ArrowLongLeftIcon className="h-5 w-5 mr-2" />
            Kembali
          </span>
        </Link>
        <h2 className="text-xl font-bold text-gray-500">Perbaharui Buku</h2>

        <FormikProvider value={formik}>
          <Form className="space-y-5" onSubmit={handleSubmit}>
            <section>
              <Label htmlFor="title" title="Title" />
              <InputText
                value={values.title}
                placeholder="Judul Buku"
                id="title"
                name="title"
                onChange={handleChange}
                onBlur={handleBlur}
                isError={!!errors.title}
                messageError={errors.title}
              />
            </section>
            <section>
              <Label htmlFor="author" title="Auhtor" />
              <InputText
                value={values.author}
                placeholder="Penulis Buku"
                id="author"
                name="author"
                onChange={handleChange}
                onBlur={handleBlur}
                isError={!!errors.author}
                messageError={errors.author}
              />
            </section>
            <section>
              <Label htmlFor="year" title="Year" />
              <Select
                value={values.year}
                id="year"
                name="year"
                onChange={handleChange}
                onBlur={handleBlur}
                options={option}
                isError={!!errors.year}
                messageError={errors.year}
              />
            </section>
            <section>
              <Button
                height="md"
                title="Perbarui"
                colorSchema="blue"
                isLoading={isLoading}
                isDisabled={isLoading}
              />
            </section>
          </Form>
        </FormikProvider>
      </section>
    </section>
  );
};

export default UpdateBook;

Alt text

Alt text