diff --git a/src/app/production/project-flock/detail/edit/page.tsx b/src/app/production/project-flock/detail/edit/page.tsx
index 7576cc27..2af068ab 100644
--- a/src/app/production/project-flock/detail/edit/page.tsx
+++ b/src/app/production/project-flock/detail/edit/page.tsx
@@ -2,7 +2,7 @@
import ProjectFlockForm from '@/components/pages/production/project-flock/form/ProjectFlockForm';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
-import { ProjectFlockApi } from '@/services/api/production';
+import { ProjectFlockApi } from '@/services/api/production/project-flock';
import { useRouter, useSearchParams } from 'next/navigation';
import useSWR from 'swr';
@@ -12,10 +12,11 @@ const ProjectFlockEdit = () => {
const projectFlockId = searchParams.get('projectFlockId');
- const { data: projectFlock, isLoading: isLoadingCostumer } = useSWR(
- projectFlockId,
- (id: number) => ProjectFlockApi.getSingle(id)
- );
+ const {
+ data: projectFlock,
+ isLoading: isLoadingProjectFlock,
+ mutate: refreshProjectFlocks,
+ } = useSWR(projectFlockId, (id: number) => ProjectFlockApi.getSingle(id));
if (!projectFlockId) {
router.back();
@@ -27,18 +28,25 @@ const ProjectFlockEdit = () => {
);
}
- if (!isLoadingCostumer && (!projectFlock || isResponseError(projectFlock))) {
+ if (
+ !isLoadingProjectFlock &&
+ (!projectFlock || isResponseError(projectFlock))
+ ) {
router.replace('/404');
return;
}
return (
-
- {isLoadingCostumer && (
+
+ {isLoadingProjectFlock && (
)}
- {!isLoadingCostumer && isResponseSuccess(projectFlock) && (
-
+ {!isLoadingProjectFlock && isResponseSuccess(projectFlock) && (
+ <>
+ {JSON.stringify(projectFlock.data)}
+
+
+ >
)}
);
diff --git a/src/app/production/project-flock/detail/page.tsx b/src/app/production/project-flock/detail/page.tsx
index e64005d4..28a577ff 100644
--- a/src/app/production/project-flock/detail/page.tsx
+++ b/src/app/production/project-flock/detail/page.tsx
@@ -3,7 +3,7 @@
import ProjectFlockForm from '@/components/pages/production/project-flock/form/ProjectFlockForm';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { FlockApi } from '@/services/api/master-data';
-import { ProjectFlockApi } from '@/services/api/production';
+import { ProjectFlockApi } from '@/services/api/production/project-flock';
import { ProjectFlock } from '@/types/api/production/project-flock';
import { useRouter, useSearchParams } from 'next/navigation';
import { useState } from 'react';
@@ -23,12 +23,13 @@ const ProjectFlockDetail = () => {
mutate: refreshProjectFlock,
} = useSWR(projectFlockId, (id: number) => ProjectFlockApi.getSingle(id));
- const flockUrl = `${FlockApi.basePath}`;
const {
- data: flock,
- isLoading: isLoadingFlock,
- mutate: refreshFlock,
- } = useSWR(flockUrl, FlockApi.getAllFetcher);
+ data: approvalLines,
+ isLoading: isLoadingApprovalLines,
+ mutate: refreshApprovalLines,
+ } = useSWR('approvals', (id: number) =>
+ ProjectFlockApi.getApprovalLines((projectFlockId ?? 0) as number)
+ );
if (!projectFlockId) {
router.back();
@@ -48,37 +49,17 @@ const ProjectFlockDetail = () => {
return;
}
- // Attach flock id to project flock
- let projectFlockAttached: ProjectFlock | undefined;
-
- if (isResponseSuccess(projectFlock) && isResponseSuccess(flock)) {
- projectFlockAttached = {
- ...projectFlock.data,
- flock: flock.data.find(
- (flock) =>
- flock.name ==
- projectFlock?.data?.flock_name
- .trim()
- .split(/\s+/)
- .slice(0, -1)
- .join(' ')
- ),
- };
- console.log('projectFlockAttached');
- console.log(projectFlockAttached);
- console.log('flocks');
- console.log(flock.data);
- }
-
return (
-
- {isLoadingProjectFlock && (
-
- )}
- {projectFlockAttached && (
+
+ {isLoadingProjectFlock ||
+ (isLoadingApprovalLines && (
+
+ ))}
+ {isResponseSuccess(projectFlock) && isResponseSuccess(approvalLines) && (
)}
diff --git a/src/components/Tabs.tsx b/src/components/Tabs.tsx
new file mode 100644
index 00000000..2ad2477d
--- /dev/null
+++ b/src/components/Tabs.tsx
@@ -0,0 +1,129 @@
+import { HTMLAttributes, ReactNode, useEffect, useState } from 'react';
+import { cn } from '@/lib/helper';
+
+export interface TabItem {
+ id: string;
+ label: ReactNode;
+ content?: ReactNode;
+ disabled?: boolean;
+}
+
+export interface TabsProps
+ extends Omit
, 'className'> {
+ tabs: TabItem[];
+ variant?: 'bordered' | 'lifted' | 'boxed';
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
+ placement?: 'top' | 'bottom';
+ /** Tab yang aktif secara default (uncontrolled mode) */
+ defaultActiveId?: string;
+ /** Tab yang aktif (controlled mode, dikontrol parent) */
+ activeTabId?: string;
+ className?:
+ | string
+ | {
+ wrapper?: string;
+ tab?: string;
+ content?: string;
+ };
+ onTabChange?: (tabId: string) => void;
+}
+
+const Tabs = ({
+ tabs,
+ variant,
+ size = 'md',
+ placement = 'top',
+ defaultActiveId,
+ activeTabId: controlledActiveId,
+ className,
+ onTabChange,
+ ...props
+}: TabsProps) => {
+ // State internal hanya dipakai kalau `activeTabId` (controlled) tidak diset
+ const [uncontrolledActiveId, setUncontrolledActiveId] = useState(
+ defaultActiveId || tabs[0]?.id || ''
+ );
+
+ const isControlled = controlledActiveId !== undefined;
+ const activeTabId = isControlled ? controlledActiveId : uncontrolledActiveId;
+
+ const handleTabChange = (tabId: string) => {
+ if (tabId === activeTabId) return;
+ if (!isControlled) setUncontrolledActiveId(tabId);
+ onTabChange?.(tabId);
+ };
+
+ const { wrapper: wrapperClassName, tab: tabClassName } =
+ typeof className === 'object'
+ ? className
+ : { wrapper: className, tab: undefined };
+
+ const getTabsClasses = () => {
+ const variantClasses: Record = {
+ bordered: 'tabs-bordered',
+ lifted: 'tabs-lift',
+ boxed: 'tabs-box',
+ };
+
+ const sizeClasses: Record = {
+ xs: 'tabs-xs',
+ sm: 'tabs-sm',
+ md: '',
+ lg: 'tabs-lg',
+ xl: 'tabs-xl',
+ };
+
+ const placementClasses: Record = {
+ top: '',
+ bottom: 'tabs-bottom',
+ };
+
+ return cn(
+ 'tabs',
+ variant && variantClasses[variant],
+ sizeClasses[size],
+ placementClasses[placement],
+ wrapperClassName
+ );
+ };
+
+ const getTabClasses = (isActive: boolean, isDisabled?: boolean) =>
+ cn(
+ 'tab',
+ {
+ 'tab-active': isActive,
+ 'tab-disabled': isDisabled,
+ },
+ tabClassName
+ );
+
+ const activeContent = tabs.find((tab) => tab.id === activeTabId)?.content;
+
+ return (
+
+
+ {tabs.map(({ id, label, disabled }) => (
+
+ ))}
+
+
+ {activeContent &&
{activeContent}
}
+
+ );
+};
+
+export default Tabs;
diff --git a/src/components/pages/production/chickin/form/ChickinForm.tsx b/src/components/pages/production/chickin/form/ChickinForm.tsx
index f007a1cf..30575d17 100644
--- a/src/components/pages/production/chickin/form/ChickinForm.tsx
+++ b/src/components/pages/production/chickin/form/ChickinForm.tsx
@@ -29,7 +29,6 @@ const ChickinFormKandang = ({
return (
@@ -96,7 +95,7 @@ const ChickinFormKandang = ({
tabs={[
{
id: 'formChickIn',
- label: 'Form Chick In',
+ label: 'Tambah Chick In',
content: (
void;
+}) => {
+ const confirmModal = useModal();
+ const [isApproveLoading, setIsApproveLoading] = useState(false);
+ const [chickinErrorMessage, setChickinErrorMessage] = useState('');
+
+ const handleClickApprove = () => {
+ confirmModal.openModal();
+ };
+
+ const confirmationModalApproveClickHandler = async () => {
+ setChickinErrorMessage('');
+ setIsApproveLoading(true);
+ const approveChickinRes = await ChickinApi.singleApproval(
+ initialValues?.id as number,
+ 'APPROVED'
+ );
+ if (isResponseSuccess(approveChickinRes)) {
+ toast.success(approveChickinRes?.message as string);
+ }
+ if (isResponseError(approveChickinRes)) {
+ toast.error(approveChickinRes?.message as string);
+ setChickinErrorMessage(approveChickinRes?.message as string);
+ }
+ confirmModal.closeModal();
+ setIsApproveLoading(false);
+ if (afterSubmit) {
+ afterSubmit();
+ }
+ };
+
+ return (
+ <>
+
+
+
+
+
+ data={initialValues?.chickins || []}
+ columns={[
+ {
+ header: '#',
+ cell: (props) => props.row.index + 1,
+ },
+ {
+ accessorFn: (row) => row.chick_in_date,
+ header: 'Tanggal Chick In',
+ cell: (props) => {
+ return formatDate(props.getValue() as string, 'DD MMM YYYY');
+ },
+ },
+ {
+ accessorFn: (row) => row.product_warehouse?.warehouse?.name,
+ header: 'Kandang',
+ },
+ {
+ accessorFn: (row) => row.product_warehouse?.product?.name,
+ header: 'Produk',
+ },
+ {
+ accessorFn: (row) => row.usage_qty ?? row.pending_usage_qty,
+ header: 'Jumlah Chick In',
+ cell: (props) => {
+ if (props.row.original.usage_qty != 0) {
+ return formatNumber(props.row.original.usage_qty);
+ } else if (props.row.original.pending_usage_qty != 0) {
+ return formatNumber(props.row.original.pending_usage_qty);
+ } else {
+ return '-';
+ }
+ },
+ },
+ {
+ accessorFn: (row) => row.pending_usage_qty,
+ header: 'Status',
+ cell: (props) => {
+ return (
+
+ );
+ },
+ },
+ ]}
+ className={{
+ containerClassName: cn({
+ 'mb-20': initialValues?.chickins?.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',
+ paginationClassName: 'hidden',
+ }}
+ />
+ {chickinErrorMessage && (
+ setChickinErrorMessage('')}>
+
{chickinErrorMessage}
+
+ )}
+
+
+ >
+ );
+};
+
+export default ChickinLogsView;
diff --git a/src/components/pages/production/chickin/form/tabs/ChickinFormView.tsx b/src/components/pages/production/chickin/form/tabs/ChickinFormView.tsx
new file mode 100644
index 00000000..1803b0a0
--- /dev/null
+++ b/src/components/pages/production/chickin/form/tabs/ChickinFormView.tsx
@@ -0,0 +1,236 @@
+'use client';
+
+import Card from '@/components/Card';
+import Table from '@/components/Table';
+import {
+ ChickinFormValues,
+ ChickinRequestFormValues,
+ ChickinSchema,
+} from '../ChickinForm.schema';
+import DateInput from '@/components/input/DateInput';
+import SelectInput, { OptionType } from '@/components/input/SelectInput';
+import NumberInput from '@/components/input/NumberInput';
+import Button from '@/components/Button';
+import { useCallback, useEffect, useState } from 'react';
+import { useFormik } from 'formik';
+import { flushSync } from 'react-dom';
+import { CreateChickinPayload } from '@/types/api/production/chickin';
+import { ChickinApi } from '@/services/api/production/chickin';
+import { isResponseError } from '@/lib/api-helper';
+import toast from 'react-hot-toast';
+import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
+import { useRouter } from 'next/navigation';
+import Alert from '@/components/Alert';
+
+const ChickinFormView = ({
+ formType = 'add',
+ initialValues,
+ afterSubmit,
+}: {
+ formType?: 'add' | 'detail' | 'edit';
+ initialValues: ProjectFlockKandang;
+ afterSubmit?: () => void;
+}) => {
+ const router = useRouter();
+ const [chickinErrorMessage, setChickinErrorMessage] = useState('');
+
+ const createChickin = useCallback(
+ async (payload: CreateChickinPayload) => {
+ const createChickinRes = await ChickinApi.create(payload);
+ if (isResponseError(createChickinRes)) {
+ setChickinErrorMessage(createChickinRes.message);
+ return;
+ }
+
+ toast.success(createChickinRes?.message as string);
+ // router.push(
+ // `/production/project-flock/chickin/add?projectFlockId=${initialValues?.project_flock?.id}`
+ // );
+ if (afterSubmit) {
+ afterSubmit();
+ }
+ },
+ [router]
+ );
+ const handleReset = async () => {
+ flushSync(() => {
+ formik.resetForm({
+ values: {
+ project_flock_kandang_id: initialValues?.id,
+ chickin_requests: initialValues?.available_qtys
+ ? initialValues.available_qtys.map((availableQty) => ({
+ chick_in_date: '',
+ product_warehouse_id: availableQty.product_warehouse.id,
+ available_qty: availableQty.available_qty,
+ note: `Chickin project-flock-kandang-${initialValues?.id} product-warehouse-${availableQty.product_warehouse.id}`,
+ }))
+ : [],
+ },
+ });
+ });
+
+ formik.setTouched({
+ chickin_requests: initialValues?.available_qtys?.map(() => ({
+ chick_in_date: true,
+ })),
+ });
+
+ const errors = await formik.validateForm();
+ formik.setErrors(errors);
+ };
+
+ const formik = useFormik({
+ enableReinitialize: true,
+ validationSchema: ChickinSchema,
+ initialValues: {
+ project_flock_kandang_id: initialValues?.id,
+ chickin_requests: initialValues?.available_qtys
+ ? initialValues.available_qtys.map((availableQty) => ({
+ chick_in_date: '',
+ product_warehouse_id: availableQty.product_warehouse.id,
+ available_qty: availableQty.available_qty,
+ note: `Chickin project-flock-kandang-${initialValues?.id} product-warehouse-${availableQty.product_warehouse.id}`,
+ }))
+ : [],
+ },
+ onSubmit: (values) => {
+ setChickinErrorMessage('');
+ createChickin(values as CreateChickinPayload);
+ if (afterSubmit) {
+ afterSubmit();
+ }
+ },
+ });
+
+ const { setValues: formikSetValues } = formik;
+
+ useEffect(() => {
+ formikSetValues({
+ project_flock_kandang_id: initialValues?.id,
+ chickin_requests: initialValues?.available_qtys
+ ? initialValues.available_qtys.map((availableQty) => ({
+ chick_in_date: '',
+ product_warehouse_id: availableQty.product_warehouse.id,
+ available_qty: availableQty.available_qty,
+ note: `Chickin project-flock-kandang-${initialValues?.id} product-warehouse-${availableQty.product_warehouse.id}`,
+ }))
+ : [],
+ });
+ }, [formikSetValues, initialValues]);
+
+ return (
+