diff --git a/package-lock.json b/package-lock.json
index a39060ef..72580442 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,6 +18,7 @@
"react": "19.1.0",
"react-dom": "19.1.0",
"react-hot-toast": "^2.6.0",
+ "react-number-format": "^5.4.4",
"react-select": "^5.10.2",
"swr": "^2.3.6",
"tailwind-merge": "^3.3.1",
@@ -5811,6 +5812,16 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT"
},
+ "node_modules/react-number-format": {
+ "version": "5.4.4",
+ "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.4.tgz",
+ "integrity": "sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/react-select": {
"version": "5.10.2",
"resolved": "https://registry.npmjs.org/react-select/-/react-select-5.10.2.tgz",
diff --git a/package.json b/package.json
index e970499c..2e806ddd 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"react": "19.1.0",
"react-dom": "19.1.0",
"react-hot-toast": "^2.6.0",
+ "react-number-format": "^5.4.4",
"react-select": "^5.10.2",
"swr": "^2.3.6",
"tailwind-merge": "^3.3.1",
diff --git a/src/app/production/chickin/add/page.tsx b/src/app/production/chickin/add/page.tsx
index 1823b41a..af834b37 100644
--- a/src/app/production/chickin/add/page.tsx
+++ b/src/app/production/chickin/add/page.tsx
@@ -59,7 +59,7 @@ const AddChickin = () => {
? listProjectFlock?.data.map((projectFlock) => {
return {
value: projectFlock.id,
- label: `${projectFlock?.flock.name} - ${projectFlock?.category} - Periode ${projectFlock.period}`,
+ label: `${projectFlock?.flock?.name} - ${projectFlock?.category} - Periode ${projectFlock.period}`,
};
})
: [];
@@ -242,6 +242,7 @@ const AddChickin = () => {
created_user: projectFlock.data.created_user,
created_at: projectFlock.data.created_at,
updated_at: projectFlock.data.updated_at,
+ approval: projectFlock.data.approval,
}}
afterSubmit={handleAfterSubmit}
/>
diff --git a/src/app/production/chickin/detail/page.tsx b/src/app/production/chickin/detail/page.tsx
index d62c92bf..aef7c4b7 100644
--- a/src/app/production/chickin/detail/page.tsx
+++ b/src/app/production/chickin/detail/page.tsx
@@ -1,24 +1,51 @@
-'use client'
+'use client';
-import { isResponseError, isResponseSuccess } from "@/lib/api-helper";
-import { ChickinApi } from "@/services/api/production";
-import { useRouter, useSearchParams } from "next/navigation";
-import useSWR from "swr";
+import Button from '@/components/Button';
+import Card from '@/components/Card';
+import Modal, { useModal } from '@/components/Modal';
+import ConfirmationModal from '@/components/modal/ConfirmationModal';
+import ChickinForm from '@/components/pages/production/chickin/form/ChickinForm';
+import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
+import { ChickinApi } from '@/services/api/production';
+import { BaseApiResponse } from '@/types/api/api-general';
+import {
+ Chickin,
+ ChickinApprovalPayload,
+} from '@/types/api/production/chickin';
+import { Icon } from '@iconify/react';
+import { useRouter, useSearchParams } from 'next/navigation';
+import { useState } from 'react';
+import toast from 'react-hot-toast';
+import useSWR from 'swr';
const DetailChickin = () => {
const router = useRouter();
const searchParams = useSearchParams();
const chickinId = searchParams.get('chickinId');
+ const [isApproveLoading, setIsApproveLoading] = useState(false);
+ const [isDeleteLoading, setIsDeleteLoading] = useState(false);
+ const confirmModal = useModal();
+ const deleteModal = useModal();
+ const chickinModal = useModal();
const {
data: chickin,
isLoading,
- } = useSWR(
- chickinId,
- (id: number) => ChickinApi.getSingle(id)
+ mutate: refreshChickin,
+ } = useSWR(chickinId, (id: number) => ChickinApi.getSingle(id));
+
+ const [isApprovedDisabled, setIsApprovedDisabled] = useState(
+ // chickin.data?.approval.step_number == 1 ? false : true
+ true
+ );
+ const [isRejectedDisabled, setIsRejectedDisabled] = useState(
+ !isApprovedDisabled
+ );
+ const [approvalAction, setApprovalAction] = useState<'APPROVED' | 'REJECTED'>(
+ !isApprovedDisabled ? 'APPROVED' : 'REJECTED'
);
- if(!chickinId){
+ if (!chickinId) {
router.back();
return (
@@ -28,46 +55,291 @@ const DetailChickin = () => {
);
}
- if (
- !isLoading &&
- (!chickin || isResponseError(chickin))
- ) {
+ if (!isLoading && (!chickin || isResponseError(chickin))) {
router.replace('/404');
return;
}
+ if (!isResponseSuccess(chickin)) {
+ return (
+
+
+
+ );
+ }
+
+ const confirmationModalClickHandler = async ({
+ action = 'APPROVED',
+ }: {
+ action: 'APPROVED' | 'REJECTED';
+ }) => {
+ if (chickin?.data.id === undefined) return;
+ setIsApproveLoading(true);
+ const approveChickinRes = await ChickinApi.customRequest<
+ BaseApiResponse,
+ ChickinApprovalPayload
+ >(`/approvals`, {
+ method: 'POST',
+ payload: {
+ action: action,
+ approvable_ids: [chickin.data.id],
+ },
+ });
+
+ if (isResponseSuccess(approveChickinRes)) {
+ if (refreshChickin) {
+ await refreshChickin();
+ }
+ toast.success(approveChickinRes.message as string);
+ }
+ if (isResponseError(approveChickinRes)) {
+ toast.error(approveChickinRes?.message as string);
+ }
+ confirmModal.closeModal();
+ setIsApproveLoading(false);
+ };
+
+ const confirmationModalDeleteClickHandler = async () => {
+ setIsDeleteLoading(true);
+ const deleteProjectFlockRes = await ChickinApi.delete(
+ chickin.data?.id as number
+ );
+
+ if (isResponseSuccess(deleteProjectFlockRes)) {
+ toast.success(deleteProjectFlockRes?.message as string);
+ router.push('/production/chickin');
+ }
+ if (isResponseError(deleteProjectFlockRes)) {
+ toast.error(deleteProjectFlockRes?.message as string);
+ }
+ setIsDeleteLoading(false);
+ };
+
return (
<>
-
+
{isLoading &&
}
{!isLoading && isResponseSuccess(chickin) && (
<>
-
-
-
- Informasi Project Flock
+ {/*
+
+
+
*/}
+
+
+
+
Flock
+
+ {
+ chickin.data.project_flock_kandang?.project_flock.flock
+ .name
+ }
+
-
-
-
-
Flock
-
{chickin.data.project_flock_kandang?.project_flock.flock.name}
+
+
Area
+
+ {
+ chickin.data.project_flock_kandang?.project_flock.area
+ .name
+ }
+
+
+
+
Kategori
+
+ {chickin.data.project_flock_kandang?.project_flock.category}
+
+
+
+
Lokasi
+
+ {
+ chickin.data.project_flock_kandang?.project_flock.location
+ .name
+ }
+
+
+
+
Periode
+
+ {chickin.data.project_flock_kandang?.project_flock.period}
+
+
+
+
Kandang
+
+ {chickin.data.project_flock_kandang?.kandang.name}
-
-
-
-
- Informasi Chickin
+
+
+
+
+
Flock Kandang
+
+ {
+ chickin.data.project_flock_kandang?.project_flock.flock
+ .name
+ }{' '}
+ - {chickin.data.project_flock_kandang?.kandang.name}
+
+
+
+
Tanggal Chickin
+
+ {chickin.data.chick_in_date
+ ? new Date(chickin.data.chick_in_date).toLocaleDateString(
+ 'id-ID'
+ )
+ : '-'}
+
+
+
+
Jumlah (Ekor)
+
+ {chickin.data.quantity?.toLocaleString('id-ID')}
+
+
+
+
Catatan
+
{chickin.data.note}
+
+
+
+
>
)}
+
+
+
+
+
+
+ Chickin Kandang -{' '}
+ {chickin?.data?.project_flock_kandang &&
+ chickin?.data?.project_flock_kandang.kandang?.name}
+
+
+
+ {
+ refreshChickin();
+ chickinModal.closeModal();
+ }}
+ />
+
+
+
{
+ confirmationModalClickHandler({
+ action: approvalAction,
+ });
+ },
+ }}
+ />
>
);
-}
+};
-export default DetailChickin;
\ No newline at end of file
+export default DetailChickin;
diff --git a/src/components/Card.tsx b/src/components/Card.tsx
new file mode 100644
index 00000000..1895c2c5
--- /dev/null
+++ b/src/components/Card.tsx
@@ -0,0 +1,178 @@
+"use client";
+
+import { HTMLAttributes, ReactNode } from "react";
+
+import { cn } from "@/lib/helper";
+import Image from "next/image";
+
+export interface CardProps
+ extends Omit, "className"> {
+ title?: string;
+ subtitle?: string;
+ image?: string;
+ imageAlt?: string;
+ imageWidth?: number;
+ imageHeight?: number;
+ actions?: ReactNode;
+ footer?: ReactNode;
+ className?: {
+ wrapper?: string;
+ image?: string;
+ body?: string;
+ title?: string;
+ subtitle?: string;
+ actions?: string;
+ footer?: string;
+ };
+ variant?: "default" | "compact" | "bordered" | "shadow" | "image-full";
+ size?: "sm" | "md" | "lg";
+}
+
+const Card = ({
+ title,
+ subtitle,
+ image,
+ imageAlt,
+ imageWidth,
+ imageHeight,
+ actions,
+ footer,
+ className,
+ variant = "default",
+ size = "md",
+ children,
+ ...props
+}: CardProps) => {
+ const getCardClasses = () => {
+ const baseClasses = "card bg-base-100";
+
+ const variantClasses = {
+ default: "",
+ compact: "card-compact",
+ bordered: "border border-base-300",
+ shadow: "shadow-xl",
+ "image-full": "card-side card-compact shadow-xl",
+ };
+
+ const sizeClasses = {
+ sm: "w-64",
+ md: "w-96",
+ lg: "w-[28rem]",
+ };
+
+ return cn(
+ baseClasses,
+ variantClasses[variant],
+ variant !== "image-full" ? sizeClasses[size] : "",
+ className?.wrapper,
+ );
+ };
+
+ const getImageDimensions = () => {
+ if (variant === "image-full") {
+ return {
+ width: imageWidth || 128,
+ height: imageHeight || 128,
+ };
+ }
+
+ const cardWidths = {
+ sm: 256, // w-64
+ md: 384, // w-96
+ lg: 448, // w-[28rem]
+ };
+
+ return {
+ width: imageWidth || cardWidths[size],
+ height: imageHeight || 192,
+ };
+ };
+
+ const getImageClasses = () => {
+ if (variant === "image-full") {
+ return cn("object-cover", className?.image);
+ }
+ return cn("w-full object-cover", className?.image);
+ };
+
+ const getBodyClasses = () => {
+ const baseClasses = "card-body";
+
+ if (variant === "compact" || variant === "image-full") {
+ return cn(baseClasses, "p-4", className?.body);
+ }
+
+ return cn(baseClasses, "p-6", className?.body);
+ };
+
+ const getTitleClasses = () => {
+ const sizeClasses = {
+ sm: "text-lg",
+ md: "text-xl",
+ lg: "text-2xl",
+ };
+
+ return cn("card-title font-bold", sizeClasses[size], className?.title);
+ };
+
+ const getSubtitleClasses = () => {
+ return cn("text-base-content/70 text-sm mt-1", className?.subtitle);
+ };
+
+ const getActionsClasses = () => {
+ return cn("card-actions justify-end mt-4", className?.actions);
+ };
+
+ const getFooterClasses = () => {
+ return cn("border-t border-base-300 mt-4 pt-4", className?.footer);
+ };
+
+ if (variant === "image-full" && image) {
+ const imageDimensions = getImageDimensions();
+ return (
+
+
+
+
+
+ {title &&
{title}
}
+ {subtitle &&
{subtitle}
}
+ {children}
+ {actions &&
{actions}
}
+
+ {footer &&
{footer}
}
+
+ );
+ }
+
+ return (
+
+ {image && (
+
+
+
+ )}
+
+ {title &&
{title}
}
+ {subtitle &&
{subtitle}
}
+ {children}
+ {actions &&
{actions}
}
+
+ {footer &&
{footer}
}
+
+ );
+};
+
+export default Card;
diff --git a/src/components/input/NumberInput.tsx b/src/components/input/NumberInput.tsx
new file mode 100644
index 00000000..4375ca20
--- /dev/null
+++ b/src/components/input/NumberInput.tsx
@@ -0,0 +1,53 @@
+'use client';
+
+import { ChangeEvent } from 'react';
+import { NumericFormat, OnValueChange } from 'react-number-format';
+import TextInput, { TextInputProps } from '@/components/input/TextInput';
+
+interface NumberInputProps extends Omit {
+ thousandSeparator?: string;
+ decimalSeparator?: string;
+ decimalScale?: number;
+ allowNegative?: boolean;
+ prefix?: string;
+ suffix?: string;
+ fixedDecimalScale?: boolean;
+}
+
+const NumberInput = ({
+ thousandSeparator = ',',
+ decimalSeparator = '.',
+ decimalScale = 5,
+ allowNegative = true,
+ onChange,
+ ...restProps
+}: NumberInputProps) => {
+ const valueChangeHandler: OnValueChange = (
+ numberFormatValues,
+ sourceInfo
+ ) => {
+ const newChangeEvent = sourceInfo.event as
+ | ChangeEvent
+ | undefined;
+
+ if (newChangeEvent) {
+ newChangeEvent.target.value = numberFormatValues.value;
+
+ onChange?.(newChangeEvent);
+ }
+ };
+
+ return (
+
+ );
+};
+
+export default NumberInput;
diff --git a/src/components/pages/production/chickin/ChickinTable.tsx b/src/components/pages/production/chickin/ChickinTable.tsx
index 2b3fdc07..65ab3c16 100644
--- a/src/components/pages/production/chickin/ChickinTable.tsx
+++ b/src/components/pages/production/chickin/ChickinTable.tsx
@@ -11,8 +11,8 @@ import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import { TableRowSizeSelector } from '@/components/table/TableRowSizeSelector';
import { ROWS_OPTIONS } from '@/config/constant';
import { isResponseSuccess } from '@/lib/api-helper';
-import { cn } from '@/lib/helper';
-import { ChickinApi, ProjectFlockApi } from '@/services/api/production';
+import { cn, formatNumber } from '@/lib/helper';
+import { ChickinApi } from '@/services/api/production';
import { useTableFilter } from '@/services/hooks/useTableFilter';
import { Chickin } from '@/types/api/production/chickin';
import { Icon } from '@iconify/react';
@@ -124,6 +124,13 @@ const ChickinTable = () => {
{
accessorFn: (row) => row.quantity,
header: 'Jumlah Chickin',
+ cell: (props) => {
+ if (props.row.original.quantity) {
+ return formatNumber(props.row.original.quantity);
+ } else {
+ return '-';
+ }
+ }
},
{
accessorFn: (row) => row.chick_in_date,
@@ -159,7 +166,7 @@ const ChickinTable = () => {
deleteModal.openModal();
};
- const editClickHandler = () => {
+ const editClickHandler = () => {
setSelectedChickin(props.row.original);
chickinModal.openModal();
};
@@ -279,7 +286,7 @@ const RowOptionsMenu = ({
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
)}
>
- {/* */}
+