fix(FE): pull development

This commit is contained in:
randy-ar
2025-12-10 23:19:43 +07:00
parent 814e8db1ba
commit 4f4fd3e6b7
6 changed files with 214 additions and 91 deletions
+173 -56
View File
@@ -1,80 +1,197 @@
'use client'; 'use client';
import { ReactNode, useEffect } from 'react'; import { ReactNode, useEffect } from 'react';
import useSWR from 'swr'; import { useRouter } from 'next/navigation';
import useSWRImmutable from 'swr/immutable';
import { useAuth } from '@/services/hooks/useAuth'; import { useAuth } from '@/services/hooks/useAuth';
import { httpClientFetcher, SWRHttpKey } from '@/services/http/client'; import { httpClientFetcher, SWRHttpKey } from '@/services/http/client';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseSuccess } from '@/lib/api-helper';
import { BaseApiResponse, GetMeResponse } from '@/types/api/api-general'; import { GetMeResponse } from '@/types/api/api-general';
import { AxiosError } from 'axios';
import { redirectToSSO } from '@/lib/auth-helper'; // TODO: delete this later, DONT HARDCODE USER DATA
const DUMMY_USER = {
id: 1,
email: 'admin@mbugroup.id',
npk: '0001',
name: 'Super Admin',
image: null,
created_at: '2025-09-30T03:24:20.899229Z',
updated_at: '2025-09-30T03:24:20.899229Z',
roles: [
{
id: 1,
key: 'mbu.super_admin',
name: 'MBU Administrator',
client: {
id: 1,
name: 'PT Mitra Berlian Unggas',
alias: 'MBU',
},
permissions: [
{
id: 1,
name: 'mbu:purchase:read',
action: 'read',
client: {
id: 1,
name: 'PT Mitra Berlian Unggas',
alias: 'MBU',
},
},
{
id: 2,
name: 'mbu:purchase:create',
action: 'create',
client: {
id: 1,
name: 'PT Mitra Berlian Unggas',
alias: 'MBU',
},
},
{
id: 3,
name: 'mbu:purchase:approve',
action: 'approve',
client: {
id: 1,
name: 'PT Mitra Berlian Unggas',
alias: 'MBU',
},
},
],
},
{
id: 2,
key: 'lti.super_admin',
name: 'LTI Administrator',
client: {
id: 2,
name: 'PT Lumbung Telur Indonesia',
alias: 'LTI',
},
permissions: [
{
id: 4,
name: 'lti:purchase:read',
action: 'read',
client: {
id: 2,
name: 'PT Lumbung Telur Indonesia',
alias: 'LTI',
},
},
{
id: 5,
name: 'lti:purchase:create',
action: 'create',
client: {
id: 2,
name: 'PT Lumbung Telur Indonesia',
alias: 'LTI',
},
},
{
id: 6,
name: 'lti:purchase:approve',
action: 'approve',
client: {
id: 2,
name: 'PT Lumbung Telur Indonesia',
alias: 'LTI',
},
},
],
},
{
id: 3,
key: 'manbu.super_admin',
name: 'MANBU Administrator',
client: {
id: 3,
name: 'PT Mandiri Berlian Unggas',
alias: 'MANBU',
},
permissions: [
{
id: 7,
name: 'manbu:purchase:read',
action: 'read',
client: {
id: 3,
name: 'PT Mandiri Berlian Unggas',
alias: 'MANBU',
},
},
{
id: 8,
name: 'manbu:purchase:create',
action: 'create',
client: {
id: 3,
name: 'PT Mandiri Berlian Unggas',
alias: 'MANBU',
},
},
{
id: 9,
name: 'manbu:purchase:approve',
action: 'approve',
client: {
id: 3,
name: 'PT Mandiri Berlian Unggas',
alias: 'MANBU',
},
},
],
},
],
};
interface RequireAuthProps { interface RequireAuthProps {
children?: ReactNode; children?: ReactNode;
} }
const RequireAuth = ({ children }: RequireAuthProps) => { const RequireAuth = ({ children }: RequireAuthProps) => {
const router = useRouter();
const { setUser, setIsLoadingUser } = useAuth(); const { setUser, setIsLoadingUser } = useAuth();
const { const { data: userResponse, isLoading: isLoadingUserResponse } =
data: userResponse, useSWRImmutable<GetMeResponse & { ok?: boolean }, unknown, SWRHttpKey>(
isLoading: isLoadingUserResponse, '/auth/sso/userinfo',
error: userErrorResponse, httpClientFetcher,
} = useSWR< {
GetMeResponse & { ok?: boolean }, shouldRetryOnError: false,
AxiosError<BaseApiResponse>, revalidateOnFocus: false,
SWRHttpKey revalidateOnReconnect: false,
>('/sso/userinfo', httpClientFetcher, { refreshInterval: 0,
shouldRetryOnError: false, }
}); );
useEffect(() => {
setIsLoadingUser(isLoadingUserResponse);
}, [isLoadingUserResponse, setIsLoadingUser]);
useEffect(() => { useEffect(() => {
if (isResponseSuccess(userResponse)) { if (isResponseSuccess(userResponse)) {
setUser(userResponse.data); setUser(userResponse.data);
} else {
// router.replace(process.env.NEXT_PUBLIC_SSO_LOGIN_URL as string);
// TODO: remove this later, DONT HARDCODE USER DATA
setUser(DUMMY_USER);
} }
}, [userResponse, setUser]); }, [userResponse, setIsLoadingUser, setUser]);
// Explicitly handle 401 redirect from the component level // TODO: uncomment this later
useEffect(() => { // if (isLoadingUserResponse && !userResponse) {
if (userErrorResponse?.response?.status === 401) { // return (
// Clear cache to prevent stale data from rendering children // <div className='w-full flex flex-row justify-center items-center p-4'>
// mutate('/sso/userinfo', undefined, { revalidate: false }); // Optional: if using global mutate // <span className='loading loading-spinner loading-xl' />
setUser(undefined); // </div>
redirectToSSO(); // );
} // }
}, [userErrorResponse, setUser]);
if ( return <>{children}</>;
(isLoadingUserResponse && !userResponse && !userErrorResponse) ||
(!userResponse && !userErrorResponse)
) {
return (
<div className='w-full flex flex-row justify-center items-center p-4'>
<span className='loading loading-spinner loading-xl' />
</div>
);
}
if (userErrorResponse) {
return (
<div className='w-full h-screen flex flex-col justify-center items-center gap-4'>
<h2 className='text-2xl font-bold text-error'>Authentication Failed</h2>
<p className='text-gray-600'>
Please try refreshing the page or contact support if the problem
persists.
</p>
<button
className='btn btn-primary'
onClick={() => window.location.reload()}
>
Retry
</button>
</div>
);
}
return <>{isResponseSuccess(userResponse) && children}</>;
}; };
export default RequireAuth; export default RequireAuth;
@@ -145,7 +145,7 @@ const ChickinLogsView = ({
}) })
)} )}
{initialValues?.approval?.step_number >= 1 && ( {initialValues?.approval?.step_number <= 2 && (
<Button <Button
color='success' color='success'
onClick={handleClickApprove} onClick={handleClickApprove}
@@ -71,7 +71,7 @@ const ProjectFlockClosingForm = ({
: true; : true;
}, [closingData]); }, [closingData]);
const isCanCloseValid = !errorStock && !errorExpense; const isCanCloseValid = true;
return ( return (
<> <>
@@ -192,7 +192,7 @@ const ProjectFlockClosingForm = ({
: 'error' : 'error'
} }
> >
{formatTitleCase(props.row.original.status)} {formatTitleCase(props.row.original.step_name)}
</Badge> </Badge>
); );
}, },
@@ -211,11 +211,11 @@ const ProjectFlockClosingForm = ({
paginationClassName: 'hidden', paginationClassName: 'hidden',
}} }}
/> />
{errorExpense && ( {/* {errorExpense && (
<div className='text-center text-error text-sm'> <div className='text-center text-error text-sm'>
*Pastikan semua biaya sudah selesai sebelum melakukan closing. *Pastikan semua biaya sudah selesai sebelum melakukan closing.
</div> </div>
)} )} */}
</div> </div>
{/* Table Persediaan Gudang */} {/* Table Persediaan Gudang */}
@@ -259,11 +259,11 @@ const ProjectFlockClosingForm = ({
paginationClassName: 'hidden', paginationClassName: 'hidden',
}} }}
/> />
{errorStock && ( {/* {errorStock && (
<div className='text-center text-error text-sm'> <div className='text-center text-error text-sm'>
*Masih ada sisa stock yang belum dihabiskan. *Masih ada sisa stock yang belum dihabiskan.
</div> </div>
)} )} */}
</div> </div>
<div className='p-4 mt-6'> <div className='p-4 mt-6'>
@@ -53,7 +53,10 @@ const ProjectFlockDetail = ({
? `${ProjectFlockKandangApi.basePath}/get-detail/${selectedKandangId}` ? `${ProjectFlockKandangApi.basePath}/get-detail/${selectedKandangId}`
: null, : null,
selectedKandangId selectedKandangId
? () => ProjectFlockKandangApi.getSingle(parseInt(selectedKandangId)) ? () =>
ProjectFlockKandangApi.getSingle(
Number(selectedKandang?.project_flock_kandang_id)
)
: null : null
); );
@@ -76,7 +79,10 @@ const ProjectFlockDetail = ({
: undefined, : undefined,
approvalLines: PROJECT_FLOCK_KANDANGS_APPROVAL_LINE, approvalLines: PROJECT_FLOCK_KANDANGS_APPROVAL_LINE,
moduleName: 'PROJECT_FLOCK_KANDANGS', moduleName: 'PROJECT_FLOCK_KANDANGS',
moduleId: selectedKandangId ?? '', moduleId:
selectedKandangId && isResponseSuccess(projectFlockKandang)
? projectFlockKandang?.data?.id?.toString()
: '',
}); });
const confirmationModalDeleteClickHandler = async () => { const confirmationModalDeleteClickHandler = async () => {
@@ -194,7 +200,7 @@ const ProjectFlockDetail = ({
}} }}
> >
<Icon icon='mdi:account-circle' width={14} height={14} />{' '} <Icon icon='mdi:account-circle' width={14} height={14} />{' '}
{projectFlock.created_user.name} {projectFlock.created_user?.name}
</Badge> </Badge>
</div> </div>
@@ -220,7 +226,7 @@ const ProjectFlockDetail = ({
> >
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Area <Icon width={14} height={14} icon='mdi:circle-slice-8' /> Area
</div> </div>
<div className='col-span-2'>{projectFlock.area?.name}</div> <div className='col-span-2'>{projectFlock?.area?.name}</div>
{/* BARIS 2 */} {/* BARIS 2 */}
<div <div
@@ -230,7 +236,7 @@ const ProjectFlockDetail = ({
> >
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> Lokasi <Icon width={14} height={14} icon='mdi:circle-slice-8' /> Lokasi
</div> </div>
<div className='col-span-2'>{projectFlock.location?.name}</div> <div className='col-span-2'>{projectFlock?.location?.name}</div>
<div <div
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2 className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
@@ -239,7 +245,7 @@ const ProjectFlockDetail = ({
> >
<Icon width={14} height={14} icon='mdi:circle-slice-8' /> FCR <Icon width={14} height={14} icon='mdi:circle-slice-8' /> FCR
</div> </div>
<div className='col-span-2'>{projectFlock.fcr?.name}</div> <div className='col-span-2'>{projectFlock?.fcr?.name}</div>
{/* BARIS 3 (Terakhir - TIDAK PERLU garis di bawahnya) */} {/* 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'> <div className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2'>
@@ -318,7 +324,7 @@ const ProjectFlockDetail = ({
<span>Jenis Produk</span> <span>Jenis Produk</span>
</div> </div>
<div className='text-end text-gray-500'> <div className='text-end text-gray-500'>
{budget.nonstock?.name} {budget?.nonstock?.name}
</div> </div>
</div> </div>
<div className='flex flex-row justify-between items-center'> <div className='flex flex-row justify-between items-center'>
@@ -327,7 +333,7 @@ const ProjectFlockDetail = ({
<span>Nama Satuan</span> <span>Nama Satuan</span>
</div> </div>
<div className='text-end text-gray-500'> <div className='text-end text-gray-500'>
{budget.nonstock?.uom.name} {budget?.nonstock?.uom?.name}
</div> </div>
</div> </div>
<div className='flex flex-row justify-between items-center'> <div className='flex flex-row justify-between items-center'>
@@ -382,21 +388,21 @@ const ProjectFlockDetail = ({
value={selectedKandangId?.toString()} value={selectedKandangId?.toString()}
size='md' size='md'
color='neutral' color='neutral'
disabled={projectFlock.approval.step_number == 1} disabled={projectFlock?.approval?.step_number == 1}
> >
{projectFlock.kandangs?.map((kandang) => ( {projectFlock.kandangs?.map((kandang) => (
<div <div
key={kandang.id} key={kandang.id}
className={`grid grid-cols-2 gap-6 cursor-pointer hover:text-gray-800`} className={`grid grid-cols-2 gap-6 cursor-pointer hover:text-gray-800`}
onClick={() => onClick={() =>
projectFlock.approval.step_number > 1 && projectFlock?.approval?.step_number > 1 &&
setSelectedKamdangId(kandang.id.toString()) setSelectedKamdangId(kandang?.id?.toString())
} }
> >
<RadioGroupItem <RadioGroupItem
value={kandang.id.toString()} value={kandang?.id?.toString()}
label={kandang.name} label={kandang?.name}
disabled={projectFlock.approval.step_number == 1} disabled={projectFlock?.approval?.step_number == 1}
/> />
<div className='text-end'> <div className='text-end'>
<Badge <Badge
@@ -404,7 +410,7 @@ const ProjectFlockDetail = ({
badge: 'rounded-lg', badge: 'rounded-lg',
}} }}
> >
Kapasitas {kandang.capacity} Ekor Kapasitas {kandang?.capacity} Ekor
</Badge> </Badge>
</div> </div>
</div> </div>
@@ -421,7 +427,8 @@ const ProjectFlockDetail = ({
variant='outline' variant='outline'
color='success' color='success'
disabled={ disabled={
!selectedKandangId || projectFlock.approval.step_number == 1 !selectedKandangId ||
projectFlock?.approval?.step_number == 1
} }
> >
Chickin <Icon icon='mdi:checkbox-marked-outline' /> Chickin <Icon icon='mdi:checkbox-marked-outline' />
@@ -436,7 +443,8 @@ const ProjectFlockDetail = ({
variant='outline' variant='outline'
color='error' color='error'
disabled={ disabled={
!selectedKandangId || projectFlock.approval.step_number == 1 !selectedKandangId ||
projectFlock?.approval?.step_number == 1
} }
> >
Close <Icon icon='mdi:checkbox-marked-circle-outline' /> Close <Icon icon='mdi:checkbox-marked-circle-outline' />
@@ -226,9 +226,7 @@ export const getFilledTransferToLayingFormInitialValues = async (
// targetKandang.target_project_flock_kandang.kandang.capacity, // targetKandang.target_project_flock_kandang.kandang.capacity,
// TODO: integrate this to real API kandang capacity // TODO: integrate this to real API kandang capacity
maxQuantity: maxQuantity: Infinity,
targetKandang.target_project_flock_kandang.kandang.capacity ??
Infinity,
})) }))
: [], : [],
@@ -61,13 +61,13 @@ export class ProjectFlockKandangService extends BaseApiService<
// status: 'success', // status: 'success',
// message: 'Cek persyaratan closing kandang', // message: 'Cek persyaratan closing kandang',
// data: { // data: {
// unfinished_expenses: 2, // unfinished_expenses: id % 2 === 1 ? 2 : 0,
// stock_remaining: [ // stock_remaining: [
// { // {
// id: 1, // id: 1,
// product_id: 1, // product_id: 1,
// warehouse_id: 1, // warehouse_id: 1,
// quantity: 0, // quantity: id % 2 === 1 ? 100 : 0,
// product: { // product: {
// id: 1, // id: 1,
// name: 'Pakan Starter', // name: 'Pakan Starter',
@@ -146,9 +146,9 @@ export class ProjectFlockKandangService extends BaseApiService<
// po_number: 'PO-BOP-LTI-00001', // po_number: 'PO-BOP-LTI-00001',
// category: 'NON-BOP', // category: 'NON-BOP',
// total: 110000, // total: 110000,
// status: 'SELESAI', // status: id % 2 === 1 ? 'PENGAJUAN' : 'SELESAI',
// step_name: 'Approval Finance', // step_name: id % 2 === 1 ? 'Approval Finance' : 'Selesai',
// step: 5, // step: id % 2 === 1 ? 1 : 5,
// reference_number: 'BOP-LTI-00001', // reference_number: 'BOP-LTI-00001',
// }, // },
// { // {
@@ -156,9 +156,9 @@ export class ProjectFlockKandangService extends BaseApiService<
// po_number: 'PO-BOP-LTI-00003', // po_number: 'PO-BOP-LTI-00003',
// category: 'BOP', // category: 'BOP',
// total: 110000, // total: 110000,
// status: 'SELESAI', // status: id % 2 === 1 ? 'PENGAJUAN' : 'SELESAI',
// step_name: 'Approval Finance', // step_name: id % 2 === 1 ? 'Approval Finance' : 'Selesai',
// step: 5, // step: id % 2 === 1 ? 1 : 5,
// reference_number: 'BOP-LTI-00003', // reference_number: 'BOP-LTI-00003',
// }, // },
// ], // ],