fix(FE): revert RequireAuth component and closing logic

This commit is contained in:
randy-ar
2025-12-06 13:10:03 +07:00
parent ffbf886718
commit b97cc39854
5 changed files with 40 additions and 683 deletions
-11
View File
@@ -1,11 +0,0 @@
import SapronakCalculationTable from '@/components/pages/us-284/SapronakCalculationTable';
const PerhitunganSapronak = () => {
return (
<div className='size-full p-4'>
<SapronakCalculationTable projectFlockId={1} />
</div>
);
};
export default PerhitunganSapronak;
+33 -166
View File
@@ -6,147 +6,9 @@ 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 { isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { GetMeResponse } from '@/types/api/api-general'; import { BaseApiResponse, GetMeResponse } from '@/types/api/api-general';
import { AxiosError } from 'axios';
// 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;
@@ -156,17 +18,20 @@ const RequireAuth = ({ children }: RequireAuthProps) => {
const router = useRouter(); const router = useRouter();
const { setUser, setIsLoadingUser } = useAuth(); const { setUser, setIsLoadingUser } = useAuth();
const { data: userResponse, isLoading: isLoadingUserResponse } = const {
useSWRImmutable<GetMeResponse & { ok?: boolean }, unknown, SWRHttpKey>( data: userResponse,
'/auth/sso/userinfo', isLoading: isLoadingUserResponse,
httpClientFetcher, error: userErrorResponse,
{ } = useSWRImmutable<
shouldRetryOnError: false, GetMeResponse & { ok?: boolean },
revalidateOnFocus: false, AxiosError<BaseApiResponse>,
revalidateOnReconnect: false, SWRHttpKey
refreshInterval: 0, >('/sso/userinfo', httpClientFetcher, {
} shouldRetryOnError: false,
); revalidateOnFocus: false,
revalidateOnReconnect: false,
refreshInterval: 0,
});
useEffect(() => { useEffect(() => {
setIsLoadingUser(isLoadingUserResponse); setIsLoadingUser(isLoadingUserResponse);
@@ -175,23 +40,25 @@ const RequireAuth = ({ children }: RequireAuthProps) => {
useEffect(() => { useEffect(() => {
if (isResponseSuccess(userResponse)) { if (isResponseSuccess(userResponse)) {
setUser(userResponse.data); setUser(userResponse.data);
} else { } else if (
// router.replace(process.env.NEXT_PUBLIC_SSO_LOGIN_URL as string); isResponseError(userErrorResponse?.response?.data) &&
// TODO: remove this later, DONT HARDCODE USER DATA typeof window !== 'undefined'
setUser(DUMMY_USER); ) {
router.replace(
`${process.env.NEXT_PUBLIC_SSO_LOGIN_URL as string}?redirect_url=${window.location.href}`
);
} }
}, [userResponse, setIsLoadingUser, setUser]); }, [userResponse, userErrorResponse, setIsLoadingUser, setUser]);
// TODO: uncomment this later if (isLoadingUserResponse && !userResponse && !userErrorResponse) {
// if (isLoadingUserResponse && !userResponse) { 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> );
// ); }
// }
return <>{children}</>; return <>{isResponseSuccess(userResponse) && children}</>;
}; };
export default RequireAuth; export default RequireAuth;
@@ -1,16 +1,15 @@
'use client'; 'use client';
import Button from '@/components/Button'; import Button from '@/components/Button';
import DrawerHeader from '@/components/helper/drawer/DrawerHeader'; import DrawerHeader from '@/components/helper/drawer/DrawerHeader';
import Table from '@/components/Table'; import Table from '@/components/Table';
import Badge from '@/components/Badge'; import Badge from '@/components/Badge';
import { cn, formatDate, formatNumber, formatTitleCase } from '@/lib/helper'; import { cn, formatDate, formatNumber, formatTitleCase } from '@/lib/helper';
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
import { ProjectFlock } from '@/types/api/production/project-flock'; import { ProjectFlock } from '@/types/api/production/project-flock';
import { import {
ClosingExpense, ClosingExpense,
ProjectFlockKandang, ProjectFlockKandang,
} from '@/types/api/production/project-flock-kandang'; } from '@/types/api/production/project-flock-kandang';
import { Purchase } from '@/types/api/purchase/purchase';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import useSWR from 'swr'; import useSWR from 'swr';
import { ProjectFlockKandangApi } from '@/services/api/production/project-flock-kandang'; import { ProjectFlockKandangApi } from '@/services/api/production/project-flock-kandang';
@@ -19,6 +18,8 @@ import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModal from '@/components/modal/ConfirmationModal';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { useRouter } from 'next/navigation';
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
const ProjectFlockClosingForm = ({ const ProjectFlockClosingForm = ({
projectFlock, projectFlock,
@@ -27,8 +28,9 @@ const ProjectFlockClosingForm = ({
projectFlock: ProjectFlock; projectFlock: ProjectFlock;
projectFlockKandang: ProjectFlockKandang; projectFlockKandang: ProjectFlockKandang;
}) => { }) => {
const router = useRouter();
const closeModal = useModal(); const closeModal = useModal();
const isCanClose = projectFlock.approval?.step_number <= 1; const isCanClose = projectFlock.approval?.step_number <= 2;
const [isClosingLoading, setIsClosingLoading] = useState(false); const [isClosingLoading, setIsClosingLoading] = useState(false);
const { data: closingData, isLoading } = useSWR( const { data: closingData, isLoading } = useSWR(
@@ -48,6 +50,7 @@ const ProjectFlockClosingForm = ({
if (isResponseSuccess(deleteProjectFlockRes)) { if (isResponseSuccess(deleteProjectFlockRes)) {
toast.success(deleteProjectFlockRes?.message as string); toast.success(deleteProjectFlockRes?.message as string);
router.push(`/production/project-flock`);
} }
if (isResponseError(deleteProjectFlockRes)) { if (isResponseError(deleteProjectFlockRes)) {
toast.error(deleteProjectFlockRes?.message as string); toast.error(deleteProjectFlockRes?.message as string);
@@ -280,7 +283,7 @@ const ProjectFlockClosingForm = ({
ref={closeModal.ref} ref={closeModal.ref}
type='error' type='error'
text={ text={
projectFlock.approval?.step_number <= 1 isCanClose
? 'Apakah kamu yakin ingin mengakhiri project ini ? *Pastikan persediaan produk di gudang terkait sudah kosong, dan BOP sudah selesai' ? 'Apakah kamu yakin ingin mengakhiri project ini ? *Pastikan persediaan produk di gudang terkait sudah kosong, dan BOP sudah selesai'
: 'Apakah kamu yakin ingin membuka kembali project ini ? *Project ini akan kembali ke status aktif' : 'Apakah kamu yakin ingin membuka kembali project ini ? *Project ini akan kembali ke status aktif'
} }
@@ -1,298 +0,0 @@
import { BaseApiService } from '@/services/api/base';
import { BaseApiResponse } from '@/types/api/api-general';
export type RowSapronakCalculation = {
id: number;
tanggal: string;
no_referensi: string;
qty_masuk: number;
qty_keluar: number;
qty_pakai: number;
uraian: string;
kategori_produk: string;
harga_beli_per_qty: number;
total_harga: number;
keterangan: string;
};
export type TotalSapronakCalculation = {
label: string;
qty_masuk: number;
qty_keluar: number;
qty_pakai: number;
harga_beli_per_qty: number;
total_harga: number;
};
export type SapronakCalculationItem = {
rows: RowSapronakCalculation[];
total: TotalSapronakCalculation;
};
export type SapronakCalculation = {
doc_broiler: SapronakCalculationItem;
ovk: SapronakCalculationItem;
pakan: SapronakCalculationItem;
};
// Dummy data
const DUMMY_SAPRONAK_CALCULATION: SapronakCalculation = {
doc_broiler: {
rows: [
{
id: 1,
tanggal: '11-Sep-2025',
no_referensi: 'PO-PULLET-388',
qty_masuk: 32800,
qty_keluar: 0,
qty_pakai: 32800,
uraian: 'PULLET LOHMANN (16 MINGGU)',
kategori_produk: 'PULLET LAYER',
harga_beli_per_qty: 60136,
total_harga: 1972556800,
keterangan: '-',
},
{
id: 2,
tanggal: '24-Sep-2025',
no_referensi: 'PO-PULLET-410',
qty_masuk: 14758,
qty_keluar: 0,
qty_pakai: 14758,
uraian: 'PULLET HY-LINE (17 MINGGU)',
kategori_produk: 'PULLET LAYER',
harga_beli_per_qty: 65421,
total_harga: 965908998,
keterangan: '-',
},
{
id: 3,
tanggal: '29-Sep-2025',
no_referensi: 'PO-PULLET-196',
qty_masuk: 35439,
qty_keluar: 0,
qty_pakai: 35439,
uraian: 'PULLET ISA BROWN (15 MINGGU)',
kategori_produk: 'PULLET LAYER',
harga_beli_per_qty: 55909,
total_harga: 1981297351,
keterangan: '-',
},
],
total: {
label: 'TOTAL PULLET',
qty_masuk: 82997,
qty_keluar: 0,
qty_pakai: 82997,
harga_beli_per_qty: 59274.65,
total_harga: 4919963149,
},
},
ovk: {
rows: [
{
id: 1,
tanggal: '28-Sep-2025',
no_referensi: 'PO-OVK-276',
qty_masuk: 52,
qty_keluar: 0,
qty_pakai: 52,
uraian: 'ND-IB VACCINE',
kategori_produk: 'OVK VAKSIN',
harga_beli_per_qty: 204652,
total_harga: 10641904,
keterangan: 'Program kesehatan & biosecurity',
},
{
id: 2,
tanggal: '26-Sep-2025',
no_referensi: 'PO-OVK-811',
qty_masuk: 43,
qty_keluar: 0,
qty_pakai: 43,
uraian: 'GUMBORO VACCINE',
kategori_produk: 'OVK VAKSIN',
harga_beli_per_qty: 298379,
total_harga: 12830297,
keterangan: 'Program kesehatan & biosecurity',
},
{
id: 3,
tanggal: '28-Sep-2025',
no_referensi: 'PO-OVK-879',
qty_masuk: 21,
qty_keluar: 0,
qty_pakai: 21,
uraian: 'AMOXITIN SOLUBLE',
kategori_produk: 'OVK OBAT',
harga_beli_per_qty: 145952,
total_harga: 3064992,
keterangan: 'Program kesehatan & biosecurity',
},
{
id: 4,
tanggal: '11-Okt-2025',
no_referensi: 'PO-OVK-340',
qty_masuk: 38,
qty_keluar: 0,
qty_pakai: 38,
uraian: 'TILOXIN SOLUBLE',
kategori_produk: 'OVK OBAT',
harga_beli_per_qty: 200424,
total_harga: 7616112,
keterangan: 'Program kesehatan & biosecurity',
},
{
id: 5,
tanggal: '27-Sep-2025',
no_referensi: 'PO-OVK-364',
qty_masuk: 7,
qty_keluar: 0,
qty_pakai: 7,
uraian: 'EGG STIMULANT',
kategori_produk: 'OVK VITAMIN',
harga_beli_per_qty: 115024,
total_harga: 805168,
keterangan: 'Program kesehatan & biosecurity',
},
{
id: 6,
tanggal: '16-Sep-2025',
no_referensi: 'PO-OVK-982',
qty_masuk: 57,
qty_keluar: 0,
qty_pakai: 57,
uraian: 'MULTIVIT-AMINO',
kategori_produk: 'OVK VITAMIN',
harga_beli_per_qty: 65123,
total_harga: 3712011,
keterangan: 'Program kesehatan & biosecurity',
},
{
id: 7,
tanggal: '04-Okt-2025',
no_referensi: 'PO-OVK-876',
qty_masuk: 4,
qty_keluar: 0,
qty_pakai: 4,
uraian: 'BKC DESINFEKTAN',
kategori_produk: 'OVK KIMIA',
harga_beli_per_qty: 105677,
total_harga: 422708,
keterangan: 'Program kesehatan & biosecurity',
},
],
total: {
label: 'TOTAL OVK',
qty_masuk: 222,
qty_keluar: 0,
qty_pakai: 222,
harga_beli_per_qty: 172965.92,
total_harga: 38481094,
},
},
pakan: {
rows: [
{
id: 1,
tanggal: '13-Ags-2025',
no_referensi: 'PO-FEED-730',
qty_masuk: 4833,
qty_keluar: 0,
qty_pakai: 4833,
uraian: 'FEED PRE-LAY',
kategori_produk: 'PAKAN PRE-LAY',
harga_beli_per_qty: 7578,
total_harga: 36625874,
keterangan: 'Konsumsi pakan kandang layer',
},
{
id: 2,
tanggal: '28-Jul-2025',
no_referensi: 'PO-FEED-555',
qty_masuk: 6500,
qty_keluar: 0,
qty_pakai: 6500,
uraian: 'FEED LAYER PHASE 1',
kategori_produk: 'PAKAN LAYER',
harga_beli_per_qty: 8116,
total_harga: 52754000,
keterangan: 'Konsumsi pakan kandang layer',
},
{
id: 3,
tanggal: '24-Agu-2025',
no_referensi: 'PO-FEED-683',
qty_masuk: 8802,
qty_keluar: 0,
qty_pakai: 8802,
uraian: 'FEED LAYER PHASE 2',
kategori_produk: 'PAKAN LAYER',
harga_beli_per_qty: 8801,
total_harga: 77465402,
keterangan: 'Konsumsi pakan kandang layer',
},
{
id: 4,
tanggal: '02-Sep-2025',
no_referensi: 'PO-FEED-448',
qty_masuk: 2185,
qty_keluar: 0,
qty_pakai: 2185,
uraian: 'JAGUNG GILING',
kategori_produk: 'PAKAN MIX',
harga_beli_per_qty: 5573,
total_harga: 12187705,
keterangan: 'Konsumsi pakan kandang layer',
},
],
total: {
label: 'TOTAL PAKAN',
qty_masuk: 22320,
qty_keluar: 0,
qty_pakai: 22320,
harga_beli_per_qty: 8092.39,
total_harga: 179032981,
},
},
};
export class ClosingService extends BaseApiService<unknown, unknown, unknown> {
constructor(basePath: string = '') {
super(basePath);
}
async getPerhitunganSapronak(
projectFlockId: number
): Promise<BaseApiResponse<SapronakCalculation> | undefined> {
// Dummy implementation - simulate API call with delay
return new Promise((resolve) => {
setTimeout(() => {
resolve({
code: 200,
status: 'success',
message: 'Retrieved sapronak calculation successfully',
data: DUMMY_SAPRONAK_CALCULATION,
});
}, 500); // Simulate 500ms network delay
});
/*
// Real API implementation - uncomment when backend is ready
try {
const path = `${this.basePath}/project-flock/${projectFlockId}/sapronak-calculation`;
return await httpClient<BaseApiResponse<SapronakCalculation>>(path, {
method: 'GET',
});
} catch (error: unknown) {
if (axios.isAxiosError<BaseApiResponse<SapronakCalculation>>(error)) {
return error.response?.data;
}
return undefined;
}
*/
}
}
export const ClosingApi = new ClosingService(`/closing`);
@@ -1,204 +0,0 @@
'use client';
import Card from '@/components/Card';
import {
ClosingApi,
RowSapronakCalculation,
} from '@/components/pages/us-284/DummyDataSapronakCalculation';
import Table from '@/components/Table';
import { isResponseSuccess } from '@/lib/api-helper';
import { cn, formatCurrency, formatNumber } from '@/lib/helper';
import { ColumnDef } from '@tanstack/react-table';
import { useMemo } from 'react';
import useSWR from 'swr';
const SapronakCalculationTable = ({
projectFlockId,
}: {
projectFlockId: number;
}) => {
const { data: sapronakCalculation, isLoading } = useSWR(
`/sapronak-calculation`,
() => ClosingApi.getPerhitunganSapronak(projectFlockId)
);
const columns: ColumnDef<RowSapronakCalculation>[] = useMemo(
() => [
{
header: 'Tanggal',
accessorKey: 'tanggal',
cell: (props) => {
const value = props.getValue() as string;
// Data already in DD-MMM-YYYY format, just display it
return value || '-';
},
},
{
header: 'No. Referensi',
accessorKey: 'no_referensi',
},
{
header: 'QTY Masuk',
accessorKey: 'qty_masuk',
cell: (props) => {
const value = props.getValue() as number;
return formatNumber(value);
},
},
{
header: 'QTY Keluar',
accessorKey: 'qty_keluar',
cell: (props) => {
const value = props.getValue() as number;
return formatNumber(value);
},
},
{
header: 'QTY Pakai',
accessorKey: 'qty_pakai',
cell: (props) => {
const value = props.getValue() as number;
return formatNumber(value);
},
},
{
header: 'Uraian',
accessorKey: 'uraian',
},
{
header: 'Kategori Produk',
accessorKey: 'kategori_produk',
},
{
header: 'Harga Beli/Qty (Rp)',
accessorKey: 'harga_beli_per_qty',
cell: (props) => {
const value = props.getValue() as number;
return formatCurrency(value);
},
},
{
header: 'Total Harga (Rp)',
accessorKey: 'total_harga',
cell: (props) => {
const value = props.getValue() as number;
return formatCurrency(value);
},
},
{
header: 'Keterangan',
accessorKey: 'keterangan',
cell: (props) => {
const value = props.getValue() as string;
return value || '-';
},
},
],
[]
);
return (
<div className='flex flex-col gap-4'>
{isLoading && (
<div className='w-full flex flex-row justify-center items-center p-4'>
<span className='loading loading-spinner loading-xl' />
</div>
)}
{isResponseSuccess(sapronakCalculation) && (
<>
<Card
title='DOC Broiler'
variant='bordered'
collapsible
defaultCollapsed={false}
className={{
wrapper: 'w-full',
}}
>
<Table<RowSapronakCalculation>
data={sapronakCalculation.data.doc_broiler.rows}
columns={columns}
className={{
containerClassName: cn({
'mb-20':
isResponseSuccess(sapronakCalculation) &&
sapronakCalculation?.data.doc_broiler.rows.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',
}}
/>
</Card>
<Card
title='OVK'
variant='bordered'
collapsible
defaultCollapsed={true}
className={{
wrapper: 'w-full',
}}
>
<Table<RowSapronakCalculation>
data={sapronakCalculation.data.ovk.rows}
columns={columns}
className={{
containerClassName: cn({
'mb-20':
isResponseSuccess(sapronakCalculation) &&
sapronakCalculation?.data.ovk.rows.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',
}}
/>
</Card>
<Card
title='Pakan'
variant='bordered'
collapsible
defaultCollapsed={true}
className={{
wrapper: 'w-full',
}}
>
<Table<RowSapronakCalculation>
data={sapronakCalculation.data.pakan.rows}
columns={columns}
className={{
containerClassName: cn({
'mb-20':
isResponseSuccess(sapronakCalculation) &&
sapronakCalculation?.data.pakan.rows.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',
}}
/>
</Card>
</>
)}
</div>
);
};
export default SapronakCalculationTable;