mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
Merge branch 'fix/project-flock' into 'development'
[FIX/FE] Project Flock See merge request mbugroup/lti-web-client!314
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
'use client';
|
||||
|
||||
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 +23,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 +37,14 @@ export default function ProjectFlockLayout({
|
||||
toggleValidate();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen && !formModal.open) {
|
||||
formModal.openModal();
|
||||
} else {
|
||||
formModal.closeModal();
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* List page always rendered */}
|
||||
@@ -43,18 +54,19 @@ export default function ProjectFlockLayout({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Render Drawer only on /add */}
|
||||
<Drawer
|
||||
open={isOpen}
|
||||
setOpen={(v) => {
|
||||
if (!v) router.push('/production/project-flock');
|
||||
}}
|
||||
closeOnBackdropClick={isDetail ? true : false}
|
||||
{/* Render Modal only on /add */}
|
||||
<Modal
|
||||
ref={formModal.ref}
|
||||
position='end'
|
||||
onBackdropClick={handleBackdropClick}
|
||||
variant='right'
|
||||
zIndex='99999'
|
||||
sidebarContent={isOpen && <div className=''>{children}</div>}
|
||||
/>
|
||||
className={{
|
||||
modalBox: 'w-full sm:w-fit p-3 rounded-xl bg-transparent shadow-none',
|
||||
}}
|
||||
>
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { ReactNode, Ref } from 'react';
|
||||
|
||||
import { cn } from '@/lib/helper';
|
||||
|
||||
interface AlertProps {
|
||||
ref?: Ref<HTMLDivElement> | 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 <div className={cn(alertBaseClassName, className)}>{children}</div>;
|
||||
return (
|
||||
<div ref={ref} className={cn(alertBaseClassName, className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Alert;
|
||||
|
||||
@@ -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 = ({
|
||||
)}
|
||||
>
|
||||
<h4 className='text-base font-medium text-base-content/50 font-roboto'>
|
||||
Progress Details
|
||||
{title}
|
||||
</h4>
|
||||
|
||||
<div
|
||||
|
||||
@@ -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 (
|
||||
<Badge
|
||||
variant='soft'
|
||||
onClick={onClick}
|
||||
className={{
|
||||
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',
|
||||
|
||||
@@ -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 (
|
||||
<div
|
||||
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
|
||||
)}
|
||||
>
|
||||
@@ -82,7 +82,7 @@ const DrawerHeader = ({
|
||||
{renderLeftIcon()}
|
||||
|
||||
{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 && (
|
||||
|
||||
@@ -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<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (formErrorList.length > 0) {
|
||||
alertRef.current?.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start',
|
||||
});
|
||||
}
|
||||
}, [formErrorList.length]);
|
||||
|
||||
if (formErrorList.length === 0) return null;
|
||||
|
||||
return (
|
||||
<Alert
|
||||
ref={alertRef}
|
||||
color='error'
|
||||
className={cn(
|
||||
'w-full flex flex-col gap-2 px-3 rounded-lg',
|
||||
@@ -57,6 +71,7 @@ const AlertErrorList = ({
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
type='button'
|
||||
onClick={onClose}
|
||||
variant='link'
|
||||
className={cn('ml-auto p-0 w-fit text-white', className?.button)}
|
||||
|
||||
@@ -53,7 +53,7 @@ const ChickinFormKandang = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className='w-full h-full sm:w-[446px] overflow-y-auto'>
|
||||
<DrawerHeader
|
||||
subtitle={`Chick In ${initialValues.kandang?.name ?? 'Kandang'}`}
|
||||
leftIcon='mdi:arrow-left'
|
||||
@@ -198,7 +198,7 @@ const ChickinFormKandang = ({
|
||||
afterSubmit={afterSubmitFormChickin}
|
||||
/>
|
||||
</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 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<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 {
|
||||
@@ -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<OptionType | null>(null);
|
||||
const [selectedLocation, setSelectedLocation] = useState<OptionType | null>(
|
||||
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<ColumnDef<ProjectFlock>[]>(
|
||||
@@ -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 (
|
||||
<Badge
|
||||
variant='soft'
|
||||
className={{
|
||||
badge: 'rounded-lg px-2 w-full flex flex-row justify-start',
|
||||
}}
|
||||
color={
|
||||
isRejected
|
||||
|
||||
let latestApprovalStepName = approval.step_name;
|
||||
|
||||
const badgeColor = isRejected
|
||||
? 'error'
|
||||
: isApproved
|
||||
? approval?.step_number == 1
|
||||
? 'neutral'
|
||||
: approval?.step_number == 2
|
||||
? 'primary'
|
||||
: approval?.step_number == 3
|
||||
? 'success'
|
||||
: 'neutral'
|
||||
: 'neutral'
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
icon='mdi:circle'
|
||||
width={12}
|
||||
height={12}
|
||||
color={
|
||||
approval?.step_number == 1
|
||||
? 'neutral'
|
||||
: approval?.step_number == 2
|
||||
? 'primary'
|
||||
: approval?.step_number == 3
|
||||
? 'success'
|
||||
? 'error'
|
||||
: 'neutral'
|
||||
: 'neutral';
|
||||
|
||||
switch (approval.action.toLowerCase()) {
|
||||
case 'pengajuan':
|
||||
latestApprovalStepName = 'Pengajuan';
|
||||
break;
|
||||
case 'aktif':
|
||||
latestApprovalStepName = 'Aktif';
|
||||
break;
|
||||
case 'Selesai':
|
||||
latestApprovalStepName = 'Closing';
|
||||
break;
|
||||
}
|
||||
/>
|
||||
{isRejected
|
||||
? 'Ditolak'
|
||||
: formatTitleCase(approval?.step_name || '')}
|
||||
</Badge>
|
||||
|
||||
if (isRejected) {
|
||||
latestApprovalStepName = 'Ditolak';
|
||||
}
|
||||
|
||||
return (
|
||||
<StatusBadge color={badgeColor} text={latestApprovalStepName} />
|
||||
);
|
||||
},
|
||||
},
|
||||
@@ -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 (
|
||||
<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 (
|
||||
<>
|
||||
<div className='min-h-screen w-full p-4'>
|
||||
<div className='flex flex-col gap-2 mb-4'>
|
||||
<div className='w-full flex flex-col justify-between items-end gap-2'>
|
||||
<div className='@container min-h-screen w-full'>
|
||||
<div className='flex flex-col mb-4'>
|
||||
{/* <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'>
|
||||
<RequirePermission permissions='lti.production.project_flocks.create'>
|
||||
<Button
|
||||
color='primary'
|
||||
className='w-full sm:w-fit'
|
||||
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='ic:round-plus' width={24} height={24} />
|
||||
Tambah
|
||||
<Icon icon='heroicons:plus' width={20} height={20} />
|
||||
Add Flock
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
<div className='ms-auto w-full sm:w-auto'>
|
||||
@@ -423,6 +601,158 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
||||
}}
|
||||
/>
|
||||
</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>
|
||||
|
||||
<Table<ProjectFlock>
|
||||
@@ -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',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FloatingActionsButton
|
||||
{/* <FloatingActionsButton
|
||||
actions={[
|
||||
{
|
||||
action: 'DETAIL',
|
||||
@@ -520,7 +844,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => {
|
||||
onClose={() => {
|
||||
setRowSelection({});
|
||||
}}
|
||||
/>
|
||||
/> */}
|
||||
|
||||
<ConfirmationModal
|
||||
ref={deleteModal.ref}
|
||||
|
||||
@@ -94,6 +94,7 @@ const ProjectFlockClosingForm = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className='w-full h-full sm:w-[446px] overflow-y-auto'>
|
||||
<DrawerHeader
|
||||
leftIcon='mdi:arrow-left'
|
||||
leftIconHref={`/production/project-flock/detail?projectFlockId=${projectFlock.id}`}
|
||||
@@ -158,11 +159,14 @@ const ProjectFlockClosingForm = ({
|
||||
>
|
||||
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Kandang
|
||||
</div>
|
||||
<div className='col-span-2'>{projectFlockKandang.kandang?.name}</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
|
||||
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Jumlah
|
||||
DOC
|
||||
</div>
|
||||
<div className='col-span-2'>
|
||||
{formatNumber(
|
||||
@@ -318,6 +322,7 @@ const ProjectFlockClosingForm = ({
|
||||
onClick: confirmationModalCloseClickHandler,
|
||||
}}
|
||||
/>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,12 +4,7 @@ import Card from '@/components/Card';
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/input/RadioInput';
|
||||
import Tooltip from '@/components/Tooltip';
|
||||
import DrawerHeader from '@/components/helper/drawer/DrawerHeader';
|
||||
import {
|
||||
formatCurrency,
|
||||
formatDate,
|
||||
formatNumber,
|
||||
formatTitleCase,
|
||||
} from '@/lib/helper';
|
||||
import { cn, formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
||||
import { ProjectFlock } from '@/types/api/production/project-flock';
|
||||
import { Icon } from '@iconify/react';
|
||||
import Link from 'next/link';
|
||||
@@ -20,16 +15,15 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import { ProjectFlockApi } from '@/services/api/production/project-flock';
|
||||
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||
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 { ProjectFlockKandangApi } from '@/services/api/production';
|
||||
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 = ({
|
||||
projectFlock,
|
||||
@@ -40,7 +34,7 @@ const ProjectFlockDetail = ({
|
||||
const deleteModal = useModal();
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||
const [openBudgets, setOpenBudget] = useState(false);
|
||||
const [selectedKandangId, setSelectedKamdangId] = useState<string | null>(
|
||||
const [selectedKandangId, setSelectedKandangId] = useState<string | null>(
|
||||
null
|
||||
);
|
||||
|
||||
@@ -61,30 +55,94 @@ const ProjectFlockDetail = ({
|
||||
: null
|
||||
);
|
||||
|
||||
const {
|
||||
approvals,
|
||||
isLoading: approvalsLoading,
|
||||
refresh: refreshApprovals,
|
||||
} = useApprovalSteps({
|
||||
latestApproval: projectFlock?.approval,
|
||||
approvalLines: PROJECT_FLOCK_APPROVAL_LINE,
|
||||
moduleName: 'PROJECT_FLOCKS',
|
||||
moduleId: projectFlock?.id?.toString() ?? '',
|
||||
});
|
||||
const { data: projectFlockApprovalResponse } = useSWR(
|
||||
projectFlock.id ? ['approval-project-flock', projectFlock.id] : undefined,
|
||||
([, id]) => ProjectFlockApi.getApprovalLineHistory(Number(id))
|
||||
);
|
||||
|
||||
const { approvals: kandangApprovals, isLoading: kandangApprovalsLoading } =
|
||||
useApprovalSteps({
|
||||
latestApproval:
|
||||
selectedKandangId && isResponseSuccess(projectFlockKandang)
|
||||
? projectFlockKandang?.data?.approval
|
||||
const projectFlockApproval = isResponseSuccess(projectFlockApprovalResponse)
|
||||
? projectFlockApprovalResponse.data
|
||||
: undefined;
|
||||
|
||||
const { data: projectFlockKandangApprovalResponse } = useSWR(
|
||||
selectedKandang?.project_flock_kandang_id
|
||||
? [
|
||||
'approval-project-flock-kandang',
|
||||
selectedKandang?.project_flock_kandang_id,
|
||||
]
|
||||
: undefined,
|
||||
approvalLines: PROJECT_FLOCK_KANDANGS_APPROVAL_LINE,
|
||||
moduleName: 'PROJECT_FLOCK_KANDANGS',
|
||||
moduleId:
|
||||
selectedKandangId && isResponseSuccess(projectFlockKandang)
|
||||
? projectFlockKandang?.data?.id?.toString()
|
||||
: '',
|
||||
});
|
||||
([, 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 () => {
|
||||
setIsDeleteLoading(true);
|
||||
@@ -104,12 +162,14 @@ const ProjectFlockDetail = ({
|
||||
|
||||
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 */}
|
||||
<DrawerHeader
|
||||
leftIcon='mdi:close'
|
||||
leftIcon='heroicons:chevron-left'
|
||||
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'>
|
||||
<Link
|
||||
@@ -118,7 +178,7 @@ const ProjectFlockDetail = ({
|
||||
>
|
||||
<Tooltip content='Edit' position='bottom'>
|
||||
<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>
|
||||
</Tooltip>
|
||||
</Link>
|
||||
@@ -132,169 +192,57 @@ const ProjectFlockDetail = ({
|
||||
}}
|
||||
>
|
||||
<Tooltip content='Hapus' position='bottom'>
|
||||
<Icon icon='mdi:trash-can-outline' width={20} height={20} />
|
||||
<Icon icon='heroicons:trash' width={20} height={20} />
|
||||
</Tooltip>
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
</DrawerHeader>
|
||||
|
||||
{/* Informasi Umum */}
|
||||
<div className='border-t-1 border-gray-300'>
|
||||
<div className='p-4 flex flex-col gap-4'>
|
||||
<h2 className='text-2xl font-semibold'>Informasi Umum</h2>
|
||||
{/* Status Approval */}
|
||||
{approvals && !approvalsLoading && (
|
||||
<div className='text-sm my-3'>
|
||||
<ApprovalSteps approvals={approvals} />
|
||||
<ApprovalStepsV2
|
||||
approvals={projectFlockApproval}
|
||||
steps={APPROVAL_WORKFLOWS.PROJECT_FLOCKS}
|
||||
/>
|
||||
|
||||
<div className='w-full p-4 flex flex-col gap-3 border-b border-base-content/10'>
|
||||
<h4 className='text-base font-medium text-base-content/50 font-roboto'>
|
||||
Informasi Umum
|
||||
</h4>
|
||||
|
||||
<Table<ProjectFlockFormConfirmationTableType>
|
||||
columns={confirmationTableColumns}
|
||||
data={confirmationTableData}
|
||||
withPagination={false}
|
||||
pageSize={10000}
|
||||
expanded={true}
|
||||
getSubRows={(row) => row.subRows}
|
||||
className={{
|
||||
headerRowClassName: 'border-b border-base-content/10',
|
||||
bodyRowClassName: 'border-none',
|
||||
bodySubRowClassName: () => 'border-none',
|
||||
bodySubRowColumnClassName: () => 'first:p-0',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{/* Badge Row */}
|
||||
|
||||
<div className='w-full p-4 flex flex-col gap-3 border-b border-base-content/10'>
|
||||
<h4 className='text-base font-medium text-base-content/50 font-roboto'>
|
||||
Kandang Aktif
|
||||
</h4>
|
||||
|
||||
<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
|
||||
<StatusBadge
|
||||
color='info'
|
||||
text={`Kandang Aktif (${projectFlock.kandangs?.length})`}
|
||||
className={{ badge: 'w-fit text-nowrap' }}
|
||||
/>
|
||||
|
||||
<StatusBadge
|
||||
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='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>
|
||||
|
||||
{/* BARIS 2 */}
|
||||
<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>
|
||||
|
||||
<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' /> 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>
|
||||
|
||||
{/* Kandang Aktif */}
|
||||
<div className='border-t-1 border-gray-300'>
|
||||
<div className='p-4 flex flex-col gap-4'>
|
||||
<h2 className='text-2xl font-semibold'>Kandang Aktif</h2>
|
||||
{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);
|
||||
}}
|
||||
>
|
||||
text={
|
||||
<>
|
||||
{` ${formatCurrency(
|
||||
(projectFlock.project_budgets ?? []).reduce(
|
||||
(acc, curr) => acc + curr.price * curr.qty,
|
||||
@@ -306,7 +254,10 @@ const ProjectFlockDetail = ({
|
||||
width={12}
|
||||
height={12}
|
||||
/>
|
||||
</Badge>
|
||||
</>
|
||||
}
|
||||
className={{ badge: 'w-fit text-nowrap cursor-pointer' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Card List Project Budgets */}
|
||||
@@ -316,7 +267,7 @@ const ProjectFlockDetail = ({
|
||||
key={budget.id}
|
||||
variant='bordered'
|
||||
className={{
|
||||
wrapper: 'w-full',
|
||||
wrapper: 'w-full rounded-lg',
|
||||
body: 'p-3',
|
||||
}}
|
||||
>
|
||||
@@ -341,11 +292,7 @@ const ProjectFlockDetail = ({
|
||||
</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}
|
||||
/>{' '}
|
||||
<Icon icon={'mdi:file-multiple'} width={14} height={14} />{' '}
|
||||
<span>Jumlah Pembelian</span>
|
||||
</div>
|
||||
<div className='text-end text-gray-500'>
|
||||
@@ -378,7 +325,7 @@ const ProjectFlockDetail = ({
|
||||
<Card
|
||||
variant='bordered'
|
||||
className={{
|
||||
wrapper: 'w-full',
|
||||
wrapper: 'w-full rounded-lg',
|
||||
body: 'p-3',
|
||||
}}
|
||||
>
|
||||
@@ -387,7 +334,7 @@ const ProjectFlockDetail = ({
|
||||
className={{
|
||||
radioWrapper: 'grid grid-cols-1 gap-6',
|
||||
}}
|
||||
onChange={(e) => setSelectedKamdangId(e.target.value)}
|
||||
onChange={(e) => setSelectedKandangId(e.target.value)}
|
||||
value={selectedKandangId?.toString()}
|
||||
size='md'
|
||||
color='neutral'
|
||||
@@ -399,7 +346,7 @@ const ProjectFlockDetail = ({
|
||||
className={`grid grid-cols-2 gap-6 cursor-pointer hover:text-gray-800`}
|
||||
onClick={() =>
|
||||
projectFlock?.approval?.step_number > 1 &&
|
||||
setSelectedKamdangId(kandang?.id?.toString())
|
||||
setSelectedKandangId(kandang?.id?.toString())
|
||||
}
|
||||
>
|
||||
<RadioGroupItem
|
||||
@@ -420,6 +367,12 @@ const ProjectFlockDetail = ({
|
||||
))}
|
||||
</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
|
||||
@@ -460,7 +413,6 @@ const ProjectFlockDetail = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ConfirmationModal
|
||||
ref={deleteModal.ref}
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
import { Icon } from '@iconify/react';
|
||||
import { FormikErrors, useFormik } from 'formik';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { ReactNode, useEffect, useMemo, useState } from 'react';
|
||||
import useSWR, { KeyedMutator } from 'swr';
|
||||
import {
|
||||
ProjectFlockBudgetsSchemaType,
|
||||
@@ -47,6 +47,13 @@ import { useUiStore } from '@/stores/ui/ui.store';
|
||||
import RequirePermission from '@/components/helper/RequirePermission';
|
||||
import DrawerHeader from '@/components/helper/drawer/DrawerHeader';
|
||||
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
||||
import { cn, formatCurrency, formatDate } from '@/lib/helper';
|
||||
import Tooltip from '@/components/Tooltip';
|
||||
import Table from '@/components/Table';
|
||||
import { ColumnDef } from '@tanstack/react-table';
|
||||
import StatusBadge from '@/components/helper/StatusBadge';
|
||||
import { getUniqueFormikErrors } from '@/lib/formik-helper';
|
||||
import ProjectFlockConfirmationModal from '../ProjectFlockConfirmationModal';
|
||||
|
||||
interface ProjectFlockFormProps {
|
||||
formType?: 'add' | 'edit' | 'detail';
|
||||
@@ -56,6 +63,150 @@ interface ProjectFlockFormProps {
|
||||
>;
|
||||
}
|
||||
|
||||
export interface ProjectFlockFormConfirmationTableType {
|
||||
label: string;
|
||||
value: ReactNode;
|
||||
subRows?: ProjectFlockFormConfirmationTableType[];
|
||||
}
|
||||
|
||||
export const ProjectFlockFormConfirmationTable = ({
|
||||
projectFlockForm,
|
||||
kandangs,
|
||||
}: {
|
||||
projectFlockForm?: ProjectFlockFormValues;
|
||||
kandangs: Kandang[];
|
||||
}) => {
|
||||
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 productsData: ProjectFlockFormConfirmationTableType[] =
|
||||
projectFlockForm?.project_budgets?.map((product) => ({
|
||||
label: 'Jenis Produk',
|
||||
value: product.nonstock?.label ?? '-',
|
||||
subRows: [
|
||||
{
|
||||
label: 'Jumlah Pembelian',
|
||||
value: String(product.qty),
|
||||
},
|
||||
{
|
||||
label: 'Harga Satuan',
|
||||
value: String(formatCurrency(Number(product.price))),
|
||||
},
|
||||
{
|
||||
label: 'Total Harga',
|
||||
value: String(formatCurrency(Number(product.total_price))),
|
||||
},
|
||||
],
|
||||
})) ?? [];
|
||||
|
||||
const confirmationTableData: ProjectFlockFormConfirmationTableType[] = [
|
||||
{
|
||||
label: 'Tanggal',
|
||||
value: formatDate(Date.now(), 'DD MMMM YYYY'),
|
||||
},
|
||||
{
|
||||
label: 'Area',
|
||||
value: projectFlockForm?.area?.label ?? '-',
|
||||
},
|
||||
{
|
||||
label: 'Lokasi',
|
||||
value: projectFlockForm?.location?.label ?? '-',
|
||||
},
|
||||
{
|
||||
label: 'Flock',
|
||||
value: projectFlockForm?.flock?.label ?? '-',
|
||||
},
|
||||
{
|
||||
label: 'Kategori',
|
||||
value: projectFlockForm?.category ?? '-',
|
||||
},
|
||||
{
|
||||
label: 'Standar Produksi',
|
||||
value: projectFlockForm?.production_standard?.label ?? '-',
|
||||
},
|
||||
{
|
||||
label: 'Informasi Kandang',
|
||||
value: '',
|
||||
subRows:
|
||||
projectFlockForm?.kandang_ids?.map((kandang_id) => {
|
||||
const kandang = kandangs.find((kandang) => kandang.id === kandang_id);
|
||||
|
||||
const kandangName = kandang?.name ?? '-';
|
||||
const kandangAvailability = kandang?.status;
|
||||
|
||||
return {
|
||||
label: kandangName,
|
||||
value: (
|
||||
<StatusBadge
|
||||
color={
|
||||
kandangAvailability === 'NON_ACTIVE' ? 'info' : 'neutral'
|
||||
}
|
||||
text={
|
||||
kandangAvailability === 'NON_ACTIVE'
|
||||
? 'Tersedia'
|
||||
: 'Tidak Tersedia'
|
||||
}
|
||||
className={{ badge: 'text-nowrap' }}
|
||||
/>
|
||||
),
|
||||
};
|
||||
}) ?? [],
|
||||
},
|
||||
{
|
||||
label: 'Estimasi Anggaran per Kandang',
|
||||
value: '',
|
||||
},
|
||||
...productsData,
|
||||
];
|
||||
|
||||
return (
|
||||
<Table<ProjectFlockFormConfirmationTableType>
|
||||
columns={confirmationTableColumns}
|
||||
data={confirmationTableData}
|
||||
withPagination={false}
|
||||
pageSize={10000}
|
||||
expanded={true}
|
||||
getSubRows={(row) => row.subRows}
|
||||
className={{
|
||||
headerRowClassName: 'border-b border-base-content/10',
|
||||
bodyRowClassName: 'border-none',
|
||||
bodySubRowClassName: () => 'border-none',
|
||||
bodySubRowColumnClassName: () => 'first:p-0',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ProjectFlockForm = ({
|
||||
formType = 'add',
|
||||
initialValues,
|
||||
@@ -64,6 +215,8 @@ const ProjectFlockForm = ({
|
||||
// State
|
||||
const router = useRouter();
|
||||
|
||||
const [formStep, setFormStep] = useState<'form' | 'confirmation'>('form');
|
||||
|
||||
const [projectFlockFormErrorMessage, setProjectFlockFormErrorMessage] =
|
||||
useState('');
|
||||
const [selectedArea, setSelectedArea] = useState('');
|
||||
@@ -87,6 +240,7 @@ const ProjectFlockForm = ({
|
||||
const subscribeValidate = useUiStore((s) => s.subscribeValidate);
|
||||
const setIsValid = useUiStore((s) => s.setIsValid);
|
||||
|
||||
const successModal = useModal();
|
||||
const deleteModal = useModal();
|
||||
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||
@@ -285,7 +439,7 @@ const ProjectFlockForm = ({
|
||||
if (isResponseSuccess(createProjectFlockRes)) {
|
||||
toast.success(createProjectFlockRes?.message as string);
|
||||
handleReset();
|
||||
router.push('/production/project-flock');
|
||||
successModal.openModal();
|
||||
}
|
||||
if (isResponseError(createProjectFlockRes)) {
|
||||
setProjectFlockFormErrorMessage(createProjectFlockRes?.message as string);
|
||||
@@ -303,7 +457,7 @@ const ProjectFlockForm = ({
|
||||
if (isResponseSuccess(updateProjectFlockRes)) {
|
||||
toast.success(updateProjectFlockRes?.message as string);
|
||||
handleReset();
|
||||
router.push('/production/project-flock');
|
||||
successModal.openModal();
|
||||
}
|
||||
if (isResponseError(updateProjectFlockRes)) {
|
||||
setProjectFlockFormErrorMessage(updateProjectFlockRes?.message as string);
|
||||
@@ -320,6 +474,10 @@ const ProjectFlockForm = ({
|
||||
formikSetValues(formikInitialValues);
|
||||
};
|
||||
|
||||
const [formikLastValues, setFormikLastValues] = useState<
|
||||
ProjectFlockFormValues | undefined
|
||||
>(undefined);
|
||||
|
||||
// Formik InitialValue
|
||||
const formikInitialValues = useMemo<ProjectFlockFormValues>(() => {
|
||||
const trimFlock =
|
||||
@@ -429,6 +587,8 @@ const ProjectFlockForm = ({
|
||||
}),
|
||||
};
|
||||
|
||||
setFormikLastValues(values);
|
||||
|
||||
switch (formType) {
|
||||
case 'add':
|
||||
await createProjectFlockHandler(payload);
|
||||
@@ -654,78 +814,119 @@ const ProjectFlockForm = ({
|
||||
});
|
||||
|
||||
// ===== Formik Error List =====
|
||||
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
|
||||
const { formErrorList, close, setFormErrorList, handleFormSubmit } =
|
||||
useFormikErrorList(formik);
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className='w-full'>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const submitHandler = async () => {
|
||||
const validateFormErrorResult = await formik.validateForm();
|
||||
const isFormError = Object.keys(validateFormErrorResult).length > 0;
|
||||
|
||||
if (isFormError) {
|
||||
const errorMessages = getUniqueFormikErrors(
|
||||
validateFormErrorResult
|
||||
);
|
||||
setFormErrorList(errorMessages);
|
||||
}
|
||||
|
||||
if (isFormError) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (formStep === 'form') {
|
||||
setFormStep('confirmation');
|
||||
return;
|
||||
}
|
||||
|
||||
handleFormSubmit(e);
|
||||
};
|
||||
|
||||
submitHandler();
|
||||
}}
|
||||
onReset={formik.handleReset}
|
||||
className='w-full h-full sm:w-[446px] flex flex-col'
|
||||
>
|
||||
{/* Header */}
|
||||
<DrawerHeader
|
||||
leftIcon={formType == 'add' ? 'mdi:close' : 'mdi:arrow-left'}
|
||||
leftIconSize={24}
|
||||
leftIconHref={
|
||||
formType == 'add'
|
||||
? '/production/project-flock'
|
||||
: `/production/project-flock/detail?projectFlockId=${initialValues?.id}`
|
||||
leftIcon={
|
||||
formType == 'add' ? 'heroicons:chevron-left' : 'mdi:arrow-left'
|
||||
}
|
||||
// leftIconHref={
|
||||
// formType == 'add'
|
||||
// ? '/production/project-flock'
|
||||
// : `/production/project-flock/detail?projectFlockId=${initialValues?.id}`
|
||||
// }
|
||||
leftIconOnClick={() => {
|
||||
if (formStep === 'confirmation') {
|
||||
setFormStep('form');
|
||||
return;
|
||||
}
|
||||
|
||||
if (formType == 'add') {
|
||||
router.push('/production/project-flock');
|
||||
} else {
|
||||
router.push(
|
||||
`/production/project-flock/detail?projectFlockId=${initialValues?.id}`
|
||||
);
|
||||
}
|
||||
}}
|
||||
leftIconClassName='hover:text-gray-400'
|
||||
subtitle={formType == 'add' ? 'Add Flock' : 'Update Flock'}
|
||||
subtitleClassName='text-sm text-neutral'
|
||||
showDivider
|
||||
subtitle={formType == 'add' ? 'Add List Flock' : 'Update Flock'}
|
||||
className='sticky top-0 z-10 bg-base-100'
|
||||
>
|
||||
{formType == 'edit' && (
|
||||
<Button
|
||||
type='button'
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={() => {
|
||||
if (initialValues?.id) {
|
||||
deleteModal.openModal();
|
||||
}
|
||||
}}
|
||||
variant='link'
|
||||
className='p-0 text-error'
|
||||
>
|
||||
<Icon
|
||||
icon='material-symbols:delete-outline-rounded'
|
||||
width={20}
|
||||
height={20}
|
||||
className='justify-start text-sm'
|
||||
/>
|
||||
<Tooltip content='Hapus' position='bottom'>
|
||||
<Icon icon='heroicons:trash' width={20} height={20} />
|
||||
</Tooltip>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{formType == 'add' && (
|
||||
<Button
|
||||
type='button'
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={() => {
|
||||
router.push('/production/project-flock');
|
||||
}}
|
||||
className='p-0 text-error'
|
||||
>
|
||||
<Tooltip content='Hapus' position='bottom'>
|
||||
<Icon icon='heroicons:trash' width={20} height={20} />
|
||||
</Tooltip>
|
||||
</Button>
|
||||
)}
|
||||
</DrawerHeader>
|
||||
{projectFlockFormErrorMessage && (
|
||||
<div className='my-4'>
|
||||
<div role='alert' className='alert alert-error'>
|
||||
<Icon
|
||||
icon='material-symbols:error-outline'
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
<span>{projectFlockFormErrorMessage}</span>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setProjectFlockFormErrorMessage('');
|
||||
}}
|
||||
variant='link'
|
||||
>
|
||||
<Icon icon='material-symbols:close' width={24} height={24} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form
|
||||
className='w-auto h-auto'
|
||||
onSubmit={handleFormSubmit}
|
||||
onReset={formik.handleReset}
|
||||
>
|
||||
<div className='w-auto h-full overflow-y-auto'>
|
||||
{formStep === 'form' && (
|
||||
<div className='h-full'>
|
||||
{/* Form Informasi Umum */}
|
||||
<div className='divider mt-3'></div>
|
||||
<div className='flex flex-col gap-4 px-4'>
|
||||
<h2 className='text-2xl font-semibold'>Informasi Umum</h2>
|
||||
<div className='flex flex-col gap-4'>
|
||||
<div className='flex flex-col p-4'>
|
||||
<h2 className='text-base font-medium text-base-content/50 font-roboto'>
|
||||
Informasi Umum
|
||||
</h2>
|
||||
|
||||
<SelectInput
|
||||
required
|
||||
label='Area'
|
||||
placeholder='Pilih Area'
|
||||
value={formik.values.area as OptionType}
|
||||
onChange={areaChangeHandler}
|
||||
options={optionsArea}
|
||||
@@ -742,6 +943,7 @@ const ProjectFlockForm = ({
|
||||
<SelectInput
|
||||
required
|
||||
label='Lokasi'
|
||||
placeholder='Pilih Lokasi'
|
||||
value={formik.values.location as OptionType}
|
||||
onChange={locationChangeHandler}
|
||||
options={
|
||||
@@ -763,6 +965,7 @@ const ProjectFlockForm = ({
|
||||
<SelectInput
|
||||
required
|
||||
label='Flock'
|
||||
placeholder='Pilih Flock'
|
||||
value={
|
||||
formik.values.flock_name
|
||||
? ({
|
||||
@@ -786,7 +989,8 @@ const ProjectFlockForm = ({
|
||||
onMenuScrollToBottom={loadMoreFlock}
|
||||
isLoading={isLoadingFlocks}
|
||||
isError={
|
||||
formik.touched.flock_name && Boolean(formik.errors.flock_name)
|
||||
formik.touched.flock_name &&
|
||||
Boolean(formik.errors.flock_name)
|
||||
}
|
||||
errorMessage={formik.errors.flock_name as string}
|
||||
isClearable
|
||||
@@ -795,6 +999,7 @@ const ProjectFlockForm = ({
|
||||
<SelectInput
|
||||
required
|
||||
label='FCR'
|
||||
placeholder='Pilih FCR'
|
||||
value={formik.values.fcr as OptionType}
|
||||
onChange={(val) => {
|
||||
optionChangeHandler(val, 'fcr');
|
||||
@@ -803,7 +1008,9 @@ const ProjectFlockForm = ({
|
||||
onMenuScrollToBottom={loadMoreFcr}
|
||||
options={optionsFcr}
|
||||
isLoading={isLoadingFcrs}
|
||||
isError={formik.touched.fcr_id && Boolean(formik.errors.fcr_id)}
|
||||
isError={
|
||||
formik.touched.fcr_id && Boolean(formik.errors.fcr_id)
|
||||
}
|
||||
errorMessage={formik.errors.fcr_id as string}
|
||||
isClearable
|
||||
isDisabled={formType != 'add'}
|
||||
@@ -811,6 +1018,7 @@ const ProjectFlockForm = ({
|
||||
<SelectInput
|
||||
required
|
||||
label='Kategori'
|
||||
placeholder='Pilih Kategori'
|
||||
value={formik.values.category_option as OptionType}
|
||||
onChange={categoryChangeHandler}
|
||||
options={FLOCK_CATEGORY_OPTIONS}
|
||||
@@ -824,6 +1032,7 @@ const ProjectFlockForm = ({
|
||||
<SelectInput
|
||||
required
|
||||
label='Standar Produksi'
|
||||
placeholder='Pilih Standar Produksi'
|
||||
value={formik.values.production_standard as OptionType}
|
||||
onChange={(val) => {
|
||||
optionChangeHandler(val, 'production_standard');
|
||||
@@ -845,19 +1054,20 @@ const ProjectFlockForm = ({
|
||||
label='Periode'
|
||||
disabled
|
||||
readOnly
|
||||
placeholder='Period'
|
||||
placeholder='Periode Flock'
|
||||
value={selectedLocation ? inputPeriod : ''}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Form Pilih Kandang */}
|
||||
<div className='divider'></div>
|
||||
<div className='flex flex-col gap-4 px-4 pb-4'>
|
||||
<h2 className='text-2xl font-semibold'>Pilih Kandang</h2>
|
||||
|
||||
<div className='flex flex-col gap-3 p-4 border-y border-base-content/10'>
|
||||
<h2 className='text-base font-medium text-base-content/50 font-roboto'>
|
||||
Informasi Kandang
|
||||
</h2>
|
||||
<div className='overflow-x-auto duration-300 ease-in-out'>
|
||||
{isLoadingKandang && (
|
||||
<span className='loading loading-dots loading-xl'></span>
|
||||
<span className='loading loading-dots loading-xl block mx-auto' />
|
||||
)}
|
||||
<ProjectFlockKandangTable
|
||||
listPeriods={
|
||||
@@ -874,53 +1084,67 @@ const ProjectFlockForm = ({
|
||||
</div>
|
||||
|
||||
{/* Card Estimasi Budget */}
|
||||
<div className='divider'></div>
|
||||
<div className='flex flex-col gap-4 px-4 pb-4'>
|
||||
<h2 className='text-2xl font-semibold'>
|
||||
Estimasi Anggaran Per Flock
|
||||
</h2>
|
||||
<div className='flex flex-col gap-4'>
|
||||
<div className='flex flex-col'>
|
||||
<div className='flex flex-col'>
|
||||
{formik.values.project_budgets &&
|
||||
formik.values.project_budgets.length > 0 ? (
|
||||
formik.values.project_budgets.map((budget, index) => (
|
||||
<Card
|
||||
formik.values.project_budgets.map((budget, index) => {
|
||||
const nonstockUomName = isResponseSuccess(nonstocks)
|
||||
? (nonstocks.data.find(
|
||||
(ns) => ns.id === budget.nonstock_id
|
||||
)?.uom?.name ?? '')
|
||||
: '';
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
variant='bordered'
|
||||
className={{
|
||||
wrapper: 'w-full',
|
||||
body: 'p-3',
|
||||
}}
|
||||
className='flex flex-col p-4 border-b border-base-content/10'
|
||||
>
|
||||
<div className='flex flex-col gap-2'>
|
||||
<div className='flex flex-row justify-between items-center mb-2'>
|
||||
<div className='text-lg'>Anggaran ke-{index + 1}</div>
|
||||
<div className='flex flex-row justify-between items-center'>
|
||||
<h2 className='text-base font-medium text-base-content/50 font-roboto'>
|
||||
Estimasi Anggaran Per Flock
|
||||
</h2>
|
||||
|
||||
{formik.values.project_budgets.length > 1 && (
|
||||
<Button
|
||||
type='button'
|
||||
color='error'
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={() =>
|
||||
onDeleteBudgetRowHandler(
|
||||
budget.nonstock_id as number,
|
||||
index
|
||||
)
|
||||
}
|
||||
className='p-0 text-error'
|
||||
>
|
||||
<Icon icon='mdi:trash' width={16} height={16} />
|
||||
<Icon
|
||||
icon='heroicons:trash'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='flex flex-row justify-between items-center'>
|
||||
<SelectInput
|
||||
isClearable
|
||||
label='Jenis Produk'
|
||||
options={filteredNonStockOptions ?? []}
|
||||
isLoading={isLoadingNonstocks}
|
||||
placeholder='Pilih barang non stock'
|
||||
value={formik.values.project_budgets[index].nonstock}
|
||||
placeholder='Pilih Jenis Produk'
|
||||
value={
|
||||
formik.values.project_budgets[index].nonstock
|
||||
}
|
||||
onInputChange={setInputValueNonstock}
|
||||
onMenuScrollToBottom={loadMoreNonstock}
|
||||
onChange={(val) => {
|
||||
const updatedBudgets = [
|
||||
...formik.values.project_budgets,
|
||||
];
|
||||
updatedBudgets[index].nonstock = val as OptionType;
|
||||
updatedBudgets[index].nonstock =
|
||||
val as OptionType;
|
||||
updatedBudgets[index].nonstock_id =
|
||||
(val as OptionType)
|
||||
? (val as OptionType).value
|
||||
@@ -957,21 +1181,26 @@ const ProjectFlockForm = ({
|
||||
<div className='flex flex-row justify-between items-center'>
|
||||
<NumberInput
|
||||
name={`project_budgets[${index}].qty`}
|
||||
placeholder='Masukkan jumlah'
|
||||
label='Jumlah Pembelian'
|
||||
placeholder='Masukkan Jumlah Pembelian'
|
||||
value={formik.values.project_budgets[index].qty}
|
||||
onChange={(e) =>
|
||||
handleBudgetChange(index, 'qty', e.target.value)
|
||||
}
|
||||
onBlur={formik.handleBlur}
|
||||
allowNegative={false}
|
||||
endAdornment={
|
||||
<div className='text-gray-500'>
|
||||
{isResponseSuccess(nonstocks)
|
||||
? (nonstocks.data.find(
|
||||
(ns) => ns.id === budget.nonstock_id
|
||||
)?.uom?.name ?? '')
|
||||
: ''}
|
||||
inputPrefix={
|
||||
<>
|
||||
{nonstockUomName && (
|
||||
<div className='w-full h-full py-1 flex flex-row items-stretch justify-between gap-3'>
|
||||
<span className='text-sm text-base-content/60 self-center text-nowrap truncate'>
|
||||
{nonstockUomName}
|
||||
</span>
|
||||
|
||||
<div className='w-px bg-base-content/10' />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
errorMessage={
|
||||
(
|
||||
@@ -990,28 +1219,39 @@ const ProjectFlockForm = ({
|
||||
)?.qty as string
|
||||
)
|
||||
}
|
||||
className={{
|
||||
inputPrefix:
|
||||
'py-0 px-0 pl-3 text-base-content/50 bg-transparent border-r-0',
|
||||
inputWrapper: cn('border-l-0 pl-0', {
|
||||
'pl-5':
|
||||
isResponseSuccess(nonstocks) &&
|
||||
budget.nonstock_id,
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-row justify-between items-center'>
|
||||
<NumberInput
|
||||
name={`project_budgets[${index}].price`}
|
||||
label='Harga Satuan (Rp)'
|
||||
value={formik.values.project_budgets[index].price}
|
||||
onChange={(e) =>
|
||||
handleBudgetChange(index, 'price', e.target.value)
|
||||
handleBudgetChange(
|
||||
index,
|
||||
'price',
|
||||
e.target.value
|
||||
)
|
||||
}
|
||||
onBlur={formik.handleBlur}
|
||||
placeholder='Masukkan harga satuan'
|
||||
placeholder='Masukkan Harga Satuan (Rp)'
|
||||
allowNegative={false}
|
||||
startAdornment='Rp'
|
||||
endAdornment={
|
||||
<div className='text-gray-500'>
|
||||
{`Per ${
|
||||
isResponseSuccess(nonstocks)
|
||||
? (nonstocks.data.find(
|
||||
(ns) => ns.id === budget.nonstock_id
|
||||
)?.uom?.name ?? 'Item')
|
||||
: 'Item'
|
||||
}`}
|
||||
inputPrefix={
|
||||
<div className='w-full h-full py-1 flex flex-row items-stretch justify-between gap-3'>
|
||||
<span className='text-sm text-base-content/60 self-center text-nowrap truncate'>
|
||||
Rp
|
||||
</span>
|
||||
|
||||
<div className='w-px bg-base-content/10' />
|
||||
</div>
|
||||
}
|
||||
errorMessage={
|
||||
@@ -1022,7 +1262,8 @@ const ProjectFlockForm = ({
|
||||
)?.price as string
|
||||
}
|
||||
isError={
|
||||
formik.touched.project_budgets?.[index]?.price &&
|
||||
formik.touched.project_budgets?.[index]
|
||||
?.price &&
|
||||
Boolean(
|
||||
(
|
||||
formik.errors.project_budgets?.[
|
||||
@@ -1031,11 +1272,17 @@ const ProjectFlockForm = ({
|
||||
)?.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',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-row justify-between items-center'>
|
||||
<NumberInput
|
||||
name={`project_budgets[${index}].total_price`}
|
||||
label='Total Harga'
|
||||
value={
|
||||
formik.values.project_budgets[index].total_price
|
||||
}
|
||||
@@ -1047,11 +1294,16 @@ const ProjectFlockForm = ({
|
||||
)
|
||||
}
|
||||
onBlur={formik.handleBlur}
|
||||
placeholder='Masukkan harga total'
|
||||
placeholder='Masukkan Total Harga'
|
||||
allowNegative={false}
|
||||
startAdornment='Rp'
|
||||
endAdornment={
|
||||
<div className='text-gray-500'>Total</div>
|
||||
inputPrefix={
|
||||
<div className='w-full h-full py-1 flex flex-row items-stretch justify-between gap-3'>
|
||||
<span className='text-sm text-base-content/60 self-center text-nowrap truncate'>
|
||||
Rp
|
||||
</span>
|
||||
|
||||
<div className='w-px bg-base-content/10' />
|
||||
</div>
|
||||
}
|
||||
errorMessage={
|
||||
(
|
||||
@@ -1071,31 +1323,85 @@ const ProjectFlockForm = ({
|
||||
)?.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',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div className='text-center py-4 text-gray-400'>
|
||||
Tidak ada data estimasi anggaran.
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='w-full p-4'>
|
||||
<Button
|
||||
type='button'
|
||||
color='primary'
|
||||
variant='outline'
|
||||
onClick={onAddBudgetRowHandler}
|
||||
disabled={filteredNonStockOptions.length == 0}
|
||||
color='success'
|
||||
className='w-fit self-center'
|
||||
className='w-full p-3 rounded-lg text-sm font-semibold hover:text-base-100 focus-visible:text-base-100'
|
||||
>
|
||||
<Icon icon='mdi:plus' width={16} height={16} /> Add Budget
|
||||
Tambah Produk
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AlertErrorList formErrorList={formErrorList} onClose={close} />
|
||||
{(formErrorList.length > 0 || projectFlockFormErrorMessage) && (
|
||||
<div className='p-4'>
|
||||
<AlertErrorList
|
||||
formErrorList={formErrorList}
|
||||
onClose={close}
|
||||
/>
|
||||
|
||||
<div className='flex flex-row justify-center gap-2 flex-wrap my-6 px-4'>
|
||||
{projectFlockFormErrorMessage && (
|
||||
<div className='my-4'>
|
||||
<div role='alert' className='alert alert-error'>
|
||||
<Icon
|
||||
icon='material-symbols:error-outline'
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
<span>{projectFlockFormErrorMessage}</span>
|
||||
<Button
|
||||
type='button'
|
||||
onClick={() => {
|
||||
setProjectFlockFormErrorMessage('');
|
||||
}}
|
||||
variant='link'
|
||||
>
|
||||
<Icon
|
||||
icon='material-symbols:close'
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{formStep === 'confirmation' && (
|
||||
<div className='p-4'>
|
||||
<ProjectFlockFormConfirmationTable
|
||||
projectFlockForm={formik.values}
|
||||
kandangs={optionsKandang}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='p-4 flex-1 flex flex-row justify-center gap-2 flex-wrap shadow-bg'>
|
||||
{formType !== 'detail' && (
|
||||
<RequirePermission
|
||||
permissions={
|
||||
@@ -1106,19 +1412,29 @@ const ProjectFlockForm = ({
|
||||
>
|
||||
<Button
|
||||
type='submit'
|
||||
color='primary'
|
||||
isLoading={formik.isSubmitting}
|
||||
disabled={formik.isSubmitting}
|
||||
className='px-4 w-full'
|
||||
className='w-full p-3 rounded-lg text-sm font-semibold text-base-100'
|
||||
>
|
||||
<Icon icon='mdi:plus' width={24} height={24} />
|
||||
{formType == 'add' ? 'Add Flock' : 'Update Flock'}
|
||||
Submit
|
||||
</Button>
|
||||
</RequirePermission>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<ProjectFlockConfirmationModal
|
||||
ref={successModal.ref}
|
||||
type='success'
|
||||
text='Data Berhasil Ditambahkan'
|
||||
subtitleText='Data project flock telah berhasil disimpan.'
|
||||
projectFlockForm={formikLastValues}
|
||||
onClose={() => {
|
||||
router.push('/production/project-flock');
|
||||
setFormikLastValues(undefined);
|
||||
}}
|
||||
secondaryButton={undefined}
|
||||
/>
|
||||
|
||||
<ConfirmationModal
|
||||
ref={deleteModal.ref}
|
||||
@@ -1133,6 +1449,9 @@ const ProjectFlockForm = ({
|
||||
isLoading: isDeleteLoading,
|
||||
onClick: confirmationModalDeleteClickHandler,
|
||||
}}
|
||||
className={{
|
||||
modal: 'w-full sm:w-[446px]',
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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 ... */}
|
||||
<div className='flex flex-row mb-4'>
|
||||
<Badge
|
||||
variant='soft'
|
||||
color='primary'
|
||||
className={{
|
||||
badge: 'rounded-lg px-2',
|
||||
}}
|
||||
>
|
||||
<Icon icon='mdi:circle' width={12} height={12} />
|
||||
Tersedia (
|
||||
{
|
||||
listKandang.filter((kandang) => kandang.status == 'NON_ACTIVE')
|
||||
.length
|
||||
}
|
||||
)
|
||||
</Badge>
|
||||
<div className='divider divider-horizontal mx-1'></div>
|
||||
<Badge
|
||||
variant='soft'
|
||||
<div className='w-fit flex flex-row items-stretch gap-3 mb-3'>
|
||||
<StatusBadge
|
||||
color='info'
|
||||
text={`Tersedia (${availableKandang})`}
|
||||
className={{ badge: 'text-nowrap' }}
|
||||
/>
|
||||
|
||||
<div className='w-px border-none bg-base-content/10' />
|
||||
|
||||
<StatusBadge
|
||||
color='neutral'
|
||||
className={{
|
||||
badge: 'rounded-lg px-2',
|
||||
}}
|
||||
>
|
||||
<Icon icon='mdi:circle' width={12} height={12} />
|
||||
Tidak Tersedia (
|
||||
{
|
||||
listKandang.filter((kandang) => kandang.status != 'NON_ACTIVE')
|
||||
.length
|
||||
}
|
||||
)
|
||||
</Badge>
|
||||
text={`Tidak Tersedia (${unavailableKandang})`}
|
||||
className={{ badge: 'text-nowrap' }}
|
||||
/>
|
||||
</div>
|
||||
{/* --- */}
|
||||
<Card
|
||||
variant='bordered'
|
||||
className={{
|
||||
wrapper: 'w-full rounded-lg',
|
||||
body: 'p-4',
|
||||
wrapper: 'w-full rounded-xl border border-base-content/5',
|
||||
body: 'p-0',
|
||||
}}
|
||||
>
|
||||
<div className='flex flex-col gap-4 w-full'>
|
||||
<div className='flex flex-col w-full'>
|
||||
{listKandang.map((kandang, index) => {
|
||||
const kandangIdString =
|
||||
kandang.id?.toString() ?? `temp-${index}`;
|
||||
@@ -112,28 +103,36 @@ const ProjectFlockKandangTable = ({
|
||||
formType == 'detail' || kandang.status != 'NON_ACTIVE';
|
||||
|
||||
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
|
||||
name={`kandang-${kandang.id}`} // Nama unik untuk setiap checkbox
|
||||
label={kandang.name}
|
||||
checked={isSelected}
|
||||
disabled={isDisabled}
|
||||
size='md'
|
||||
onChange={(e) =>
|
||||
handleCheckboxChange(kandang, e.currentTarget.checked)
|
||||
}
|
||||
/>
|
||||
<Badge
|
||||
variant='soft'
|
||||
color={
|
||||
kandang.status == 'NON_ACTIVE' ? 'primary' : 'neutral'
|
||||
}
|
||||
className={{
|
||||
badge: 'rounded-lg px-2',
|
||||
classNames={{
|
||||
inputWrapper: cn('gap-3 text-base-content/50', {
|
||||
'text-base-content/20': isDisabled,
|
||||
}),
|
||||
label: 'cursor-pointer',
|
||||
checkbox: cn({
|
||||
'bg-base-200 border border-base-content/10 opacity-100':
|
||||
isDisabled,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<Icon icon='mdi:circle' width={12} height={12} />
|
||||
{kandang.status != 'NON_ACTIVE' && 'Tidak'} Tersedia
|
||||
</Badge>
|
||||
/>
|
||||
|
||||
<StatusBadge
|
||||
color={!isDisabled ? 'info' : 'neutral'}
|
||||
text={!isDisabled ? 'Tersedia' : 'Tidak Tersedia'}
|
||||
className={{ badge: 'w-fit' }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -446,6 +446,24 @@ export const APPROVAL_WORKFLOWS = {
|
||||
step_number: 2,
|
||||
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: [
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
ClosingProjectFlockKandangPayload,
|
||||
CheckClosingResponse,
|
||||
} 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 axios from 'axios';
|
||||
|
||||
@@ -181,6 +181,33 @@ export class ProjectFlockKandangService extends BaseApiService<
|
||||
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(
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
} from '@/types/api/production/project-flock';
|
||||
import { BaseApiService } from '@/services/api/base';
|
||||
import {
|
||||
Approvals,
|
||||
BaseApiResponse,
|
||||
BaseGroupedApproval,
|
||||
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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user