mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
fix(FE): resolve merge conflict
This commit is contained in:
@@ -14,13 +14,13 @@ const ProjectFlockClosingPage = () => {
|
|||||||
const projectFlockKandangId = searchParams.get('projectFlockKandangId');
|
const projectFlockKandangId = searchParams.get('projectFlockKandangId');
|
||||||
|
|
||||||
const { data: projectFlockKandang, isLoading: isLoadingProjectFlockKandang } =
|
const { data: projectFlockKandang, isLoading: isLoadingProjectFlockKandang } =
|
||||||
useSWR(projectFlockKandangId, (id: number) =>
|
useSWR(`get-flock-kandang-id/${projectFlockKandangId}`, () =>
|
||||||
ProjectFlockKandangApi.getSingle(id)
|
ProjectFlockKandangApi.getSingle(parseInt(projectFlockKandangId ?? ''))
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: projectFlock, isLoading: isLoadingProjectFlock } = useSWR(
|
const { data: projectFlock, isLoading: isLoadingProjectFlock } = useSWR(
|
||||||
projectFlockId,
|
`get-flock-id/${projectFlockId}`,
|
||||||
(id: number) => ProjectFlockApi.getSingle(id)
|
() => ProjectFlockApi.getSingle(parseInt(projectFlockId ?? ''))
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!projectFlockId || !projectFlockKandangId) {
|
if (!projectFlockId || !projectFlockKandangId) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export default function ProjectFlockLayout({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const toggleValidate = useUiStore((s) => s.toggleValidate);
|
const toggleValidate = useUiStore((s) => s.toggleValidate);
|
||||||
|
|
||||||
const isAdd = pathname.endsWith('/add');
|
const isAdd = pathname.includes('/add');
|
||||||
const isEdit = pathname.includes('/detail/edit');
|
const isEdit = pathname.includes('/detail/edit');
|
||||||
const isDetail = pathname.includes('/detail');
|
const isDetail = pathname.includes('/detail');
|
||||||
const isChickin = pathname.includes('/chickin/add/kandang');
|
const isChickin = pathname.includes('/chickin/add/kandang');
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const RecordingEdit = () => {
|
|||||||
|
|
||||||
const { data: recording, isLoading: isLoadingRecording } = useSWR(
|
const { data: recording, isLoading: isLoadingRecording } = useSWR(
|
||||||
recordingId,
|
recordingId,
|
||||||
(id: number) => RecordingApi.getSingle(id) // Gunakan RecordingApi
|
(id: string) => RecordingApi.getSingle(parseInt(id))
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!recordingId) {
|
if (!recordingId) {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const RecordingDetail = () => {
|
|||||||
|
|
||||||
const { data: recording, isLoading: isLoadingRecording } = useSWR(
|
const { data: recording, isLoading: isLoadingRecording } = useSWR(
|
||||||
recordingId,
|
recordingId,
|
||||||
(id: number) => RecordingApi.getSingle(id)
|
(id: string) => RecordingApi.getSingle(parseInt(id))
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!recordingId) {
|
if (!recordingId) {
|
||||||
|
|||||||
@@ -1,87 +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 { user, setUser, setIsLoadingUser } = useAuth();
|
const router = useRouter();
|
||||||
|
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 (
|
// return (
|
||||||
isResponseError(userResponse) &&
|
// <div className='w-full flex flex-row justify-center items-center p-4'>
|
||||||
userErrorResponse?.response?.status === 401
|
// <span className='loading loading-spinner loading-xl' />
|
||||||
) {
|
// </div>
|
||||||
// Clear cache to prevent stale data from rendering children
|
// );
|
||||||
// mutate('/sso/userinfo', undefined, { revalidate: false }); // Optional: if using global mutate
|
// }
|
||||||
setUser(undefined);
|
|
||||||
redirectToSSO();
|
|
||||||
}
|
|
||||||
}, [userErrorResponse, setUser, userResponse]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
return <>{children}</>;
|
||||||
setIsLoadingUser(isLoadingUserResponse);
|
|
||||||
}, [isLoadingUserResponse]);
|
|
||||||
|
|
||||||
if (
|
|
||||||
(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) && user && children}</>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RequireAuth;
|
export default RequireAuth;
|
||||||
|
|||||||
@@ -144,33 +144,45 @@ const ApprovalSteps = ({ approvals }: ApprovalStepsProps) => {
|
|||||||
|
|
||||||
export const formatGroupedApprovalsToApprovalSteps = (
|
export const formatGroupedApprovalsToApprovalSteps = (
|
||||||
approvalLine: ApprovalLine,
|
approvalLine: ApprovalLine,
|
||||||
groupedApprovals: BaseGroupedApproval[],
|
groupedApprovals: BaseGroupedApproval[] | undefined,
|
||||||
latestApproval: BaseApproval
|
latestApproval: BaseApproval | undefined
|
||||||
): ApprovalStepsProps['approvals'] => {
|
): ApprovalStepsProps['approvals'] => {
|
||||||
const formattedApprovalSteps: ApprovalStepsProps['approvals'] =
|
const formattedApprovalSteps: ApprovalStepsProps['approvals'] =
|
||||||
approvalLine.map((approvalLineItem) => {
|
approvalLine.map((approvalLineItem) => {
|
||||||
const approvalGroup = groupedApprovals.find(
|
const approvalGroup = groupedApprovals?.find(
|
||||||
(approvalGroupItem) =>
|
(approvalGroupItem) =>
|
||||||
approvalGroupItem.step_number === approvalLineItem.step_number
|
approvalGroupItem.step_number === approvalLineItem.step_number
|
||||||
);
|
);
|
||||||
|
|
||||||
const currentStepNumber = approvalLineItem.step_number;
|
const currentStepNumber = approvalLineItem.step_number;
|
||||||
const lastStepNumber =
|
const lastStepNumber =
|
||||||
groupedApprovals[groupedApprovals.length - 1]?.step_number;
|
groupedApprovals?.[groupedApprovals.length - 1]?.step_number;
|
||||||
|
|
||||||
const isLatestApprovalRejected = latestApproval.action === 'REJECTED';
|
const isLatestApprovalRejected = latestApproval?.action === 'REJECTED';
|
||||||
|
|
||||||
if (!approvalGroup && currentStepNumber <= lastStepNumber) {
|
// Only throw error if we have a valid lastStepNumber to compare against
|
||||||
throw new Error(
|
if (
|
||||||
`Approval dengan ${approvalLineItem.step_name} tidak ditemukan!`
|
!approvalGroup &&
|
||||||
);
|
lastStepNumber !== undefined &&
|
||||||
|
currentStepNumber <= lastStepNumber
|
||||||
|
) {
|
||||||
|
// throw new Error(
|
||||||
|
// `Approval dengan ${approvalLineItem.step_name} tidak ditemukan!`
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!approvalGroup) {
|
if (!approvalGroup) {
|
||||||
const isWaiting = currentStepNumber === latestApproval.step_number + 1;
|
// Check if this step is waiting (only if we have latestApproval)
|
||||||
|
const isWaiting =
|
||||||
|
latestApproval?.step_number !== undefined &&
|
||||||
|
currentStepNumber === latestApproval.step_number + 1;
|
||||||
|
|
||||||
|
// Check if previous approval was rejected
|
||||||
const isPreviousApprovalRejected =
|
const isPreviousApprovalRejected =
|
||||||
groupedApprovals[groupedApprovals.length - 1].approvals[0].action ===
|
groupedApprovals &&
|
||||||
'REJECTED';
|
groupedApprovals.length > 0 &&
|
||||||
|
groupedApprovals[groupedApprovals.length - 1]?.approvals?.[0]
|
||||||
|
?.action === 'REJECTED';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: approvalLineItem.step_name,
|
name: approvalLineItem.step_name,
|
||||||
@@ -184,7 +196,11 @@ export const formatGroupedApprovalsToApprovalSteps = (
|
|||||||
|
|
||||||
let approvalStatus: ApprovalStepStatus = 'IDLE';
|
let approvalStatus: ApprovalStepStatus = 'IDLE';
|
||||||
|
|
||||||
if (approvalGroup.step_number <= latestApproval.step_number) {
|
// Only compare if latestApproval and its step_number exist
|
||||||
|
if (
|
||||||
|
latestApproval?.step_number !== undefined &&
|
||||||
|
approvalGroup.step_number <= latestApproval.step_number
|
||||||
|
) {
|
||||||
if (approvalGroup.approvals) {
|
if (approvalGroup.approvals) {
|
||||||
switch (approvalGroup?.approvals[0]?.action) {
|
switch (approvalGroup?.approvals[0]?.action) {
|
||||||
case 'CREATED':
|
case 'CREATED':
|
||||||
@@ -203,6 +219,7 @@ export const formatGroupedApprovalsToApprovalSteps = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
|
latestApproval?.step_number !== undefined &&
|
||||||
approvalGroup.step_number === latestApproval.step_number + 1 &&
|
approvalGroup.step_number === latestApproval.step_number + 1 &&
|
||||||
!isLatestApprovalRejected
|
!isLatestApprovalRejected
|
||||||
) {
|
) {
|
||||||
@@ -353,14 +370,33 @@ const useApprovalSteps = ({
|
|||||||
|
|
||||||
// Formatting Akhir
|
// Formatting Akhir
|
||||||
const approvals = useMemo(() => {
|
const approvals = useMemo(() => {
|
||||||
if (isLoading || !approvalLines.length || !latestApproval) {
|
if (isLoading || !approvalLines.length) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to derive latestApproval from groupedApprovals if not provided
|
||||||
|
let effectiveLatestApproval = latestApproval;
|
||||||
|
|
||||||
|
if (!effectiveLatestApproval && groupedApprovals.length > 0) {
|
||||||
|
// Get all approvals from grouped data
|
||||||
|
const allApprovals = groupedApprovals.flatMap((group) => group.approvals);
|
||||||
|
|
||||||
|
if (allApprovals.length > 0) {
|
||||||
|
// Use the most recent approval (last in array)
|
||||||
|
effectiveLatestApproval = allApprovals[allApprovals.length - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If still no latestApproval, return empty
|
||||||
|
if (!effectiveLatestApproval) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return formatGroupedApprovalsToApprovalSteps(
|
return formatGroupedApprovalsToApprovalSteps(
|
||||||
approvalLines,
|
approvalLines,
|
||||||
groupedApprovals,
|
groupedApprovals,
|
||||||
latestApproval
|
effectiveLatestApproval
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Gagal memformat approval steps:', error);
|
console.warn('Gagal memformat approval steps:', error);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
} from '@/types/api/closing';
|
} from '@/types/api/closing';
|
||||||
import ClosingSapronakTabContent from './ClosingSapronakTabContent';
|
import ClosingSapronakTabContent from './ClosingSapronakTabContent';
|
||||||
import ClosingSapronakCalculationTabContent from '@/components/pages/closing/ClosingSapronakCalculationTabContent';
|
import ClosingSapronakCalculationTabContent from '@/components/pages/closing/ClosingSapronakCalculationTabContent';
|
||||||
|
import ClosingOverheadTabContent from '@/components/pages/closing/ClosingOverheadTabContent';
|
||||||
import SalesReportTable from './sale/SalesReportTable';
|
import SalesReportTable from './sale/SalesReportTable';
|
||||||
|
|
||||||
interface ClosingDetailProps {
|
interface ClosingDetailProps {
|
||||||
@@ -48,7 +49,7 @@ const ClosingDetail: React.FC<ClosingDetailProps> = ({
|
|||||||
{
|
{
|
||||||
id: 'overhead',
|
id: 'overhead',
|
||||||
label: 'Overhead',
|
label: 'Overhead',
|
||||||
content: 'Overhead',
|
content: <ClosingOverheadTabContent projectFlockId={id} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'hppEkspedisi',
|
id: 'hppEkspedisi',
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import ClosingOverheadTable from '@/components/pages/closing/ClosingOverheadTable';
|
||||||
|
|
||||||
|
interface ClosingOverheadTabContentProps {
|
||||||
|
projectFlockId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ClosingOverheadTabContent = ({
|
||||||
|
projectFlockId,
|
||||||
|
}: ClosingOverheadTabContentProps) => {
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col gap-4'>
|
||||||
|
{projectFlockId && (
|
||||||
|
<ClosingOverheadTable projectFlockId={projectFlockId} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ClosingOverheadTabContent;
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
import Card from '@/components/Card';
|
||||||
|
import Table, { TABLE_DEFAULT_STYLING } from '@/components/Table';
|
||||||
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
import { cn, formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
||||||
|
import { ClosingApi } from '@/services/api/closing';
|
||||||
|
import { Overhead, OverheadTotal } from '@/types/api/closing';
|
||||||
|
import { ColumnDef } from '@tanstack/react-table';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
|
||||||
|
interface ClosingOverheadTableProps {
|
||||||
|
type?: 'detail';
|
||||||
|
projectFlockId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ClosingOverheadTable = ({
|
||||||
|
type,
|
||||||
|
projectFlockId,
|
||||||
|
}: ClosingOverheadTableProps) => {
|
||||||
|
const { data: overhead, isLoading: isLoadingOverhead } = useSWR(
|
||||||
|
`${ClosingApi.basePath}/${projectFlockId}/overhead`,
|
||||||
|
() => ClosingApi.getOverhead(projectFlockId),
|
||||||
|
{
|
||||||
|
keepPreviousData: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Helper function to create columns with footer support
|
||||||
|
const createColumns = (total?: OverheadTotal): ColumnDef<Overhead>[] => [
|
||||||
|
// Group untuk kolom tanpa footer
|
||||||
|
{
|
||||||
|
header: 'Nama Item',
|
||||||
|
accessorFn: (props) => props.item_name,
|
||||||
|
footer: 'Total Pengeluaran Overhead',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Satuan',
|
||||||
|
accessorFn: (props) => props.uom_name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Budget Pengajuan',
|
||||||
|
footer: '',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
id: 'budget_quantity',
|
||||||
|
header: 'Jumlah',
|
||||||
|
accessorFn: (props) =>
|
||||||
|
props.budget_quantity ? formatNumber(props.budget_quantity) : '-',
|
||||||
|
footer: total ? () => formatNumber(total.budget_quantity) : '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'budget_unit_price',
|
||||||
|
header: 'Harga Satuan',
|
||||||
|
accessorFn: (props) =>
|
||||||
|
props.budget_unit_price
|
||||||
|
? formatCurrency(props.budget_unit_price)
|
||||||
|
: '-',
|
||||||
|
footer: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'budget_total_amount',
|
||||||
|
header: 'Total',
|
||||||
|
accessorFn: (props) =>
|
||||||
|
props.budget_total_amount
|
||||||
|
? formatCurrency(props.budget_total_amount)
|
||||||
|
: '-',
|
||||||
|
footer: total ? () => formatCurrency(total.budget_total_amount) : '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Realisasi',
|
||||||
|
footer: '',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
id: 'actual_date',
|
||||||
|
header: 'Tanggal',
|
||||||
|
accessorFn: (props) =>
|
||||||
|
props.actual_date
|
||||||
|
? formatDate(props.actual_date, 'DD MMM, YYYY')
|
||||||
|
: '-',
|
||||||
|
footer: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'actual_quantity',
|
||||||
|
header: 'Jumlah',
|
||||||
|
accessorFn: (props) =>
|
||||||
|
props.actual_quantity ? formatNumber(props.actual_quantity) : '-',
|
||||||
|
footer: total ? () => formatNumber(total.actual_quantity) : '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'actual_unit_price',
|
||||||
|
header: 'Harga Satuan',
|
||||||
|
accessorFn: (props) =>
|
||||||
|
props.actual_unit_price
|
||||||
|
? formatCurrency(props.actual_unit_price)
|
||||||
|
: '-',
|
||||||
|
footer: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'actual_total_amount',
|
||||||
|
header: 'Total',
|
||||||
|
accessorFn: (props) =>
|
||||||
|
props.actual_total_amount
|
||||||
|
? formatCurrency(props.actual_total_amount)
|
||||||
|
: '-',
|
||||||
|
footer: total ? () => formatCurrency(total.actual_total_amount) : '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cost_per_bird',
|
||||||
|
header: 'Rp/Ekor',
|
||||||
|
accessorFn: (props) =>
|
||||||
|
props.cost_per_bird ? formatCurrency(props.cost_per_bird) : '-',
|
||||||
|
footer: total ? () => formatCurrency(total.cost_per_bird) : '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const columns = useMemo(
|
||||||
|
() =>
|
||||||
|
isResponseSuccess(overhead)
|
||||||
|
? createColumns(overhead.data?.total)
|
||||||
|
: createColumns(),
|
||||||
|
[overhead]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Card
|
||||||
|
title='Pengeluaran Overhead'
|
||||||
|
collapsible
|
||||||
|
defaultCollapsed={false}
|
||||||
|
className={{
|
||||||
|
wrapper: 'w-full',
|
||||||
|
body: 'p-4 shadow',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Table<Overhead>
|
||||||
|
data={
|
||||||
|
isResponseSuccess(overhead) ? (overhead.data?.overheads ?? []) : []
|
||||||
|
}
|
||||||
|
columns={columns}
|
||||||
|
className={{
|
||||||
|
containerClassName: 'my-4',
|
||||||
|
headerColumnClassName: cn(
|
||||||
|
TABLE_DEFAULT_STYLING.headerColumnClassName,
|
||||||
|
'whitespace-nowrap'
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
renderFooter={
|
||||||
|
isResponseSuccess(overhead)
|
||||||
|
? overhead.data?.overheads.length > 0
|
||||||
|
: false
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ClosingOverheadTable;
|
||||||
@@ -25,7 +25,10 @@ const ClosingSapronakCalculationTable = ({
|
|||||||
}: ClosingSapronakCalculationTableProps) => {
|
}: ClosingSapronakCalculationTableProps) => {
|
||||||
const { data: sapronakCalculation, isLoading } = useSWR(
|
const { data: sapronakCalculation, isLoading } = useSWR(
|
||||||
`/closing/sapronak-calculation/${projectFlockId}`,
|
`/closing/sapronak-calculation/${projectFlockId}`,
|
||||||
() => ClosingApi.getPerhitunganSapronak(projectFlockId)
|
() => ClosingApi.getPerhitunganSapronak(projectFlockId),
|
||||||
|
{
|
||||||
|
keepPreviousData: true,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Helper function to create columns with footer support
|
// Helper function to create columns with footer support
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ import { useState } from 'react';
|
|||||||
import ApprovalSteps, {
|
import ApprovalSteps, {
|
||||||
useApprovalSteps,
|
useApprovalSteps,
|
||||||
} from '@/components/pages/ApprovalSteps';
|
} from '@/components/pages/ApprovalSteps';
|
||||||
import { PROJECT_FLOCK_KANDANG_APPROVAL_LINE } from '@/config/approval-line';
|
|
||||||
import ChickinFormView from '@/components/pages/production/chickin/form/tabs/ChickinFormView';
|
import ChickinFormView from '@/components/pages/production/chickin/form/tabs/ChickinFormView';
|
||||||
import ChickinLogsView from '@/components/pages/production/chickin/form/tabs/ChickLogsView';
|
import ChickinLogsView from '@/components/pages/production/chickin/form/tabs/ChickLogsView';
|
||||||
import DrawerHeader from '@/components/helper/drawer/DrawerHeader';
|
import DrawerHeader from '@/components/helper/drawer/DrawerHeader';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import Badge from '@/components/Badge';
|
import Badge from '@/components/Badge';
|
||||||
|
import { CHICKINS_APPROVAL_LINE } from '@/config/approval-line';
|
||||||
const ChickinFormKandang = ({
|
const ChickinFormKandang = ({
|
||||||
formType = 'add',
|
formType = 'add',
|
||||||
initialValues,
|
initialValues,
|
||||||
@@ -34,8 +34,8 @@ const ChickinFormKandang = ({
|
|||||||
refresh: refreshApprovals,
|
refresh: refreshApprovals,
|
||||||
} = useApprovalSteps({
|
} = useApprovalSteps({
|
||||||
latestApproval: initialValues?.approval,
|
latestApproval: initialValues?.approval,
|
||||||
approvalLines: PROJECT_FLOCK_KANDANG_APPROVAL_LINE,
|
approvalLines: CHICKINS_APPROVAL_LINE,
|
||||||
moduleName: 'PROJECT_FLOCK_KANDANGS',
|
moduleName: 'CHICKINS',
|
||||||
moduleId: initialValues?.id.toString() ?? '',
|
moduleId: initialValues?.id.toString() ?? '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { ProjectFlock } from '@/types/api/production/project-flock';
|
|||||||
import {
|
import {
|
||||||
ClosingExpense,
|
ClosingExpense,
|
||||||
ProjectFlockKandang,
|
ProjectFlockKandang,
|
||||||
|
StockItem,
|
||||||
} from '@/types/api/production/project-flock-kandang';
|
} from '@/types/api/production/project-flock-kandang';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
@@ -41,9 +42,9 @@ const ProjectFlockClosingForm = ({
|
|||||||
const confirmationModalCloseClickHandler = async () => {
|
const confirmationModalCloseClickHandler = async () => {
|
||||||
setIsClosingLoading(true);
|
setIsClosingLoading(true);
|
||||||
const deleteProjectFlockRes = await ProjectFlockKandangApi.closing(
|
const deleteProjectFlockRes = await ProjectFlockKandangApi.closing(
|
||||||
projectFlock?.id as number,
|
projectFlockKandang?.id as number,
|
||||||
{
|
{
|
||||||
closed_date: formatDate(new Date(), 'yyyy-MM-dd'),
|
closed_date: formatDate(new Date(), 'YYYY-MM-DD'),
|
||||||
action: isCanClose ? 'close' : 'unclose',
|
action: isCanClose ? 'close' : 'unclose',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -62,16 +63,16 @@ const ProjectFlockClosingForm = ({
|
|||||||
const errorStock = useMemo(() => {
|
const errorStock = useMemo(() => {
|
||||||
return isResponseSuccess(closingData)
|
return isResponseSuccess(closingData)
|
||||||
? closingData?.data?.stock_remaining.every((stock) => stock.quantity > 0)
|
? closingData?.data?.stock_remaining.every((stock) => stock.quantity > 0)
|
||||||
: false;
|
: true;
|
||||||
}, [closingData]);
|
}, [closingData]);
|
||||||
|
|
||||||
const errorExpense = useMemo(() => {
|
const errorExpense = useMemo(() => {
|
||||||
return isResponseSuccess(closingData)
|
return isResponseSuccess(closingData)
|
||||||
? closingData?.data?.expenses.every((expense) => expense.step < 5)
|
? closingData?.data?.expenses.every((expense) => expense.step < 5)
|
||||||
: false;
|
: true;
|
||||||
}, [closingData]);
|
}, [closingData]);
|
||||||
|
|
||||||
const isCanCloseValid = !errorStock && !errorExpense;
|
const isCanCloseValid = true;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -192,7 +193,7 @@ const ProjectFlockClosingForm = ({
|
|||||||
: 'error'
|
: 'error'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{formatTitleCase(props.row.original.status)}
|
{formatTitleCase(props.row.original.step_name)}
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -211,18 +212,18 @@ const ProjectFlockClosingForm = ({
|
|||||||
paginationClassName: 'hidden',
|
paginationClassName: 'hidden',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{errorExpense && (
|
{/* {errorExpense && (
|
||||||
<div className='text-center text-error'>
|
<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 */}
|
||||||
<div className='divider'></div>
|
<div className='divider'></div>
|
||||||
<div className='px-4 pb-4'>
|
<div className='px-4 pb-4'>
|
||||||
<h2 className='text-2xl font-semibold'>Persediaan Gudang</h2>
|
<h2 className='text-2xl font-semibold'>Persediaan Gudang</h2>
|
||||||
<Table<ProductWarehouse>
|
<Table<StockItem>
|
||||||
data={
|
data={
|
||||||
isResponseSuccess(closingData)
|
isResponseSuccess(closingData)
|
||||||
? closingData.data?.stock_remaining
|
? closingData.data?.stock_remaining
|
||||||
@@ -231,11 +232,11 @@ const ProjectFlockClosingForm = ({
|
|||||||
columns={[
|
columns={[
|
||||||
{
|
{
|
||||||
header: 'Product',
|
header: 'Product',
|
||||||
accessorKey: 'product.name',
|
accessorKey: 'product_name',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Kategori',
|
header: 'Kategori',
|
||||||
accessorKey: 'product.product_category.name',
|
accessorKey: 'product_category',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Quantity',
|
header: 'Quantity',
|
||||||
@@ -243,7 +244,7 @@ const ProjectFlockClosingForm = ({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'UOM',
|
header: 'UOM',
|
||||||
accessorKey: 'product.uom.name',
|
accessorKey: 'uom',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
className={{
|
className={{
|
||||||
@@ -259,11 +260,11 @@ const ProjectFlockClosingForm = ({
|
|||||||
paginationClassName: 'hidden',
|
paginationClassName: 'hidden',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{errorStock && (
|
{/* {errorStock && (
|
||||||
<div className='text-center text-error'>
|
<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'>
|
||||||
|
|||||||
@@ -23,7 +23,12 @@ import toast from 'react-hot-toast';
|
|||||||
import ApprovalSteps, {
|
import ApprovalSteps, {
|
||||||
useApprovalSteps,
|
useApprovalSteps,
|
||||||
} from '@/components/pages/ApprovalSteps';
|
} from '@/components/pages/ApprovalSteps';
|
||||||
import { PROJECT_FLOCK_APPROVAL_LINE } from '@/config/approval-line';
|
import {
|
||||||
|
PROJECT_FLOCK_APPROVAL_LINE,
|
||||||
|
PROJECT_FLOCK_KANDANGS_APPROVAL_LINE,
|
||||||
|
} from '@/config/approval-line';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
import { ProjectFlockKandangApi } from '@/services/api/production';
|
||||||
|
|
||||||
const ProjectFlockDetail = ({
|
const ProjectFlockDetail = ({
|
||||||
projectFlock,
|
projectFlock,
|
||||||
@@ -38,10 +43,23 @@ const ProjectFlockDetail = ({
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectedKandang = projectFlock.kandangs.find(
|
const selectedKandang = projectFlock.kandangs?.find(
|
||||||
(kandang) => kandang.id === Number(selectedKandangId)
|
(kandang) => kandang.id === Number(selectedKandangId)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { data: projectFlockKandang, isLoading: projectFlockKandangLoading } =
|
||||||
|
useSWR(
|
||||||
|
selectedKandangId
|
||||||
|
? `${ProjectFlockKandangApi.basePath}/get-detail/${selectedKandangId}`
|
||||||
|
: null,
|
||||||
|
selectedKandangId
|
||||||
|
? () =>
|
||||||
|
ProjectFlockKandangApi.getSingle(
|
||||||
|
Number(selectedKandang?.project_flock_kandang_id)
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
approvals,
|
approvals,
|
||||||
isLoading: approvalsLoading,
|
isLoading: approvalsLoading,
|
||||||
@@ -53,6 +71,20 @@ const ProjectFlockDetail = ({
|
|||||||
moduleId: projectFlock?.id.toString() ?? '',
|
moduleId: projectFlock?.id.toString() ?? '',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { approvals: kandangApprovals, isLoading: kandangApprovalsLoading } =
|
||||||
|
useApprovalSteps({
|
||||||
|
latestApproval:
|
||||||
|
selectedKandangId && isResponseSuccess(projectFlockKandang)
|
||||||
|
? projectFlockKandang?.data?.approval
|
||||||
|
: undefined,
|
||||||
|
approvalLines: PROJECT_FLOCK_KANDANGS_APPROVAL_LINE,
|
||||||
|
moduleName: 'PROJECT_FLOCK_KANDANGS',
|
||||||
|
moduleId:
|
||||||
|
selectedKandangId && isResponseSuccess(projectFlockKandang)
|
||||||
|
? projectFlockKandang?.data?.id?.toString()
|
||||||
|
: '',
|
||||||
|
});
|
||||||
|
|
||||||
const confirmationModalDeleteClickHandler = async () => {
|
const confirmationModalDeleteClickHandler = async () => {
|
||||||
setIsDeleteLoading(true);
|
setIsDeleteLoading(true);
|
||||||
const deleteProjectFlockRes = await ProjectFlockApi.delete(
|
const deleteProjectFlockRes = await ProjectFlockApi.delete(
|
||||||
@@ -151,7 +183,7 @@ const ProjectFlockDetail = ({
|
|||||||
className={{ badge: 'rounded-lg px-2' }}
|
className={{ badge: 'rounded-lg px-2' }}
|
||||||
>
|
>
|
||||||
<Icon icon='mdi:bookmark' width={12} height={12} />
|
<Icon icon='mdi:bookmark' width={12} height={12} />
|
||||||
{` ${formatTitleCase(projectFlock.category)}`}
|
{` ${formatTitleCase(projectFlock.category ?? '')}`}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
{/* Information Grid */}
|
{/* Information Grid */}
|
||||||
@@ -168,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>
|
||||||
|
|
||||||
@@ -194,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
|
||||||
@@ -204,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
|
||||||
@@ -213,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'>
|
||||||
@@ -221,7 +253,7 @@ const ProjectFlockDetail = ({
|
|||||||
Kategori
|
Kategori
|
||||||
</div>
|
</div>
|
||||||
<div className='col-span-2'>
|
<div className='col-span-2'>
|
||||||
{formatTitleCase(projectFlock.category)}
|
{formatTitleCase(projectFlock.category ?? '')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -231,6 +263,9 @@ const ProjectFlockDetail = ({
|
|||||||
<div className='border-t-1 border-gray-300'>
|
<div className='border-t-1 border-gray-300'>
|
||||||
<div className='p-4 flex flex-col gap-4'>
|
<div className='p-4 flex flex-col gap-4'>
|
||||||
<h2 className='text-2xl font-semibold'>Kandang Aktif</h2>
|
<h2 className='text-2xl font-semibold'>Kandang Aktif</h2>
|
||||||
|
{kandangApprovals && !kandangApprovalsLoading && (
|
||||||
|
<ApprovalSteps approvals={kandangApprovals} />
|
||||||
|
)}
|
||||||
{/* Badge Row */}
|
{/* Badge Row */}
|
||||||
<div className='flex flex-row gap-2'>
|
<div className='flex flex-row gap-2'>
|
||||||
<Badge
|
<Badge
|
||||||
@@ -246,7 +281,7 @@ const ProjectFlockDetail = ({
|
|||||||
height={12}
|
height={12}
|
||||||
color={'success'}
|
color={'success'}
|
||||||
/>{' '}
|
/>{' '}
|
||||||
Kandang Aktif ({projectFlock.kandangs.length})
|
Kandang Aktif ({projectFlock.kandangs?.length})
|
||||||
</Badge>
|
</Badge>
|
||||||
<div className='divider divider-horizontal p-0 m-0'></div>
|
<div className='divider divider-horizontal p-0 m-0'></div>
|
||||||
<Badge
|
<Badge
|
||||||
@@ -289,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'>
|
||||||
@@ -298,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'>
|
||||||
@@ -353,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
|
||||||
@@ -375,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>
|
||||||
@@ -392,14 +427,15 @@ 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' />
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href={`/production/project-flock/closing?projectFlockId=${projectFlock.id}&projectFlockKandangId=${selectedKandangId}`}
|
href={`/production/project-flock/closing?projectFlockId=${projectFlock.id}&projectFlockKandangId=${selectedKandang?.project_flock_kandang_id}`}
|
||||||
className='m-0 p-0'
|
className='m-0 p-0'
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
@@ -407,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' />
|
||||||
|
|||||||
@@ -50,12 +50,17 @@ const RowOptionsMenu = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isRecordingRejected = (recording: Recording) => {
|
||||||
|
return recording.approval?.action === 'REJECTED';
|
||||||
|
};
|
||||||
|
|
||||||
const isApproved = isRecordingApproved(props.row.original);
|
const isApproved = isRecordingApproved(props.row.original);
|
||||||
|
const isRejected = isRecordingRejected(props.row.original);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RowOptionsMenuWrapper type={type}>
|
<RowOptionsMenuWrapper type={type}>
|
||||||
<Button
|
<Button
|
||||||
href={`recording/detail/?recordingId=${props.row.original.id}`}
|
href={`/production/recording/detail/?recordingId=${props.row.original.id}`}
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
color='primary'
|
color='primary'
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
@@ -64,7 +69,7 @@ const RowOptionsMenu = ({
|
|||||||
Detail
|
Detail
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
href={`recording/detail/edit/?recordingId=${props.row.original.id}`}
|
href={`/production/recording/detail/edit/?recordingId=${props.row.original.id}`}
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
color='warning'
|
color='warning'
|
||||||
className='justify-start text-sm'
|
className='justify-start text-sm'
|
||||||
@@ -72,7 +77,7 @@ const RowOptionsMenu = ({
|
|||||||
<Icon icon='mdi:pencil-outline' width={16} height={16} />
|
<Icon icon='mdi:pencil-outline' width={16} height={16} />
|
||||||
Edit
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
{!isApproved && (
|
{!isApproved && !isRejected && (
|
||||||
<Button
|
<Button
|
||||||
onClick={approveClickHandler}
|
onClick={approveClickHandler}
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
@@ -83,7 +88,7 @@ const RowOptionsMenu = ({
|
|||||||
Approve
|
Approve
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{!isApproved && (
|
{!isApproved && !isRejected && (
|
||||||
<Button
|
<Button
|
||||||
onClick={rejectClickHandler}
|
onClick={rejectClickHandler}
|
||||||
variant='ghost'
|
variant='ghost'
|
||||||
@@ -510,7 +515,7 @@ const RecordingTable = () => {
|
|||||||
<div className='w-full flex flex-col xl:flex-row justify-between items-end xl:items-center gap-2'>
|
<div className='w-full flex flex-col xl:flex-row justify-between items-end xl:items-center gap-2'>
|
||||||
<div className='w-full sm:w-fit flex flex-col sm:flex-row self-start gap-2'>
|
<div className='w-full sm:w-fit flex flex-col sm:flex-row self-start gap-2'>
|
||||||
<Button
|
<Button
|
||||||
href='recording/add'
|
href='/production/recording/add'
|
||||||
variant='outline'
|
variant='outline'
|
||||||
color='primary'
|
color='primary'
|
||||||
className='w-full sm:w-fit'
|
className='w-full sm:w-fit'
|
||||||
|
|||||||
@@ -112,6 +112,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const isRecordingRejected = useCallback((recording?: Recording) => {
|
||||||
|
return recording?.approval?.action === 'REJECTED';
|
||||||
|
}, []);
|
||||||
|
|
||||||
// ===== PAYLOAD CREATION HELPERS =====
|
// ===== PAYLOAD CREATION HELPERS =====
|
||||||
const createGrowingPayload = useCallback(
|
const createGrowingPayload = useCallback(
|
||||||
(values: RecordingGrowingFormValues) => {
|
(values: RecordingGrowingFormValues) => {
|
||||||
@@ -1483,37 +1487,48 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
Kembali
|
Kembali
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{type === 'detail' && !isRecordingApproved(initialValues) && (
|
{type === 'detail' &&
|
||||||
<div className='flex flex-row gap-2'>
|
initialValues?.approval &&
|
||||||
<Button
|
!isRecordingApproved(initialValues) &&
|
||||||
variant='outline'
|
!isRecordingRejected(initialValues) && (
|
||||||
color='success'
|
<div className='flex flex-row gap-2'>
|
||||||
onClick={() => {
|
<Button
|
||||||
setApprovalNotes('');
|
variant='outline'
|
||||||
approveModal.openModal();
|
color='success'
|
||||||
}}
|
onClick={() => {
|
||||||
isLoading={isApproveLoading}
|
setApprovalNotes('');
|
||||||
className='w-full sm:w-fit'
|
approveModal.openModal();
|
||||||
>
|
}}
|
||||||
<Icon icon='material-symbols:check' width={24} height={24} />
|
isLoading={isApproveLoading}
|
||||||
Approve
|
className='w-full sm:w-fit'
|
||||||
</Button>
|
>
|
||||||
|
<Icon
|
||||||
|
icon='material-symbols:check'
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
/>
|
||||||
|
Approve
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant='outline'
|
variant='outline'
|
||||||
color='error'
|
color='error'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setApprovalNotes('');
|
setApprovalNotes('');
|
||||||
rejectModal.openModal();
|
rejectModal.openModal();
|
||||||
}}
|
}}
|
||||||
isLoading={isRejectLoading}
|
isLoading={isRejectLoading}
|
||||||
className='w-full sm:w-fit'
|
className='w-full sm:w-fit'
|
||||||
>
|
>
|
||||||
<Icon icon='material-symbols:close' width={24} height={24} />
|
<Icon
|
||||||
Reject
|
icon='material-symbols:close'
|
||||||
</Button>
|
width={24}
|
||||||
</div>
|
height={24}
|
||||||
)}
|
/>
|
||||||
|
Reject
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className='text-2xl font-bold text-center'>
|
<h1 className='text-2xl font-bold text-center'>
|
||||||
@@ -2803,7 +2818,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
|
|
||||||
{/* Approve Confirmation Modal */}
|
{/* Approve Confirmation Modal */}
|
||||||
{(type as 'add' | 'edit' | 'detail') === 'detail' &&
|
{(type as 'add' | 'edit' | 'detail') === 'detail' &&
|
||||||
!isRecordingApproved(initialValues) && (
|
!isRecordingApproved(initialValues) &&
|
||||||
|
!isRecordingRejected(initialValues) && (
|
||||||
<ConfirmationModalWithNotes
|
<ConfirmationModalWithNotes
|
||||||
ref={approveModal.ref}
|
ref={approveModal.ref}
|
||||||
type='success'
|
type='success'
|
||||||
|
|||||||
+1
-3
@@ -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,
|
|
||||||
}))
|
}))
|
||||||
: [],
|
: [],
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,28 @@ export const PROJECT_FLOCK_APPROVAL_LINE: ApprovalLine = [
|
|||||||
step_number: 2,
|
step_number: 2,
|
||||||
step_name: 'Aktif',
|
step_name: 'Aktif',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
step_number: 3,
|
||||||
|
step_name: 'Selesai',
|
||||||
|
},
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const PROJECT_FLOCK_KANDANG_APPROVAL_LINE: ApprovalLine = [
|
export const PROJECT_FLOCK_KANDANGS_APPROVAL_LINE: ApprovalLine = [
|
||||||
|
{
|
||||||
|
step_number: 1,
|
||||||
|
step_name: 'Pengajuan',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step_number: 2,
|
||||||
|
step_name: 'Disetujui',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
step_number: 3,
|
||||||
|
step_name: 'Selesai',
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export const CHICKINS_APPROVAL_LINE: ApprovalLine = [
|
||||||
{
|
{
|
||||||
step_number: 1,
|
step_number: 1,
|
||||||
step_name: 'Pengajuan',
|
step_name: 'Pengajuan',
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ import {
|
|||||||
ClosingGeneralInformation,
|
ClosingGeneralInformation,
|
||||||
ClosingIncomingSapronak,
|
ClosingIncomingSapronak,
|
||||||
ClosingOutgoingSapronak,
|
ClosingOutgoingSapronak,
|
||||||
|
ClosingOverhead,
|
||||||
ClosingSapronakCalculation,
|
ClosingSapronakCalculation,
|
||||||
} from '@/types/api/closing';
|
} from '@/types/api/closing';
|
||||||
import { CreatedUser, BaseApiResponse } from '@/types/api/api-general';
|
import { CreatedUser, BaseApiResponse } from '@/types/api/api-general';
|
||||||
@@ -846,6 +847,141 @@ export const dummySapronakCalculation: ClosingSapronakCalculation = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ======================
|
||||||
|
// 💰 Overhead Dummy Data
|
||||||
|
// ======================
|
||||||
|
export const dummyOverhead: ClosingOverhead = {
|
||||||
|
overheads: [
|
||||||
|
{
|
||||||
|
item_name: 'Expedisi DOC',
|
||||||
|
uom_name: 'Ekor',
|
||||||
|
budget_quantity: 500,
|
||||||
|
budget_unit_price: 8000,
|
||||||
|
budget_total_amount: 4000000,
|
||||||
|
actual_date: '',
|
||||||
|
actual_quantity: 0,
|
||||||
|
actual_unit_price: 0,
|
||||||
|
actual_total_amount: 0,
|
||||||
|
cost_per_bird: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
item_name: 'Solar',
|
||||||
|
uom_name: 'Liter',
|
||||||
|
budget_quantity: 0,
|
||||||
|
budget_unit_price: 0,
|
||||||
|
budget_total_amount: 0,
|
||||||
|
actual_date: today,
|
||||||
|
actual_quantity: 20,
|
||||||
|
actual_unit_price: 10000,
|
||||||
|
actual_total_amount: 200000,
|
||||||
|
cost_per_bird: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
item_name: 'Gaji Karyawan Kandang',
|
||||||
|
uom_name: 'Orang',
|
||||||
|
budget_quantity: 3,
|
||||||
|
budget_unit_price: 3000000,
|
||||||
|
budget_total_amount: 9000000,
|
||||||
|
actual_date: today,
|
||||||
|
actual_quantity: 3,
|
||||||
|
actual_unit_price: 3200000,
|
||||||
|
actual_total_amount: 9600000,
|
||||||
|
cost_per_bird: 640,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
item_name: 'Listrik Kandang',
|
||||||
|
uom_name: 'Bulan',
|
||||||
|
budget_quantity: 1,
|
||||||
|
budget_unit_price: 2500000,
|
||||||
|
budget_total_amount: 2500000,
|
||||||
|
actual_date: today,
|
||||||
|
actual_quantity: 1,
|
||||||
|
actual_unit_price: 2800000,
|
||||||
|
actual_total_amount: 2800000,
|
||||||
|
cost_per_bird: 187,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
item_name: 'Air Bersih',
|
||||||
|
uom_name: 'Bulan',
|
||||||
|
budget_quantity: 1,
|
||||||
|
budget_unit_price: 500000,
|
||||||
|
budget_total_amount: 500000,
|
||||||
|
actual_date: today,
|
||||||
|
actual_quantity: 1,
|
||||||
|
actual_unit_price: 450000,
|
||||||
|
actual_total_amount: 450000,
|
||||||
|
cost_per_bird: 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
item_name: 'Perbaikan Kandang',
|
||||||
|
uom_name: 'Paket',
|
||||||
|
budget_quantity: 1,
|
||||||
|
budget_unit_price: 3000000,
|
||||||
|
budget_total_amount: 3000000,
|
||||||
|
actual_date: yesterday,
|
||||||
|
actual_quantity: 1,
|
||||||
|
actual_unit_price: 3500000,
|
||||||
|
actual_total_amount: 3500000,
|
||||||
|
cost_per_bird: 233,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
item_name: 'Service Peralatan',
|
||||||
|
uom_name: 'Kali',
|
||||||
|
budget_quantity: 2,
|
||||||
|
budget_unit_price: 500000,
|
||||||
|
budget_total_amount: 1000000,
|
||||||
|
actual_date: lastWeek,
|
||||||
|
actual_quantity: 2,
|
||||||
|
actual_unit_price: 550000,
|
||||||
|
actual_total_amount: 1100000,
|
||||||
|
cost_per_bird: 73,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
item_name: 'ATK & Supplies',
|
||||||
|
uom_name: 'Paket',
|
||||||
|
budget_quantity: 1,
|
||||||
|
budget_unit_price: 500000,
|
||||||
|
budget_total_amount: 500000,
|
||||||
|
actual_date: today,
|
||||||
|
actual_quantity: 1,
|
||||||
|
actual_unit_price: 450000,
|
||||||
|
actual_total_amount: 450000,
|
||||||
|
cost_per_bird: 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
item_name: 'Biaya Komunikasi',
|
||||||
|
uom_name: 'Bulan',
|
||||||
|
budget_quantity: 1,
|
||||||
|
budget_unit_price: 300000,
|
||||||
|
budget_total_amount: 300000,
|
||||||
|
actual_date: today,
|
||||||
|
actual_quantity: 1,
|
||||||
|
actual_unit_price: 320000,
|
||||||
|
actual_total_amount: 320000,
|
||||||
|
cost_per_bird: 21,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
item_name: 'BBM Kendaraan Operasional',
|
||||||
|
uom_name: 'Liter',
|
||||||
|
budget_quantity: 200,
|
||||||
|
budget_unit_price: 10000,
|
||||||
|
budget_total_amount: 2000000,
|
||||||
|
actual_date: today,
|
||||||
|
actual_quantity: 220,
|
||||||
|
actual_unit_price: 10500,
|
||||||
|
actual_total_amount: 2310000,
|
||||||
|
cost_per_bird: 154,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
total: {
|
||||||
|
budget_quantity: 710,
|
||||||
|
budget_total_amount: 23300000,
|
||||||
|
actual_quantity: 250,
|
||||||
|
actual_total_amount: 24530000,
|
||||||
|
cost_per_bird: 1568,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// ======================
|
// ======================
|
||||||
// 🔧 Dummy API Response Functions
|
// 🔧 Dummy API Response Functions
|
||||||
// ======================
|
// ======================
|
||||||
@@ -982,3 +1118,19 @@ export const dummyGetPerhitunganSapronak = async (
|
|||||||
data: dummySapronakCalculation,
|
data: dummySapronakCalculation,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dummy implementation for getOverhead
|
||||||
|
* Returns overhead data
|
||||||
|
*/
|
||||||
|
export const dummyGetOverhead = async (
|
||||||
|
id: number
|
||||||
|
): Promise<BaseApiResponse<ClosingOverhead> | undefined> => {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 400));
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
status: 'success',
|
||||||
|
message: 'Data overhead berhasil diambil',
|
||||||
|
data: dummyOverhead,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
ClosingGeneralInformation,
|
ClosingGeneralInformation,
|
||||||
ClosingIncomingSapronak,
|
ClosingIncomingSapronak,
|
||||||
ClosingOutgoingSapronak,
|
ClosingOutgoingSapronak,
|
||||||
|
ClosingOverhead,
|
||||||
ClosingSapronakCalculation,
|
ClosingSapronakCalculation,
|
||||||
} from '@/types/api/closing';
|
} from '@/types/api/closing';
|
||||||
import { BaseApiResponse } from '@/types/api/api-general';
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
@@ -16,6 +17,7 @@ import {
|
|||||||
dummyGetAllOutgoingSapronakFetcher,
|
dummyGetAllOutgoingSapronakFetcher,
|
||||||
dummyGetGeneralInfo,
|
dummyGetGeneralInfo,
|
||||||
dummyGetPerhitunganSapronak,
|
dummyGetPerhitunganSapronak,
|
||||||
|
dummyGetOverhead,
|
||||||
} from '@/dummy/closing.dummy';
|
} from '@/dummy/closing.dummy';
|
||||||
import { httpClient, httpClientFetcher } from '@/services/http/client';
|
import { httpClient, httpClientFetcher } from '@/services/http/client';
|
||||||
import { ClosingSales } from '@/types/api/closing';
|
import { ClosingSales } from '@/types/api/closing';
|
||||||
@@ -91,12 +93,12 @@ export class ClosingApiService extends BaseApiService<Closing, null, null> {
|
|||||||
endpoint: string
|
endpoint: string
|
||||||
): Promise<BaseApiResponse<ClosingOutgoingSapronak[]>> {
|
): Promise<BaseApiResponse<ClosingOutgoingSapronak[]>> {
|
||||||
// TODO: Remove this block when backend is ready
|
// TODO: Remove this block when backend is ready
|
||||||
// return await dummyGetAllOutgoingSapronakFetcher();
|
return await dummyGetAllOutgoingSapronakFetcher();
|
||||||
|
|
||||||
// Uncomment this when backend is ready
|
// Uncomment this when backend is ready
|
||||||
return await httpClientFetcher<BaseApiResponse<ClosingOutgoingSapronak[]>>(
|
// return await httpClientFetcher<BaseApiResponse<ClosingOutgoingSapronak[]>>(
|
||||||
endpoint
|
// endpoint
|
||||||
);
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGeneralInfo(
|
async getGeneralInfo(
|
||||||
@@ -165,6 +167,33 @@ export class ClosingApiService extends BaseApiService<Closing, null, null> {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getOverhead(
|
||||||
|
id: number
|
||||||
|
): Promise<BaseApiResponse<ClosingOverhead> | undefined> {
|
||||||
|
// TODO: Remove this block when backend is ready
|
||||||
|
// try {
|
||||||
|
// return await dummyGetOverhead(id);
|
||||||
|
// } catch (error) {
|
||||||
|
// if (axios.isAxiosError<BaseApiResponse<ClosingOverhead>>(error)) {
|
||||||
|
// return error.response?.data;
|
||||||
|
// }
|
||||||
|
// return undefined;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Uncomment this when backend is ready
|
||||||
|
try {
|
||||||
|
const path = `${this.basePath}/${id}/overhead`;
|
||||||
|
return await httpClient<BaseApiResponse<ClosingOverhead>>(path, {
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (axios.isAxiosError<BaseApiResponse<ClosingOverhead>>(error)) {
|
||||||
|
return error.response?.data;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ClosingApi = new ClosingApiService('/closings');
|
export const ClosingApi = new ClosingApiService('/closings');
|
||||||
|
|||||||
@@ -54,120 +54,119 @@ export class ProjectFlockKandangService extends BaseApiService<
|
|||||||
id: number
|
id: number
|
||||||
): Promise<BaseApiResponse<CheckClosingResponse> | undefined> {
|
): Promise<BaseApiResponse<CheckClosingResponse> | undefined> {
|
||||||
// Dummy data - replace with actual API call when backend is ready
|
// Dummy data - replace with actual API call when backend is ready
|
||||||
return new Promise((resolve) => {
|
// return new Promise((resolve) => {
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
resolve({
|
// resolve({
|
||||||
code: 200,
|
// code: 200,
|
||||||
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',
|
||||||
brand: 'Brand A',
|
// brand: 'Brand A',
|
||||||
sku: 'PKN-STR-001',
|
// sku: 'PKN-STR-001',
|
||||||
product_price: 15000,
|
// product_price: 15000,
|
||||||
selling_price: 17000,
|
// selling_price: 17000,
|
||||||
tax: 0,
|
// tax: 0,
|
||||||
expiry_period: 365,
|
// expiry_period: 365,
|
||||||
flags: ['active'],
|
// flags: ['active'],
|
||||||
uom: {
|
// uom: {
|
||||||
id: 1,
|
// id: 1,
|
||||||
name: 'Kg',
|
// name: 'Kg',
|
||||||
created_user: {
|
// created_user: {
|
||||||
id: 1,
|
// id: 1,
|
||||||
id_user: 1,
|
// id_user: 1,
|
||||||
email: 'admin@example.com',
|
// email: 'admin@example.com',
|
||||||
name: 'Admin User',
|
// name: 'Admin User',
|
||||||
},
|
// },
|
||||||
created_at: '2024-01-01',
|
// created_at: '2024-01-01',
|
||||||
updated_at: '2024-01-01',
|
// updated_at: '2024-01-01',
|
||||||
},
|
// },
|
||||||
product_category: {
|
// product_category: {
|
||||||
id: 1,
|
// id: 1,
|
||||||
name: 'Pakan',
|
// name: 'Pakan',
|
||||||
code: 'PKN',
|
// code: 'PKN',
|
||||||
created_user: {
|
// created_user: {
|
||||||
id: 1,
|
// id: 1,
|
||||||
id_user: 1,
|
// id_user: 1,
|
||||||
email: 'admin@example.com',
|
// email: 'admin@example.com',
|
||||||
name: 'Admin User',
|
// name: 'Admin User',
|
||||||
},
|
// },
|
||||||
created_at: '2024-01-01',
|
// created_at: '2024-01-01',
|
||||||
updated_at: '2024-01-01',
|
// updated_at: '2024-01-01',
|
||||||
},
|
// },
|
||||||
suppliers: [],
|
// suppliers: [],
|
||||||
created_user: {
|
// created_user: {
|
||||||
id: 1,
|
// id: 1,
|
||||||
id_user: 1,
|
// id_user: 1,
|
||||||
email: 'admin@example.com',
|
// email: 'admin@example.com',
|
||||||
name: 'Admin User',
|
// name: 'Admin User',
|
||||||
},
|
// },
|
||||||
created_at: '2024-01-01',
|
// created_at: '2024-01-01',
|
||||||
updated_at: '2024-01-01',
|
// updated_at: '2024-01-01',
|
||||||
},
|
// },
|
||||||
warehouse: {
|
// warehouse: {
|
||||||
id: 1,
|
// id: 1,
|
||||||
name: 'Gudang Utama',
|
// name: 'Gudang Utama',
|
||||||
type: 'AREA',
|
// type: 'AREA',
|
||||||
area: {
|
// area: {
|
||||||
id: 1,
|
// id: 1,
|
||||||
name: 'Area 1',
|
// name: 'Area 1',
|
||||||
},
|
// },
|
||||||
created_user: {
|
// created_user: {
|
||||||
id: 1,
|
// id: 1,
|
||||||
id_user: 1,
|
// id_user: 1,
|
||||||
email: 'admin@example.com',
|
// email: 'admin@example.com',
|
||||||
name: 'Admin User',
|
// name: 'Admin User',
|
||||||
},
|
// },
|
||||||
created_at: '2024-01-01',
|
// created_at: '2024-01-01',
|
||||||
updated_at: '2024-01-01',
|
// updated_at: '2024-01-01',
|
||||||
},
|
// },
|
||||||
created_user: {
|
// created_user: {
|
||||||
id: 1,
|
// id: 1,
|
||||||
id_user: 1,
|
// id_user: 1,
|
||||||
email: 'admin@example.com',
|
// email: 'admin@example.com',
|
||||||
name: 'Admin User',
|
// name: 'Admin User',
|
||||||
},
|
// },
|
||||||
created_at: '2025-01-01',
|
// created_at: '2025-01-01',
|
||||||
updated_at: '2025-01-01',
|
// updated_at: '2025-01-01',
|
||||||
},
|
// },
|
||||||
],
|
// ],
|
||||||
expenses: [
|
// expenses: [
|
||||||
{
|
// {
|
||||||
id: 1,
|
// id: 1,
|
||||||
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',
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
id: 3,
|
// id: 3,
|
||||||
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',
|
||||||
},
|
// },
|
||||||
],
|
// ],
|
||||||
},
|
// },
|
||||||
});
|
// });
|
||||||
}, 500); // Simulate network delay
|
// }, 500); // Simulate network delay
|
||||||
});
|
// });
|
||||||
|
|
||||||
/*
|
|
||||||
// Original API call - uncomment when backend is ready
|
// Original API call - uncomment when backend is ready
|
||||||
try {
|
try {
|
||||||
const path = `${this.basePath}/${id}/closing/check`;
|
const path = `${this.basePath}/${id}/closing/check`;
|
||||||
@@ -181,7 +180,6 @@ export class ProjectFlockKandangService extends BaseApiService<
|
|||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
+27
@@ -114,4 +114,31 @@ export type ClosingSapronakCalculation = {
|
|||||||
ovk: ClosingSapronakCalculationItem;
|
ovk: ClosingSapronakCalculationItem;
|
||||||
pakan: ClosingSapronakCalculationItem;
|
pakan: ClosingSapronakCalculationItem;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ====== OVERHEAD ======
|
||||||
|
export type ClosingOverhead = {
|
||||||
|
overheads: Overhead[];
|
||||||
|
total: OverheadTotal;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Overhead = {
|
||||||
|
item_name: string;
|
||||||
|
uom_name: string;
|
||||||
|
budget_quantity: number;
|
||||||
|
budget_unit_price: number;
|
||||||
|
budget_total_amount: number;
|
||||||
|
actual_date: string;
|
||||||
|
actual_quantity: number;
|
||||||
|
actual_unit_price: number;
|
||||||
|
actual_total_amount: number;
|
||||||
|
cost_per_bird: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OverheadTotal = {
|
||||||
|
budget_quantity: number;
|
||||||
|
budget_total_amount: number;
|
||||||
|
actual_quantity: number;
|
||||||
|
actual_total_amount: number;
|
||||||
|
cost_per_bird: number;
|
||||||
|
};
|
||||||
export type ClosingSales = BaseMetadata & BaseClosingSales;
|
export type ClosingSales = BaseMetadata & BaseClosingSales;
|
||||||
|
|||||||
+19
-1
@@ -56,8 +56,26 @@ export type ClosingExpense = {
|
|||||||
reference_number: string;
|
reference_number: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// "flag_name": "PAKAN",
|
||||||
|
// "product_warehouse_id": 14,
|
||||||
|
// "product_id": 8,
|
||||||
|
// "product_name": "281 SPECIAL STARTER",
|
||||||
|
// "product_category": "Bahan Baku",
|
||||||
|
// "uom": "Kilogram",
|
||||||
|
// "quantity": 1100
|
||||||
|
|
||||||
|
export type StockItem = {
|
||||||
|
flag_name: string;
|
||||||
|
product_warehouse_id: number;
|
||||||
|
product_id: number;
|
||||||
|
product_name: string;
|
||||||
|
product_category: string;
|
||||||
|
uom: string;
|
||||||
|
quantity: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type CheckClosingResponse = {
|
export type CheckClosingResponse = {
|
||||||
unfinished_expenses: number;
|
unfinished_expenses: number;
|
||||||
stock_remaining: ProductWarehouse[];
|
stock_remaining: StockItem[];
|
||||||
expenses: ClosingExpense[];
|
expenses: ClosingExpense[];
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user