refactor(FE-84-87) refactor checkbox using reuseable component checkboxinput

This commit is contained in:
randy-ar
2025-10-25 13:58:46 +07:00
parent 4f3dfb4221
commit f0f6ec53cb
10 changed files with 476 additions and 275 deletions
+6 -9
View File
@@ -15,7 +15,7 @@ import { Kandang } from '@/types/api/master-data/kandang';
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang'; import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import { useRouter, useSearchParams } from 'next/navigation'; import { useRouter, useSearchParams } from 'next/navigation';
import { use, useEffect, useState } from 'react'; import { useState } from 'react';
import useSWR from 'swr'; import useSWR from 'swr';
@@ -34,9 +34,8 @@ const AddChickin = () => {
const [selectedKandang, setSelectedKandang] = useState<Kandang | undefined>( const [selectedKandang, setSelectedKandang] = useState<Kandang | undefined>(
undefined undefined
); );
const [projectFlockKandang, setProjectFlockKandang] = useState< const [projectFlockKandang, setProjectFlockKandang] =
BaseApiResponse<ProjectFlockKandang> useState<BaseApiResponse<ProjectFlockKandang>>();
>();
const [isLoadingProjectFlockKandang, setIsLoadingProjectFlockKandang] = const [isLoadingProjectFlockKandang, setIsLoadingProjectFlockKandang] =
useState(false); useState(false);
const [searchProjectFlock, setSearchProjectFlock] = useState(''); const [searchProjectFlock, setSearchProjectFlock] = useState('');
@@ -87,7 +86,7 @@ const AddChickin = () => {
} }
// Handle Function // Handle Function
const handleChickinClick = async (kandang: Kandang) => { const handleChickinClick = async (kandang: Kandang) => {
setIsLoadingProjectFlockKandang(true); setIsLoadingProjectFlockKandang(true);
setSelectedKandang(kandang); setSelectedKandang(kandang);
const ProjectFlockKandangRes = await ProjectFlockApi.customRequest< const ProjectFlockKandangRes = await ProjectFlockApi.customRequest<
@@ -100,9 +99,7 @@ const AddChickin = () => {
kandang_id: kandang.id, kandang_id: kandang.id,
}, },
}); });
if ( if (isResponseSuccess(ProjectFlockKandangRes)) {
isResponseSuccess(ProjectFlockKandangRes)
) {
setProjectFlockKandang(ProjectFlockKandangRes); setProjectFlockKandang(ProjectFlockKandangRes);
setIsLoadingProjectFlockKandang(false); setIsLoadingProjectFlockKandang(false);
if ( if (
@@ -110,7 +107,7 @@ const AddChickin = () => {
ProjectFlockKandangRes.data.available_quantity > 0 ProjectFlockKandangRes.data.available_quantity > 0
) { ) {
chickinModal.openModal(); chickinModal.openModal();
}else{ } else {
alertModal.openModal(); alertModal.openModal();
} }
} }
@@ -13,7 +13,6 @@ const DetailChickin = () => {
const { const {
data: chickin, data: chickin,
isLoading, isLoading,
mutate: refreshChickin,
} = useSWR( } = useSWR(
chickinId, chickinId,
(id: number) => ChickinApi.getSingle(id) (id: number) => ChickinApi.getSingle(id)
@@ -1,46 +1,51 @@
'use client' 'use client';
import ProjectFlockForm from '@/components/pages/production/project-flock/form/ProjectFlockForm';
import ProjectFlockForm from "@/components/pages/production/project-flock/form/ProjectFlockForm"; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { isResponseError, isResponseSuccess } from "@/lib/api-helper"; import { ProjectFlockApi } from '@/services/api/production';
import { ProjectFlockApi } from "@/services/api/production"; import { useRouter, useSearchParams } from 'next/navigation';
import { useRouter, useSearchParams } from "next/navigation"; import useSWR from 'swr';
import useSWR from "swr";
const ProjectFlockDetail = () => { const ProjectFlockDetail = () => {
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const projectFlockId = searchParams.get("projectFlockId"); const projectFlockId = searchParams.get('projectFlockId');
const { data: projectFlock, isLoading: isLoadingProjectFlock } = useSWR( const {
projectFlockId, data: projectFlock,
(id: number) => ProjectFlockApi.getSingle(id) isLoading: isLoadingProjectFlock,
); mutate: refreshProjectFlock,
} = useSWR(projectFlockId, (id: number) => ProjectFlockApi.getSingle(id));
if(!projectFlockId){ if (!projectFlockId) {
router.back(); router.back();
return ( return (
<div className="w-full flex flex-row justify-center items-center p-4"> <div className='w-full flex flex-row justify-center items-center p-4'>
<span className="loading loading-spinner loading-xl" /> <span className='loading loading-spinner loading-xl' />
</div> </div>
); );
} }
if(!isLoadingProjectFlock && (!projectFlock || isResponseError(projectFlock))){ if (
router.replace("/404"); !isLoadingProjectFlock &&
(!projectFlock || isResponseError(projectFlock))
) {
router.replace('/404');
return; return;
} }
return ( return (
<div className="w-full p-4 flex flex-row justify-center"> <div className='w-full p-4 flex flex-row justify-center'>
{isLoadingProjectFlock && <span className="loading loading-spinner loading-xl" />} {isLoadingProjectFlock && (
<span className='loading loading-spinner loading-xl' />
)}
{!isLoadingProjectFlock && isResponseSuccess(projectFlock) && ( {!isLoadingProjectFlock && isResponseSuccess(projectFlock) && (
<ProjectFlockForm formType="detail" initialValues={projectFlock.data} /> <ProjectFlockForm formType='detail' initialValues={projectFlock.data} refreshProjectFlocks={refreshProjectFlock} />
)} )}
</div> </div>
) );
} };
export default ProjectFlockDetail; export default ProjectFlockDetail;
@@ -1,4 +1,3 @@
import ProjectFlockForm from "@/components/pages/production/project-flock/form/ProjectFlockForm"
import ProjectFlockTable from "@/components/pages/production/project-flock/ProjectFlockTable"; import ProjectFlockTable from "@/components/pages/production/project-flock/ProjectFlockTable";
const ProjectFlock = () => { const ProjectFlock = () => {
+31
View File
@@ -0,0 +1,31 @@
import { ReactNode } from 'react';
import { cn } from '@/lib/helper';
interface PillBadgeProps {
content: ReactNode;
color?: 'yellow' | 'blue' | 'green' | 'red' | 'purple' | 'gray';
className?: string;
}
const PillBadge = ({ content, color = 'gray', className }: PillBadgeProps) => {
return (
<div
className={cn(
'w-fit min-w-max px-2 py-0.5 flex justify-center items-center gap-1 rounded-full border border-gray-200 bg-gray-50 text-gray-500 drop-shadow-xs capitalize',
{
'border-yellow-200 bg-yellow-50 text-yellow-500': color === 'yellow',
'border-blue-200 bg-blue-50 text-blue-500': color === 'blue',
'border-green-200 bg-green-50 text-green-500': color === 'green',
'border-red-200 bg-red-50 text-red-500': color === 'red',
'border-purple-200 bg-purple-50 text-purple-500': color === 'purple',
'border-neutral-200 bg-neutral-50 text-neutral-500': color === 'gray',
},
className
)}
>
{content}
</div>
);
};
export default PillBadge;
+13
View File
@@ -48,6 +48,8 @@ export interface TableProps<TData extends object> {
sorting?: SortingState; sorting?: SortingState;
setSorting?: OnChangeFn<SortingState>; setSorting?: OnChangeFn<SortingState>;
manualSorting?: boolean; manualSorting?: boolean;
rowSelection?: Record<string, boolean>;
setRowSelection?: OnChangeFn<Record<string, boolean>>;
} }
const DUMMY_SKELETON_DATA = [{}, {}, {}, {}, {}]; const DUMMY_SKELETON_DATA = [{}, {}, {}, {}, {}];
@@ -86,6 +88,8 @@ const Table = <TData extends object>({
sorting, sorting,
setSorting, setSorting,
manualSorting = false, manualSorting = false,
rowSelection,
setRowSelection,
}: TableProps<TData>) => { }: TableProps<TData>) => {
const isServerSideTable = const isServerSideTable =
totalItems !== undefined && totalItems !== undefined &&
@@ -137,6 +141,15 @@ const Table = <TData extends object>({
}; };
} }
if (rowSelection && setRowSelection) {
tableOptions.onRowSelectionChange = setRowSelection;
tableOptions.state = {
...tableOptions.state,
rowSelection,
};
tableOptions.getRowId = (row) => (row as { id: string }).id;
}
const table = useReactTable(tableOptions); const table = useReactTable(tableOptions);
const { setPageSize } = table; const { setPageSize } = table;
+89
View File
@@ -0,0 +1,89 @@
'use client';
import { HTMLProps, useEffect, useRef } from 'react';
import { cn } from '@/lib/helper';
interface CheckboxInputProps extends HTMLProps<HTMLInputElement> {
name: string;
label?: string;
indeterminate?: boolean;
classNames?: {
wrapper?: string;
inputWrapper?: string;
checkbox?: string;
label?: string;
};
isError?: boolean;
isValid?: boolean;
errorMessage?: string;
}
const CheckboxInput = ({
indeterminate,
name,
label,
className,
classNames,
isValid,
isError,
errorMessage,
...rest
}: CheckboxInputProps) => {
const ref = useRef<HTMLInputElement>(null!);
useEffect(() => {
if (typeof indeterminate === 'boolean') {
ref.current.indeterminate = !rest.checked && indeterminate;
}
}, [ref, indeterminate]);
return (
<div
className={cn('flex flex-col items-center gap-1', classNames?.wrapper)}
>
<div
className={cn(
'flex flex-row justify-center items-center gap-2',
classNames?.inputWrapper
)}
>
<input
type='checkbox'
ref={ref}
id={name}
name={name}
className={cn(
'checkbox cursor-pointer',
{
'border-error': isError,
'border-success': isValid,
},
className,
classNames?.checkbox
)}
{...rest}
/>
{label && (
<label
htmlFor={name}
className={cn(
'text-inherit',
{
'text-error': isError,
'text-success': isValid,
},
classNames?.label
)}
>
{label}
</label>
)}
</div>
{errorMessage && <span className='text-error'>{errorMessage}</span>}
</div>
);
};
export default CheckboxInput;
@@ -1,6 +1,7 @@
'use client'; 'use client';
import Button from '@/components/Button'; import Button from '@/components/Button';
import CheckboxInput from '@/components/input/CheckboxInput';
import DebouncedTextInput from '@/components/input/DebouncedTextInput'; import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import SelectInput, { OptionType } from '@/components/input/SelectInput'; import SelectInput, { OptionType } from '@/components/input/SelectInput';
import { useModal } from '@/components/Modal'; import { useModal } from '@/components/Modal';
@@ -120,10 +121,15 @@ const ProjectFlockTable = () => {
periodFilter: 'period', periodFilter: 'period',
}, },
}); });
// State
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
const selectedRowIds = Object.keys(rowSelection)
.filter((id) => rowSelection[id])
.map((id) => parseInt(id));
const [locationSelectInputValue, setLocationSelectInputValue] = useState(''); const [locationSelectInputValue, setLocationSelectInputValue] = useState('');
const [areaSelectInputValue, setAreaSelectInputValue] = useState(''); const [areaSelectInputValue, setAreaSelectInputValue] = useState('');
const [kandangSelectInputValue, setKandangSelectInputValue] = useState(''); const [kandangSelectInputValue, setKandangSelectInputValue] = useState('');
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
@@ -132,6 +138,14 @@ const ProjectFlockTable = () => {
null null
); );
const [periodInputValue, setPeriodInputValue] = useState<number | null>(null); const [periodInputValue, setPeriodInputValue] = useState<number | null>(null);
const [sorting, setSorting] = useState<SortingState>([]);
const [selectedProjectFlock, setSelectedProjectFlock] =
useState<ProjectFlock>();
const deleteModal = useModal();
const confirmModal = useModal();
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [selectedFlocks, setSelectedFlocks] = useState<ProjectFlock[]>([]);
const [isApproveLoading, setIsApproveLoading] = useState(false);
// Fetch Data // Fetch Data
const { const {
@@ -193,16 +207,6 @@ const ProjectFlockTable = () => {
})) }))
: []; : [];
// State
const [sorting, setSorting] = useState<SortingState>([]);
const [selectedProjectFlock, setSelectedProjectFlock] =
useState<ProjectFlock>();
const deleteModal = useModal();
const confirmModal = useModal();
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [selectedIds, setSelectedIds] = useState<number[]>([]);
const [selectedFlocks, setSelectedFlocks] = useState<ProjectFlock[]>([]);
const [isApproveLoading, setIsApproveLoading] = useState(false);
// Handler // Handler
const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => { const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
const newVal = val as OptionType; const newVal = val as OptionType;
@@ -221,40 +225,6 @@ const ProjectFlockTable = () => {
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => { const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
updateFilter('search', e.target.value); updateFilter('search', e.target.value);
}; };
const handleSelectAll = (checked: boolean) => {
if (checked && isResponseSuccess(projectFlocks)) {
const allIds = projectFlocks.data
.filter((item) => item.approval.step_name === 'Pengajuan')
.map((item) => item.id);
setSelectedIds(allIds);
setSelectedFlocks(
projectFlocks.data.filter(
(item) => item.approval.step_name === 'Pengajuan'
)
);
} else {
setSelectedIds([]);
setSelectedFlocks([]);
}
};
const handleSelectRow = (id: number, checked: boolean) => {
if (!isResponseSuccess(projectFlocks)) return;
const targetFlock = projectFlocks.data.find((item) => item.id === id);
if (!targetFlock) return;
if (checked) {
setSelectedIds((prev) => [...prev, id]);
setSelectedFlocks((prev) => [...(prev || []), targetFlock]);
} else {
setSelectedIds((prev) => prev.filter((val) => val !== id));
setSelectedFlocks((prev) =>
(prev || []).filter((flock) => flock.id !== id)
);
}
};
const confirmationModalApproveClickHandler = async () => { const confirmationModalApproveClickHandler = async () => {
setIsApproveLoading(true); setIsApproveLoading(true);
@@ -265,7 +235,7 @@ const ProjectFlockTable = () => {
method: 'POST', method: 'POST',
payload: { payload: {
action: 'APPROVED', action: 'APPROVED',
approvable_ids: selectedFlocks.map((flock) => flock.id), approvable_ids: selectedRowIds.map((id) => id),
}, },
}); });
@@ -277,8 +247,7 @@ const ProjectFlockTable = () => {
toast.error(approveProjectFlockRes?.message as string); toast.error(approveProjectFlockRes?.message as string);
confirmModal.closeModal(); confirmModal.closeModal();
} }
setSelectedIds([]); setRowSelection({});
setSelectedFlocks([]);
refreshProjectFlocks(); refreshProjectFlocks();
setIsApproveLoading(false); setIsApproveLoading(false);
}; };
@@ -301,11 +270,9 @@ const ProjectFlockTable = () => {
variant='outline' variant='outline'
color='success' color='success'
onClick={() => { onClick={() => {
if (selectedIds.length > 0) { confirmModal.openModal();
confirmModal.openModal();
}
}} }}
disabled={!(selectedIds.length > 0)} disabled={selectedRowIds.length === 0}
className='w-full sm:w-fit' className='w-full sm:w-fit'
> >
<Icon icon='material-symbols:check' width={24} height={24} /> <Icon icon='material-symbols:check' width={24} height={24} />
@@ -400,33 +367,58 @@ const ProjectFlockTable = () => {
columns={[ columns={[
{ {
id: 'select', id: 'select',
header: () => { header: ({ table }) => {
const allSelected = const allRows = table.getRowModel().rows;
isResponseSuccess(projectFlocks) && const selectableRows = allRows.filter(
projectFlocks.data.length > 0 && (row) => row.original?.approval?.step_number == 1
selectedIds.length === projectFlocks.data.length;
return (
<input
type='checkbox'
className='checkbox checkbox-sm'
checked={allSelected}
onChange={(e) => handleSelectAll(e.target.checked)}
/>
); );
},
cell: (props) => { const allSelected = selectableRows.every((row) =>
const id = props.row.original.id; row.getIsSelected()
const isChecked = selectedIds.includes(id); ) && selectableRows.length != 0;
const someSelected =
selectableRows.some((row) => row.getIsSelected()) &&
!allSelected;
const toggleSelectableRows = () => {
const shouldSelect = !allSelected;
selectableRows.forEach((row) =>
row.toggleSelected(shouldSelect)
);
};
return ( return (
<input <div className='w-full flex flex-row justify-center'>
disabled={ <CheckboxInput
props.row.original.approval.step_name != 'Pengajuan' name='allRow'
checked={allSelected}
indeterminate={someSelected}
onChange={toggleSelectableRows}
disabled={
isResponseSuccess(projectFlocks) &&
projectFlocks?.data?.filter(
(flock) => flock.approval.step_number == 1
).length == 0
}
/>
</div>
);
},
cell: ({ row }) => {
return (
<CheckboxInput
name='row'
checked={
row.getIsSelected() &&
row.original.approval.step_number == 1
} }
type='checkbox' disabled={
className='checkbox checkbox-sm' !row.getCanSelect() ||
checked={isChecked} row.original.approval.step_number != 1
onChange={(e) => handleSelectRow(id, e.target.checked)} }
indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()}
/> />
); );
}, },
@@ -541,6 +533,8 @@ const ProjectFlockTable = () => {
isLoading={isLoading} isLoading={isLoading}
sorting={sorting} sorting={sorting}
setSorting={setSorting} setSorting={setSorting}
rowSelection={rowSelection}
setRowSelection={setRowSelection}
className={{ className={{
containerClassName: cn({ containerClassName: cn({
'mb-20': 'mb-20':
@@ -578,18 +572,19 @@ const ProjectFlockTable = () => {
<ConfirmationModal <ConfirmationModal
ref={confirmModal.ref} ref={confirmModal.ref}
type='success' type='success'
text={ text={`Apakah anda yakin ingin reject data transfer ke laying ini (${selectedRowIds.length} data)?`}
selectedFlocks.length > 0 // text={
? `Apakah anda yakin ingin approve Project Flock berikut? (${selectedFlocks // selectedFlocks.length > 0
.map( // ? `Apakah anda yakin ingin approve Project Flock berikut? (${selectedFlocks
(flock) => // .map(
`${flock.flock?.name ?? '(Tanpa nama)'} - ${ // (flock) =>
flock.area?.name ?? '-' // `${flock.flock?.name ?? '(Tanpa nama)'} - ${
}` // flock.area?.name ?? '-'
) // }`
.join(', ')})` // )
: 'Tidak ada Project Flock yang dipilih.' // .join(', ')})`
} // : 'Tidak ada Project Flock yang dipilih.'
// }
secondaryButton={{ secondaryButton={{
text: 'Tidak', text: 'Tidak',
}} }}
@@ -14,7 +14,7 @@ import { Icon } from '@iconify/react';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import useSWR from 'swr'; import useSWR, { KeyedMutator } from 'swr';
import { import {
ProjectFlockFormSchema, ProjectFlockFormSchema,
ProjectFlockFormValues, ProjectFlockFormValues,
@@ -35,15 +35,20 @@ import { BaseApiResponse } from '@/types/api/api-general';
import { FLOCK_CATEGORY_OPTIONS } from '@/config/constant'; import { FLOCK_CATEGORY_OPTIONS } from '@/config/constant';
import { useModal } from '@/components/Modal'; import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModal from '@/components/modal/ConfirmationModal';
import ProjectFlockKandangTable from './ProjectFlockKandangTable';
interface ProjectFlockFormProps { interface ProjectFlockFormProps {
formType?: 'add' | 'edit' | 'detail'; formType?: 'add' | 'edit' | 'detail';
initialValues?: ProjectFlock; initialValues?: ProjectFlock;
refreshProjectFlocks?: KeyedMutator<
BaseApiResponse<ProjectFlock> | undefined
>;
} }
const ProjectFlockForm = ({ const ProjectFlockForm = ({
formType = 'add', formType = 'add',
initialValues, initialValues,
refreshProjectFlocks,
}: ProjectFlockFormProps) => { }: ProjectFlockFormProps) => {
// State // State
const router = useRouter(); const router = useRouter();
@@ -71,18 +76,34 @@ const ProjectFlockForm = ({
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [isApproveLoading, setIsApproveLoading] = useState(false); const [isApproveLoading, setIsApproveLoading] = useState(false);
const [isApprovedDisabled, setIsApprovedDisabled] = useState(initialValues?.approval.step_name == 'Pengajuan' ? false : true); const [isApprovedDisabled, setIsApprovedDisabled] = useState(
const [isRejectedDisabled, setIsRejectedDisabled] = useState(!isApprovedDisabled); initialValues?.approval.step_name == 'Pengajuan' ? false : true
const [approvalAction, setApprovalAction] = useState<'APPROVED' | 'REJECTED'>(!isApprovedDisabled ? 'APPROVED' : 'REJECTED'); );
const [isRejectedDisabled, setIsRejectedDisabled] = useState(
!isApprovedDisabled
);
const [approvalAction, setApprovalAction] = useState<'APPROVED' | 'REJECTED'>(
!isApprovedDisabled ? 'APPROVED' : 'REJECTED'
);
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>(
() =>
Object.fromEntries(
(initialValues?.kandangs ?? []).map((k: Kandang) => [
k.id.toString(),
true,
])
)
);
useEffect(() => { useEffect(() => {
if (initialValues?.approval?.step_name) { if (initialValues?.approval?.step_name) {
const approvedDisabled = initialValues.approval.step_name !== 'Pengajuan'; const approvedDisabled = initialValues.approval.step_name !== 'Pengajuan';
setIsApprovedDisabled(approvedDisabled); setIsApprovedDisabled(approvedDisabled);
setIsRejectedDisabled(!approvedDisabled); setIsRejectedDisabled(!approvedDisabled);
setApprovalAction(!approvedDisabled ? 'APPROVED' : 'REJECTED'); setApprovalAction(!approvedDisabled ? 'APPROVED' : 'REJECTED');
} }
}, [initialValues]); }, [initialValues]);
// Fetch Data // Fetch Data
const flockUrl = `${FlockApi.basePath}?${new URLSearchParams({ const flockUrl = `${FlockApi.basePath}?${new URLSearchParams({
@@ -122,7 +143,7 @@ const ProjectFlockForm = ({
search: '', search: '',
location_id: selectedLocation == '' ? '0' : selectedLocation, location_id: selectedLocation == '' ? '0' : selectedLocation,
}).toString()}`; }).toString()}`;
const { data: kandang, isLoading: isLoadingKandang } = useSWR( const { data: kandang, isLoading: isLoadingKandang, mutate: refreshKandang} = useSWR(
kandangUrl, kandangUrl,
KandangApi.getAllFetcher KandangApi.getAllFetcher
); );
@@ -180,6 +201,20 @@ const ProjectFlockForm = ({
} }
} }
}, [kandang]); }, [kandang]);
useEffect(() => {
if (initialValues?.kandangs) {
refreshKandang();
setOpenSelectKandangs(true);
const newRowSelection = Object.fromEntries(
initialValues.kandangs.map((k: Kandang) => [
k.id.toString(),
true,
])
);
setRowSelection(newRowSelection);
}
}, [initialValues, refreshKandang]);
// Options Handler // Options Handler
const areaChangeHandler = (val: OptionType | OptionType[] | null) => { const areaChangeHandler = (val: OptionType | OptionType[] | null) => {
@@ -224,38 +259,6 @@ const ProjectFlockForm = ({
formik.setFieldTouched('category', true); formik.setFieldTouched('category', true);
}; };
const kandangChangeHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
const { value, checked } = event.target;
if (checked) {
formik.setFieldValue(
'kandang_ids',
formik.values.kandang_ids.concat(parseInt(value))
);
} else {
formik.setFieldValue(
'kandang_ids',
formik.values.kandang_ids.filter((id) => id !== parseInt(value))
);
}
};
const kandangCheckAll = (event: React.ChangeEvent<HTMLInputElement>) => {
const { checked } = event.target;
if (checked) {
formik.setFieldValue(
'kandang_ids',
optionsKandang
.filter(
(kandang) =>
kandang.status === 'NON_ACTIVE' ||
formik.values.kandang_ids.includes(kandang.id)
)
.map((kandang) => kandang.id)
);
} else {
formik.setFieldValue('kandang_ids', []);
}
};
// Submit Handler // Submit Handler
const createProjectFlockHandler = async ( const createProjectFlockHandler = async (
payload: CreateProjectFlockPayload payload: CreateProjectFlockPayload
@@ -418,6 +421,16 @@ const ProjectFlockForm = ({
} }
}, [periodFlocks]); }, [periodFlocks]);
useEffect(() => {
const selectedRowIds = Object.keys(rowSelection)
.filter((id) => rowSelection[id])
.map((id) => parseInt(id));
formikSetValues({
...formik.values,
kandang_ids: selectedRowIds,
});
}, [rowSelection, formikSetValues]);
// Actions handler // Actions handler
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
setIsDeleteLoading(true); setIsDeleteLoading(true);
@@ -454,14 +467,17 @@ const ProjectFlockForm = ({
}); });
if (isResponseSuccess(approveProjectFlockRes)) { if (isResponseSuccess(approveProjectFlockRes)) {
if(action == 'APPROVED'){ if (refreshProjectFlocks) {
setIsApprovedDisabled(true); await refreshProjectFlocks();
setIsRejectedDisabled(false);
}
if(action == 'REJECTED'){
setIsRejectedDisabled(true);
setIsApprovedDisabled(false);
} }
// if (action == 'APPROVED') {
// setIsApprovedDisabled(true);
// setIsRejectedDisabled(false);
// }
// if (action == 'REJECTED') {
// setIsRejectedDisabled(true);
// setIsApprovedDisabled(false);
// }
toast.success(approveProjectFlockRes.message as string); toast.success(approveProjectFlockRes.message as string);
} }
if (isResponseError(approveProjectFlockRes)) { if (isResponseError(approveProjectFlockRes)) {
@@ -520,10 +536,7 @@ const ProjectFlockForm = ({
confirmModal.openModal(); confirmModal.openModal();
} }
}} }}
disabled={ disabled={!initialValues?.id || isApprovedDisabled}
!initialValues?.id ||
isApprovedDisabled
}
className='w-full sm:w-fit' className='w-full sm:w-fit'
> >
<Icon icon='material-symbols:check' width={24} height={24} /> <Icon icon='material-symbols:check' width={24} height={24} />
@@ -538,10 +551,7 @@ const ProjectFlockForm = ({
confirmModal.openModal(); confirmModal.openModal();
} }
}} }}
disabled={ disabled={!initialValues?.id || isRejectedDisabled}
!initialValues?.id ||
isRejectedDisabled
}
className='w-full sm:w-fit' className='w-full sm:w-fit'
> >
<Icon icon='mdi:times' width={24} height={24} /> <Icon icon='mdi:times' width={24} height={24} />
@@ -682,100 +692,13 @@ const ProjectFlockForm = ({
{isLoadingKandang && ( {isLoadingKandang && (
<span className='loading loading-dots loading-xl'></span> <span className='loading loading-dots loading-xl'></span>
)} )}
<table className='table'> <ProjectFlockKandangTable
{/* head */} listKandang={optionsKandang}
<thead> rowSelection={rowSelection}
<tr> setRowSelection={setRowSelection}
<th> selectedIds={formik.values.kandang_ids}
<label> formType={formType}
<input />
type='checkbox'
checked={
optionsKandang
.filter(
(k) =>
k.status === 'NON_ACTIVE' ||
formik.values.kandang_ids.includes(k.id)
)
.every((k) =>
formik.values.kandang_ids.includes(k.id)
) &&
optionsKandang.filter(
(k) =>
k.status === 'NON_ACTIVE' ||
formik.values.kandang_ids.includes(k.id)
).length > 0
}
className='checkbox transition-none'
disabled={
formType === 'detail' ||
optionsKandang.filter(
(k) => k.status === 'NON_ACTIVE'
).length == 0
}
onChange={
formType === 'detail'
? () => {}
: kandangCheckAll
}
/>
</label>
</th>
<th>Kandang</th>
{/* <th>Status</th> */}
<th>Penanggung Jawab</th>
</tr>
</thead>
<tbody>
{/* rows */}
{selectedLocation != '' &&
optionsKandang.map((kandang) => (
<tr key={kandang.id}>
<th>
<label>
<input
value={kandang.id}
type='checkbox'
className='checkbox transition-none'
checked={formik.values.kandang_ids.includes(
kandang.id
)}
onChange={
formType === 'detail'
? () => {}
: kandangChangeHandler
}
disabled={
formType === 'detail' ||
kandang.status != 'NON_ACTIVE'
}
/>
</label>
</th>
<td>{kandang.name}</td>
{/* <td>{kandang.status}</td> */}
<td>{kandang.pic?.name}</td>
</tr>
))}
{selectedLocation == '' && (
<tr>
<td colSpan={3} className='text-center text-muted'>
Data tidak tersedia
</td>
</tr>
)}
</tbody>
{/* foot */}
{selectedLocation != '' && (
<tfoot>
<tr>
<th></th>
<th>Kandang</th>
<th>Penanggung Jawab</th>
</tr>
</tfoot>
)}
</table>
</div> </div>
</Collapse> </Collapse>
</div> </div>
@@ -845,7 +768,11 @@ const ProjectFlockForm = ({
<ConfirmationModal <ConfirmationModal
ref={confirmModal.ref} ref={confirmModal.ref}
type={approvalAction == 'APPROVED' ? 'success' : 'error'} type={approvalAction == 'APPROVED' ? 'success' : 'error'}
text={`Apakah anda yakin ingin ${approvalAction == 'APPROVED' ? 'approve' : 'reject'} Project Flock berikut? (${initialValues?.flock?.name} - ${initialValues?.area?.name})?`} text={`Apakah anda yakin ingin ${
approvalAction == 'APPROVED' ? 'approve' : 'reject'
} Project Flock berikut? (${initialValues?.flock?.name} - ${
initialValues?.area?.name
})?`}
secondaryButton={{ secondaryButton={{
text: 'Tidak', text: 'Tidak',
}} }}
@@ -0,0 +1,146 @@
'use client';
import CheckboxInput from '@/components/input/CheckboxInput';
import PillBadge from '@/components/PillBadge';
import Table from '@/components/Table';
import { cn } from '@/lib/helper';
import { Kandang } from '@/types/api/master-data/kandang';
import { OnChangeFn } from '@tanstack/react-table';
const ProjectFlockKandangTable = ({
listKandang,
rowSelection,
setRowSelection,
selectedIds,
formType = 'add',
}: {
listKandang: Kandang[];
rowSelection: Record<string, boolean>;
setRowSelection: OnChangeFn<Record<string, boolean>>;
selectedIds: (number | undefined)[];
formType: 'add' | 'edit' | 'detail';
}) => {
console.log('selectedIds');
console.log(selectedIds);
return (
<Table<Kandang>
data={listKandang}
columns={[
{
id: 'select',
header: ({ table }) => {
const allRows = table.getRowModel().rows;
const selectableRows = allRows.filter(
(row) =>
row.original.status == 'NON_ACTIVE' ||
row.original.status == 'PENGAJUAN'
);
const allSelected =
selectableRows.every((row) => row.getIsSelected()) &&
selectableRows.length != 0 && formType != 'detail';
const someSelected =
selectableRows.some((row) => row.getIsSelected()) && !allSelected && formType != 'detail';
const toggleSelectableRows = () => {
const shouldSelect = !allSelected;
selectableRows.forEach((row) => row.toggleSelected(shouldSelect));
};
return (
<div className='w-full flex flex-row justify-center'>
<CheckboxInput
name='allRow'
checked={allSelected}
indeterminate={someSelected}
onChange={toggleSelectableRows}
disabled={
listKandang.filter(
(kandang) =>
kandang.status == 'NON_ACTIVE' ||
kandang.status == 'PENGAJUAN'
).length == 0 || formType == 'detail'
}
/>
</div>
);
},
cell: ({ row }) => {
return (
<CheckboxInput
name='row'
checked={
(row.getIsSelected() &&
(row.original.status == 'NON_ACTIVE' ||
row.original.status == 'PENGAJUAN')) ||
selectedIds.includes(row.original.id)
}
disabled={
!row.getCanSelect() ||
(row.original.status != 'NON_ACTIVE' &&
row.original.status != 'PENGAJUAN') ||
formType == 'detail'
}
indeterminate={row.getIsSomeSelected()}
onChange={row.getToggleSelectedHandler()}
/>
);
},
},
{
accessorFn: (row) => row.name,
header: 'Kandang',
},
{
accessorFn: (row) => row.status,
header: 'Status',
cell: (props) => {
return (
<PillBadge
color={(() => {
switch (props.row.original.status) {
case 'ACTIVE':
return 'red';
case 'PENGAJUAN':
return 'green';
case 'NON_ACTIVE':
return 'blue';
default:
return 'gray';
}
})()}
content={props.row.original.status
.toLowerCase()
.replace(/_/g, ' ')
.replace(/\b\w/g, (char) => char.toUpperCase())}
/>
);
},
},
{
accessorFn: (row) => row.pic?.name,
header: 'Penanggung Jawab',
},
]}
className={{
containerClassName: cn({
'mb-20': listKandang?.length === 0,
}),
tableWrapperClassName: 'overflow-x-auto min-h-full!',
tableClassName: 'font-inter w-full table-auto min-h-full!',
headerRowClassName: 'border-b border-b-gray-200',
headerColumnClassName:
'px-6 py-3 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end',
bodyRowClassName: 'border-b border-b-gray-200',
bodyColumnClassName:
'px-6 py-3 last:flex last:flex-row last:justify-end',
paginationClassName: 'hidden',
}}
rowSelection={rowSelection}
setRowSelection={setRowSelection}
/>
);
};
export default ProjectFlockKandangTable;