From 4e00ded84366488c9a0b33aeabaf671529452565 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Thu, 29 Jan 2026 15:56:31 +0700 Subject: [PATCH 01/22] chore: add new step in PROJECT_FLOCKS approval workflows --- src/config/constant.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/config/constant.ts b/src/config/constant.ts index fff9fa92..5eb5df38 100644 --- a/src/config/constant.ts +++ b/src/config/constant.ts @@ -418,6 +418,10 @@ export const APPROVAL_WORKFLOWS = { step_number: 2, step_name: 'Aktif', }, + { + step_number: 3, + step_name: 'Selesai', + }, ], RECORDINGS: [ { From c5269c4fc540ebb443a0bb09a786cbcafa0fe596 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Thu, 29 Jan 2026 16:17:27 +0700 Subject: [PATCH 02/22] fix: adjust ProjectFlockTable UI --- .../project-flock/ProjectFlockTable.tsx | 448 +++++++++++++++--- 1 file changed, 386 insertions(+), 62 deletions(-) diff --git a/src/components/pages/production/project-flock/ProjectFlockTable.tsx b/src/components/pages/production/project-flock/ProjectFlockTable.tsx index ab14ef84..cad76310 100644 --- a/src/components/pages/production/project-flock/ProjectFlockTable.tsx +++ b/src/components/pages/production/project-flock/ProjectFlockTable.tsx @@ -13,6 +13,7 @@ import { useModal } from '@/components/Modal'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes'; import Table from '@/components/Table'; +import Dropdown from '@/components/Dropdown'; import { ROWS_OPTIONS } from '@/config/constant'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { cn, formatDate, formatTitleCase } from '@/lib/helper'; @@ -29,6 +30,111 @@ import toast from 'react-hot-toast'; import useSWR from 'swr'; import RequirePermission from '@/components/helper/RequirePermission'; +import StatusBadge from '@/components/helper/StatusBadge'; +import PopoverButton from '@/components/popover/PopoverButton'; +import PopoverContent from '@/components/popover/PopoverContent'; + +const RowOptionsMenu = ({ + props, + popoverPosition = 'bottom', + editClickHandler, + detailClickHandler, + deleteClickHandler, +}: { + props: CellContext; + popoverPosition: 'bottom' | 'top'; + editClickHandler: (id: number) => void; + detailClickHandler: (id: number) => void; + deleteClickHandler: () => void; +}) => { + // TODO: change this to real condition + const showEditButton = true; + + const showDeleteButton = showEditButton; + + const popoverId = `projectFlock#${props.row.original.id}`; + const popoverAnchorName = `--anchor-projectFlock#${props.row.original.id}`; + + const closePopover = () => { + document.getElementById(popoverId)?.hidePopover(); + }; + + const detailClickHandlerWrapper = () => { + detailClickHandler(props.row.original.id); + closePopover(); + }; + + const editClickHandlerWrapper = () => { + editClickHandler(props.row.original.id); + closePopover(); + }; + + return ( +
+ + + + + +
+ + + + + {showEditButton && ( + + + + )} + + {showDeleteButton && ( + +
+ +
+ )} +
+
+
+ ); +}; const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { const { @@ -62,6 +168,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { const selectedRowIds = Object.keys(rowSelection) .filter((id) => rowSelection[id]) .map((id) => parseInt(id)); + const [selectedArea, setSelectedArea] = useState(null); const [selectedLocation, setSelectedLocation] = useState( null @@ -78,6 +185,8 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { ); const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isApproveLoading, setIsApproveLoading] = useState(false); + const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] = + useState(false); // ===== Fetch Data ===== const { @@ -175,14 +284,27 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { : null; }, [rowSelection]); + // const canApprove = useMemo(() => { + // if (!selectedSingleRow || isApproveLoading) return false; + + // const isPengajuan = selectedSingleRow.approval?.step_number == 1; + // const isNotRejected = selectedSingleRow.approval?.action != 'REJECTED'; + + // return isPengajuan && isNotRejected; + // }, [selectedSingleRow, isApproveLoading]); + const canApprove = useMemo(() => { - if (!selectedSingleRow || isApproveLoading) return false; + return selectedRowIds.every((id) => { + const projectFlock = isResponseSuccess(projectFlocks) + ? projectFlocks?.data.find((row) => row.id === id) + : null; - const isPengajuan = selectedSingleRow.approval?.step_number == 1; - const isNotRejected = selectedSingleRow.approval?.action != 'REJECTED'; - - return isPengajuan && isNotRejected; - }, [selectedSingleRow, isApproveLoading]); + const isProjectFlockRequesting = projectFlock?.approval?.step_number == 1; + const isProjectFlockNotRejected = + projectFlock?.approval?.action != 'REJECTED'; + return isProjectFlockRequesting && isProjectFlockNotRejected; + }); + }, [selectedRowIds, projectFlocks]); // ====== COLUMNS ====== const columns = useMemo[]>( @@ -256,44 +378,39 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { const approval = props.row.original.approval; const isRejected = approval?.action == 'REJECTED'; const isApproved = approval?.action == 'APPROVED'; - return ( - - - {isRejected - ? 'Ditolak' - : formatTitleCase(approval?.step_name || '')} - + : 'neutral'; + + switch (approval.action.toLowerCase()) { + case 'pengajuan': + latestApprovalStepName = 'Pengajuan'; + break; + case 'aktif': + latestApprovalStepName = 'Aktif'; + break; + case 'Selesai': + latestApprovalStepName = 'Closing'; + break; + } + + if (isRejected) { + latestApprovalStepName = 'Ditolak'; + } + + return ( + ); }, }, @@ -325,27 +442,88 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { cell: (props) => formatDate(props.row.original.created_at, 'MMM DD, YYYY'), }, + { + id: 'actions', + 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 detailClickHandler = (id: number) => { + router.push( + `/production/project-flock/detail/?projectFlockId=${id}` + ); + }; + + const editClickHandler = (id: number) => { + router.push( + `/production/project-flock/detail/edit/?projectFlockId=${id}` + ); + }; + + const deleteClickHandler = () => { + // Set row selection + setRowSelection({ + [String(props.row.original.id)]: true, + }); + + deleteModal.openModal(); + }; + + return ( + + ); + }, + }, ], [] ); + const exportToExcelHandler = async () => { + setIsLoadingExportingToExcel(true); + + toast.error('Not implemented yet!'); + + setIsLoadingExportingToExcel(false); + }; + + const bulkApproveClickHandler = () => { + setApprovalAction('APPROVED'); + confirmModal.openModal(); + }; + + const bulkRejectClickHandler = () => { + setApprovalAction('REJECTED'); + confirmModal.openModal(); + }; + return ( <> -
-
-
+
+
+ {/*
@@ -423,6 +601,158 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { }} />
+
*/} + +
+
+ + + + + {selectedRowIds.length > 0 && canApprove && ( + <> +
+ + + + + + + + + + )} +
+ +
+ + } + className={{ + wrapper: 'w-full min-w-24 max-w-3xs', + inputWrapper: 'rounded-xl! shadow-button-soft', + input: + 'placeholder:font-semibold placeholder:text-base-content/50', + }} + /> + + + + +
+ + + Export + +
+ + +
+ + } + > + + +
@@ -448,26 +778,20 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { setSorting={setSorting} rowSelection={rowSelection} setRowSelection={setRowSelection} + withCheckbox className={{ - containerClassName: cn({ - 'mb-40': + containerClassName: cn('p-3', { + 'w-full mb-20': isResponseSuccess(projectFlocks) && - projectFlocks?.data?.length > 0, + projectFlocks?.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', + headerColumnClassName: 'text-nowrap', }} />
- void }) => { onClose={() => { setRowSelection({}); }} - /> + /> */} Date: Mon, 2 Feb 2026 11:15:38 +0700 Subject: [PATCH 03/22] chore: lint --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9bdfffa4..1922fa2b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -23,8 +23,8 @@ default: - node_modules/ variables: - NODE_ENV: "" - NPM_CONFIG_PRODUCTION: "false" + NODE_ENV: '' + NPM_CONFIG_PRODUCTION: 'false' script: # Install dependencies @@ -69,7 +69,7 @@ default: EOF artifacts: - name: "out-$CI_COMMIT_SHORT_SHA" + name: 'out-$CI_COMMIT_SHORT_SHA' paths: - out/ expire_in: 1 week From 39b18f7efcf9fb23ecff86602dd3f85fb49774bf Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Tue, 3 Feb 2026 09:17:17 +0700 Subject: [PATCH 04/22] refactor: use Modal instead of Drawer --- src/app/production/project-flock/layout.tsx | 31 +++++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/app/production/project-flock/layout.tsx b/src/app/production/project-flock/layout.tsx index 3e9a65b7..3e46bbfc 100644 --- a/src/app/production/project-flock/layout.tsx +++ b/src/app/production/project-flock/layout.tsx @@ -2,9 +2,10 @@ import { usePathname, useRouter } from 'next/navigation'; import Drawer from '@/components/Drawer'; -import React, { ReactNode } from 'react'; +import React, { ReactNode, useEffect } from 'react'; import ProjectFlockTable from '@/components/pages/production/project-flock/ProjectFlockTable'; import { useUiStore } from '@/stores/ui/ui.store'; +import Modal, { useModal } from '@/components/Modal'; export default function ProjectFlockLayout({ children, @@ -23,9 +24,12 @@ export default function ProjectFlockLayout({ const isOpen = isAdd || isEdit || isDetail || isChickin || isClosing; + const formModal = useModal(); + const handleBackdropClick = () => { const unsub = useUiStore.getState().subscribeIsValid((isValid) => { if (isValid) { + formModal.closeModal(); unsub(); // berhenti listen router.push('/production/project-flock'); } @@ -34,6 +38,14 @@ export default function ProjectFlockLayout({ toggleValidate(); }; + useEffect(() => { + if (isOpen && !formModal.open) { + formModal.openModal(); + } else { + formModal.closeModal(); + } + }, [isOpen]); + return ( <> {/* List page always rendered */} @@ -44,7 +56,7 @@ export default function ProjectFlockLayout({
{/* Render Drawer only on /add */} - { if (!v) router.push('/production/project-flock'); @@ -54,7 +66,20 @@ export default function ProjectFlockLayout({ variant='right' zIndex='99999' sidebarContent={isOpen &&
{children}
} - /> + /> */} + + +
+ {isOpen && children} +
+
); } From d826746f2973547479f1f0cb39903b8cf3d2ede2 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Tue, 3 Feb 2026 09:39:58 +0700 Subject: [PATCH 05/22] chore: adjust DrawerHeader styling --- src/components/helper/drawer/DrawerHeader.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/helper/drawer/DrawerHeader.tsx b/src/components/helper/drawer/DrawerHeader.tsx index 8bb635ae..ef3ba1db 100644 --- a/src/components/helper/drawer/DrawerHeader.tsx +++ b/src/components/helper/drawer/DrawerHeader.tsx @@ -27,7 +27,7 @@ export interface DrawerHeaderProps { const DrawerHeader = ({ leftIcon = 'mdi:close', - leftIconSize = 24, + leftIconSize = 20, leftIconHref, leftIconOnClick, leftIconClassName, @@ -43,7 +43,7 @@ const DrawerHeader = ({ icon={leftIcon} width={leftIconSize} height={leftIconSize} - className={cn('cursor-pointer', leftIconClassName)} + className={cn('cursor-pointer text-base-content ', leftIconClassName)} /> ); @@ -73,7 +73,7 @@ const DrawerHeader = ({ return (
@@ -82,7 +82,7 @@ const DrawerHeader = ({ {renderLeftIcon()} {showDivider && subtitle && ( -
+
)} {subtitle && ( From 68f3c95b818523ea20681f8fd4e7f5cf2a5c361d Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Tue, 3 Feb 2026 09:40:16 +0700 Subject: [PATCH 06/22] chore: adjust ProjectFlockForm styling --- .../project-flock/form/ProjectFlockForm.tsx | 683 ++++++++++-------- 1 file changed, 365 insertions(+), 318 deletions(-) diff --git a/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx index 89769787..f0ee4752 100644 --- a/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx +++ b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx @@ -658,37 +658,48 @@ const ProjectFlockForm = ({ return ( <> -
+
{/* Header */} {formType == 'edit' && ( + )} + + {formType == 'add' && ( + )} @@ -719,145 +730,147 @@ const ProjectFlockForm = ({ onReset={formik.handleReset} > {/* Form Informasi Umum */} -
-
-

Informasi Umum

-
- - - { - return flock.label === formik.values.flock_name; - })?.value, - } as OptionType) - : undefined - } - onChange={(val) => { - optionChangeHandler(val, 'flock'); - setSelectedFlock((val as OptionType)?.label); - formik.setFieldValue( - 'flock_name', - (val as OptionType)?.label - ); - }} - options={optionsFlock} - onInputChange={setInputValueFlock} - onMenuScrollToBottom={loadMoreFlock} - isLoading={isLoadingFlocks} - isError={ - formik.touched.flock_name && Boolean(formik.errors.flock_name) - } - errorMessage={formik.errors.flock_name as string} - isClearable - isDisabled={formType != 'add'} - /> - { - optionChangeHandler(val, 'fcr'); - }} - onInputChange={setInputValueFcr} - onMenuScrollToBottom={loadMoreFcr} - options={optionsFcr} - isLoading={isLoadingFcrs} - isError={formik.touched.fcr_id && Boolean(formik.errors.fcr_id)} - errorMessage={formik.errors.fcr_id as string} - isClearable - isDisabled={formType != 'add'} - /> - - { - optionChangeHandler(val, 'production_standard'); - }} - onInputChange={setInputValueProductionStandard} - onMenuScrollToBottom={loadMoreProductionStandard} - options={optionsProductionStandards} - isLoading={isLoadingProductionStandards} - isError={ - formik.touched.production_standard_id && - Boolean(formik.errors.production_standard_id) - } - errorMessage={formik.errors.production_standard_id as string} - isClearable - isDisabled={formType != 'add'} - /> - -
+
+

+ Informasi Umum +

+ + + + { + return flock.label === formik.values.flock_name; + })?.value, + } as OptionType) + : undefined + } + onChange={(val) => { + optionChangeHandler(val, 'flock'); + setSelectedFlock((val as OptionType)?.label); + formik.setFieldValue('flock_name', (val as OptionType)?.label); + }} + options={optionsFlock} + onInputChange={setInputValueFlock} + onMenuScrollToBottom={loadMoreFlock} + isLoading={isLoadingFlocks} + isError={ + formik.touched.flock_name && Boolean(formik.errors.flock_name) + } + errorMessage={formik.errors.flock_name as string} + isClearable + isDisabled={formType != 'add'} + /> + { + optionChangeHandler(val, 'fcr'); + }} + onInputChange={setInputValueFcr} + onMenuScrollToBottom={loadMoreFcr} + options={optionsFcr} + isLoading={isLoadingFcrs} + isError={formik.touched.fcr_id && Boolean(formik.errors.fcr_id)} + errorMessage={formik.errors.fcr_id as string} + isClearable + isDisabled={formType != 'add'} + /> + + { + optionChangeHandler(val, 'production_standard'); + }} + onInputChange={setInputValueProductionStandard} + onMenuScrollToBottom={loadMoreProductionStandard} + options={optionsProductionStandards} + isLoading={isLoadingProductionStandards} + isError={ + formik.touched.production_standard_id && + Boolean(formik.errors.production_standard_id) + } + errorMessage={formik.errors.production_standard_id as string} + isClearable + isDisabled={formType != 'add'} + /> +
{/* Form Pilih Kandang */} -
-
-

Pilih Kandang

+ +
+

+ Informasi Kandang +

{isLoadingKandang && ( - + )} {/* Card Estimasi Budget */} -
-
-

- Estimasi Anggaran Per Flock -

+
{formik.values.project_budgets && formik.values.project_budgets.length > 0 ? ( formik.values.project_budgets.map((budget, index) => ( - -
-
-
Anggaran ke-{index + 1}
- +
*/} + +
+

+ Estimasi Anggaran Per Flock +

+ + +
+ +
+ { + const updatedBudgets = [ + ...formik.values.project_budgets, + ]; + updatedBudgets[index].nonstock = val as OptionType; + updatedBudgets[index].nonstock_id = + (val as OptionType) ? (val as OptionType).value : 0; + formik.setFieldValue( + 'project_budgets', + updatedBudgets + ); + formik.setFieldTouched( + `project_budgets[${index}].nonstock_id`, + true + ); + }} + errorMessage={ + ( + formik.errors.project_budgets?.[ index - ) - } - > - - -
-
- { - const updatedBudgets = [ - ...formik.values.project_budgets, - ]; - updatedBudgets[index].nonstock = val as OptionType; - updatedBudgets[index].nonstock_id = - (val as OptionType) - ? (val as OptionType).value - : 0; - formik.setFieldValue( - 'project_budgets', - updatedBudgets - ); - formik.setFieldTouched( - `project_budgets[${index}].nonstock_id`, - true - ); - }} - errorMessage={ + ] as FormikErrors + )?.nonstock_id as string + } + isError={ + formik.touched.project_budgets?.[index] + ?.nonstock_id && + Boolean( ( formik.errors.project_budgets?.[ index ] as FormikErrors )?.nonstock_id as string - } - isError={ - formik.touched.project_budgets?.[index] - ?.nonstock_id && - Boolean( - ( - formik.errors.project_budgets?.[ - index - ] as FormikErrors - )?.nonstock_id as string - ) - } - /> -
-
- - handleBudgetChange(index, 'qty', e.target.value) - } - onBlur={formik.handleBlur} - allowNegative={false} - endAdornment={ -
+ ) + } + /> +
+
+ + handleBudgetChange(index, 'qty', e.target.value) + } + onBlur={formik.handleBlur} + allowNegative={false} + inputPrefix={ +
+ {isResponseSuccess(nonstocks) ? (nonstocks.data.find( (ns) => ns.id === budget.nonstock_id )?.uom?.name ?? '') : ''} -
- } - errorMessage={ + + +
+
+ } + errorMessage={ + ( + formik.errors.project_budgets?.[ + index + ] as FormikErrors + )?.qty as string + } + isError={ + formik.touched.project_budgets?.[index]?.qty && + Boolean( ( formik.errors.project_budgets?.[ index ] as FormikErrors )?.qty as string - } - isError={ - formik.touched.project_budgets?.[index]?.qty && - Boolean( - ( - formik.errors.project_budgets?.[ - index - ] as FormikErrors - )?.qty as string - ) - } - /> -
-
- - handleBudgetChange(index, 'price', e.target.value) - } - onBlur={formik.handleBlur} - placeholder='Masukkan harga satuan' - allowNegative={false} - startAdornment='Rp' - endAdornment={ -
- {`Per ${ - isResponseSuccess(nonstocks) - ? (nonstocks.data.find( - (ns) => ns.id === budget.nonstock_id - )?.uom?.name ?? 'Item') - : 'Item' - }`} -
- } - errorMessage={ + ) + } + className={{ + inputPrefix: + 'py-0 px-0 pl-3 text-base-content/50 bg-transparent border-r-0', + inputWrapper: 'border-l-0 pl-5', + }} + /> +
+
+ + handleBudgetChange(index, 'price', e.target.value) + } + onBlur={formik.handleBlur} + placeholder='Masukkan Harga Satuan (Rp)' + allowNegative={false} + inputPrefix={ +
+ + Rp + + +
+
+ } + errorMessage={ + ( + formik.errors.project_budgets?.[ + index + ] as FormikErrors + )?.price as string + } + isError={ + formik.touched.project_budgets?.[index]?.price && + Boolean( ( formik.errors.project_budgets?.[ index ] as FormikErrors )?.price as string - } - isError={ - formik.touched.project_budgets?.[index]?.price && - Boolean( - ( - formik.errors.project_budgets?.[ - index - ] as FormikErrors - )?.price as string - ) - } - /> -
-
- - handleBudgetChange( - index, - 'total_price', - e.target.value - ) - } - onBlur={formik.handleBlur} - placeholder='Masukkan harga total' - allowNegative={false} - startAdornment='Rp' - endAdornment={ -
Total
- } - errorMessage={ + ) + } + className={{ + inputPrefix: + 'py-0 px-0 pl-3 text-base-content/50 bg-transparent border-r-0', + inputWrapper: 'border-l-0 pl-5', + }} + /> +
+
+ + handleBudgetChange( + index, + 'total_price', + e.target.value + ) + } + onBlur={formik.handleBlur} + placeholder='Masukkan Total Harga' + allowNegative={false} + inputPrefix={ +
+ + Rp + + +
+
+ } + errorMessage={ + ( + formik.errors.project_budgets?.[ + index + ] as FormikErrors + )?.total_price as string + } + isError={ + formik.touched.project_budgets?.[index] + ?.total_price && + Boolean( ( formik.errors.project_budgets?.[ index ] as FormikErrors )?.total_price as string - } - isError={ - formik.touched.project_budgets?.[index] - ?.total_price && - Boolean( - ( - formik.errors.project_budgets?.[ - index - ] as FormikErrors - )?.total_price as string - ) - } - /> -
+ ) + } + className={{ + inputPrefix: + 'py-0 px-0 pl-3 text-base-content/50 bg-transparent border-r-0', + inputWrapper: 'border-l-0 pl-5', + }} + />
- +
)) ) : (
@@ -1093,7 +1135,9 @@ const ProjectFlockForm = ({
- +
+ +
{formType !== 'detail' && ( @@ -1133,6 +1177,9 @@ const ProjectFlockForm = ({ isLoading: isDeleteLoading, onClick: confirmationModalDeleteClickHandler, }} + className={{ + modal: 'w-full sm:w-[446px]', + }} /> ); From dc5bd6b3292e2a6e8a78af9f801b94bef91b37b9 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Tue, 3 Feb 2026 09:40:34 +0700 Subject: [PATCH 07/22] chore: adjust ProjectFlockKandangTable styling --- .../form/ProjectFlockKandangTable.tsx | 93 +++++++++---------- 1 file changed, 46 insertions(+), 47 deletions(-) diff --git a/src/components/pages/production/project-flock/form/ProjectFlockKandangTable.tsx b/src/components/pages/production/project-flock/form/ProjectFlockKandangTable.tsx index 14a9b11a..bba4a229 100644 --- a/src/components/pages/production/project-flock/form/ProjectFlockKandangTable.tsx +++ b/src/components/pages/production/project-flock/form/ProjectFlockKandangTable.tsx @@ -2,6 +2,7 @@ import Badge from '@/components/Badge'; import Card from '@/components/Card'; +import StatusBadge from '@/components/helper/StatusBadge'; import CheckboxInput from '@/components/input/CheckboxInput'; import PillBadge from '@/components/PillBadge'; import Table from '@/components/Table'; @@ -32,6 +33,14 @@ const ProjectFlockKandangTable = ({ initialValues?: ProjectFlock; formType: 'add' | 'edit' | 'detail'; }) => { + const availableKandang = listKandang.filter( + (kandang) => kandang.status == 'NON_ACTIVE' + ).length; + + const unavailableKandang = listKandang.filter( + (kandang) => kandang.status != 'NON_ACTIVE' + ).length; + // Fungsi untuk menangani perubahan checkbox const handleCheckboxChange = (kandang: Kandang, isChecked: boolean) => { // Hanya izinkan perubahan jika tidak dalam mode 'detail' @@ -57,48 +66,30 @@ const ProjectFlockKandangTable = ({ {listKandang.length > 0 ? ( <> {/* ... Bagian Badge Status ... */} -
- - - Tersedia ( - { - listKandang.filter((kandang) => kandang.status == 'NON_ACTIVE') - .length - } - ) - -
- + + +
+ + - - Tidak Tersedia ( - { - listKandang.filter((kandang) => kandang.status != 'NON_ACTIVE') - .length - } - ) - + text={`Tidak Tersedia (${unavailableKandang})`} + className={{ badge: 'text-nowrap' }} + />
{/* --- */} -
+
{listKandang.map((kandang, index) => { const kandangIdString = kandang.id?.toString() ?? `temp-${index}`; @@ -112,28 +103,36 @@ const ProjectFlockKandangTable = ({ formType == 'detail' || kandang.status != 'NON_ACTIVE'; return ( -
+
handleCheckboxChange(kandang, e.currentTarget.checked) } - /> - - - {kandang.status != 'NON_ACTIVE' && 'Tidak'} Tersedia - + /> + +
); })} From db7219e2614cbf51703dd3b13e4355f963579d14 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Fri, 6 Feb 2026 09:42:45 +0700 Subject: [PATCH 08/22] chore: add shadow-bg class name --- src/app/globals.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/globals.css b/src/app/globals.css index 96339bf2..fde8d2b4 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -68,6 +68,8 @@ --shadow-button-soft: 0 3px 2px -2px var(--color-base-200), 0 4px 3px -2px var(--color-base-200); + + --shadow-bg: 0px -2px 4px 0px #00000014; } html { From 66f017549cfdec9aecd1d0a80ec0a9b8252ef3a8 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Fri, 6 Feb 2026 09:43:15 +0700 Subject: [PATCH 09/22] chore: use modal for project flock layout --- src/app/production/project-flock/layout.tsx | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/app/production/project-flock/layout.tsx b/src/app/production/project-flock/layout.tsx index 3e46bbfc..1b1b4edf 100644 --- a/src/app/production/project-flock/layout.tsx +++ b/src/app/production/project-flock/layout.tsx @@ -1,7 +1,6 @@ 'use client'; import { usePathname, useRouter } from 'next/navigation'; -import Drawer from '@/components/Drawer'; import React, { ReactNode, useEffect } from 'react'; import ProjectFlockTable from '@/components/pages/production/project-flock/ProjectFlockTable'; import { useUiStore } from '@/stores/ui/ui.store'; @@ -55,19 +54,7 @@ export default function ProjectFlockLayout({ />
- {/* Render Drawer only on /add */} - {/* { - if (!v) router.push('/production/project-flock'); - }} - closeOnBackdropClick={isDetail ? true : false} - onBackdropClick={handleBackdropClick} - variant='right' - zIndex='99999' - sidebarContent={isOpen &&
{children}
} - /> */} - + {/* Render Modal only on /add */} -
+
{isOpen && children}
From f6cf4a29ad1bda490ff90990356ba5426b39ac93 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Fri, 6 Feb 2026 09:43:27 +0700 Subject: [PATCH 10/22] feat: add ref prop to Alert --- src/components/Alert.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/Alert.tsx b/src/components/Alert.tsx index 61792d0c..6b243cf9 100644 --- a/src/components/Alert.tsx +++ b/src/components/Alert.tsx @@ -1,15 +1,16 @@ -import { ReactNode } from 'react'; +import { ReactNode, Ref } from 'react'; import { cn } from '@/lib/helper'; interface AlertProps { + ref?: Ref | undefined; variant?: 'outline' | 'dash' | 'soft'; color?: 'info' | 'success' | 'warning' | 'error'; children?: ReactNode; className?: string; } -const Alert = ({ children, variant, color, className }: AlertProps) => { +const Alert = ({ children, ref, variant, color, className }: AlertProps) => { const alertBaseClassName = cn('alert', { 'alert-soft': variant === 'soft', 'alert-outline': variant === 'outline', @@ -21,7 +22,11 @@ const Alert = ({ children, variant, color, className }: AlertProps) => { 'alert-error': color === 'error', }); - return
{children}
; + return ( +
+ {children} +
+ ); }; export default Alert; From f0637e2ce9d75c4444bbd99c2efff208e7b9f60f Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Fri, 6 Feb 2026 09:43:34 +0700 Subject: [PATCH 11/22] chore: add title prop --- src/components/helper/ApprovalStepsV2.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/helper/ApprovalStepsV2.tsx b/src/components/helper/ApprovalStepsV2.tsx index dc21453e..0288b0f9 100644 --- a/src/components/helper/ApprovalStepsV2.tsx +++ b/src/components/helper/ApprovalStepsV2.tsx @@ -9,6 +9,7 @@ import Button from '@/components/Button'; import { cn, formatDate } from '@/lib/helper'; interface ApprovalStepsV2Props { + title?: string; approvals?: BaseApproval[]; steps: { step_number: number; @@ -23,6 +24,7 @@ interface ApprovalStepsV2Props { } const ApprovalStepsV2 = ({ + title = 'Progress Details', approvals, steps, maxVisibleSteps = 2, @@ -99,7 +101,7 @@ const ApprovalStepsV2 = ({ )} >

- Progress Details + {title}

Date: Fri, 6 Feb 2026 09:43:56 +0700 Subject: [PATCH 12/22] feat: add onClick prop and set text type to react node --- src/components/helper/StatusBadge.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/helper/StatusBadge.tsx b/src/components/helper/StatusBadge.tsx index c90ddaee..d1b32a24 100644 --- a/src/components/helper/StatusBadge.tsx +++ b/src/components/helper/StatusBadge.tsx @@ -1,24 +1,30 @@ +import { ReactNode } from 'react'; + import Badge from '@/components/Badge'; + import { cn } from '@/lib/helper'; import { Color } from '@/types/theme'; interface StatusBadgeProps { color: Color; - text: string; + text: ReactNode; className?: { badge?: string; status?: string; }; + onClick?: () => void; } const StatusBadge = ({ color = 'neutral', text, className, + onClick, }: StatusBadgeProps) => { return ( Date: Fri, 6 Feb 2026 09:44:06 +0700 Subject: [PATCH 13/22] chore: adjust divider styling --- src/components/helper/drawer/DrawerHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/helper/drawer/DrawerHeader.tsx b/src/components/helper/drawer/DrawerHeader.tsx index ef3ba1db..b3fe24f1 100644 --- a/src/components/helper/drawer/DrawerHeader.tsx +++ b/src/components/helper/drawer/DrawerHeader.tsx @@ -82,7 +82,7 @@ const DrawerHeader = ({ {renderLeftIcon()} {showDivider && subtitle && ( -
+
)} {subtitle && ( From ade8fefe0db2642592a2e18e6f47923ac436cbed Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Fri, 6 Feb 2026 09:44:33 +0700 Subject: [PATCH 14/22] feat: auto scroll to AlertErrorList if AlertErrorList is showing --- src/components/helper/form/FormErrors.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/components/helper/form/FormErrors.tsx b/src/components/helper/form/FormErrors.tsx index 60ec1687..e1de97ed 100644 --- a/src/components/helper/form/FormErrors.tsx +++ b/src/components/helper/form/FormErrors.tsx @@ -1,8 +1,10 @@ +'use client'; + import Alert from '@/components/Alert'; import Button from '@/components/Button'; import { cn } from '@/lib/helper'; import { Icon } from '@iconify/react'; -import { useState } from 'react'; +import { useEffect, useRef } from 'react'; /** * Alert Unique Error List @@ -29,10 +31,22 @@ const AlertErrorList = ({ onClose: () => void; title?: string; }) => { + const alertRef = useRef(null); + + useEffect(() => { + if (formErrorList.length > 0) { + alertRef.current?.scrollIntoView({ + behavior: 'smooth', + block: 'start', + }); + } + }, [formErrorList.length]); + if (formErrorList.length === 0) return null; return (