feat(FE-86-88): Adding reject button and integrate with approval api

This commit is contained in:
randy-ar
2025-10-23 20:23:25 +07:00
parent 8a467c2d65
commit 51bce1a2c7
11 changed files with 298 additions and 219 deletions
@@ -13,7 +13,7 @@ const ProjectFlockDetail = () => {
const projectFlockId = searchParams.get("projectFlockId"); const projectFlockId = searchParams.get("projectFlockId");
const { data: projectFlock, isLoading: isLoadingCostumer } = useSWR( const { data: projectFlock, isLoading: isLoadingProjectFlock } = useSWR(
projectFlockId, projectFlockId,
(id: number) => ProjectFlockApi.getSingle(id) (id: number) => ProjectFlockApi.getSingle(id)
); );
@@ -28,15 +28,15 @@ const ProjectFlockDetail = () => {
); );
} }
if(!isLoadingCostumer && (!projectFlock || isResponseError(projectFlock))){ if(!isLoadingProjectFlock && (!projectFlock || isResponseError(projectFlock))){
router.replace("/404"); 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">
{isLoadingCostumer && <span className="loading loading-spinner loading-xl" />} {isLoadingProjectFlock && <span className="loading loading-spinner loading-xl" />}
{!isLoadingCostumer && isResponseSuccess(projectFlock) && ( {!isLoadingProjectFlock && isResponseSuccess(projectFlock) && (
<ProjectFlockForm formType="detail" initialValues={projectFlock.data} /> <ProjectFlockForm formType="detail" initialValues={projectFlock.data} />
)} )}
</div> </div>
+1 -1
View File
@@ -88,7 +88,7 @@ const DateInput = ({
<div <div
className={cn( className={cn(
'input h-12 px-4 py-2 text-base font-normal leading-6 w-full rounded-lg! outline-none! transition-all duration-200 flex items-center', 'input h-12 px-4 py-2 text-base font-normal leading-6 w-full rounded outline-none! transition-all duration-200 flex items-center',
{ {
'border-error': isError, 'border-error': isError,
'border-success!': isValid, 'border-success!': isValid,
+1 -1
View File
@@ -70,7 +70,7 @@ const FileInput = ({
onBlur={onBlur} onBlur={onBlur}
disabled={disabled} disabled={disabled}
className={cn( className={cn(
'grow file-input w-full h-12 rounded-lg!', 'grow file-input w-full h-12 rounded',
className?.input className?.input
)} )}
readOnly={readOnly} readOnly={readOnly}
+4 -4
View File
@@ -160,7 +160,7 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
classNames={{ classNames={{
control: ({ isFocused, isDisabled }) => control: ({ isFocused, isDisabled }) =>
cn( cn(
'w-full min-h-12! rounded-lg! border bg-white transition-shadow cursor-pointer!', 'w-full min-h-12! rounded border bg-white transition-shadow cursor-pointer!',
{ {
'border-red-500! ring-2 ring-red-200': isError, 'border-red-500! ring-2 ring-red-200': isError,
'border-indigo-500 ring-2 ring-indigo-200': isFocused, 'border-indigo-500 ring-2 ring-indigo-200': isFocused,
@@ -176,7 +176,7 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
input: () => cn('text-gray-900'), input: () => cn('text-gray-900'),
indicatorsContainer: () => cn('flex items-center gap-1 pr-2'), indicatorsContainer: () => cn('flex items-center gap-1 pr-2'),
dropdownIndicator: ({ isFocused }) => dropdownIndicator: ({ isFocused }) =>
cn('p-1 rounded-md hover:bg-gray-100', { cn('p-1 rounded hover:bg-gray-100', {
'text-gray-900': isFocused, 'text-gray-900': isFocused,
'text-gray-500': !isFocused, 'text-gray-500': !isFocused,
'text-error!': isError, 'text-error!': isError,
@@ -185,7 +185,7 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
cn('border border-gray-200 rounded-lg bg-white shadow-lg!'), cn('border border-gray-200 rounded-lg bg-white shadow-lg!'),
menuList: () => cn('p-2! max-h-60 overflow-auto'), menuList: () => cn('p-2! max-h-60 overflow-auto'),
option: ({ isFocused, isSelected }) => option: ({ isFocused, isSelected }) =>
cn('mt-1 px-3 py-2 rounded-md cursor-pointer!', { cn('mt-1 px-3 py-2 rounded cursor-pointer!', {
'bg-indigo-600 text-white': isFocused, 'bg-indigo-600 text-white': isFocused,
'bg-blue-500!': isSelected, 'bg-blue-500!': isSelected,
'text-gray-700': !isFocused && !isSelected, 'text-gray-700': !isFocused && !isSelected,
@@ -193,7 +193,7 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
multiValue: ({ getValue, index }) => { multiValue: ({ getValue, index }) => {
const selectedValues = getValue() as T[]; const selectedValues = getValue() as T[];
return cn( return cn(
'bg-indigo-50 rounded-md py-0.5 pl-2 pr-1 flex items-center gap-1!', 'bg-indigo-50 rounded py-0.5 pl-2 pr-1 flex items-center gap-1!',
selectedValues[index]?.className selectedValues[index]?.className
); );
}, },
+1 -1
View File
@@ -87,7 +87,7 @@ const TextArea = ({
<textarea <textarea
className={cn( className={cn(
'input h-auto px-4 py-2 text-base font-normal leading-6 w-full rounded-lg! outline-none! transition-all', 'input h-auto px-4 py-2 text-base font-normal leading-6 w-full rounded outline-none! transition-all',
{ {
'border-error': isError, 'border-error': isError,
'border-success!': isValid, 'border-success!': isValid,
+1 -1
View File
@@ -87,7 +87,7 @@ const TextInput = ({
<div <div
className={cn( className={cn(
'input h-12 px-4 py-2 text-base font-normal leading-6 w-full rounded-lg! outline-none! transition-all duration-200', 'input h-12 px-4 py-2 text-base font-normal leading-6 w-full rounded outline-none! transition-all duration-200',
{ {
'border-error': isError, 'border-error': isError,
'border-success!': isValid, 'border-success!': isValid,
@@ -57,13 +57,6 @@ const ChickinTable = () => {
`${ChickinApi.basePath}${getTableFilterQueryString()}`, `${ChickinApi.basePath}${getTableFilterQueryString()}`,
ChickinApi.getAllFetcher ChickinApi.getAllFetcher
); );
const {
data: projectFlocks,
isLoading: isLoadingProjectFlocks,
} = useSWR(
`${ProjectFlockApi.basePath}${getTableFilterQueryString()}`,
ProjectFlockApi.getAllFetcher
);
const searchChangeHandler = (event: React.ChangeEvent<HTMLInputElement>) => { const searchChangeHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
updateFilter('search', event.target.value); updateFilter('search', event.target.value);
@@ -16,13 +16,12 @@ import { ProjectFlockApi } from '@/services/api/production';
import { useTableFilter } from '@/services/hooks/useTableFilter'; import { useTableFilter } from '@/services/hooks/useTableFilter';
import { BaseApiResponse } from '@/types/api/api-general'; import { BaseApiResponse } from '@/types/api/api-general';
import { Kandang } from '@/types/api/master-data/kandang'; import { Kandang } from '@/types/api/master-data/kandang';
import { ProjectFlock } from '@/types/api/production/project-flock';
import { Icon } from '@iconify/react';
import { import {
CellContext, ProjectFlockApprovalPayload,
ColumnDef, ProjectFlock,
SortingState, } from '@/types/api/production/project-flock';
} from '@tanstack/react-table'; import { Icon } from '@iconify/react';
import { CellContext, SortingState } from '@tanstack/react-table';
import { ChangeEventHandler, useState } from 'react'; import { ChangeEventHandler, useState } from 'react';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import useSWR from 'swr'; import useSWR from 'swr';
@@ -56,24 +55,28 @@ const RowOptionsMenu = ({
<Icon icon='mdi:eye-outline' width={16} height={16} /> <Icon icon='mdi:eye-outline' width={16} height={16} />
Detail Detail
</Button> </Button>
<Button {props.row.original.approval.step_name === 'Aktif' && (
href={`/production/chickin/add?projectFlockId=${props.row.original.id}`} <Button
variant='ghost' href={`/production/chickin/add?projectFlockId=${props.row.original.id}`}
color='success' variant='ghost'
className='justify-start text-sm' color='success'
> className='justify-start text-sm'
<Icon icon='mdi:home-import-outline' width={16} height={16} /> >
Chickin <Icon icon='mdi:home-import-outline' width={16} height={16} />
</Button> Chickin
{/* <Button </Button>
href={`/production/project-flock/detail/edit?projectFlockId=${props.row.original.id}`} )}
variant='ghost' {props.row.original.approval.step_name === 'Pengajuan' && (
color='warning' <Button
className='justify-start text-sm' href={`/production/project-flock/detail/edit?projectFlockId=${props.row.original.id}`}
> variant='ghost'
<Icon icon='mdi:pencil-outline' width={16} height={16} /> color='warning'
Edit className='justify-start text-sm'
</Button> */} >
<Icon icon='mdi:pencil-outline' width={16} height={16} />
Edit
</Button>
)}
<Button <Button
onClick={deleteClickHandler} onClick={deleteClickHandler}
variant='ghost' variant='ghost'
@@ -144,20 +147,20 @@ const ProjectFlockTable = () => {
search: areaSelectInputValue, search: areaSelectInputValue,
limit: '100', limit: '100',
}).toString()}`; }).toString()}`;
const { const { data: areas, isLoading: isLoadingAreas } = useSWR(
data: areas, areaUrl,
isLoading: isLoadingAreas, AreaApi.getAllFetcher
} = useSWR(areaUrl, AreaApi.getAllFetcher); );
const locationUrl = `${LocationApi.basePath}?${new URLSearchParams({ const locationUrl = `${LocationApi.basePath}?${new URLSearchParams({
search: locationSelectInputValue, search: locationSelectInputValue,
area_id: selectedArea != null ? selectedArea.value.toString() : '', area_id: selectedArea != null ? selectedArea.value.toString() : '',
limit: '100', limit: '100',
}).toString()}`; }).toString()}`;
const { const { data: locations, isLoading: isLoadingLocations } = useSWR(
data: locations, locationUrl,
isLoading: isLoadingLocations, LocationApi.getAllFetcher
} = useSWR(locationUrl, LocationApi.getAllFetcher); );
const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({ const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({
search: kandangSelectInputValue, search: kandangSelectInputValue,
@@ -165,10 +168,10 @@ const ProjectFlockTable = () => {
selectedLocation != null ? selectedLocation.value.toString() : '', selectedLocation != null ? selectedLocation.value.toString() : '',
limit: '100', limit: '100',
}).toString()}`; }).toString()}`;
const { const { data: kandangs, isLoading: isLoadingKandang } = useSWR(
data: kandangs, kandangUrl,
isLoading: isLoadingKandang, KandangApi.getAllFetcher
} = useSWR(kandangUrl, KandangApi.getAllFetcher); );
// Data to Options Mapping // Data to Options Mapping
const optionsArea = isResponseSuccess(areas) const optionsArea = isResponseSuccess(areas)
@@ -200,129 +203,6 @@ const ProjectFlockTable = () => {
const [selectedIds, setSelectedIds] = useState<number[]>([]); const [selectedIds, setSelectedIds] = useState<number[]>([]);
const [selectedFlocks, setSelectedFlocks] = useState<ProjectFlock[]>([]); const [selectedFlocks, setSelectedFlocks] = useState<ProjectFlock[]>([]);
const [isApproveLoading, setIsApproveLoading] = useState(false); const [isApproveLoading, setIsApproveLoading] = useState(false);
// Columns
const projectFlocksColumns: ColumnDef<ProjectFlock>[] = [
{
id: 'select',
header: () => {
const allSelected =
isResponseSuccess(projectFlocks) &&
projectFlocks.data.length > 0 &&
selectedIds.length === projectFlocks.data.length;
return (
<input
type='checkbox'
className='checkbox checkbox-sm'
checked={allSelected}
onChange={(e) => handleSelectAll(e.target.checked)}
/>
);
},
cell: (props) => {
const id = props.row.original.id;
const isChecked = selectedIds.includes(id);
return (
<input
type='checkbox'
className='checkbox checkbox-sm'
checked={isChecked}
onChange={(e) => handleSelectRow(id, e.target.checked)}
/>
);
},
},
{
accessorKey: 'flock.name',
header: 'Flock',
},
{
accessorKey: 'area.name',
header: 'Area',
},
{
accessorKey: 'location.name',
header: 'Lokasi',
},
{
accessorKey: 'fcr.name',
header: 'FCR',
},
{
accessorKey: 'category',
header: 'Kategori',
},
{
header: 'Kandang',
cell: (props) => {
const kandang = props.row.original.kandangs;
if (kandang) {
const kandangNames = kandang.map((k: Kandang) => k.name);
return (
<div>
{kandangNames.length > 0 ? kandangNames.join(', ') : 'Tidak ada'}
</div>
);
} else {
return '-';
}
},
},
{
accessorKey: 'period',
header: 'Periode',
},
{
accessorKey: 'created_at',
header: 'Dibuat pada',
cell: (props) =>
new Date(props.row.original.created_at).toLocaleDateString(),
},
{
header: 'Aksi',
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 deleteClickHandler = () => {
setSelectedProjectFlock(props.row.original);
deleteModal.openModal();
};
return (
<>
{currentPageSize > 2 && (
<RowDropdownOptions isLast2Rows={isLast2Rows}>
<RowOptionsMenu
type='dropdown'
props={props}
deleteClickHandler={deleteClickHandler}
/>
</RowDropdownOptions>
)}
{currentPageSize <= 2 && (
<RowCollapseOptions>
<RowOptionsMenu
type='dropdown'
props={props}
deleteClickHandler={deleteClickHandler}
/>
</RowCollapseOptions>
)}
</>
);
},
},
];
// Handler // Handler
const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => { const pageSizeChangeHandler = (val: OptionType | OptionType[] | null) => {
const newVal = val as OptionType; const newVal = val as OptionType;
@@ -343,9 +223,15 @@ const ProjectFlockTable = () => {
}; };
const handleSelectAll = (checked: boolean) => { const handleSelectAll = (checked: boolean) => {
if (checked && isResponseSuccess(projectFlocks)) { if (checked && isResponseSuccess(projectFlocks)) {
const allIds = projectFlocks.data.map((item) => item.id); const allIds = projectFlocks.data
.filter((item) => item.approval.step_name === 'Pengajuan')
.map((item) => item.id);
setSelectedIds(allIds); setSelectedIds(allIds);
setSelectedFlocks(projectFlocks.data); setSelectedFlocks(
projectFlocks.data.filter(
(item) => item.approval.step_name === 'Pengajuan'
)
);
} else { } else {
setSelectedIds([]); setSelectedIds([]);
setSelectedFlocks([]); setSelectedFlocks([]);
@@ -374,12 +260,12 @@ const ProjectFlockTable = () => {
setIsApproveLoading(true); setIsApproveLoading(true);
const approveProjectFlockRes = await ProjectFlockApi.customRequest< const approveProjectFlockRes = await ProjectFlockApi.customRequest<
BaseApiResponse<ProjectFlock>, BaseApiResponse<ProjectFlock>,
'POST' ProjectFlockApprovalPayload
>(`/approve`, { >(`/approvals`, {
method: 'POST', method: 'POST',
payload: 'POST', payload: {
params: { action: 'APPROVED',
ids: selectedFlocks.map((flock) => flock.id).join(','), approvable_ids: selectedFlocks.map((flock) => flock.id),
}, },
}); });
@@ -391,6 +277,9 @@ const ProjectFlockTable = () => {
toast.error(approveProjectFlockRes?.message as string); toast.error(approveProjectFlockRes?.message as string);
confirmModal.closeModal(); confirmModal.closeModal();
} }
setSelectedIds([]);
setSelectedFlocks([]);
refreshProjectFlocks();
setIsApproveLoading(false); setIsApproveLoading(false);
}; };
@@ -508,7 +397,137 @@ const ProjectFlockTable = () => {
<Table<ProjectFlock> <Table<ProjectFlock>
data={isResponseSuccess(projectFlocks) ? projectFlocks?.data : []} data={isResponseSuccess(projectFlocks) ? projectFlocks?.data : []}
columns={projectFlocksColumns} columns={[
{
id: 'select',
header: () => {
const allSelected =
isResponseSuccess(projectFlocks) &&
projectFlocks.data.length > 0 &&
selectedIds.length === projectFlocks.data.length;
return (
<input
type='checkbox'
className='checkbox checkbox-sm'
checked={allSelected}
onChange={(e) => handleSelectAll(e.target.checked)}
/>
);
},
cell: (props) => {
const id = props.row.original.id;
const isChecked = selectedIds.includes(id);
return (
<input
disabled={
props.row.original.approval.step_name != 'Pengajuan'
}
type='checkbox'
className='checkbox checkbox-sm'
checked={isChecked}
onChange={(e) => handleSelectRow(id, e.target.checked)}
/>
);
},
},
{
accessorKey: 'flock.name',
header: 'Flock',
},
{
accessorKey: 'area.name',
header: 'Area',
},
{
accessorKey: 'location.name',
header: 'Lokasi',
},
{
accessorKey: 'fcr.name',
header: 'FCR',
},
{
accessorKey: 'category',
header: 'Kategori',
},
{
accessorKey: 'approval.step_name',
header: 'Status',
},
{
header: 'Kandang',
cell: (props) => {
const kandang = props.row.original.kandangs;
if (kandang) {
const kandangNames = kandang.map((k: Kandang) => k.name);
return (
<div>
{kandangNames.length > 0
? kandangNames.join(', ')
: 'Tidak ada'}
</div>
);
} else {
return '-';
}
},
},
{
accessorKey: 'period',
header: 'Periode',
},
{
accessorKey: 'created_at',
header: 'Dibuat pada',
cell: (props) =>
new Date(props.row.original.created_at).toLocaleDateString(),
},
{
header: 'Aksi',
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 deleteClickHandler = () => {
setSelectedProjectFlock(props.row.original);
deleteModal.openModal();
};
return (
<>
{currentPageSize > 2 && (
<RowDropdownOptions isLast2Rows={isLast2Rows}>
<RowOptionsMenu
type='dropdown'
props={props}
deleteClickHandler={deleteClickHandler}
/>
</RowDropdownOptions>
)}
{currentPageSize <= 2 && (
<RowCollapseOptions>
<RowOptionsMenu
type='dropdown'
props={props}
deleteClickHandler={deleteClickHandler}
/>
</RowCollapseOptions>
)}
</>
);
},
},
]}
pageSize={tableFilterState.pageSize} pageSize={tableFilterState.pageSize}
page={ page={
isResponseSuccess(projectFlocks) ? projectFlocks?.meta?.page : 0 isResponseSuccess(projectFlocks) ? projectFlocks?.meta?.page : 0
@@ -21,6 +21,7 @@ import {
UpdateProjectFlockFormSchema, UpdateProjectFlockFormSchema,
} from '@/components/pages/production/project-flock/form/ProjectFlockForm.schema'; } from '@/components/pages/production/project-flock/form/ProjectFlockForm.schema';
import { import {
ProjectFlockApprovalPayload,
CreateProjectFlockPayload, CreateProjectFlockPayload,
PeriodFlock, PeriodFlock,
ProjectFlock, ProjectFlock,
@@ -70,6 +71,18 @@ 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 [isRejectedDisabled, setIsRejectedDisabled] = useState(!isApprovedDisabled);
const [approvalAction, setApprovalAction] = useState<'APPROVED' | 'REJECTED'>(!isApprovedDisabled ? 'APPROVED' : 'REJECTED');
useEffect(() => {
if (initialValues?.approval?.step_name) {
const approvedDisabled = initialValues.approval.step_name !== 'Pengajuan';
setIsApprovedDisabled(approvedDisabled);
setIsRejectedDisabled(!approvedDisabled);
setApprovalAction(!approvedDisabled ? 'APPROVED' : 'REJECTED');
}
}, [initialValues]);
// Fetch Data // Fetch Data
const flockUrl = `${FlockApi.basePath}?${new URLSearchParams({ const flockUrl = `${FlockApi.basePath}?${new URLSearchParams({
@@ -375,7 +388,7 @@ const ProjectFlockForm = ({
formik.setFieldValue('period', initialValues?.period); formik.setFieldValue('period', initialValues?.period);
} }
}, [initialValues, setSelectedArea, formType]); }, [initialValues, setSelectedArea, formType]);
useEffect(() => { useEffect(() => {
formikSetValues(formikInitialValues); formikSetValues(formikInitialValues);
}, [formikSetValues, formikInitialValues]); }, [formikSetValues, formikInitialValues]);
@@ -400,7 +413,7 @@ const ProjectFlockForm = ({
}, [formik.values]); }, [formik.values]);
useEffect(() => { useEffect(() => {
if(isResponseSuccess(periodFlocks)){ if (isResponseSuccess(periodFlocks)) {
formik.setFieldValue('period', periodFlocks.data.next_period); formik.setFieldValue('period', periodFlocks.data.next_period);
} }
}, [periodFlocks]); }, [periodFlocks]);
@@ -422,23 +435,39 @@ const ProjectFlockForm = ({
setIsDeleteLoading(false); setIsDeleteLoading(false);
}; };
const confirmationModalApproveClickHandler = async () => { const confirmationModalClickHandler = async ({
action = 'APPROVED',
}: {
action: 'APPROVED' | 'REJECTED';
}) => {
if (initialValues?.id === undefined) return;
setIsApproveLoading(true); setIsApproveLoading(true);
const approveProjectFlockRes = await ProjectFlockApi.customRequest< const approveProjectFlockRes = await ProjectFlockApi.customRequest<
BaseApiResponse<ProjectFlock>, BaseApiResponse<ProjectFlock>,
'POST' ProjectFlockApprovalPayload
>(`/${initialValues?.id}/approve`, { >(`/approvals`, {
method: 'POST', method: 'POST',
payload: {
action: action,
approvable_ids: [initialValues.id],
},
}); });
if (isResponseSuccess(approveProjectFlockRes)) { if (isResponseSuccess(approveProjectFlockRes)) {
toast.success('Project Flock berhasil di-approve!'); if(action == 'APPROVED'){
confirmModal.closeModal(); setIsApprovedDisabled(true);
setIsRejectedDisabled(false);
}
if(action == 'REJECTED'){
setIsRejectedDisabled(true);
setIsApprovedDisabled(false);
}
toast.success(approveProjectFlockRes.message as string);
} }
if (isResponseError(approveProjectFlockRes)) { if (isResponseError(approveProjectFlockRes)) {
toast.error(approveProjectFlockRes?.message as string); toast.error(approveProjectFlockRes?.message as string);
confirmModal.closeModal();
} }
confirmModal.closeModal();
setIsApproveLoading(false); setIsApproveLoading(false);
}; };
@@ -481,21 +510,43 @@ const ProjectFlockForm = ({
</div> </div>
)} )}
{formType == 'detail' && ( {formType == 'detail' && (
<div className='w-full py-4'> <div className='w-full flex flex-col sm:flex-row gap-2 py-4'>
<Button <Button
variant='outline' variant='outline'
color='success' color='success'
onClick={() => { onClick={() => {
if (initialValues?.id) { if (initialValues?.id) {
setApprovalAction('APPROVED');
confirmModal.openModal(); confirmModal.openModal();
} }
}} }}
disabled={!initialValues?.id} disabled={
!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} />
Approve Approve
</Button> </Button>
<Button
variant='outline'
color='error'
onClick={() => {
if (initialValues?.id) {
setApprovalAction('REJECTED');
confirmModal.openModal();
}
}}
disabled={
!initialValues?.id ||
isRejectedDisabled
}
className='w-full sm:w-fit'
>
<Icon icon='mdi:times' width={24} height={24} />
Reject
</Button>
</div> </div>
)} )}
<form <form
@@ -505,9 +556,7 @@ const ProjectFlockForm = ({
> >
<div className='card bg-base-100 shadow w-full mb-6'> <div className='card bg-base-100 shadow w-full mb-6'>
<div className='card-body'> <div className='card-body'>
<div className='card-title mb-4'> <div className='card-title mb-4'>Informasi Umum</div>
Informasi Umum
</div>
<div className='grid sm:grid-cols-2 gap-4'> <div className='grid sm:grid-cols-2 gap-4'>
<SelectInput <SelectInput
@@ -614,7 +663,7 @@ const ProjectFlockForm = ({
variant='link' variant='link'
className={`text-primary rotate-${ className={`text-primary rotate-${
openSelectKandangs ? '180' : '0' openSelectKandangs ? '180' : '0'
} transition-transform hover:text-inherit`} } transition-transform hover:text-inherit me-3`}
> >
<Icon <Icon
icon='material-symbols:keyboard-arrow-down' icon='material-symbols:keyboard-arrow-down'
@@ -631,7 +680,7 @@ const ProjectFlockForm = ({
> >
<div className='overflow-x-auto'> <div className='overflow-x-auto'>
{isLoadingKandang && ( {isLoadingKandang && (
<span className="loading loading-dots loading-xl"></span> <span className='loading loading-dots loading-xl'></span>
)} )}
<table className='table'> <table className='table'>
{/* head */} {/* head */}
@@ -673,7 +722,7 @@ const ProjectFlockForm = ({
</label> </label>
</th> </th>
<th>Kandang</th> <th>Kandang</th>
<th>Status</th> {/* <th>Status</th> */}
<th>Penanggung Jawab</th> <th>Penanggung Jawab</th>
</tr> </tr>
</thead> </thead>
@@ -704,7 +753,7 @@ const ProjectFlockForm = ({
</label> </label>
</th> </th>
<td>{kandang.name}</td> <td>{kandang.name}</td>
<td>{kandang.status}</td> {/* <td>{kandang.status}</td> */}
<td>{kandang.pic?.name}</td> <td>{kandang.pic?.name}</td>
</tr> </tr>
))} ))}
@@ -795,16 +844,20 @@ const ProjectFlockForm = ({
<ConfirmationModal <ConfirmationModal
ref={confirmModal.ref} ref={confirmModal.ref}
type='success' type={approvalAction == 'APPROVED' ? 'success' : 'error'}
text={`Apakah anda yakin ingin approve 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',
}} }}
primaryButton={{ primaryButton={{
text: 'Ya', text: 'Ya',
color: 'success', color: approvalAction == 'APPROVED' ? 'success' : 'error',
isLoading: isApproveLoading, isLoading: isApproveLoading,
onClick: confirmationModalApproveClickHandler, onClick: () => {
confirmationModalClickHandler({
action: approvalAction,
});
},
}} }}
/> />
</> </>
+9
View File
@@ -104,3 +104,12 @@ export type ApprovalsLine = {
role?: string; role?: string;
status: 'approved' | 'rejected' | 'waiting'; status: 'approved' | 'rejected' | 'waiting';
}[]; }[];
export type BaseApproval = {
step_number: number;
step_name: string;
action: string;
notes: string | null;
action_by: CreatedUser;
action_at: string;
};
+17 -12
View File
@@ -1,9 +1,9 @@
import { Area } from "@/types/api/master-data/area"; import { Area } from '@/types/api/master-data/area';
import { Fcr } from "@/types/api/master-data/fcr"; import { Fcr } from '@/types/api/master-data/fcr';
import { Flock } from "@/types/api/master-data/flock"; import { Flock } from '@/types/api/master-data/flock';
import { Kandang } from "@/types/api/master-data/kandang"; import { Kandang } from '@/types/api/master-data/kandang';
import { Location } from "@/types/api/master-data/location"; import { Location } from '@/types/api/master-data/location';
import { BaseMetadata } from "@/types/api/api-general"; import { BaseApproval, BaseMetadata } from '@/types/api/api-general';
export type BaseProjectFlock = { export type BaseProjectFlock = {
id: number; id: number;
@@ -21,15 +21,15 @@ export type BaseProjectFlock = {
period: number; period: number;
kandang_ids: number[]; kandang_ids: number[];
kandangs: Kandang[]; kandangs: Kandang[];
} approval: BaseApproval;
};
export type PeriodFlock = { export type PeriodFlock = {
flock: Flock; flock: Flock;
next_period: number; next_period: number;
} };
export type ProjectFlock = BaseMetadata & BaseProjectFlock;
export type ProjectFlock = BaseMetadata & BaseProjectFlock
export type CreateProjectFlockPayload = { export type CreateProjectFlockPayload = {
flock_id: number; flock_id: number;
@@ -39,6 +39,11 @@ export type CreateProjectFlockPayload = {
location_id: number; location_id: number;
period: number; period: number;
kandang_ids: number[]; kandang_ids: number[];
} };
export type UpdateProjectFlockPayload = CreateProjectFlockPayload; export type UpdateProjectFlockPayload = CreateProjectFlockPayload;
export type ProjectFlockApprovalPayload = {
action: 'APPROVED' | 'REJECTED';
approvable_ids: number[];
};