Merge branch 'fix/project-flock' into 'development'

[FIX/FE] Project Flock

See merge request mbugroup/lti-web-client!314
This commit is contained in:
Rivaldi A N S
2026-02-06 02:58:02 +00:00
17 changed files with 1998 additions and 1125 deletions
+2
View File
@@ -68,6 +68,8 @@
--shadow-button-soft: --shadow-button-soft:
0 3px 2px -2px var(--color-base-200), 0 4px 3px -2px var(--color-base-200); 0 3px 2px -2px var(--color-base-200), 0 4px 3px -2px var(--color-base-200);
--shadow-bg: 0px -2px 4px 0px #00000014;
} }
html { html {
+25 -13
View File
@@ -1,10 +1,10 @@
'use client'; 'use client';
import { usePathname, useRouter } from 'next/navigation'; import { usePathname, useRouter } from 'next/navigation';
import Drawer from '@/components/Drawer'; import React, { ReactNode, useEffect } from 'react';
import React, { ReactNode } from 'react';
import ProjectFlockTable from '@/components/pages/production/project-flock/ProjectFlockTable'; import ProjectFlockTable from '@/components/pages/production/project-flock/ProjectFlockTable';
import { useUiStore } from '@/stores/ui/ui.store'; import { useUiStore } from '@/stores/ui/ui.store';
import Modal, { useModal } from '@/components/Modal';
export default function ProjectFlockLayout({ export default function ProjectFlockLayout({
children, children,
@@ -23,9 +23,12 @@ export default function ProjectFlockLayout({
const isOpen = isAdd || isEdit || isDetail || isChickin || isClosing; const isOpen = isAdd || isEdit || isDetail || isChickin || isClosing;
const formModal = useModal();
const handleBackdropClick = () => { const handleBackdropClick = () => {
const unsub = useUiStore.getState().subscribeIsValid((isValid) => { const unsub = useUiStore.getState().subscribeIsValid((isValid) => {
if (isValid) { if (isValid) {
formModal.closeModal();
unsub(); // berhenti listen unsub(); // berhenti listen
router.push('/production/project-flock'); router.push('/production/project-flock');
} }
@@ -34,6 +37,14 @@ export default function ProjectFlockLayout({
toggleValidate(); toggleValidate();
}; };
useEffect(() => {
if (isOpen && !formModal.open) {
formModal.openModal();
} else {
formModal.closeModal();
}
}, [isOpen]);
return ( return (
<> <>
{/* List page always rendered */} {/* List page always rendered */}
@@ -43,18 +54,19 @@ export default function ProjectFlockLayout({
/> />
</div> </div>
{/* Render Drawer only on /add */} {/* Render Modal only on /add */}
<Drawer <Modal
open={isOpen} ref={formModal.ref}
setOpen={(v) => { position='end'
if (!v) router.push('/production/project-flock');
}}
closeOnBackdropClick={isDetail ? true : false}
onBackdropClick={handleBackdropClick} onBackdropClick={handleBackdropClick}
variant='right' className={{
zIndex='99999' modalBox: 'w-full sm:w-fit p-3 rounded-xl bg-transparent shadow-none',
sidebarContent={isOpen && <div className=''>{children}</div>} }}
/> >
<div className='w-full sm:w-[446px] h-full flex flex-col sm:flex-row items-stretch bg-base-100 rounded-xl overflow-hidden'>
{isOpen && children}
</div>
</Modal>
</> </>
); );
} }
+8 -3
View File
@@ -1,15 +1,16 @@
import { ReactNode } from 'react'; import { ReactNode, Ref } from 'react';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
interface AlertProps { interface AlertProps {
ref?: Ref<HTMLDivElement> | undefined;
variant?: 'outline' | 'dash' | 'soft'; variant?: 'outline' | 'dash' | 'soft';
color?: 'info' | 'success' | 'warning' | 'error'; color?: 'info' | 'success' | 'warning' | 'error';
children?: ReactNode; children?: ReactNode;
className?: string; className?: string;
} }
const Alert = ({ children, variant, color, className }: AlertProps) => { const Alert = ({ children, ref, variant, color, className }: AlertProps) => {
const alertBaseClassName = cn('alert', { const alertBaseClassName = cn('alert', {
'alert-soft': variant === 'soft', 'alert-soft': variant === 'soft',
'alert-outline': variant === 'outline', 'alert-outline': variant === 'outline',
@@ -21,7 +22,11 @@ const Alert = ({ children, variant, color, className }: AlertProps) => {
'alert-error': color === 'error', 'alert-error': color === 'error',
}); });
return <div className={cn(alertBaseClassName, className)}>{children}</div>; return (
<div ref={ref} className={cn(alertBaseClassName, className)}>
{children}
</div>
);
}; };
export default Alert; export default Alert;
+3 -1
View File
@@ -9,6 +9,7 @@ import Button from '@/components/Button';
import { cn, formatDate } from '@/lib/helper'; import { cn, formatDate } from '@/lib/helper';
interface ApprovalStepsV2Props { interface ApprovalStepsV2Props {
title?: string;
approvals?: BaseApproval[]; approvals?: BaseApproval[];
steps: { steps: {
step_number: number; step_number: number;
@@ -23,6 +24,7 @@ interface ApprovalStepsV2Props {
} }
const ApprovalStepsV2 = ({ const ApprovalStepsV2 = ({
title = 'Progress Details',
approvals, approvals,
steps, steps,
maxVisibleSteps = 2, maxVisibleSteps = 2,
@@ -99,7 +101,7 @@ const ApprovalStepsV2 = ({
)} )}
> >
<h4 className='text-base font-medium text-base-content/50 font-roboto'> <h4 className='text-base font-medium text-base-content/50 font-roboto'>
Progress Details {title}
</h4> </h4>
<div <div
+7 -1
View File
@@ -1,24 +1,30 @@
import { ReactNode } from 'react';
import Badge from '@/components/Badge'; import Badge from '@/components/Badge';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
import { Color } from '@/types/theme'; import { Color } from '@/types/theme';
interface StatusBadgeProps { interface StatusBadgeProps {
color: Color; color: Color;
text: string; text: ReactNode;
className?: { className?: {
badge?: string; badge?: string;
status?: string; status?: string;
}; };
onClick?: () => void;
} }
const StatusBadge = ({ const StatusBadge = ({
color = 'neutral', color = 'neutral',
text, text,
className, className,
onClick,
}: StatusBadgeProps) => { }: StatusBadgeProps) => {
return ( return (
<Badge <Badge
variant='soft' variant='soft'
onClick={onClick}
className={{ className={{
badge: cn( badge: cn(
'px-2 py-1 w-full flex flex-row justify-start gap-1 rounded-lg border border-base-content/10 text-xs font-medium text-base-content', 'px-2 py-1 w-full flex flex-row justify-start gap-1 rounded-lg border border-base-content/10 text-xs font-medium text-base-content',
@@ -27,7 +27,7 @@ export interface DrawerHeaderProps {
const DrawerHeader = ({ const DrawerHeader = ({
leftIcon = 'mdi:close', leftIcon = 'mdi:close',
leftIconSize = 24, leftIconSize = 20,
leftIconHref, leftIconHref,
leftIconOnClick, leftIconOnClick,
leftIconClassName, leftIconClassName,
@@ -43,7 +43,7 @@ const DrawerHeader = ({
icon={leftIcon} icon={leftIcon}
width={leftIconSize} width={leftIconSize}
height={leftIconSize} height={leftIconSize}
className={cn('cursor-pointer', leftIconClassName)} className={cn('cursor-pointer text-base-content ', leftIconClassName)}
/> />
); );
@@ -73,7 +73,7 @@ const DrawerHeader = ({
return ( return (
<div <div
className={cn( className={cn(
'flex flex-row justify-between items-center px-4 pt-4 pb-4 border-b border-base-content/10', 'flex flex-row justify-between items-center p-4 border-b border-base-content/10',
className className
)} )}
> >
@@ -82,7 +82,7 @@ const DrawerHeader = ({
{renderLeftIcon()} {renderLeftIcon()}
{showDivider && subtitle && ( {showDivider && subtitle && (
<div className='divider divider-horizontal p-0 m-0'></div> <div className='w-px h-full border-none bg-base-content/10' />
)} )}
{subtitle && ( {subtitle && (
+16 -1
View File
@@ -1,8 +1,10 @@
'use client';
import Alert from '@/components/Alert'; import Alert from '@/components/Alert';
import Button from '@/components/Button'; import Button from '@/components/Button';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import { useState } from 'react'; import { useEffect, useRef } from 'react';
/** /**
* Alert Unique Error List * Alert Unique Error List
@@ -29,10 +31,22 @@ const AlertErrorList = ({
onClose: () => void; onClose: () => void;
title?: string; title?: string;
}) => { }) => {
const alertRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (formErrorList.length > 0) {
alertRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}
}, [formErrorList.length]);
if (formErrorList.length === 0) return null; if (formErrorList.length === 0) return null;
return ( return (
<Alert <Alert
ref={alertRef}
color='error' color='error'
className={cn( className={cn(
'w-full flex flex-col gap-2 px-3 rounded-lg', 'w-full flex flex-col gap-2 px-3 rounded-lg',
@@ -57,6 +71,7 @@ const AlertErrorList = ({
</span> </span>
</div> </div>
<Button <Button
type='button'
onClick={onClose} onClick={onClose}
variant='link' variant='link'
className={cn('ml-auto p-0 w-fit text-white', className?.button)} className={cn('ml-auto p-0 w-fit text-white', className?.button)}
@@ -53,7 +53,7 @@ const ChickinFormKandang = ({
}; };
return ( return (
<> <section className='w-full h-full sm:w-[446px] overflow-y-auto'>
<DrawerHeader <DrawerHeader
subtitle={`Chick In ${initialValues.kandang?.name ?? 'Kandang'}`} subtitle={`Chick In ${initialValues.kandang?.name ?? 'Kandang'}`}
leftIcon='mdi:arrow-left' leftIcon='mdi:arrow-left'
@@ -198,7 +198,7 @@ const ChickinFormKandang = ({
afterSubmit={afterSubmitFormChickin} afterSubmit={afterSubmitFormChickin}
/> />
</RequirePermission> </RequirePermission>
</> </section>
); );
}; };
@@ -0,0 +1,159 @@
'use client';
import { ChangeEventHandler, RefObject, useId, useState } from 'react';
import useSWR from 'swr';
import ConfirmationModal, {
ConfirmationModalProps,
} from '@/components/modal/ConfirmationModal';
import TextArea from '@/components/input/TextArea';
import { ProjectFlockFormConfirmationTable } from '@/components/pages/production/project-flock/form/ProjectFlockForm';
import { ProjectFlockFormValues } from '@/components/pages/production/project-flock/form/ProjectFlockForm.schema';
import { Color } from '@/types/theme';
import { isResponseSuccess } from '@/lib/api-helper';
import { KandangApi } from '@/services/api/master-data';
interface ProjectFlockConfirmationModalProps
extends Omit<ConfirmationModalProps, 'children' | 'primaryButton'> {
ref: RefObject<HTMLDialogElement | null>;
type?: 'info' | 'success' | 'error';
projectFlockIds?: number[];
projectFlockForm?: ProjectFlockFormValues;
onClose?: () => void;
withNote?: boolean;
noteLabel?: string;
rows?: number;
placeholder?: string;
primaryButton?: {
text?: string;
color?: Color;
isLoading?: boolean;
onClick?: (notes: string) => void;
};
}
const ProjectFlockConfirmationModal = ({
ref,
type = 'success',
projectFlockForm,
projectFlockIds,
onClose,
withNote,
rows = 4,
noteLabel,
placeholder = 'Alasan',
primaryButton,
secondaryButton,
...props
}: ProjectFlockConfirmationModalProps) => {
const randomId = useId();
const [notes, setNotes] = useState('');
const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({
search: '',
location_id: projectFlockForm?.location_id
? String(projectFlockForm?.location_id)
: '',
limit: '500',
}).toString()}`;
const {
data: kandang,
isLoading: isLoadingKandang,
mutate: refreshKandang,
} = useSWR(kandangUrl, KandangApi.getAllFetcher);
const notesChangeHandler: ChangeEventHandler<HTMLTextAreaElement> = (e) => {
setNotes(e.target.value);
};
const closeModalHandler = () => {
onClose?.();
ref.current?.close();
};
return (
<ConfirmationModal
ref={ref}
iconPosition='left'
type={type}
primaryButton={{
...primaryButton,
text: primaryButton?.text ?? 'Oke',
color: primaryButton?.color ?? 'primary',
className: 'rounded-lg',
onClick: (e) => {
if (withNote) {
primaryButton?.onClick?.(notes);
} else if (primaryButton && primaryButton?.onClick) {
primaryButton?.onClick?.('');
} else {
closeModalHandler();
}
setNotes('');
},
}}
secondaryButton={
secondaryButton
? {
text: secondaryButton?.text ?? 'Cancel',
color: secondaryButton?.color ?? 'none',
onClick: (e) => {
if (secondaryButton && secondaryButton?.onClick) {
secondaryButton.onClick?.(e);
} else {
closeModalHandler();
}
setNotes('');
},
}
: undefined
}
className={{
modalBox: 'max-h-full',
}}
{...props}
>
<div className='flex flex-col gap-4'>
{!projectFlockIds && projectFlockForm && (
<ProjectFlockFormConfirmationTable
projectFlockForm={projectFlockForm}
kandangs={
isResponseSuccess(kandang) && kandang?.data ? kandang.data : []
}
/>
)}
{/* {projectFlockIds &&
!projectFlockForm &&
projectFlockIds.map((projectFlockId, idx) => (
<ProjectFlockFormConfirmationTable
key={idx}
projectFlockId={projectFlockId}
kandangs={
isResponseSuccess(kandang) && kandang?.data ? kandang.data : []
}
/>
))} */}
{withNote && (
<TextArea
name={randomId}
label={noteLabel}
placeholder={placeholder}
value={notes}
onChange={notesChangeHandler}
rows={rows}
/>
)}
</div>
</ConfirmationModal>
);
};
export default ProjectFlockConfirmationModal;
@@ -13,6 +13,7 @@ import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModal from '@/components/modal/ConfirmationModal';
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes'; import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
import Table from '@/components/Table'; import Table from '@/components/Table';
import Dropdown from '@/components/Dropdown';
import { ROWS_OPTIONS } from '@/config/constant'; import { ROWS_OPTIONS } from '@/config/constant';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { cn, formatDate, formatTitleCase } from '@/lib/helper'; import { cn, formatDate, formatTitleCase } from '@/lib/helper';
@@ -29,6 +30,111 @@ import toast from 'react-hot-toast';
import useSWR from 'swr'; import useSWR from 'swr';
import RequirePermission from '@/components/helper/RequirePermission'; 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<ProjectFlock, unknown>;
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 (
<div className='relative'>
<PopoverButton
tabIndex={0}
variant='ghost'
color='none'
popoverTarget={popoverId}
anchorName={popoverAnchorName}
>
<Icon icon='material-symbols:more-vert' width={16} height={16} />
</PopoverButton>
<PopoverContent
id={popoverId}
anchorName={popoverAnchorName}
position={popoverPosition === 'bottom' ? 'bottom-start' : 'left'}
className='w-full max-w-40 rounded-xl border border-base-content/5 shadow-sm'
>
<div className='flex flex-col bg-base-100 rounded-xl'>
<RequirePermission permissions='lti.production.project_flocks.detail'>
<Button
// href={`/production/project-flock/detail/?projectFlockId=${props.row.original.id}`}
variant='ghost'
color='none'
onClick={detailClickHandlerWrapper}
className='p-3 justify-start text-sm font-semibold w-full'
>
<Icon icon='heroicons:eye' width={20} height={20} />
View Details
</Button>
</RequirePermission>
{showEditButton && (
<RequirePermission permissions='lti.production.project_flocks.update'>
<Button
// href={`/production/project-flock/detail/edit/?projectFlockId=${props.row.original.id}`}
variant='ghost'
color='none'
onClick={editClickHandlerWrapper}
className='p-3 justify-start text-sm font-semibold w-full'
>
<Icon icon='heroicons:pencil-square' width={20} height={20} />
Edit
</Button>
</RequirePermission>
)}
{showDeleteButton && (
<RequirePermission permissions='lti.production.project_flocks.delete'>
<hr className='mx-3 border-base-content/10 h-px' />
<Button
onClick={deleteClickHandler}
variant='ghost'
color='error'
className='p-3 justify-start text-sm font-semibold w-full'
>
<Icon icon='heroicons:trash' width={20} height={20} />
Delete
</Button>
</RequirePermission>
)}
</div>
</PopoverContent>
</div>
);
};
const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
const { const {
@@ -62,6 +168,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
const selectedRowIds = Object.keys(rowSelection) const selectedRowIds = Object.keys(rowSelection)
.filter((id) => rowSelection[id]) .filter((id) => rowSelection[id])
.map((id) => parseInt(id)); .map((id) => parseInt(id));
const [selectedArea, setSelectedArea] = useState<OptionType | null>(null); const [selectedArea, setSelectedArea] = useState<OptionType | null>(null);
const [selectedLocation, setSelectedLocation] = useState<OptionType | null>( const [selectedLocation, setSelectedLocation] = useState<OptionType | null>(
null null
@@ -78,6 +185,8 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
); );
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [isApproveLoading, setIsApproveLoading] = useState(false); const [isApproveLoading, setIsApproveLoading] = useState(false);
const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] =
useState(false);
// ===== Fetch Data ===== // ===== Fetch Data =====
const { const {
@@ -175,14 +284,27 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
: null; : null;
}, [rowSelection]); }, [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(() => { 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 isProjectFlockRequesting = projectFlock?.approval?.step_number == 1;
const isNotRejected = selectedSingleRow.approval?.action != 'REJECTED'; const isProjectFlockNotRejected =
projectFlock?.approval?.action != 'REJECTED';
return isPengajuan && isNotRejected; return isProjectFlockRequesting && isProjectFlockNotRejected;
}, [selectedSingleRow, isApproveLoading]); });
}, [selectedRowIds, projectFlocks]);
// ====== COLUMNS ====== // ====== COLUMNS ======
const columns = useMemo<ColumnDef<ProjectFlock>[]>( const columns = useMemo<ColumnDef<ProjectFlock>[]>(
@@ -256,44 +378,39 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
const approval = props.row.original.approval; const approval = props.row.original.approval;
const isRejected = approval?.action == 'REJECTED'; const isRejected = approval?.action == 'REJECTED';
const isApproved = approval?.action == 'APPROVED'; const isApproved = approval?.action == 'APPROVED';
return (
<Badge let latestApprovalStepName = approval.step_name;
variant='soft'
className={{ const badgeColor = isRejected
badge: 'rounded-lg px-2 w-full flex flex-row justify-start', ? 'error'
}} : isApproved
color={ ? approval?.step_number == 1
isRejected ? 'neutral'
? 'error' : approval?.step_number == 2
: isApproved ? 'success'
? approval?.step_number == 1 : approval?.step_number == 3
? 'neutral' ? 'error'
: approval?.step_number == 2
? 'primary'
: approval?.step_number == 3
? 'success'
: 'neutral'
: 'neutral' : 'neutral'
} : 'neutral';
>
<Icon switch (approval.action.toLowerCase()) {
icon='mdi:circle' case 'pengajuan':
width={12} latestApprovalStepName = 'Pengajuan';
height={12} break;
color={ case 'aktif':
approval?.step_number == 1 latestApprovalStepName = 'Aktif';
? 'neutral' break;
: approval?.step_number == 2 case 'Selesai':
? 'primary' latestApprovalStepName = 'Closing';
: approval?.step_number == 3 break;
? 'success' }
: 'neutral'
} if (isRejected) {
/> latestApprovalStepName = 'Ditolak';
{isRejected }
? 'Ditolak'
: formatTitleCase(approval?.step_name || '')} return (
</Badge> <StatusBadge color={badgeColor} text={latestApprovalStepName} />
); );
}, },
}, },
@@ -325,27 +442,88 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
cell: (props) => cell: (props) =>
formatDate(props.row.original.created_at, 'MMM DD, YYYY'), 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 (
<RowOptionsMenu
props={props}
detailClickHandler={detailClickHandler}
editClickHandler={editClickHandler}
deleteClickHandler={deleteClickHandler}
popoverPosition={isLast2Rows ? 'top' : 'bottom'}
/>
);
},
},
], ],
[] []
); );
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 ( return (
<> <>
<div className='min-h-screen w-full p-4'> <div className='@container min-h-screen w-full'>
<div className='flex flex-col gap-2 mb-4'> <div className='flex flex-col mb-4'>
<div className='w-full flex flex-col justify-between items-end gap-2'> {/* <div className='w-full flex flex-col justify-between items-end gap-2'>
<div className='flex flex-col sm:flex-row gap-3 w-full'> <div className='flex flex-col sm:flex-row gap-3 w-full'>
<RequirePermission permissions='lti.production.project_flocks.create'> <RequirePermission permissions='lti.production.project_flocks.create'>
<Button <Button
color='primary' color='primary'
className='w-full sm:w-fit'
onClick={() => { onClick={() => {
setRowSelection({}); setRowSelection({});
router.push('/production/project-flock/add'); router.push('/production/project-flock/add');
}} }}
className='px-3 py-2.5 w-fit text-sm text-base-100 rounded-lg shadow-sm'
> >
<Icon icon='ic:round-plus' width={24} height={24} /> <Icon icon='heroicons:plus' width={20} height={20} />
Tambah Add Flock
</Button> </Button>
</RequirePermission> </RequirePermission>
<div className='ms-auto w-full sm:w-auto'> <div className='ms-auto w-full sm:w-auto'>
@@ -423,6 +601,158 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
}} }}
/> />
</div> </div>
</div> */}
<div className='w-full p-3 flex flex-row justify-between gap-3 flex-wrap border-b border-base-content/10'>
<div className='w-fit flex flex-row gap-3 flex-wrap'>
<RequirePermission permissions='lti.production.project_flocks.create'>
<Button
color='primary'
onClick={() => {
setRowSelection({});
router.push('/production/project-flock/add');
}}
className='px-3 py-2.5 w-fit text-sm text-base-100 rounded-lg shadow-sm'
>
<Icon icon='heroicons:plus' width={20} height={20} />
Add Flock
</Button>
</RequirePermission>
{selectedRowIds.length > 0 && canApprove && (
<>
<hr className='w-px h-full border-none bg-base-content/10 hidden @sm:block' />
<RequirePermission permissions='lti.production.transfer_to_laying.approve'>
<Button
variant='outline'
color='none'
onClick={bulkRejectClickHandler}
disabled={selectedRowIds.length === 0}
className='px-3 py-2.5 gap-1.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
>
<Icon
icon='heroicons:x-mark'
width={20}
height={20}
className='text-error'
/>
Reject
</Button>
</RequirePermission>
<RequirePermission permissions='lti.production.transfer_to_laying.approve'>
<Button
variant='outline'
color='none'
onClick={bulkApproveClickHandler}
disabled={selectedRowIds.length === 0}
className='px-3 py-2.5 gap-1.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
>
<Icon
icon='heroicons:check'
width={20}
height={20}
className='text-success'
/>
Approve
</Button>
</RequirePermission>
</>
)}
</div>
<div className='flex flex-1 flex-row justify-start sm:justify-end items-center gap-3 flex-wrap'>
<DebouncedTextInput
name='search'
placeholder='Search'
value={tableFilterState.search ?? ''}
onChange={searchChangeHandler}
startAdornment={
<Icon
icon='heroicons:magnifying-glass'
width={20}
height={20}
/>
}
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',
}}
/>
<Button
variant='outline'
color='none'
// onClick={filterModal.openModal}
className={cn(
'px-3 py-2.5 gap-1.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft transition-all',
{
// 'border-primary-gradient text-primary': isFilterActive,
}
)}
>
<Icon icon='heroicons:funnel' width={20} height={20} />
Filter
{/* {isFilterActive && (
<Badge
className={{
badge:
'p-1.5 bg-[#FF3535] text-xs text-base-100 border border-base-300 rounded-lg',
}}
>
{filterCount}
</Badge>
)} */}
</Button>
<Dropdown
align='end'
direction='bottom'
className={{
content:
'mt-1 rounded-xl border border-base-content/5 shadow-sm overflow-hidden',
}}
trigger={
<Button
variant='outline'
color='none'
className='px-3 py-2.5 text-sm text-base-content/50 border border-base-content/10 rounded-xl shadow-button-soft'
>
<div className='flex flex-row items-center gap-1.5'>
<Icon
icon='heroicons:cloud-arrow-down'
width={20}
height={20}
/>
<span>Export</span>
<div className='w-px self-stretch bg-base-content/10' />
<Icon
icon='heroicons:chevron-down'
width={14}
height={14}
/>
</div>
</Button>
}
>
<Button
variant='ghost'
color='none'
onClick={exportToExcelHandler}
isLoading={isLoadingExportingToExcel}
className='w-full p-3 justify-start text-sm text-base-content/50 font-semibold text-nowrap'
>
<Icon icon='heroicons:table-cells' width={20} height={20} />
Export to Excel
</Button>
</Dropdown>
</div>
</div> </div>
<Table<ProjectFlock> <Table<ProjectFlock>
@@ -448,26 +778,20 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
setSorting={setSorting} setSorting={setSorting}
rowSelection={rowSelection} rowSelection={rowSelection}
setRowSelection={setRowSelection} setRowSelection={setRowSelection}
withCheckbox
className={{ className={{
containerClassName: cn({ containerClassName: cn('p-3', {
'mb-40': 'w-full mb-20':
isResponseSuccess(projectFlocks) && isResponseSuccess(projectFlocks) &&
projectFlocks?.data?.length > 0, projectFlocks?.data?.length === 0,
}), }),
tableWrapperClassName: 'overflow-x-auto min-h-full!', headerColumnClassName: 'text-nowrap',
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',
}} }}
/> />
</div> </div>
</div> </div>
<FloatingActionsButton {/* <FloatingActionsButton
actions={[ actions={[
{ {
action: 'DETAIL', action: 'DETAIL',
@@ -520,7 +844,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
onClose={() => { onClose={() => {
setRowSelection({}); setRowSelection({});
}} }}
/> /> */}
<ConfirmationModal <ConfirmationModal
ref={deleteModal.ref} ref={deleteModal.ref}
@@ -94,230 +94,235 @@ const ProjectFlockClosingForm = ({
return ( return (
<> <>
<DrawerHeader <section className='w-full h-full sm:w-[446px] overflow-y-auto'>
leftIcon='mdi:arrow-left' <DrawerHeader
leftIconHref={`/production/project-flock/detail?projectFlockId=${projectFlock.id}`} leftIcon='mdi:arrow-left'
subtitle={`Close ${projectFlock.flock_name}`} leftIconHref={`/production/project-flock/detail?projectFlockId=${projectFlock.id}`}
></DrawerHeader> subtitle={`Close ${projectFlock.flock_name}`}
></DrawerHeader>
{/* Informasi Kandang */} {/* Informasi Kandang */}
<div className='divider'></div> <div className='divider'></div>
<div className='px-4 pb-4 flex flex-col gap-4'> <div className='px-4 pb-4 flex flex-col gap-4'>
<h2 className='text-2xl font-semibold'>Informasi Kandang</h2> <h2 className='text-2xl font-semibold'>Informasi Kandang</h2>
{/* Badge Row */} {/* Badge Row */}
<div className='flex flex-row gap-2'> <div className='flex flex-row gap-2'>
<Badge <Badge
variant='soft' variant='soft'
color='success' color='success'
className={{ className={{
badge: 'rounded-lg px-2', badge: 'rounded-lg px-2',
}} }}
> >
<Icon icon='mdi:circle' width={12} height={12} color='success' />{' '} <Icon icon='mdi:circle' width={12} height={12} color='success' />{' '}
Aktif Aktif
</Badge> </Badge>
<div className='divider divider-horizontal p-0 m-0'></div> <div className='divider divider-horizontal p-0 m-0'></div>
<Badge <Badge
color='neutral' color='neutral'
variant='soft' variant='soft'
className={{ badge: 'rounded-lg px-2' }} className={{ badge: 'rounded-lg px-2' }}
> >
<Icon icon='mdi:home' width={12} height={12} /> <Icon icon='mdi:home' width={12} height={12} />
{` Kapasitas ${formatNumber(projectFlockKandang.kandang?.capacity)} Ekor`} {` Kapasitas ${formatNumber(projectFlockKandang.kandang?.capacity)} Ekor`}
</Badge> </Badge>
</div>
{/* Information Grid */}
<div className='grid grid-cols-3 gap-4'>
{/* Area */}
<div
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
relative
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400'
>
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Area
</div>
<div className='col-span-2'>{projectFlock.area?.name}</div>
{/* Lokasi */}
<div
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
relative
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400'
>
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Lokasi
</div>
<div className='col-span-2'>{projectFlock.location?.name}</div>
{/* Kandang */}
<div
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
relative
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400'
>
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Kandang
</div>
<div className='col-span-2'>
{projectFlockKandang.kandang?.name}
</div>
{/* Jumlah DOC */}
<div className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2'>
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Jumlah
DOC
</div>
<div className='col-span-2'>
{formatNumber(
projectFlockKandang.chickins?.reduce(
(total, chickin) => total + chickin.usage_qty,
0
) ?? 0
)}{' '}
Ekor
</div>
</div>
</div> </div>
{/* Information Grid */} {/* Table Biaya */}
<div className='grid grid-cols-3 gap-4'> <div className='divider'></div>
{/* Area */} <div className='px-4 pb-4'>
<div <h2 className='text-2xl font-semibold'>Biaya</h2>
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2 <Table<ClosingExpense>
relative data={
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400' isResponseSuccess(closingData) ? closingData.data?.expenses : []
> }
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Area columns={[
</div> {
<div className='col-span-2'>{projectFlock.area?.name}</div> header: 'PO Number',
accessorKey: 'po_number',
{/* Lokasi */}
<div
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
relative
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400'
>
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Lokasi
</div>
<div className='col-span-2'>{projectFlock.location?.name}</div>
{/* Kandang */}
<div
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
relative
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400'
>
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Kandang
</div>
<div className='col-span-2'>{projectFlockKandang.kandang?.name}</div>
{/* Jumlah DOC */}
<div className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2'>
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Jumlah DOC
</div>
<div className='col-span-2'>
{formatNumber(
projectFlockKandang.chickins?.reduce(
(total, chickin) => total + chickin.usage_qty,
0
) ?? 0
)}{' '}
Ekor
</div>
</div>
</div>
{/* Table Biaya */}
<div className='divider'></div>
<div className='px-4 pb-4'>
<h2 className='text-2xl font-semibold'>Biaya</h2>
<Table<ClosingExpense>
data={
isResponseSuccess(closingData) ? closingData.data?.expenses : []
}
columns={[
{
header: 'PO Number',
accessorKey: 'po_number',
},
{
header: 'Total',
accessorKey: 'total',
},
{
header: 'Status',
accessorKey: 'status',
cell(props) {
return (
<Badge
className={{
badge: 'rounded-lg',
}}
variant='soft'
color={
props.row.original.step < 5
? props.row.original.step == 1
? 'neutral'
: 'success'
: 'error'
}
>
{formatTitleCase(props.row.original.step_name)}
</Badge>
);
}, },
}, {
]} header: 'Total',
className={{ accessorKey: 'total',
containerClassName: cn('my-4'), },
tableWrapperClassName: 'overflow-x-auto min-h-full! max-w-120', {
tableClassName: 'font-inter w-full table-sm min-h-full!', header: 'Status',
headerRowClassName: 'border-b border-b-gray-200', accessorKey: 'status',
headerColumnClassName: cell(props) {
'px-3 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end', return (
bodyRowClassName: 'border-b border-b-gray-200', <Badge
bodyColumnClassName: className={{
'px-3 py-3 last:flex last:flex-row last:justify-end', badge: 'rounded-lg',
paginationClassName: 'hidden', }}
}} variant='soft'
/> color={
{/* {errorExpense && ( props.row.original.step < 5
? props.row.original.step == 1
? 'neutral'
: 'success'
: 'error'
}
>
{formatTitleCase(props.row.original.step_name)}
</Badge>
);
},
},
]}
className={{
containerClassName: cn('my-4'),
tableWrapperClassName: 'overflow-x-auto min-h-full! max-w-120',
tableClassName: 'font-inter w-full table-sm min-h-full!',
headerRowClassName: 'border-b border-b-gray-200',
headerColumnClassName:
'px-3 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-3 py-3 last:flex last:flex-row last:justify-end',
paginationClassName: 'hidden',
}}
/>
{/* {errorExpense && (
<div className='text-center text-error text-sm'> <div className='text-center text-error text-sm'>
*Pastikan semua biaya sudah selesai sebelum melakukan closing. *Pastikan semua biaya sudah selesai sebelum melakukan closing.
</div> </div>
)} */} )} */}
</div> </div>
{/* Table Persediaan Gudang */} {/* Table Persediaan Gudang */}
<div className='divider'></div> <div className='divider'></div>
<div className='px-4 pb-4'> <div className='px-4 pb-4'>
<h2 className='text-2xl font-semibold'>Persediaan Gudang</h2> <h2 className='text-2xl font-semibold'>Persediaan Gudang</h2>
<Table<StockItem> <Table<StockItem>
data={ data={
isResponseSuccess(closingData) isResponseSuccess(closingData)
? closingData.data?.stock_remaining ? closingData.data?.stock_remaining
: [] : []
} }
columns={[ columns={[
{ {
header: 'Product', header: 'Product',
accessorKey: 'product_name', accessorKey: 'product_name',
}, },
{ {
header: 'Kategori', header: 'Kategori',
accessorKey: 'product_category', accessorKey: 'product_category',
}, },
{ {
header: 'Quantity', header: 'Quantity',
accessorKey: 'quantity', accessorKey: 'quantity',
}, },
{ {
header: 'UOM', header: 'UOM',
accessorKey: 'uom', accessorKey: 'uom',
}, },
]} ]}
className={{ className={{
containerClassName: cn('my-4'), containerClassName: cn('my-4'),
tableWrapperClassName: 'overflow-x-auto min-h-full! max-w-120', tableWrapperClassName: 'overflow-x-auto min-h-full! max-w-120',
tableClassName: 'font-inter w-full table-sm min-h-full!', tableClassName: 'font-inter w-full table-sm min-h-full!',
headerRowClassName: 'border-b border-b-gray-200', headerRowClassName: 'border-b border-b-gray-200',
headerColumnClassName: headerColumnClassName:
'px-3 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end', 'px-3 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
bodyRowClassName: 'border-b border-b-gray-200', bodyRowClassName: 'border-b border-b-gray-200',
bodyColumnClassName: bodyColumnClassName:
'px-3 py-3 last:flex last:flex-row last:justify-end', 'px-3 py-3 last:flex last:flex-row last:justify-end',
paginationClassName: 'hidden', paginationClassName: 'hidden',
}} }}
/> />
{/* {errorStock && ( {/* {errorStock && (
<div className='text-center text-error text-sm'> <div className='text-center text-error text-sm'>
*Masih ada sisa stock yang belum dihabiskan. *Masih ada sisa stock yang belum dihabiskan.
</div> </div>
)} */} )} */}
</div> </div>
<div className='p-4 mt-6'> <div className='p-4 mt-6'>
<RequirePermission permissions='lti.production.project_flock_kandangs.closing'> <RequirePermission permissions='lti.production.project_flock_kandangs.closing'>
<Button <Button
className='w-full' className='w-full'
color='error' color='error'
isLoading={isLoading} isLoading={isLoading}
disabled={!isCanCloseValid} disabled={!isCanCloseValid}
onClick={() => closeModal.openModal()} onClick={() => closeModal.openModal()}
> >
<Icon icon='mdi:checkbox-marked-circle-outline' />{' '} <Icon icon='mdi:checkbox-marked-circle-outline' />{' '}
{isCanClose ? 'Close' : 'Unclose'} {isCanClose ? 'Close' : 'Unclose'}
</Button> </Button>
</RequirePermission> </RequirePermission>
</div> </div>
<ConfirmationModal <ConfirmationModal
ref={closeModal.ref} ref={closeModal.ref}
type='error' type='error'
text={ text={
isCanClose isCanClose
? 'Apakah kamu yakin ingin mengakhiri project ini ? *Pastikan persediaan produk di gudang terkait sudah kosong, dan BOP sudah selesai' ? 'Apakah kamu yakin ingin mengakhiri project ini ? *Pastikan persediaan produk di gudang terkait sudah kosong, dan BOP sudah selesai'
: 'Apakah kamu yakin ingin membuka kembali project ini ? *Project ini akan kembali ke status aktif' : 'Apakah kamu yakin ingin membuka kembali project ini ? *Project ini akan kembali ke status aktif'
} }
secondaryButton={{ secondaryButton={{
text: 'Tidak', text: 'Tidak',
}} }}
primaryButton={{ primaryButton={{
text: 'Ya', text: 'Ya',
color: 'error', color: 'error',
isLoading: isClosingLoading, isLoading: isClosingLoading,
onClick: confirmationModalCloseClickHandler, onClick: confirmationModalCloseClickHandler,
}} }}
/> />
</section>
</> </>
); );
}; };
@@ -4,12 +4,7 @@ import Card from '@/components/Card';
import { RadioGroup, RadioGroupItem } from '@/components/input/RadioInput'; import { RadioGroup, RadioGroupItem } from '@/components/input/RadioInput';
import Tooltip from '@/components/Tooltip'; import Tooltip from '@/components/Tooltip';
import DrawerHeader from '@/components/helper/drawer/DrawerHeader'; import DrawerHeader from '@/components/helper/drawer/DrawerHeader';
import { import { cn, formatCurrency, formatDate, formatNumber } from '@/lib/helper';
formatCurrency,
formatDate,
formatNumber,
formatTitleCase,
} from '@/lib/helper';
import { ProjectFlock } from '@/types/api/production/project-flock'; import { ProjectFlock } from '@/types/api/production/project-flock';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import Link from 'next/link'; import Link from 'next/link';
@@ -20,16 +15,15 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
import { ProjectFlockApi } from '@/services/api/production/project-flock'; import { ProjectFlockApi } from '@/services/api/production/project-flock';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import ApprovalSteps, {
useApprovalSteps,
} from '@/components/pages/ApprovalSteps';
import {
PROJECT_FLOCK_APPROVAL_LINE,
PROJECT_FLOCK_KANDANGS_APPROVAL_LINE,
} from '@/config/approval-line';
import useSWR from 'swr'; import useSWR from 'swr';
import { ProjectFlockKandangApi } from '@/services/api/production';
import RequirePermission from '@/components/helper/RequirePermission'; import RequirePermission from '@/components/helper/RequirePermission';
import ApprovalStepsV2 from '@/components/helper/ApprovalStepsV2';
import { APPROVAL_WORKFLOWS } from '@/config/constant';
import Table from '@/components/Table';
import { ProjectFlockFormConfirmationTableType } from '../form/ProjectFlockForm';
import { ColumnDef } from '@tanstack/react-table';
import StatusBadge from '@/components/helper/StatusBadge';
import { ProjectFlockKandangApi } from '@/services/api/production/project-flock-kandang';
const ProjectFlockDetail = ({ const ProjectFlockDetail = ({
projectFlock, projectFlock,
@@ -40,7 +34,7 @@ const ProjectFlockDetail = ({
const deleteModal = useModal(); const deleteModal = useModal();
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [openBudgets, setOpenBudget] = useState(false); const [openBudgets, setOpenBudget] = useState(false);
const [selectedKandangId, setSelectedKamdangId] = useState<string | null>( const [selectedKandangId, setSelectedKandangId] = useState<string | null>(
null null
); );
@@ -61,30 +55,94 @@ const ProjectFlockDetail = ({
: null : null
); );
const { const { data: projectFlockApprovalResponse } = useSWR(
approvals, projectFlock.id ? ['approval-project-flock', projectFlock.id] : undefined,
isLoading: approvalsLoading, ([, id]) => ProjectFlockApi.getApprovalLineHistory(Number(id))
refresh: refreshApprovals, );
} = useApprovalSteps({
latestApproval: projectFlock?.approval,
approvalLines: PROJECT_FLOCK_APPROVAL_LINE,
moduleName: 'PROJECT_FLOCKS',
moduleId: projectFlock?.id?.toString() ?? '',
});
const { approvals: kandangApprovals, isLoading: kandangApprovalsLoading } = const projectFlockApproval = isResponseSuccess(projectFlockApprovalResponse)
useApprovalSteps({ ? projectFlockApprovalResponse.data
latestApproval: : undefined;
selectedKandangId && isResponseSuccess(projectFlockKandang)
? projectFlockKandang?.data?.approval const { data: projectFlockKandangApprovalResponse } = useSWR(
: undefined, selectedKandang?.project_flock_kandang_id
approvalLines: PROJECT_FLOCK_KANDANGS_APPROVAL_LINE, ? [
moduleName: 'PROJECT_FLOCK_KANDANGS', 'approval-project-flock-kandang',
moduleId: selectedKandang?.project_flock_kandang_id,
selectedKandangId && isResponseSuccess(projectFlockKandang) ]
? projectFlockKandang?.data?.id?.toString() : undefined,
: '', ([, id]) => ProjectFlockKandangApi.getApprovalLineHistory(Number(id))
}); );
const projectFlockKandangApproval = isResponseSuccess(
projectFlockKandangApprovalResponse
)
? projectFlockKandangApprovalResponse.data
: undefined;
const confirmationTableColumns: ColumnDef<ProjectFlockFormConfirmationTableType>[] =
[
{
header: 'Label',
accessorKey: 'label',
enableSorting: false,
cell: ({ row }) => {
const isSubRow = row.depth > 0;
return (
<>
{!isSubRow && row.original.label}
{isSubRow && (
<div
className={cn('w-full min-h-full flex items-stretch gap-0')}
>
<div className='w-px mx-4 bg-base-content/10' />
<span className='p-3'>{row.original.label}</span>
</div>
)}
</>
);
},
},
{
header: 'Value',
accessorKey: 'value',
enableSorting: false,
cell: ({ row }) => row.original.value,
},
];
const confirmationTableData: ProjectFlockFormConfirmationTableType[] = [
{
label: 'Tanggal',
value: formatDate(projectFlock.created_at, 'DD MMMM YYYY'),
},
{
label: 'Area',
value: projectFlock.area.name ?? '-',
},
{
label: 'Lokasi',
value: projectFlock.location.name ?? '-',
},
{
label: 'Flock',
value: projectFlock.flock_name ?? '-',
},
{
label: 'Kategori',
value: projectFlock.category ?? '-',
},
{
label: 'Standar Produksi',
value: projectFlock.production_standard.name ?? '-',
},
{
label: 'Periode',
value: projectFlock.period ?? '-',
},
];
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true); setIsDeleteLoading(true);
@@ -104,12 +162,14 @@ const ProjectFlockDetail = ({
return ( return (
<> <>
<div className='h-full w-full flex flex-col gap-4'> <div className='h-full w-full flex flex-col overflow-x-hidden overflow-y-auto'>
{/* Header */} {/* Header */}
<DrawerHeader <DrawerHeader
leftIcon='mdi:close' leftIcon='heroicons:chevron-left'
leftIconHref='/production/project-flock' leftIconHref='/production/project-flock'
subtitle={`Created On ${formatDate(projectFlock.created_at, 'MMM DD, YYYY')}`} leftIconClassName='hover:text-gray-400'
subtitle='Detail Flock'
className='sticky top-0 z-10 bg-base-100'
> >
<RequirePermission permissions='lti.production.project_flocks.update'> <RequirePermission permissions='lti.production.project_flocks.update'>
<Link <Link
@@ -118,7 +178,7 @@ const ProjectFlockDetail = ({
> >
<Tooltip content='Edit' position='bottom'> <Tooltip content='Edit' position='bottom'>
<Button variant='link' className='p-0 text-neutral'> <Button variant='link' className='p-0 text-neutral'>
<Icon icon='mdi:square-edit-outline' width={20} height={20} /> <Icon icon='heroicons:pencil-square' width={20} height={20} />
</Button> </Button>
</Tooltip> </Tooltip>
</Link> </Link>
@@ -132,332 +192,224 @@ const ProjectFlockDetail = ({
}} }}
> >
<Tooltip content='Hapus' position='bottom'> <Tooltip content='Hapus' position='bottom'>
<Icon icon='mdi:trash-can-outline' width={20} height={20} /> <Icon icon='heroicons:trash' width={20} height={20} />
</Tooltip> </Tooltip>
</Button> </Button>
</RequirePermission> </RequirePermission>
</DrawerHeader> </DrawerHeader>
{/* Informasi Umum */} <ApprovalStepsV2
<div className='border-t-1 border-gray-300'> approvals={projectFlockApproval}
<div className='p-4 flex flex-col gap-4'> steps={APPROVAL_WORKFLOWS.PROJECT_FLOCKS}
<h2 className='text-2xl font-semibold'>Informasi Umum</h2> />
{/* Status Approval */}
{approvals && !approvalsLoading && (
<div className='text-sm my-3'>
<ApprovalSteps approvals={approvals} />
</div>
)}
{/* Badge Row */}
<div className='flex flex-row gap-2'>
<Badge
variant='soft'
color={
projectFlock.approval?.step_number == 1
? 'neutral'
: projectFlock.approval?.step_number == 2
? 'primary'
: projectFlock.approval?.step_number == 3
? 'success'
: undefined
}
className={{
badge: 'rounded-lg px-2',
}}
>
<Icon
icon='mdi:circle'
width={12}
height={12}
color={
projectFlock.approval?.step_number == 1
? 'neutral'
: projectFlock.approval?.step_number == 2
? 'primary'
: projectFlock.approval?.step_number == 3
? 'success'
: undefined
}
/>{' '}
{projectFlock.approval?.step_name}
</Badge>
<div className='divider divider-horizontal p-0 m-0'></div>
<Badge
color='neutral'
variant='soft'
className={{ badge: 'rounded-lg px-2' }}
>
<Icon icon='mdi:bookmark' width={12} height={12} />
{` ${formatTitleCase(projectFlock.category ?? '')}`}
</Badge>
</div>
{/* Information Grid */}
<div className='grid grid-cols-3 gap-4'>
<div className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2'>
<Icon width={14} height={14} icon='mdi:account' /> Submitted
</div>
<div className='col-span-2'>
<Badge
variant='soft'
color='neutral'
className={{
badge: 'rounded-lg px-2',
}}
>
<Icon icon='mdi:account-circle' width={14} height={14} />{' '}
{projectFlock.created_user?.name}
</Badge>
</div>
{/* BARIS 1 */} <div className='w-full p-4 flex flex-col gap-3 border-b border-base-content/10'>
<div <h4 className='text-base font-medium text-base-content/50 font-roboto'>
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2 Informasi Umum
relative </h4>
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400'
>
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Area
</div>
<div className='col-span-2'>{projectFlock?.area?.name}</div>
{/* BARIS 2 */} <Table<ProjectFlockFormConfirmationTableType>
<div columns={confirmationTableColumns}
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2 data={confirmationTableData}
relative withPagination={false}
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400' pageSize={10000}
> expanded={true}
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Lokasi getSubRows={(row) => row.subRows}
</div> className={{
<div className='col-span-2'>{projectFlock?.location?.name}</div> headerRowClassName: 'border-b border-base-content/10',
bodyRowClassName: 'border-none',
<div bodySubRowClassName: () => 'border-none',
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2 bodySubRowColumnClassName: () => 'first:p-0',
relative }}
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400' />
>
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> FCR
</div>
<div className='col-span-2'>{projectFlock?.fcr?.name}</div>
<div
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
relative
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400'
>
<Icon width={14} height={14} icon='mdi:circle-slice-8' />{' '}
Standard
</div>
<div className='col-span-2'>
{projectFlock?.production_standard?.name ?? '-'}
</div>
{/* BARIS 3 (Terakhir - TIDAK PERLU garis di bawahnya) */}
<div className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2'>
<Icon width={14} height={14} icon='mdi:circle-slice-8' />{' '}
Kategori
</div>
<div className='col-span-2'>
{formatTitleCase(projectFlock.category ?? '')}
</div>
</div>
</div>
</div> </div>
{/* Kandang Aktif */} <div className='w-full p-4 flex flex-col gap-3 border-b border-base-content/10'>
<div className='border-t-1 border-gray-300'> <h4 className='text-base font-medium text-base-content/50 font-roboto'>
<div className='p-4 flex flex-col gap-4'> Kandang Aktif
<h2 className='text-2xl font-semibold'>Kandang Aktif</h2> </h4>
{kandangApprovals && !kandangApprovalsLoading && (
<ApprovalSteps approvals={kandangApprovals} />
)}
{/* Badge Row */}
<div className='flex flex-row gap-2'>
<Badge
variant='soft'
color={'primary'}
className={{
badge: 'rounded-lg px-2',
}}
>
<Icon
icon='mdi:circle'
width={12}
height={12}
color={'primary'}
/>{' '}
Kandang Aktif ({projectFlock.kandangs?.length})
</Badge>
<div className='divider divider-horizontal p-0 m-0'></div>
<Badge
color='neutral'
variant='soft'
className={{ badge: 'rounded-lg px-2 cursor-pointer' }}
onClick={() => {
setOpenBudget(!openBudgets);
}}
>
{` ${formatCurrency(
(projectFlock.project_budgets ?? []).reduce(
(acc, curr) => acc + curr.price * curr.qty,
0
)
)}`}
<Icon
icon={`mdi:${openBudgets ? 'eye' : 'eye-off'}`}
width={12}
height={12}
/>
</Badge>
</div>
{/* Card List Project Budgets */} <div className='flex flex-row gap-2'>
{openBudgets && <StatusBadge
(projectFlock.project_budgets ?? []).map((budget) => ( color='info'
<Card text={`Kandang Aktif (${projectFlock.kandangs?.length})`}
key={budget.id} className={{ badge: 'w-fit text-nowrap' }}
variant='bordered' />
className={{
wrapper: 'w-full',
body: 'p-3',
}}
>
<div className='flex flex-col gap-6'>
<div className='flex flex-row justify-between items-center'>
<div className='flex flex-row gap-2 items-center text-gray-400'>
<Icon icon={'mdi:tag'} width={14} height={14} />{' '}
<span>Jenis Produk</span>
</div>
<div className='text-end text-gray-500'>
{budget?.nonstock?.name}
</div>
</div>
<div className='flex flex-row justify-between items-center'>
<div className='flex flex-row gap-2 items-center text-gray-400'>
<Icon icon={'mdi:tag'} width={14} height={14} />{' '}
<span>Nama Satuan</span>
</div>
<div className='text-end text-gray-500'>
{budget?.nonstock?.uom?.name}
</div>
</div>
<div className='flex flex-row justify-between items-center'>
<div className='flex flex-row gap-2 items-center text-gray-400'>
<Icon
icon={'mdi:file-multiple'}
width={14}
height={14}
/>{' '}
<span>Jumlah Pembelian</span>
</div>
<div className='text-end text-gray-500'>
{formatNumber(budget.qty)}
</div>
</div>
<div className='flex flex-row justify-between items-center'>
<div className='flex flex-row gap-2 items-center text-gray-400'>
<Icon icon={'mdi:file'} width={14} height={14} />{' '}
<span>Harga Satuan</span>
</div>
<div className='text-end text-gray-500'>
{formatCurrency(budget.price)}
</div>
</div>
<div className='flex flex-row justify-between items-center'>
<div className='flex flex-row gap-2 items-center text-gray-400'>
<Icon icon={'mdi:calculator'} width={14} height={14} />{' '}
<span>Total Harga</span>
</div>
<div className='text-end text-gray-500'>
{formatCurrency(budget.price * budget.qty)}
</div>
</div>
</div>
</Card>
))}
{/* Card Kandangs */} <StatusBadge
<Card color='neutral'
variant='bordered' onClick={() => {
className={{ setOpenBudget(!openBudgets);
wrapper: 'w-full',
body: 'p-3',
}} }}
> text={
<RadioGroup <>
name='gender' {` ${formatCurrency(
(projectFlock.project_budgets ?? []).reduce(
(acc, curr) => acc + curr.price * curr.qty,
0
)
)}`}
<Icon
icon={`mdi:${openBudgets ? 'eye' : 'eye-off'}`}
width={12}
height={12}
/>
</>
}
className={{ badge: 'w-fit text-nowrap cursor-pointer' }}
/>
</div>
{/* Card List Project Budgets */}
{openBudgets &&
(projectFlock.project_budgets ?? []).map((budget) => (
<Card
key={budget.id}
variant='bordered'
className={{ className={{
radioWrapper: 'grid grid-cols-1 gap-6', wrapper: 'w-full rounded-lg',
body: 'p-3',
}} }}
onChange={(e) => setSelectedKamdangId(e.target.value)}
value={selectedKandangId?.toString()}
size='md'
color='neutral'
disabled={projectFlock?.approval?.step_number == 1}
> >
{projectFlock.kandangs?.map((kandang) => ( <div className='flex flex-col gap-6'>
<div <div className='flex flex-row justify-between items-center'>
key={kandang.id} <div className='flex flex-row gap-2 items-center text-gray-400'>
className={`grid grid-cols-2 gap-6 cursor-pointer hover:text-gray-800`} <Icon icon={'mdi:tag'} width={14} height={14} />{' '}
onClick={() => <span>Jenis Produk</span>
projectFlock?.approval?.step_number > 1 && </div>
setSelectedKamdangId(kandang?.id?.toString()) <div className='text-end text-gray-500'>
} {budget?.nonstock?.name}
>
<RadioGroupItem
value={kandang?.id?.toString()}
label={kandang?.name}
disabled={projectFlock?.approval?.step_number == 1}
/>
<div className='text-end'>
<Badge
className={{
badge: 'rounded-lg',
}}
>
Kapasitas {kandang?.capacity} Ekor
</Badge>
</div> </div>
</div> </div>
))} <div className='flex flex-row justify-between items-center'>
</RadioGroup> <div className='flex flex-row gap-2 items-center text-gray-400'>
</Card> <Icon icon={'mdi:tag'} width={14} height={14} />{' '}
<div className='grid grid-cols-4 gap-3'> <span>Nama Satuan</span>
<RequirePermission permissions='lti.production.chickins.detail'> </div>
<Link <div className='text-end text-gray-500'>
href={`/production/project-flock/chickin/add/kandang?projectFlockKandangId=${selectedKandang?.project_flock_kandang_id}&projectFlockId=${projectFlock.id}`} {budget?.nonstock?.uom?.name}
className='m-0 p-0' </div>
</div>
<div className='flex flex-row justify-between items-center'>
<div className='flex flex-row gap-2 items-center text-gray-400'>
<Icon icon={'mdi:file-multiple'} width={14} height={14} />{' '}
<span>Jumlah Pembelian</span>
</div>
<div className='text-end text-gray-500'>
{formatNumber(budget.qty)}
</div>
</div>
<div className='flex flex-row justify-between items-center'>
<div className='flex flex-row gap-2 items-center text-gray-400'>
<Icon icon={'mdi:file'} width={14} height={14} />{' '}
<span>Harga Satuan</span>
</div>
<div className='text-end text-gray-500'>
{formatCurrency(budget.price)}
</div>
</div>
<div className='flex flex-row justify-between items-center'>
<div className='flex flex-row gap-2 items-center text-gray-400'>
<Icon icon={'mdi:calculator'} width={14} height={14} />{' '}
<span>Total Harga</span>
</div>
<div className='text-end text-gray-500'>
{formatCurrency(budget.price * budget.qty)}
</div>
</div>
</div>
</Card>
))}
{/* Card Kandangs */}
<Card
variant='bordered'
className={{
wrapper: 'w-full rounded-lg',
body: 'p-3',
}}
>
<RadioGroup
name='gender'
className={{
radioWrapper: 'grid grid-cols-1 gap-6',
}}
onChange={(e) => setSelectedKandangId(e.target.value)}
value={selectedKandangId?.toString()}
size='md'
color='neutral'
disabled={projectFlock?.approval?.step_number == 1}
>
{projectFlock.kandangs?.map((kandang) => (
<div
key={kandang.id}
className={`grid grid-cols-2 gap-6 cursor-pointer hover:text-gray-800`}
onClick={() =>
projectFlock?.approval?.step_number > 1 &&
setSelectedKandangId(kandang?.id?.toString())
}
> >
<Button <RadioGroupItem
className='w-full px-2 py-1 text-sm' value={kandang?.id?.toString()}
variant='outline' label={kandang?.name}
color='success' disabled={projectFlock?.approval?.step_number == 1}
disabled={ />
!selectedKandangId || <div className='text-end'>
projectFlock?.approval?.step_number == 1 <Badge
} className={{
> badge: 'rounded-lg',
Chickin <Icon icon='mdi:checkbox-marked-outline' /> }}
</Button> >
</Link> Kapasitas {kandang?.capacity} Ekor
</RequirePermission> </Badge>
<RequirePermission permissions='lti.production.project_flock_kandangs.closing.detail'> </div>
<Link </div>
href={`/production/project-flock/closing?projectFlockId=${projectFlock.id}&projectFlockKandangId=${selectedKandang?.project_flock_kandang_id}`} ))}
className='m-0 p-0' </RadioGroup>
</Card>
<ApprovalStepsV2
approvals={projectFlockKandangApproval}
steps={APPROVAL_WORKFLOWS.PROJECT_FLOCK_KANDANGS}
/>
<div className='grid grid-cols-4 gap-3'>
<RequirePermission permissions='lti.production.chickins.detail'>
<Link
href={`/production/project-flock/chickin/add/kandang?projectFlockKandangId=${selectedKandang?.project_flock_kandang_id}&projectFlockId=${projectFlock.id}`}
className='m-0 p-0'
>
<Button
className='w-full px-2 py-1 text-sm'
variant='outline'
color='success'
disabled={
!selectedKandangId ||
projectFlock?.approval?.step_number == 1
}
> >
<Button Chickin <Icon icon='mdi:checkbox-marked-outline' />
className='w-full px-2 py-1 text-sm' </Button>
variant='outline' </Link>
color='error' </RequirePermission>
disabled={ <RequirePermission permissions='lti.production.project_flock_kandangs.closing.detail'>
!selectedKandangId || <Link
projectFlock?.approval?.step_number == 1 href={`/production/project-flock/closing?projectFlockId=${projectFlock.id}&projectFlockKandangId=${selectedKandang?.project_flock_kandang_id}`}
} className='m-0 p-0'
> >
Close <Icon icon='mdi:checkbox-marked-circle-outline' /> <Button
</Button> className='w-full px-2 py-1 text-sm'
</Link> variant='outline'
</RequirePermission> color='error'
</div> disabled={
!selectedKandangId ||
projectFlock?.approval?.step_number == 1
}
>
Close <Icon icon='mdi:checkbox-marked-circle-outline' />
</Button>
</Link>
</RequirePermission>
</div> </div>
</div> </div>
</div> </div>
File diff suppressed because it is too large Load Diff
@@ -2,6 +2,7 @@
import Badge from '@/components/Badge'; import Badge from '@/components/Badge';
import Card from '@/components/Card'; import Card from '@/components/Card';
import StatusBadge from '@/components/helper/StatusBadge';
import CheckboxInput from '@/components/input/CheckboxInput'; import CheckboxInput from '@/components/input/CheckboxInput';
import PillBadge from '@/components/PillBadge'; import PillBadge from '@/components/PillBadge';
import Table from '@/components/Table'; import Table from '@/components/Table';
@@ -32,6 +33,14 @@ const ProjectFlockKandangTable = ({
initialValues?: ProjectFlock; initialValues?: ProjectFlock;
formType: 'add' | 'edit' | 'detail'; 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 // Fungsi untuk menangani perubahan checkbox
const handleCheckboxChange = (kandang: Kandang, isChecked: boolean) => { const handleCheckboxChange = (kandang: Kandang, isChecked: boolean) => {
// Hanya izinkan perubahan jika tidak dalam mode 'detail' // Hanya izinkan perubahan jika tidak dalam mode 'detail'
@@ -57,48 +66,30 @@ const ProjectFlockKandangTable = ({
{listKandang.length > 0 ? ( {listKandang.length > 0 ? (
<> <>
{/* ... Bagian Badge Status ... */} {/* ... Bagian Badge Status ... */}
<div className='flex flex-row mb-4'> <div className='w-fit flex flex-row items-stretch gap-3 mb-3'>
<Badge <StatusBadge
variant='soft' color='info'
color='primary' text={`Tersedia (${availableKandang})`}
className={{ className={{ badge: 'text-nowrap' }}
badge: 'rounded-lg px-2', />
}}
> <div className='w-px border-none bg-base-content/10' />
<Icon icon='mdi:circle' width={12} height={12} />
Tersedia ( <StatusBadge
{
listKandang.filter((kandang) => kandang.status == 'NON_ACTIVE')
.length
}
)
</Badge>
<div className='divider divider-horizontal mx-1'></div>
<Badge
variant='soft'
color='neutral' color='neutral'
className={{ text={`Tidak Tersedia (${unavailableKandang})`}
badge: 'rounded-lg px-2', className={{ badge: 'text-nowrap' }}
}} />
>
<Icon icon='mdi:circle' width={12} height={12} />
Tidak Tersedia (
{
listKandang.filter((kandang) => kandang.status != 'NON_ACTIVE')
.length
}
)
</Badge>
</div> </div>
{/* --- */} {/* --- */}
<Card <Card
variant='bordered' variant='bordered'
className={{ className={{
wrapper: 'w-full rounded-lg', wrapper: 'w-full rounded-xl border border-base-content/5',
body: 'p-4', body: 'p-0',
}} }}
> >
<div className='flex flex-col gap-4 w-full'> <div className='flex flex-col w-full'>
{listKandang.map((kandang, index) => { {listKandang.map((kandang, index) => {
const kandangIdString = const kandangIdString =
kandang.id?.toString() ?? `temp-${index}`; kandang.id?.toString() ?? `temp-${index}`;
@@ -112,28 +103,36 @@ const ProjectFlockKandangTable = ({
formType == 'detail' || kandang.status != 'NON_ACTIVE'; formType == 'detail' || kandang.status != 'NON_ACTIVE';
return ( return (
<div key={index} className='flex flex-row justify-between'> <div
key={index}
className='w-full p-3 flex flex-row items-center justify-between'
>
<CheckboxInput <CheckboxInput
name={`kandang-${kandang.id}`} // Nama unik untuk setiap checkbox name={`kandang-${kandang.id}`} // Nama unik untuk setiap checkbox
label={kandang.name} label={kandang.name}
checked={isSelected} checked={isSelected}
disabled={isDisabled} disabled={isDisabled}
size='md'
onChange={(e) => onChange={(e) =>
handleCheckboxChange(kandang, e.currentTarget.checked) handleCheckboxChange(kandang, e.currentTarget.checked)
} }
/> classNames={{
<Badge inputWrapper: cn('gap-3 text-base-content/50', {
variant='soft' 'text-base-content/20': isDisabled,
color={ }),
kandang.status == 'NON_ACTIVE' ? 'primary' : 'neutral' label: 'cursor-pointer',
} checkbox: cn({
className={{ 'bg-base-200 border border-base-content/10 opacity-100':
badge: 'rounded-lg px-2', isDisabled,
}),
}} }}
> />
<Icon icon='mdi:circle' width={12} height={12} />
{kandang.status != 'NON_ACTIVE' && 'Tidak'} Tersedia <StatusBadge
</Badge> color={!isDisabled ? 'info' : 'neutral'}
text={!isDisabled ? 'Tersedia' : 'Tidak Tersedia'}
className={{ badge: 'w-fit' }}
/>
</div> </div>
); );
})} })}
+18
View File
@@ -446,6 +446,24 @@ export const APPROVAL_WORKFLOWS = {
step_number: 2, step_number: 2,
step_name: 'Aktif', step_name: 'Aktif',
}, },
{
step_number: 3,
step_name: 'Selesai',
},
],
PROJECT_FLOCK_KANDANGS: [
{
step_number: 1,
step_name: 'Pengajuan',
},
{
step_number: 2,
step_name: 'Disetujui',
},
{
step_number: 3,
step_name: 'Selesai',
},
], ],
RECORDINGS: [ RECORDINGS: [
{ {
@@ -5,7 +5,7 @@ import {
ClosingProjectFlockKandangPayload, ClosingProjectFlockKandangPayload,
CheckClosingResponse, CheckClosingResponse,
} from '@/types/api/production/project-flock-kandang'; } from '@/types/api/production/project-flock-kandang';
import { BaseApiResponse } from '@/types/api/api-general'; import { Approvals, BaseApiResponse } from '@/types/api/api-general';
import { httpClient } from '@/services/http/client'; import { httpClient } from '@/services/http/client';
import axios from 'axios'; import axios from 'axios';
@@ -181,6 +181,33 @@ export class ProjectFlockKandangService extends BaseApiService<
return undefined; return undefined;
} }
} }
async getApprovalLineHistory(
id: number,
page: number = 1,
limit: number = 100
) {
try {
const approvalHistoryRes = await httpClient<Approvals>('/approvals', {
query: {
module_name: 'PROJECT_FLOCK_KANDANGS',
module_id: id,
group_step_number: 'false',
page,
limit,
order_by_date: 'ASC',
},
});
return approvalHistoryRes;
} catch (error) {
if (axios.isAxiosError<Approvals>(error)) {
return error.response?.data;
}
return undefined;
}
}
} }
export const ProjectFlockKandangApi = new ProjectFlockKandangService( export const ProjectFlockKandangApi = new ProjectFlockKandangService(
@@ -5,6 +5,7 @@ import {
} from '@/types/api/production/project-flock'; } from '@/types/api/production/project-flock';
import { BaseApiService } from '@/services/api/base'; import { BaseApiService } from '@/services/api/base';
import { import {
Approvals,
BaseApiResponse, BaseApiResponse,
BaseGroupedApproval, BaseGroupedApproval,
ErrorApiResponse, ErrorApiResponse,
@@ -53,6 +54,33 @@ export class ProjectFlockService extends BaseApiService<
} }
} }
async getApprovalLineHistory(
id: number,
page: number = 1,
limit: number = 100
) {
try {
const approvalHistoryRes = await httpClient<Approvals>('/approvals', {
query: {
module_name: 'PROJECT_FLOCKS',
module_id: id,
group_step_number: 'false',
page,
limit,
order_by_date: 'ASC',
},
});
return approvalHistoryRes;
} catch (error) {
if (axios.isAxiosError<Approvals>(error)) {
return error.response?.data;
}
return undefined;
}
}
/** /**
* Lookup for Project Flock Kandang * Lookup for Project Flock Kandang
*/ */