diff --git a/src/app/globals.css b/src/app/globals.css
index 386e7620..0fb52327 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1,6 +1,43 @@
@import 'tailwindcss';
@plugin "daisyui";
+@plugin "daisyui/theme" {
+ name: "corporate";
+ default: false;
+ prefersdark: false;
+ color-scheme: "light";
+ --color-base-100: oklch(98% 0.001 106.423);
+ --color-base-200: oklch(97% 0.001 106.424);
+ --color-base-300: oklch(92% 0.003 48.717);
+ --color-base-content: oklch(22.389% 0.031 278.072);
+ --color-primary: oklch(60% 0.126 221.723);
+ --color-primary-content: oklch(100% 0 0);
+ --color-secondary: oklch(52% 0.105 223.128);
+ --color-secondary-content: oklch(100% 0 0);
+ --color-accent: oklch(45% 0.085 224.283);
+ --color-accent-content: oklch(100% 0 0);
+ --color-neutral: oklch(39% 0.07 227.392);
+ --color-neutral-content: oklch(100% 0 0);
+ --color-info: oklch(58% 0.158 241.966);
+ --color-info-content: oklch(100% 0 0);
+ --color-success: oklch(62% 0.194 149.214);
+ --color-success-content: oklch(100% 0 0);
+ --color-warning: oklch(85% 0.199 91.936);
+ --color-warning-content: oklch(0% 0 0);
+ --color-error: oklch(57% 0.245 27.325);
+ --color-error-content: oklch(100% 0 0);
+ --radius-selector: 0rem;
+ --radius-field: 0.25rem;
+ --radius-box: 0.25rem;
+ --size-selector: 0.21875rem;
+ --size-field: 0.1875rem;
+ --border: 1px;
+ --depth: 0;
+ --noise: 0;
+}
+
+
+
:root {
--color-primary: #1f74bf;
}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index ef28da38..c19b8a77 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -28,7 +28,7 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
-
+
{children}
diff --git a/src/app/master-data/flock/add/page.tsx b/src/app/master-data/flock/add/page.tsx
new file mode 100644
index 00000000..5ee3958e
--- /dev/null
+++ b/src/app/master-data/flock/add/page.tsx
@@ -0,0 +1,11 @@
+import FlockForm from "@/components/pages/master-data/flock/form/FlockForm";
+
+const AddFlock = () => {
+ return (
+
+ );
+}
+
+export default AddFlock;
diff --git a/src/app/master-data/flock/detail/edit/page.tsx b/src/app/master-data/flock/detail/edit/page.tsx
new file mode 100644
index 00000000..c3903555
--- /dev/null
+++ b/src/app/master-data/flock/detail/edit/page.tsx
@@ -0,0 +1,47 @@
+import FlockForm from "@/components/pages/master-data/flock/form/FlockForm";
+import { isResponseError, isResponseSuccess } from "@/lib/api-helper";
+import { FlockApi } from "@/services/api/master-data";
+import { useRouter, useSearchParams } from "next/navigation";
+import useSWR from "swr";
+
+const FlockEdit = () => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ // Get Query Params
+ const flockId = searchParams.get('flockId');
+
+ // Fetch Data
+ const { data: flock, isLoading: isLoadingFlock } = useSWR(
+ flockId,
+ (id: number) => FlockApi.getSingle(id)
+ );
+
+ if (!flockId) {
+ router.back();
+
+ return (
+
+
+
+ );
+ }
+
+ if (!isLoadingFlock && (!flock || isResponseError(flock))) {
+ router.replace('/404');
+ return;
+ }
+
+ return (
+
+ {isLoadingFlock && (
+
+ )}
+ {!isLoadingFlock && isResponseSuccess(flock) && (
+
+ )}
+
+ );
+}
+
+export default FlockEdit;
\ No newline at end of file
diff --git a/src/app/master-data/flock/detail/page.tsx b/src/app/master-data/flock/detail/page.tsx
new file mode 100644
index 00000000..cedc3243
--- /dev/null
+++ b/src/app/master-data/flock/detail/page.tsx
@@ -0,0 +1,44 @@
+import FlockForm from "@/components/pages/master-data/flock/form/FlockForm";
+import { isResponseError, isResponseSuccess } from "@/lib/api-helper";
+import { FlockApi } from "@/services/api/master-data";
+import { useRouter, useSearchParams } from "next/navigation";
+import useSWR from "swr";
+
+const FlockDetail = () => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+
+ // Get Query Params
+ const flockId = searchParams.get('flockId');
+
+ // Fetch Data
+ const { data: flock, isLoading: isLoadingFlock } = useSWR(flockId, (id: number) => FlockApi.getSingle(id));
+
+ if(!flockId){
+ router.back();
+
+ return (
+
+
+
+ );
+ }
+
+ if(!isLoadingFlock && (!flock || isResponseError(flock))){
+ router.replace('/404');
+ return;
+ }
+
+ return (
+
+ {isLoadingFlock && (
+
+ )}
+ {!isLoadingFlock && isResponseSuccess(flock) && (
+
+ )}
+
+ );
+}
+
+export default FlockDetail;
\ No newline at end of file
diff --git a/src/app/master-data/flock/page.tsx b/src/app/master-data/flock/page.tsx
new file mode 100644
index 00000000..b317091a
--- /dev/null
+++ b/src/app/master-data/flock/page.tsx
@@ -0,0 +1,11 @@
+import FlockTable from "@/components/pages/master-data/flock/FlocksTable";
+
+const Flock = () => {
+ return (
+
+ );
+}
+
+export default Flock;
diff --git a/src/components/Button.tsx b/src/components/Button.tsx
index c67a29c2..5da6e5ad 100644
--- a/src/components/Button.tsx
+++ b/src/components/Button.tsx
@@ -43,7 +43,7 @@ const Button = ({
'btn-warning': color === 'warning',
'btn-error': color === 'error',
},
- 'h-fit justify-center items-center gap-2 rounded-xl p-2 text-base transition-all'
+ 'h-fit justify-center items-center gap-2 rounded p-2 text-base transition-all'
);
return (
diff --git a/src/components/pages/master-data/flock/FlocksTable.tsx b/src/components/pages/master-data/flock/FlocksTable.tsx
new file mode 100644
index 00000000..817eff40
--- /dev/null
+++ b/src/components/pages/master-data/flock/FlocksTable.tsx
@@ -0,0 +1,264 @@
+'use client';
+
+import { CellContext, ColumnDef } from '@tanstack/react-table';
+import { Flock } from '@/types/api/master-data/flock';
+import { cn } from '@/lib/helper';
+import Button from '@/components/Button';
+import { Icon } from '@iconify/react';
+import { useTableFilter } from '@/services/hooks/useTableFilter';
+import { use, useState } from 'react';
+import useSWR from 'swr';
+import { FlockApi } from '@/services/api/master-data';
+import { useModal } from '@/components/Modal';
+import RowDropdownOptions from '@/components/table/RowDropdownOptions';
+import RowCollapseOptions from '@/components/table/RowCollapseOptions';
+import toast from 'react-hot-toast';
+import DebouncedTextInput from '@/components/input/DebouncedTextInput';
+import SelectInput, { OptionType } from '@/components/input/SelectInput';
+import { ROWS_OPTIONS } from '@/config/constant';
+import Table from '@/components/Table';
+import { isResponseSuccess } from '@/lib/api-helper';
+import ConfirmationModal from '@/components/modal/ConfirmationModal';
+
+const RowsOptions = ({
+ type = 'dropdown',
+ props,
+ deleteClickHandler,
+}: {
+ type: 'dropdown' | 'collapse';
+ props: CellContext;
+ deleteClickHandler: () => void;
+}) => {
+ return (
+
+
+
+
+ );
+};
+
+const FlockTable = () => {
+ const {
+ state: tableFilterState,
+ updateFilter,
+ setPage,
+ setPageSize,
+ toQueryString: getTableFilterQueryString,
+ } = useTableFilter({
+ initial: { search: '', nameSort: '' },
+ paramMap: {
+ page: 'page',
+ pageSize: 'limit',
+ nameSort: 'sort_name',
+ },
+ });
+
+ // Fetch Data
+ const {
+ data: flocks,
+ isLoading,
+ mutate: refreshFlocks,
+ } = useSWR(
+ `${FlockApi.basePath}${getTableFilterQueryString()}`,
+ FlockApi.getAllFetcher
+ );
+
+ // State
+ const deleteModal = useModal();
+ const [selectedFlock, setSelectedFlock] = useState(
+ undefined
+ );
+ const [isDeleteLoading, setIsDeleteLoading] = useState(false);
+
+ // Columns Definition
+ const flocksColumns: ColumnDef[] = [
+ {
+ header: '#',
+ cell: (props) =>
+ tableFilterState.pageSize * (tableFilterState.page - 1) +
+ props.row.index +
+ 1,
+ },
+ {
+ accessorKey: 'name',
+ header: 'Nama',
+ },
+ {
+ accessorKey: 'created_at',
+ header: 'Dibuat pada',
+ cell: (props) =>
+ new Date(props.row.original.created_at).toLocaleDateString(),
+ },
+ {
+ header: 'Aksi',
+ cell: (props) => {
+ const currentPageSize = props.table.getPaginationRowModel().rows.length;
+ const currentPageRows = props.table.getPaginationRowModel().flatRows;
+ const currentRowRelativeIndex =
+ currentPageRows.findIndex((r) => r.id === props.row.id) + 1;
+
+ const isLast2Rows = currentRowRelativeIndex > currentPageSize - 2;
+
+ const deleteClickHandler = () => {
+ setSelectedFlock(props.row.original);
+ deleteModal.openModal();
+ };
+
+ return (
+ <>
+ {currentPageSize > 2 && (
+
+
+
+ )}
+ {currentPageSize <= 2 && (
+
+
+
+ )}
+ >
+ );
+ },
+ },
+ ];
+
+ // Handler
+ const confirmationModalDeleteClickHandler = async () => {
+ setIsDeleteLoading(true);
+
+ await FlockApi.delete(selectedFlock?.id as number);
+ refreshFlocks();
+
+ deleteModal.closeModal();
+ toast.success('Successfully delete Flock!');
+ setIsDeleteLoading(false);
+ };
+ const searchChangeHandler = (e: React.ChangeEvent) => {
+ updateFilter('search', e.target.value);
+ };
+ const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
+ const newVal = val as OptionType;
+ setPageSize(newVal.value as number);
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ data={isResponseSuccess(flocks) ? flocks?.data : []}
+ columns={flocksColumns}
+ pageSize={tableFilterState.pageSize}
+ page={isResponseSuccess(flocks) ? flocks?.meta?.page : 0}
+ totalItems={
+ isResponseSuccess(flocks) ? flocks?.meta?.total_results : 0
+ }
+ onPageChange={setPage}
+ isLoading={isLoading}
+ className={{
+ containerClassName: cn({
+ 'mb-20': isResponseSuccess(flocks) && flocks?.data?.length === 0,
+ }),
+ tableWrapperClassName: 'overflow-x-auto min-h-full!',
+ tableClassName: 'font-inter w-full table-auto min-h-full!',
+ headerRowClassName: 'border-b border-b-gray-200',
+ headerColumnClassName:
+ 'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
+ bodyRowClassName: 'border-b border-b-gray-200',
+ bodyColumnClassName:
+ 'px-6 py-3 last:flex last:flex-row last:justify-end',
+ }}
+ />
+
+
+ >
+ );
+};
+
+export default FlockTable;
\ No newline at end of file
diff --git a/src/components/pages/master-data/flock/form/FlockForm.schema.ts b/src/components/pages/master-data/flock/form/FlockForm.schema.ts
new file mode 100644
index 00000000..0a85b0fc
--- /dev/null
+++ b/src/components/pages/master-data/flock/form/FlockForm.schema.ts
@@ -0,0 +1,14 @@
+import * as Yup from 'yup';
+
+export const FlockFormSchema = Yup.object({
+ name: Yup.string()
+ .required('Nama wajib diisi!')
+ .matches(
+ /^[a-zA-Z0-9]+$/,
+ 'Nama hanya boleh berisi huruf dan angka (tanpa spasi atau simbol)'
+ ),
+});
+
+export const UpdateFlockFormSchema = FlockFormSchema;
+
+export type FlockFormValues = Yup.InferType;
\ No newline at end of file
diff --git a/src/components/pages/master-data/flock/form/FlockForm.tsx b/src/components/pages/master-data/flock/form/FlockForm.tsx
new file mode 100644
index 00000000..f73d47f0
--- /dev/null
+++ b/src/components/pages/master-data/flock/form/FlockForm.tsx
@@ -0,0 +1,217 @@
+'use client'
+
+import { useModal } from '@/components/Modal';
+import { FlockApi } from '@/services/api/master-data';
+import { Flock } from '@/types/api/master-data/flock';
+import { useRouter } from 'next/navigation';
+import { useEffect, useMemo, useState } from 'react';
+import { FlockFormSchema, FlockFormValues, UpdateFlockFormSchema } from './FlockForm.schema';
+import { useFormik } from 'formik';
+import Button from '@/components/Button';
+import { Icon } from '@iconify/react';
+import TextInput from '@/components/input/TextInput';
+import { cn } from '@/lib/helper';
+import ConfirmationModal from '@/components/modal/ConfirmationModal';
+
+interface FlockCustomProps {
+ formType?: 'add' | 'edit' | 'detail';
+ initialValues?: Flock;
+}
+
+const FlockForm = ({ formType = 'add', initialValues }: FlockCustomProps) => {
+ const router = useRouter();
+ const deleteModal = useModal();
+
+ // State
+ const [flockFormErrorMessage, setFlockFormErrorMessage] = useState('');
+ const [isDeleteLoading, setIsDeleteLoading] = useState(false);
+
+ // Handler
+ const confirmationModalDeleteClickHandler = async () => {
+ setIsDeleteLoading(true);
+
+ await FlockApi.delete(initialValues?.id as number);
+
+ deleteModal.closeModal();
+ setIsDeleteLoading(false);
+ router.push('/master-data/flock');
+ };
+
+ // Initital Value
+ const formikInitialValue = useMemo(() => {
+ return {
+ name: initialValues?.name ?? '',
+ };
+ }, [initialValues]);
+
+ // Formik
+ const formik = useFormik({
+ initialValues: formikInitialValue,
+ enableReinitialize: true,
+ validationSchema: formType === 'edit' ? UpdateFlockFormSchema : FlockFormSchema,
+ onSubmit: async (values) => {
+ // reset error message
+ setFlockFormErrorMessage('');
+
+ // create payload
+ const payload = {
+ name: values.name,
+ };
+
+ // cek type form yang disubmit
+ switch (formType) {
+ case 'add':
+ await FlockApi.create(payload);
+ break;
+ case 'edit':
+ await FlockApi.update(initialValues?.id as number, payload);
+ break;
+ default:
+ break;
+ }
+
+ router.push('/master-data/flock');
+ },
+ });
+
+ // Initialize Formik
+ const { setValues: formikSetValues } = formik;
+ useEffect(() => {
+ formikSetValues(formikInitialValue);
+ }, [formikSetValues, formikInitialValue]);
+
+ // Render
+ return (
+ <>
+
+
+
+
+
+ {formType === 'add' && 'Tambah Flock'}
+ {formType === 'edit' && 'Ubah Flock'}
+ {formType === 'detail' && 'Detail Flock'}
+
+
+
+
+
+ {formType !== 'add' && (
+
+ )}
+ >
+ );
+};
+
+export default FlockForm;
diff --git a/src/config/constant.ts b/src/config/constant.ts
index ed68adb5..97e4c285 100644
--- a/src/config/constant.ts
+++ b/src/config/constant.ts
@@ -77,6 +77,11 @@ export const MAIN_DRAWER_LINKS: MAIN_DRAWER_MENU[] = [
link: '/master-data/supplier',
icon: 'material-symbols:add-business-outline-rounded',
},
+ {
+ title: 'Flock',
+ link: '/master-data/flock',
+ icon: 'material-symbols:raven-outline-rounded',
+ },
],
},
{
diff --git a/src/services/api/master-data.ts b/src/services/api/master-data.ts
index dce528e7..854bb8f3 100644
--- a/src/services/api/master-data.ts
+++ b/src/services/api/master-data.ts
@@ -59,6 +59,11 @@ import {
Fcr,
UpdateFcrPayload,
} from '@/types/api/master-data/fcr';
+import {
+ CreateFlockPayload,
+ Flock,
+ UpdateFlockPayload,
+} from '@/types/api/master-data/flock';
export const UomApi = new BaseApiService<
Uom,
@@ -130,3 +135,9 @@ export const FcrApi = new BaseApiService<
CreateFcrPayload,
UpdateFcrPayload
>('/master-data/fcrs');
+
+export const FlockApi = new BaseApiService<
+ Flock,
+ CreateFlockPayload,
+ UpdateFlockPayload
+>('/master-data/flocks');
\ No newline at end of file
diff --git a/src/types/api/master-data/flock.d.ts b/src/types/api/master-data/flock.d.ts
new file mode 100644
index 00000000..0c59b84c
--- /dev/null
+++ b/src/types/api/master-data/flock.d.ts
@@ -0,0 +1,14 @@
+import { BaseMetadata } from "../api-general";
+
+export type BaseFlock = {
+ id: number;
+ name: string;
+}
+
+export type Flock = BaseMetadata & BaseFlock;
+
+export type CreateFlockPayload = {
+ name: string;
+}
+
+export type UpdateFlockPayload = CreateFlockPayload;
\ No newline at end of file