mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-23 06:45:46 +00:00
feat(FE): Closing Finance and adjust reports expense filter request
This commit is contained in:
@@ -12,10 +12,9 @@ const ReportExpense = () => {
|
|||||||
locationId: null,
|
locationId: null,
|
||||||
supplierId: null,
|
supplierId: null,
|
||||||
kandangId: null,
|
kandangId: null,
|
||||||
startDate: null,
|
nonstockId: null,
|
||||||
endDate: null,
|
realizationDate: null,
|
||||||
category: null,
|
category: null,
|
||||||
period: '',
|
|
||||||
search: '',
|
search: '',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -23,10 +22,9 @@ const ReportExpense = () => {
|
|||||||
location_id: params.locationId ?? '',
|
location_id: params.locationId ?? '',
|
||||||
supplier_id: params.supplierId ?? '',
|
supplier_id: params.supplierId ?? '',
|
||||||
kandang_id: params.kandangId ?? '',
|
kandang_id: params.kandangId ?? '',
|
||||||
start_date: params.startDate ?? '',
|
nonstock_id: params.nonstockId ?? '',
|
||||||
end_date: params.endDate ?? '',
|
realization_date: params.realizationDate ?? '',
|
||||||
category: params.category ?? '',
|
category: params.category ?? '',
|
||||||
period: params.period.toString(),
|
|
||||||
search: params.search,
|
search: params.search,
|
||||||
})}`;
|
})}`;
|
||||||
const { data: reportExpenses } = useSWR(reportUrl, () =>
|
const { data: reportExpenses } = useSWR(reportUrl, () =>
|
||||||
|
|||||||
+35
-17
@@ -60,6 +60,12 @@ export interface TableProps<TData extends object> {
|
|||||||
renderFooter?: boolean;
|
renderFooter?: boolean;
|
||||||
withCheckbox?: boolean;
|
withCheckbox?: boolean;
|
||||||
rowOptions?: number[];
|
rowOptions?: number[];
|
||||||
|
/**
|
||||||
|
* Custom row renderer. Should return a complete <tr> element or null.
|
||||||
|
* This gives full control over the row structure including colspan.
|
||||||
|
* Return null to render the default row.
|
||||||
|
*/
|
||||||
|
renderCustomRow?: (row: Row<TData>) => ReactNode | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DUMMY_SKELETON_DATA = [{}, {}, {}, {}, {}];
|
const DUMMY_SKELETON_DATA = [{}, {}, {}, {}, {}];
|
||||||
@@ -112,6 +118,7 @@ const Table = <TData extends object>({
|
|||||||
renderFooter = false,
|
renderFooter = false,
|
||||||
withCheckbox = false,
|
withCheckbox = false,
|
||||||
rowOptions = [10, 20, 50, 100],
|
rowOptions = [10, 20, 50, 100],
|
||||||
|
renderCustomRow,
|
||||||
}: TableProps<TData>) => {
|
}: TableProps<TData>) => {
|
||||||
const isServerSideTable =
|
const isServerSideTable =
|
||||||
totalItems !== undefined &&
|
totalItems !== undefined &&
|
||||||
@@ -305,24 +312,35 @@ const Table = <TData extends object>({
|
|||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody className={tableClassNames.tableBodyClassName}>
|
<tbody className={tableClassNames.tableBodyClassName}>
|
||||||
{table.getRowModel().rows.map((row) => (
|
{table.getRowModel().rows.map((row) => {
|
||||||
<tr key={row.id} className={tableClassNames.bodyRowClassName}>
|
const customRowContent = renderCustomRow?.(row);
|
||||||
{row.getVisibleCells().map((cell) => (
|
|
||||||
<td
|
|
||||||
key={cell.id}
|
|
||||||
className={cn(
|
|
||||||
{ 'first:w-9 first:pr-0': withCheckbox },
|
|
||||||
tableClassNames.bodyColumnClassName
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{!isLoading &&
|
|
||||||
flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
||||||
|
|
||||||
{isLoading && <div className='skeleton w-full h-4' />}
|
if (customRowContent) {
|
||||||
</td>
|
return renderCustomRow?.(row);
|
||||||
))}
|
}
|
||||||
</tr>
|
|
||||||
))}
|
return (
|
||||||
|
<tr key={row.id} className={tableClassNames.bodyRowClassName}>
|
||||||
|
{row.getVisibleCells().map((cell) => (
|
||||||
|
<td
|
||||||
|
key={cell.id}
|
||||||
|
className={cn(
|
||||||
|
{ 'first:w-9 first:pr-0': withCheckbox },
|
||||||
|
tableClassNames.bodyColumnClassName
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{!isLoading &&
|
||||||
|
flexRender(
|
||||||
|
cell.column.columnDef.cell,
|
||||||
|
cell.getContext()
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isLoading && <div className='skeleton w-full h-4' />}
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot className={cn(tableClassNames.tableFooterClassName)}>
|
<tfoot className={cn(tableClassNames.tableFooterClassName)}>
|
||||||
{renderFooter && (
|
{renderFooter && (
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ 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 ClosingOverheadTabContent from '@/components/pages/closing/ClosingOverheadTabContent';
|
||||||
import SalesReportTable from './sale/SalesReportTable';
|
import SalesReportTable from './sale/SalesReportTable';
|
||||||
|
import ClosingFinanceTabContent from '@/components/pages/closing/ClosingFinanceTabContent';
|
||||||
|
|
||||||
interface ClosingDetailProps {
|
interface ClosingDetailProps {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -64,7 +65,7 @@ const ClosingDetail: React.FC<ClosingDetailProps> = ({
|
|||||||
{
|
{
|
||||||
id: 'keuangan',
|
id: 'keuangan',
|
||||||
label: 'Keuangan',
|
label: 'Keuangan',
|
||||||
content: 'Keuangan',
|
content: <ClosingFinanceTabContent projectFlockId={id} />,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import ClosingFinanceTable from '@/components/pages/closing/ClosingFinanceTable';
|
||||||
|
|
||||||
|
const ClosingFinanceTabContent = ({
|
||||||
|
projectFlockId,
|
||||||
|
}: {
|
||||||
|
projectFlockId: number;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col gap-4'>
|
||||||
|
{projectFlockId && (
|
||||||
|
<ClosingFinanceTable projectFlockId={projectFlockId} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ClosingFinanceTabContent;
|
||||||
@@ -0,0 +1,518 @@
|
|||||||
|
import Card from '@/components/Card';
|
||||||
|
import Table, { TABLE_DEFAULT_STYLING } from '@/components/Table';
|
||||||
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
import { formatCurrency, formatTitleCase } from '@/lib/helper';
|
||||||
|
import { ClosingApi } from '@/services/api/closing';
|
||||||
|
import {
|
||||||
|
DataSummarySubTotal,
|
||||||
|
HppPurchaseData,
|
||||||
|
ProfitLossDataAmount,
|
||||||
|
} from '@/types/api/closing';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
|
||||||
|
type HppTableRow =
|
||||||
|
| (HppPurchaseData & {
|
||||||
|
group_name: string;
|
||||||
|
group_index: number;
|
||||||
|
isGroupHeader?: boolean;
|
||||||
|
})
|
||||||
|
| {
|
||||||
|
group_name: string;
|
||||||
|
group_index: number;
|
||||||
|
isGroupHeader: true;
|
||||||
|
type?: never;
|
||||||
|
budgeting?: never;
|
||||||
|
realization?: never;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ProfitLossTableRow =
|
||||||
|
| (DataSummarySubTotal & {
|
||||||
|
type: string;
|
||||||
|
group_name: string;
|
||||||
|
group_index: number;
|
||||||
|
isGroupHeader?: boolean;
|
||||||
|
})
|
||||||
|
| {
|
||||||
|
group_name: string;
|
||||||
|
group_index: number;
|
||||||
|
isGroupHeader: true;
|
||||||
|
type?: never;
|
||||||
|
rp_per_bird?: never;
|
||||||
|
rp_per_kg?: never;
|
||||||
|
amount?: never;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ClosingFinanceTable = ({
|
||||||
|
projectFlockId,
|
||||||
|
}: {
|
||||||
|
projectFlockId: number;
|
||||||
|
}) => {
|
||||||
|
const { data: finance, isLoading } = useSWR(
|
||||||
|
`/closing/finance/${projectFlockId}`,
|
||||||
|
() => ClosingApi.getFinance(projectFlockId)
|
||||||
|
);
|
||||||
|
|
||||||
|
const hppTableData: HppTableRow[] = isResponseSuccess(finance)
|
||||||
|
? finance.data.hpp_purchases.hpp.flatMap((hpp, groupIndex) => [
|
||||||
|
// Group header row
|
||||||
|
{
|
||||||
|
group_name: hpp.group_name,
|
||||||
|
group_index: groupIndex,
|
||||||
|
isGroupHeader: true as const,
|
||||||
|
},
|
||||||
|
// Data rows
|
||||||
|
...hpp.data.map((item) => ({
|
||||||
|
group_name: hpp.group_name,
|
||||||
|
group_index: groupIndex,
|
||||||
|
type: item.type,
|
||||||
|
budgeting: item.budgeting,
|
||||||
|
realization: item.realization,
|
||||||
|
isGroupHeader: false as const,
|
||||||
|
})),
|
||||||
|
])
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const profitLossTableData: ProfitLossTableRow[] = isResponseSuccess(finance)
|
||||||
|
? [
|
||||||
|
// Penjualan group
|
||||||
|
{
|
||||||
|
label: 'Penjualan',
|
||||||
|
group_name: 'Penjualan',
|
||||||
|
group_index: 0,
|
||||||
|
isGroupHeader: true as const,
|
||||||
|
},
|
||||||
|
...finance.data.profit_loss.data.penjualan.map((item) => ({
|
||||||
|
label: 'Penjualan',
|
||||||
|
group_name: 'Penjualan',
|
||||||
|
group_index: 0,
|
||||||
|
type: item.type,
|
||||||
|
rp_per_bird: item.rp_per_bird,
|
||||||
|
rp_per_kg: item.rp_per_kg,
|
||||||
|
amount: item.amount,
|
||||||
|
isGroupHeader: false as const,
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
label: finance.data.profit_loss.data.summary.gross_profit.label,
|
||||||
|
group_name: 'Penjualan',
|
||||||
|
group_index: 0,
|
||||||
|
isGroupHeader: true as const,
|
||||||
|
type: finance.data.profit_loss.data.summary.gross_profit.label,
|
||||||
|
rp_per_bird:
|
||||||
|
finance.data.profit_loss.data.summary.gross_profit.rp_per_bird,
|
||||||
|
rp_per_kg:
|
||||||
|
finance.data.profit_loss.data.summary.gross_profit.rp_per_kg,
|
||||||
|
amount: finance.data.profit_loss.data.summary.gross_profit.amount,
|
||||||
|
},
|
||||||
|
// Pembelian group
|
||||||
|
{
|
||||||
|
label: 'Pembelian',
|
||||||
|
group_name: 'Pembelian',
|
||||||
|
group_index: 1,
|
||||||
|
isGroupHeader: true as const,
|
||||||
|
},
|
||||||
|
...finance.data.profit_loss.data.pembelian.map((item) => ({
|
||||||
|
label: 'Pembelian',
|
||||||
|
group_name: 'Pembelian',
|
||||||
|
group_index: 1,
|
||||||
|
type: item.type,
|
||||||
|
rp_per_bird: item.rp_per_bird,
|
||||||
|
rp_per_kg: item.rp_per_kg,
|
||||||
|
amount: item.amount,
|
||||||
|
isGroupHeader: false as const,
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
label: finance.data.profit_loss.data.summary.sub_total.label,
|
||||||
|
group_name: 'Pembelian',
|
||||||
|
group_index: 1,
|
||||||
|
isGroupHeader: true as const,
|
||||||
|
type: finance.data.profit_loss.data.summary.sub_total.label,
|
||||||
|
rp_per_bird:
|
||||||
|
finance.data.profit_loss.data.summary.sub_total.rp_per_bird,
|
||||||
|
rp_per_kg: finance.data.profit_loss.data.summary.sub_total.rp_per_kg,
|
||||||
|
amount: finance.data.profit_loss.data.summary.sub_total.amount,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col gap-4'>
|
||||||
|
{isResponseSuccess(finance) && (
|
||||||
|
<>
|
||||||
|
<Card
|
||||||
|
variant='bordered'
|
||||||
|
className={{
|
||||||
|
wrapper: 'w-full',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='grid grid-cols-2 gap-6'>
|
||||||
|
<div className='flex flex-col gap-1'>
|
||||||
|
<div>
|
||||||
|
{formatTitleCase(
|
||||||
|
finance.data.profit_loss.data.summary.gross_profit.label ||
|
||||||
|
'-'
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className='text-lg font-bold'>
|
||||||
|
{formatCurrency(
|
||||||
|
finance.data.profit_loss.data.summary.gross_profit.amount
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-col gap-1'>
|
||||||
|
<div>
|
||||||
|
{formatTitleCase(
|
||||||
|
finance.data.profit_loss.data.summary.net_profit.label ||
|
||||||
|
'-'
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className='text-lg font-bold'>
|
||||||
|
{formatCurrency(
|
||||||
|
finance.data.profit_loss.data.summary.net_profit.amount
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
<Card
|
||||||
|
title={finance.data.hpp_purchases.title}
|
||||||
|
variant='bordered'
|
||||||
|
collapsible
|
||||||
|
className={{
|
||||||
|
wrapper: 'w-full',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='mt-6 p-0 mb-0'>
|
||||||
|
<Table<HppTableRow>
|
||||||
|
data={hppTableData}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
header: 'No.',
|
||||||
|
enableSorting: false,
|
||||||
|
accessorFn: (item, index) => {
|
||||||
|
if (item.isGroupHeader) return '-';
|
||||||
|
// Calculate row number excluding group headers
|
||||||
|
const dataRowsBefore = hppTableData
|
||||||
|
.slice(0, index)
|
||||||
|
.filter((row) => !row.isGroupHeader).length;
|
||||||
|
return dataRowsBefore + 1;
|
||||||
|
},
|
||||||
|
footer: (props) => {
|
||||||
|
return 'HPP';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Type',
|
||||||
|
enableSorting: false,
|
||||||
|
accessorFn: (item) => formatTitleCase(item.type || '-'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Budgeting',
|
||||||
|
enableSorting: false,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
header: 'Rp/Ekor',
|
||||||
|
id: 'budgeting_rp_per_bird',
|
||||||
|
enableSorting: false,
|
||||||
|
accessorFn: (item) =>
|
||||||
|
formatCurrency(item.budgeting?.rp_per_bird || 0),
|
||||||
|
footer: (props) => {
|
||||||
|
return props.column.id === 'budgeting_rp_per_bird'
|
||||||
|
? formatCurrency(
|
||||||
|
finance.data.hpp_purchases.hpp.reduce(
|
||||||
|
(total, hpp) =>
|
||||||
|
total +
|
||||||
|
(finance.data.hpp_purchases.summary_hpp
|
||||||
|
.budgeting.rp_per_bird || 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: '-';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Rp/Kg',
|
||||||
|
id: 'budgeting_rp_per_kg',
|
||||||
|
enableSorting: false,
|
||||||
|
accessorFn: (item) =>
|
||||||
|
formatCurrency(item.budgeting?.rp_per_kg || 0),
|
||||||
|
footer: (props) => {
|
||||||
|
return props.column.id === 'budgeting_rp_per_kg'
|
||||||
|
? formatCurrency(
|
||||||
|
finance.data.hpp_purchases.hpp.reduce(
|
||||||
|
(total, hpp) =>
|
||||||
|
total +
|
||||||
|
(finance.data.hpp_purchases.summary_hpp
|
||||||
|
.budgeting.rp_per_kg || 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: '-';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Jumlah (Rp)',
|
||||||
|
id: 'budgeting_amount',
|
||||||
|
enableSorting: false,
|
||||||
|
accessorFn: (item) =>
|
||||||
|
formatCurrency(item.budgeting?.amount || 0),
|
||||||
|
footer: (props) => {
|
||||||
|
return props.column.id === 'budgeting_amount'
|
||||||
|
? formatCurrency(
|
||||||
|
finance.data.hpp_purchases.hpp.reduce(
|
||||||
|
(total, hpp) =>
|
||||||
|
total +
|
||||||
|
(finance.data.hpp_purchases.summary_hpp
|
||||||
|
.budgeting.amount || 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: '-';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Realization',
|
||||||
|
enableSorting: false,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
header: 'Rp/Ekor',
|
||||||
|
id: 'realization_rp_per_bird',
|
||||||
|
enableSorting: false,
|
||||||
|
accessorFn: (item) =>
|
||||||
|
formatCurrency(item.realization?.rp_per_bird || 0),
|
||||||
|
footer: (props) => {
|
||||||
|
return props.column.id === 'realization_rp_per_bird'
|
||||||
|
? formatCurrency(
|
||||||
|
finance.data.hpp_purchases.hpp.reduce(
|
||||||
|
(total, hpp) =>
|
||||||
|
total +
|
||||||
|
(finance.data.hpp_purchases.summary_hpp
|
||||||
|
.realization.rp_per_bird || 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: '-';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Rp/Kg',
|
||||||
|
id: 'realization_rp_per_kg',
|
||||||
|
enableSorting: false,
|
||||||
|
accessorFn: (item) =>
|
||||||
|
formatCurrency(item.realization?.rp_per_kg || 0),
|
||||||
|
footer: (props) => {
|
||||||
|
return props.column.id === 'realization_rp_per_kg'
|
||||||
|
? formatCurrency(
|
||||||
|
finance.data.hpp_purchases.hpp.reduce(
|
||||||
|
(total, hpp) =>
|
||||||
|
total +
|
||||||
|
(finance.data.hpp_purchases.summary_hpp
|
||||||
|
.realization.rp_per_kg || 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: '-';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Jumlah (Rp)',
|
||||||
|
id: 'realization_amount',
|
||||||
|
enableSorting: false,
|
||||||
|
accessorFn: (item) =>
|
||||||
|
formatCurrency(item.realization?.amount || 0),
|
||||||
|
footer: (props) => {
|
||||||
|
return props.column.id === 'realization_amount'
|
||||||
|
? formatCurrency(
|
||||||
|
finance.data.hpp_purchases.hpp.reduce(
|
||||||
|
(total, hpp) =>
|
||||||
|
total +
|
||||||
|
(finance.data.hpp_purchases.summary_hpp
|
||||||
|
.realization.amount || 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: '-';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
renderCustomRow={(row) => {
|
||||||
|
const rowData = row.original;
|
||||||
|
if (rowData.isGroupHeader) {
|
||||||
|
return (
|
||||||
|
<tr
|
||||||
|
key={row.id}
|
||||||
|
className={TABLE_DEFAULT_STYLING.bodyRowClassName}
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
className={TABLE_DEFAULT_STYLING.bodyColumnClassName}
|
||||||
|
></td>
|
||||||
|
<td
|
||||||
|
colSpan={7}
|
||||||
|
className={TABLE_DEFAULT_STYLING.bodyColumnClassName}
|
||||||
|
>
|
||||||
|
<div className='font-bold'>
|
||||||
|
{formatTitleCase(rowData.group_name ?? '-')}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}}
|
||||||
|
renderFooter
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
<Card
|
||||||
|
title={finance.data.profit_loss.title}
|
||||||
|
variant='bordered'
|
||||||
|
collapsible
|
||||||
|
className={{
|
||||||
|
wrapper: 'w-full',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='mt-6 p-0 mb-0'>
|
||||||
|
<Table<ProfitLossTableRow>
|
||||||
|
data={profitLossTableData}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
header: 'Type',
|
||||||
|
enableSorting: false,
|
||||||
|
accessorFn: (item) => item.type,
|
||||||
|
cell: (item) => (
|
||||||
|
<div className='ps-6'>
|
||||||
|
{formatTitleCase(item.row.original.type || '-')}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
footer: (item) => (
|
||||||
|
<div className='font-bold'>
|
||||||
|
{formatTitleCase(
|
||||||
|
finance.data.profit_loss.data.summary.net_profit
|
||||||
|
.label || '-'
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Rp/Ekor',
|
||||||
|
enableSorting: false,
|
||||||
|
accessorFn: (item) => formatCurrency(item.rp_per_bird || 0),
|
||||||
|
footer: (item) => (
|
||||||
|
<div className='font-bold'>
|
||||||
|
{formatCurrency(
|
||||||
|
finance.data.profit_loss.data.summary.net_profit
|
||||||
|
.rp_per_bird || 0
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Rp/Kg',
|
||||||
|
enableSorting: false,
|
||||||
|
accessorFn: (item) => formatCurrency(item.rp_per_kg || 0),
|
||||||
|
footer: (item) => (
|
||||||
|
<div className='font-bold'>
|
||||||
|
{formatCurrency(
|
||||||
|
finance.data.profit_loss.data.summary.net_profit
|
||||||
|
.rp_per_kg || 0
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Jumlah (Rp)',
|
||||||
|
enableSorting: false,
|
||||||
|
accessorFn: (item) => formatCurrency(item.amount || 0),
|
||||||
|
footer: (item) => (
|
||||||
|
<div className='font-bold'>
|
||||||
|
{formatCurrency(
|
||||||
|
finance.data.profit_loss.data.summary.net_profit
|
||||||
|
.amount || 0
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
renderCustomRow={(row) => {
|
||||||
|
const rowData = row.original;
|
||||||
|
if (rowData.isGroupHeader) {
|
||||||
|
if (rowData.amount) {
|
||||||
|
return (
|
||||||
|
<tr
|
||||||
|
key={row.id}
|
||||||
|
className={TABLE_DEFAULT_STYLING.footerRowClassName}
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
className={
|
||||||
|
TABLE_DEFAULT_STYLING.bodyColumnClassName
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className='font-bold'>
|
||||||
|
{formatTitleCase(rowData.label ?? '-')}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className={
|
||||||
|
TABLE_DEFAULT_STYLING.bodyColumnClassName
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className='font-bold'>
|
||||||
|
{formatCurrency(rowData.rp_per_bird ?? 0)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className={
|
||||||
|
TABLE_DEFAULT_STYLING.bodyColumnClassName
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className='font-bold'>
|
||||||
|
{formatCurrency(rowData.rp_per_kg ?? 0)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className={
|
||||||
|
TABLE_DEFAULT_STYLING.bodyColumnClassName
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className='font-bold'>
|
||||||
|
{formatCurrency(rowData.amount ?? 0)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<tr
|
||||||
|
key={row.id}
|
||||||
|
className={TABLE_DEFAULT_STYLING.bodyRowClassName}
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
colSpan={4}
|
||||||
|
className={TABLE_DEFAULT_STYLING.bodyColumnClassName}
|
||||||
|
>
|
||||||
|
<div className='font-bold'>
|
||||||
|
{formatTitleCase(rowData.group_name ?? '-')}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}}
|
||||||
|
className={{
|
||||||
|
paginationClassName: 'hidden',
|
||||||
|
}}
|
||||||
|
renderFooter
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ClosingFinanceTable;
|
||||||
@@ -12,7 +12,10 @@ import ExpenseStatusBadge from '@/components/pages/expense/ExpenseStatusBadge';
|
|||||||
import RealizationStatusBadge from '@/components/pages/expense/RealizationStatusBadge';
|
import RealizationStatusBadge from '@/components/pages/expense/RealizationStatusBadge';
|
||||||
import Table, { TABLE_DEFAULT_STYLING } from '@/components/Table';
|
import Table, { TABLE_DEFAULT_STYLING } from '@/components/Table';
|
||||||
import { cn, formatCurrency, formatDate } from '@/lib/helper';
|
import { cn, formatCurrency, formatDate } from '@/lib/helper';
|
||||||
import { ReportExpense } from '@/types/api/report/report-expense';
|
import {
|
||||||
|
ReportExpense,
|
||||||
|
ReportExpenseSearchParams,
|
||||||
|
} from '@/types/api/report/report-expense';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import { ColumnDef } from '@tanstack/react-table';
|
import { ColumnDef } from '@tanstack/react-table';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
@@ -23,16 +26,7 @@ const ReportExpenseTable = ({
|
|||||||
onSearch,
|
onSearch,
|
||||||
}: {
|
}: {
|
||||||
reportExpenses: ReportExpense[];
|
reportExpenses: ReportExpense[];
|
||||||
onSearch: (params: {
|
onSearch: (params: ReportExpenseSearchParams) => void;
|
||||||
locationId: string | null;
|
|
||||||
supplierId: string | null;
|
|
||||||
kandangId: string | null;
|
|
||||||
startDate: string | null;
|
|
||||||
endDate: string | null;
|
|
||||||
category: string | null;
|
|
||||||
period: string | number;
|
|
||||||
search: string;
|
|
||||||
}) => void;
|
|
||||||
}) => {
|
}) => {
|
||||||
const [selectedLocation, setSelectedLocation] = useState<OptionType | null>(
|
const [selectedLocation, setSelectedLocation] = useState<OptionType | null>(
|
||||||
null
|
null
|
||||||
@@ -46,10 +40,11 @@ const ReportExpenseTable = ({
|
|||||||
const [selectedKandang, setSelectedKandang] = useState<OptionType | null>(
|
const [selectedKandang, setSelectedKandang] = useState<OptionType | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
const [selectedNonstock, setSelectedNonstock] = useState<OptionType | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [startDate, setStartDate] = useState<string | null>(null);
|
const [realizationDate, setRealizationDate] = useState<string | null>(null);
|
||||||
const [endDate, setEndDate] = useState<string | null>(null);
|
|
||||||
const [period, setPeriod] = useState<number | string>('');
|
|
||||||
|
|
||||||
const { options: optionsLocation, isLoadingOptions: isLoadingLocation } =
|
const { options: optionsLocation, isLoadingOptions: isLoadingLocation } =
|
||||||
useSelect(`/master-data/locations`, 'id', 'name');
|
useSelect(`/master-data/locations`, 'id', 'name');
|
||||||
@@ -59,6 +54,8 @@ const ReportExpenseTable = ({
|
|||||||
useSelect(`/master-data/kandangs`, 'id', 'name', '', {
|
useSelect(`/master-data/kandangs`, 'id', 'name', '', {
|
||||||
location_id: selectedLocation?.value.toString() || '',
|
location_id: selectedLocation?.value.toString() || '',
|
||||||
});
|
});
|
||||||
|
const { options: optionsNonstock, isLoadingOptions: isLoadingNonstock } =
|
||||||
|
useSelect(`/master-data/nonstocks`, 'id', 'name');
|
||||||
|
|
||||||
const columns = useMemo((): ColumnDef<ReportExpense>[] => {
|
const columns = useMemo((): ColumnDef<ReportExpense>[] => {
|
||||||
return [
|
return [
|
||||||
@@ -92,13 +89,17 @@ const ReportExpenseTable = ({
|
|||||||
header: 'Kategori',
|
header: 'Kategori',
|
||||||
accessorKey: 'category',
|
accessorKey: 'category',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
header: 'Produk',
|
||||||
|
accessorFn: (row) => row.pengajuan.nonstock.name,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
header: 'Supplier',
|
header: 'Supplier',
|
||||||
accessorFn: (row) => row.supplier.name,
|
accessorFn: (row) => row.supplier.name,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Lokasi',
|
header: 'Lokasi',
|
||||||
accessorFn: (row) => row.location.name,
|
accessorFn: (row) => row.kandang.location.name,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
header: 'Kandang',
|
header: 'Kandang',
|
||||||
@@ -181,44 +182,31 @@ const ReportExpenseTable = ({
|
|||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
onSearch({
|
onSearch({
|
||||||
search,
|
search,
|
||||||
period,
|
realizationDate,
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
locationId: selectedLocation?.value.toString() ?? '',
|
locationId: selectedLocation?.value.toString() ?? '',
|
||||||
kandangId: selectedKandang?.value.toString() ?? '',
|
kandangId: selectedKandang?.value.toString() ?? '',
|
||||||
|
nonstockId: selectedNonstock?.value.toString() ?? '',
|
||||||
supplierId: selectedSupplier?.value.toString() ?? '',
|
supplierId: selectedSupplier?.value.toString() ?? '',
|
||||||
category: selectedCategory?.value.toString() ?? '',
|
category: selectedCategory?.value.toString() ?? '',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const handleSearchInput = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleSearchInput = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setSearch(e.target.value);
|
setSearch(e.target.value);
|
||||||
onSearch({
|
|
||||||
search: e.target.value,
|
|
||||||
period,
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
locationId: selectedLocation?.value.toString() ?? '',
|
|
||||||
kandangId: selectedKandang?.value.toString() ?? '',
|
|
||||||
supplierId: selectedSupplier?.value.toString() ?? '',
|
|
||||||
category: selectedCategory?.value.toString() ?? '',
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
setSearch('');
|
setSearch('');
|
||||||
setPeriod('');
|
setRealizationDate('');
|
||||||
setStartDate('');
|
|
||||||
setEndDate('');
|
|
||||||
setSelectedLocation(null);
|
setSelectedLocation(null);
|
||||||
setSelectedKandang(null);
|
setSelectedKandang(null);
|
||||||
|
setSelectedNonstock(null);
|
||||||
setSelectedSupplier(null);
|
setSelectedSupplier(null);
|
||||||
setSelectedCategory(null);
|
setSelectedCategory(null);
|
||||||
onSearch({
|
onSearch({
|
||||||
search: '',
|
search: '',
|
||||||
period: '',
|
realizationDate: '',
|
||||||
startDate: '',
|
|
||||||
endDate: '',
|
|
||||||
locationId: '',
|
locationId: '',
|
||||||
kandangId: '',
|
kandangId: '',
|
||||||
|
nonstockId: '',
|
||||||
supplierId: '',
|
supplierId: '',
|
||||||
category: '',
|
category: '',
|
||||||
});
|
});
|
||||||
@@ -283,6 +271,15 @@ const ReportExpenseTable = ({
|
|||||||
value={selectedSupplier}
|
value={selectedSupplier}
|
||||||
onChange={(option) => setSelectedSupplier(option as OptionType)}
|
onChange={(option) => setSelectedSupplier(option as OptionType)}
|
||||||
/>
|
/>
|
||||||
|
<SelectInput
|
||||||
|
isClearable
|
||||||
|
label='Produk'
|
||||||
|
options={optionsNonstock}
|
||||||
|
isLoading={isLoadingNonstock}
|
||||||
|
placeholder='Produk'
|
||||||
|
value={selectedNonstock}
|
||||||
|
onChange={(option) => setSelectedNonstock(option as OptionType)}
|
||||||
|
/>
|
||||||
<SelectInput
|
<SelectInput
|
||||||
isClearable
|
isClearable
|
||||||
label='Kategori'
|
label='Kategori'
|
||||||
@@ -292,7 +289,7 @@ const ReportExpenseTable = ({
|
|||||||
label: 'BOP',
|
label: 'BOP',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'NON BOP',
|
value: 'NON-BOP',
|
||||||
label: 'Non BOP',
|
label: 'Non BOP',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
@@ -300,26 +297,12 @@ const ReportExpenseTable = ({
|
|||||||
value={selectedCategory}
|
value={selectedCategory}
|
||||||
onChange={(option) => setSelectedCategory(option as OptionType)}
|
onChange={(option) => setSelectedCategory(option as OptionType)}
|
||||||
/>
|
/>
|
||||||
<NumberInput
|
|
||||||
label='Periode'
|
|
||||||
value={period}
|
|
||||||
onChange={(e) => setPeriod(e.target.value)}
|
|
||||||
name='periode'
|
|
||||||
placeholder='Periode'
|
|
||||||
/>
|
|
||||||
<DateInput
|
<DateInput
|
||||||
label='Tanggal Mulai'
|
label='Tanggal Realisasi'
|
||||||
value={startDate as string}
|
value={realizationDate as string}
|
||||||
onChange={(e) => setStartDate(e.target.value)}
|
onChange={(e) => setRealizationDate(e.target.value)}
|
||||||
name='start_date'
|
name='realization_date'
|
||||||
placeholder='Tanggal Mulai'
|
placeholder='Tanggal Realisasi'
|
||||||
/>
|
|
||||||
<DateInput
|
|
||||||
label='Tanggal Selesai'
|
|
||||||
value={endDate as string}
|
|
||||||
onChange={(e) => setEndDate(e.target.value)}
|
|
||||||
name='end_date'
|
|
||||||
placeholder='Tanggal Selesai'
|
|
||||||
/>
|
/>
|
||||||
<DebouncedTextInput
|
<DebouncedTextInput
|
||||||
label='Cari'
|
label='Cari'
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useMemo, useState } from 'react';
|
|||||||
import { formatCurrency, formatDate } from '@/lib/helper';
|
import { formatCurrency, formatDate } from '@/lib/helper';
|
||||||
import pdfStyles from '@/components/pages/report/expense/pdf/styles/ReportExpenseStyles';
|
import pdfStyles from '@/components/pages/report/expense/pdf/styles/ReportExpenseStyles';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
import ExpenseStatusBadge from '@/components/pages/expense/ExpenseStatusBadge';
|
||||||
|
|
||||||
interface ReportExpenseExportProps {
|
interface ReportExpenseExportProps {
|
||||||
data: ReportExpense[];
|
data: ReportExpense[];
|
||||||
@@ -134,58 +135,208 @@ const PDFDocument = ({ data }: { data: ReportExpense[] }) => {
|
|||||||
|
|
||||||
{/* Table */}
|
{/* Table */}
|
||||||
<View style={pdfStyles.table}>
|
<View style={pdfStyles.table}>
|
||||||
{/* Table Header */}
|
{/* Header Row 1: Group Headers */}
|
||||||
<View style={[pdfStyles.tableRow, pdfStyles.tableHeader]}>
|
<View style={[pdfStyles.tableRow, pdfStyles.tableHeader]}>
|
||||||
<View style={pdfStyles.tableCellHeader}>
|
<View
|
||||||
|
style={[
|
||||||
|
pdfStyles.tableCellNarrowHeader,
|
||||||
|
{ borderBottomWidth: 0 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
<Text>No</Text>
|
<Text>No</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCellHeader}>
|
<View
|
||||||
|
style={[
|
||||||
|
pdfStyles.tableCellWrapHeader,
|
||||||
|
{ borderBottomWidth: 0 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
<Text>No. PO</Text>
|
<Text>No. PO</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCellHeader}>
|
<View
|
||||||
|
style={[
|
||||||
|
pdfStyles.tableCellWrapHeader,
|
||||||
|
{ borderBottomWidth: 0 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
<Text>No. Referensi</Text>
|
<Text>No. Referensi</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCellHeader}>
|
<View
|
||||||
|
style={[
|
||||||
|
pdfStyles.tableCellMediumHeader,
|
||||||
|
{ borderBottomWidth: 0 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
<Text>Tgl Realisasi</Text>
|
<Text>Tgl Realisasi</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCellHeader}>
|
<View
|
||||||
|
style={[
|
||||||
|
pdfStyles.tableCellMediumHeader,
|
||||||
|
{ borderBottomWidth: 0 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
<Text>Tgl Transaksi</Text>
|
<Text>Tgl Transaksi</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCellHeader}>
|
<View
|
||||||
|
style={[
|
||||||
|
pdfStyles.tableCellXSmallHeader,
|
||||||
|
{ borderBottomWidth: 0 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
<Text>Kategori</Text>
|
<Text>Kategori</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCellHeader}>
|
<View
|
||||||
|
style={[
|
||||||
|
pdfStyles.tableCellHeader,
|
||||||
|
{ borderBottomWidth: 0 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text>Produk</Text>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
pdfStyles.tableCellSmallHeader,
|
||||||
|
{ borderBottomWidth: 0 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
<Text>Lokasi</Text>
|
<Text>Lokasi</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCellHeader}>
|
<View
|
||||||
|
style={[
|
||||||
|
pdfStyles.tableCellSmallHeader,
|
||||||
|
{ borderBottomWidth: 0 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
<Text>Kandang</Text>
|
<Text>Kandang</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCellHeader}>
|
|
||||||
<Text>Qty Pengajuan</Text>
|
{/* Pengajuan Group - spans 3 columns: XSmall + Medium + Medium */}
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
pdfStyles.tableCellXSmallHeader,
|
||||||
|
{ borderBottomWidth: 0, borderRightWidth: 0 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCellHeader}>
|
<View
|
||||||
<Text>Harga Pengajuan</Text>
|
style={[
|
||||||
|
pdfStyles.tableCellMediumHeader,
|
||||||
|
{
|
||||||
|
borderBottomWidth: 0,
|
||||||
|
borderRightWidth: 0,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text>Pengajuan</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCellHeader}>
|
<View
|
||||||
<Text>Total Pengajuan</Text>
|
style={[
|
||||||
|
pdfStyles.tableCellMediumHeader,
|
||||||
|
{ borderBottomWidth: 0 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCellHeader}>
|
|
||||||
<Text>Qty Realisasi</Text>
|
{/* Realisasi Group - spans 3 columns: XSmall + Medium + Medium */}
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
pdfStyles.tableCellXSmallHeader,
|
||||||
|
{ borderBottomWidth: 0, borderRightWidth: 0 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCellHeader}>
|
<View
|
||||||
<Text>Harga Realisasi</Text>
|
style={[
|
||||||
|
pdfStyles.tableCellMediumHeader,
|
||||||
|
{
|
||||||
|
borderBottomWidth: 0,
|
||||||
|
borderRightWidth: 0,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text>Realisasi</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCellHeader}>
|
<View
|
||||||
<Text>Total Realisasi</Text>
|
style={[
|
||||||
|
pdfStyles.tableCellMediumHeader,
|
||||||
|
{ borderBottomWidth: 0 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCellHeader}>
|
|
||||||
<Text>Status Pencairan</Text>
|
<View
|
||||||
</View>
|
style={[
|
||||||
<View style={pdfStyles.tableCellHeaderLast}>
|
pdfStyles.tableCellMediumHeader,
|
||||||
|
{ borderBottomWidth: 0 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
<Text>Status BOP</Text>
|
<Text>Status BOP</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* Header Row 2: Sub Headers */}
|
||||||
|
<View style={[pdfStyles.tableRow, pdfStyles.tableHeader]}>
|
||||||
|
<View style={pdfStyles.tableCellNarrowHeader}>
|
||||||
|
<Text></Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellWrapHeader}>
|
||||||
|
<Text></Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellWrapHeader}>
|
||||||
|
<Text></Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellMediumHeader}>
|
||||||
|
<Text></Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellMediumHeader}>
|
||||||
|
<Text></Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellXSmallHeader}>
|
||||||
|
<Text></Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellHeader}>
|
||||||
|
<Text></Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellSmallHeader}>
|
||||||
|
<Text></Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellSmallHeader}>
|
||||||
|
<Text></Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Pengajuan sub-headers */}
|
||||||
|
<View style={pdfStyles.tableCellXSmallHeader}>
|
||||||
|
<Text>Qty</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellMediumHeader}>
|
||||||
|
<Text>Harga</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellMediumHeader}>
|
||||||
|
<Text>Total</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Realisasi sub-headers */}
|
||||||
|
<View style={pdfStyles.tableCellXSmallHeader}>
|
||||||
|
<Text>Qty</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellMediumHeader}>
|
||||||
|
<Text>Harga</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellMediumHeader}>
|
||||||
|
<Text>Total</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={pdfStyles.tableCellMediumHeader}>
|
||||||
|
<Text></Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
{/* Table Body */}
|
{/* Table Body */}
|
||||||
{items.map((item, index) => {
|
{items.map((item, index) => {
|
||||||
const pengajuanTotal =
|
const pengajuanTotal =
|
||||||
@@ -195,74 +346,60 @@ const PDFDocument = ({ data }: { data: ReportExpense[] }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View key={index} style={pdfStyles.tableRow}>
|
<View key={index} style={pdfStyles.tableRow}>
|
||||||
<View style={pdfStyles.tableCell}>
|
<View style={pdfStyles.tableCellNarrow}>
|
||||||
<Text>{index + 1}</Text>
|
<Text>{index + 1}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCell}>
|
<View style={pdfStyles.tableCellWrap}>
|
||||||
<Text>{item.po_number}</Text>
|
<Text>{item.po_number}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCell}>
|
<View style={pdfStyles.tableCellWrap}>
|
||||||
<Text>{item.reference_number}</Text>
|
<Text>{item.reference_number}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCell}>
|
<View style={pdfStyles.tableCellMedium}>
|
||||||
<Text>
|
<Text>
|
||||||
{formatDate(item.realization_date, 'DD MMM YY')}
|
{formatDate(item.realization_date, 'DD MMM YY')}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCell}>
|
<View style={pdfStyles.tableCellMedium}>
|
||||||
<Text>
|
<Text>
|
||||||
{formatDate(item.transaction_date, 'DD MMM YY')}
|
{formatDate(item.transaction_date, 'DD MMM YY')}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCell}>
|
<View style={pdfStyles.tableCellXSmall}>
|
||||||
<Text>{item.category}</Text>
|
<Text>{item.category.split('-').join(' ')}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCell}>
|
<View style={pdfStyles.tableCell}>
|
||||||
<Text>{item.location.name}</Text>
|
<Text>{item.pengajuan.nonstock.name}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCell}>
|
<View style={pdfStyles.tableCellSmall}>
|
||||||
|
<Text>{item.kandang.location.name}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellSmall}>
|
||||||
<Text>{item.kandang.name}</Text>
|
<Text>{item.kandang.name}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCellRight}>
|
<View style={pdfStyles.tableCellRightXSmall}>
|
||||||
<Text>
|
<Text>
|
||||||
{item.pengajuan.qty.toLocaleString('id-ID')}
|
{item.pengajuan.qty.toLocaleString('id-ID')}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCellRight}>
|
<View style={pdfStyles.tableCellRightMedium}>
|
||||||
<Text>{formatCurrency(item.pengajuan.price)}</Text>
|
<Text>{formatCurrency(item.pengajuan.price)}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCellRight}>
|
<View style={pdfStyles.tableCellRightMedium}>
|
||||||
<Text>{formatCurrency(pengajuanTotal)}</Text>
|
<Text>{formatCurrency(pengajuanTotal)}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCellRight}>
|
<View style={pdfStyles.tableCellRightXSmall}>
|
||||||
<Text>
|
<Text>
|
||||||
{item.realisasi.qty.toLocaleString('id-ID')}
|
{item.realisasi.qty.toLocaleString('id-ID')}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCellRight}>
|
<View style={pdfStyles.tableCellRightMedium}>
|
||||||
<Text>{formatCurrency(item.realisasi.price)}</Text>
|
<Text>{formatCurrency(item.realisasi.price)}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCellRight}>
|
<View style={pdfStyles.tableCellRightMedium}>
|
||||||
<Text>{formatCurrency(realisasiTotal)}</Text>
|
<Text>{formatCurrency(realisasiTotal)}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCell}>
|
<View style={pdfStyles.tableCellMedium}>
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
fontSize: 7,
|
|
||||||
backgroundColor:
|
|
||||||
item.latest_approval.step_number === 3
|
|
||||||
? '#dcfce7'
|
|
||||||
: '#fef3c7',
|
|
||||||
padding: 2,
|
|
||||||
borderRadius: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item.latest_approval.step_number === 3
|
|
||||||
? 'Lunas'
|
|
||||||
: 'Belum Lunas'}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View style={pdfStyles.tableCellLast}>
|
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
fontSize: 7,
|
fontSize: 7,
|
||||||
@@ -276,7 +413,7 @@ const PDFDocument = ({ data }: { data: ReportExpense[] }) => {
|
|||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.latest_approval.action}
|
{item.latest_approval.step_name}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -285,78 +422,112 @@ const PDFDocument = ({ data }: { data: ReportExpense[] }) => {
|
|||||||
|
|
||||||
{/* Supplier Subtotal Row */}
|
{/* Supplier Subtotal Row */}
|
||||||
<View style={pdfStyles.grandTotalRow}>
|
<View style={pdfStyles.grandTotalRow}>
|
||||||
|
{/* Empty cells for columns before subtotal */}
|
||||||
<View
|
<View
|
||||||
style={[pdfStyles.tableCell, { borderRightWidth: 0 }]}
|
style={[
|
||||||
|
pdfStyles.tableCellNarrow,
|
||||||
|
{ borderRightWidth: 0 },
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Text></Text>
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
style={[pdfStyles.tableCell, { borderRightWidth: 0 }]}
|
style={[pdfStyles.tableCellWrap, { borderRightWidth: 0 }]}
|
||||||
>
|
>
|
||||||
<Text></Text>
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
style={[pdfStyles.tableCell, { borderRightWidth: 0 }]}
|
style={[pdfStyles.tableCellWrap, { borderRightWidth: 0 }]}
|
||||||
>
|
>
|
||||||
<Text></Text>
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
style={[pdfStyles.tableCell, { borderRightWidth: 0 }]}
|
style={[
|
||||||
|
pdfStyles.tableCellMedium,
|
||||||
|
{ borderRightWidth: 0 },
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Text></Text>
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
style={[pdfStyles.tableCell, { borderRightWidth: 0 }]}
|
style={[
|
||||||
|
pdfStyles.tableCellMedium,
|
||||||
|
{ borderRightWidth: 0 },
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Text></Text>
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
style={[pdfStyles.tableCell, { borderRightWidth: 0 }]}
|
style={[
|
||||||
|
pdfStyles.tableCellXSmall,
|
||||||
|
{ borderRightWidth: 0 },
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Text></Text>
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
style={[pdfStyles.tableCell, { borderRightWidth: 0 }]}
|
style={[
|
||||||
|
pdfStyles.tableCellXSmall,
|
||||||
|
{ borderRightWidth: 0 },
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Text></Text>
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
style={[pdfStyles.tableCell, { borderRightWidth: 0 }]}
|
style={[
|
||||||
|
pdfStyles.tableCellSmall,
|
||||||
|
{ borderRightWidth: 0 },
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Text></Text>
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
style={[pdfStyles.tableCell, { borderRightWidth: 0 }]}
|
style={[
|
||||||
|
pdfStyles.tableCellSmall,
|
||||||
|
{ borderRightWidth: 0 },
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Text></Text>
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCellRight}>
|
|
||||||
|
{/* Pengajuan Subtotal */}
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
pdfStyles.tableCellRightXSmall,
|
||||||
|
{ borderRightWidth: 0 },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text></Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellRightMedium}>
|
||||||
<Text style={{ fontWeight: 'bold' }}>Subtotal</Text>
|
<Text style={{ fontWeight: 'bold' }}>Subtotal</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCellRight}>
|
<View style={pdfStyles.tableCellRightMedium}>
|
||||||
<Text style={{ fontWeight: 'bold' }}>
|
<Text style={{ fontWeight: 'bold' }}>
|
||||||
{formatCurrency(supplierTotals.pengajuan)}
|
{formatCurrency(supplierTotals.pengajuan)}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* Realisasi Subtotal */}
|
||||||
<View
|
<View
|
||||||
style={[pdfStyles.tableCell, { borderRightWidth: 0 }]}
|
style={[
|
||||||
|
pdfStyles.tableCellRightXSmall,
|
||||||
|
{ borderRightWidth: 0 },
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Text></Text>
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCellRight}>
|
<View style={pdfStyles.tableCellRightMedium}>
|
||||||
<Text style={{ fontWeight: 'bold' }}>Subtotal</Text>
|
<Text style={{ fontWeight: 'bold' }}>Subtotal</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={pdfStyles.tableCellRight}>
|
<View style={pdfStyles.tableCellRightMedium}>
|
||||||
<Text style={{ fontWeight: 'bold' }}>
|
<Text style={{ fontWeight: 'bold' }}>
|
||||||
{formatCurrency(supplierTotals.realisasi)}
|
{formatCurrency(supplierTotals.realisasi)}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View
|
|
||||||
style={[pdfStyles.tableCell, { borderRightWidth: 0 }]}
|
{/* Empty cell for Status BOP */}
|
||||||
>
|
<View style={pdfStyles.tableCellMedium}>
|
||||||
<Text></Text>
|
|
||||||
</View>
|
|
||||||
<View style={pdfStyles.tableCellLast}>
|
|
||||||
<Text></Text>
|
<Text></Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -112,6 +112,159 @@ const pdfStyles = StyleSheet.create({
|
|||||||
fontSize: 7,
|
fontSize: 7,
|
||||||
textAlign: 'right',
|
textAlign: 'right',
|
||||||
},
|
},
|
||||||
|
tableCellNarrow: {
|
||||||
|
width: '1%',
|
||||||
|
minWidth: 20,
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: '#000000',
|
||||||
|
borderRightStyle: 'solid',
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
tableCellNarrowHeader: {
|
||||||
|
width: '1%',
|
||||||
|
minWidth: 20,
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: '#000000',
|
||||||
|
borderRightStyle: 'solid',
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
backgroundColor: '#F5F5F5',
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
tableCellWrap: {
|
||||||
|
flex: 1,
|
||||||
|
maxWidth: 80,
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: '#000000',
|
||||||
|
borderRightStyle: 'solid',
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
},
|
||||||
|
tableCellWrapHeader: {
|
||||||
|
flex: 1,
|
||||||
|
maxWidth: 80,
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: '#000000',
|
||||||
|
borderRightStyle: 'solid',
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
backgroundColor: '#F5F5F5',
|
||||||
|
},
|
||||||
|
// Nested header styles
|
||||||
|
tableHeaderGroup: {
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: '#000000',
|
||||||
|
borderRightStyle: 'solid',
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#000000',
|
||||||
|
borderBottomStyle: 'solid',
|
||||||
|
backgroundColor: '#F5F5F5',
|
||||||
|
},
|
||||||
|
tableHeaderGroupLast: {
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#000000',
|
||||||
|
borderBottomStyle: 'solid',
|
||||||
|
backgroundColor: '#F5F5F5',
|
||||||
|
},
|
||||||
|
tableHeaderGroupTitle: {
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
textAlign: 'center',
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#000000',
|
||||||
|
borderBottomStyle: 'solid',
|
||||||
|
},
|
||||||
|
tableSubHeaderRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
},
|
||||||
|
// Specific width columns
|
||||||
|
tableCellXSmall: {
|
||||||
|
width: 30,
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: '#000000',
|
||||||
|
borderRightStyle: 'solid',
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
},
|
||||||
|
tableCellXSmallHeader: {
|
||||||
|
width: 30,
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: '#000000',
|
||||||
|
borderRightStyle: 'solid',
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
backgroundColor: '#F5F5F5',
|
||||||
|
},
|
||||||
|
tableCellSmall: {
|
||||||
|
width: 40,
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: '#000000',
|
||||||
|
borderRightStyle: 'solid',
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
},
|
||||||
|
tableCellSmallHeader: {
|
||||||
|
width: 40,
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: '#000000',
|
||||||
|
borderRightStyle: 'solid',
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
backgroundColor: '#F5F5F5',
|
||||||
|
},
|
||||||
|
tableCellMedium: {
|
||||||
|
width: 60,
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: '#000000',
|
||||||
|
borderRightStyle: 'solid',
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
},
|
||||||
|
tableCellMediumHeader: {
|
||||||
|
width: 60,
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: '#000000',
|
||||||
|
borderRightStyle: 'solid',
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
backgroundColor: '#F5F5F5',
|
||||||
|
},
|
||||||
|
tableCellRightXSmall: {
|
||||||
|
width: 30,
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: '#000000',
|
||||||
|
borderRightStyle: 'solid',
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
textAlign: 'right',
|
||||||
|
},
|
||||||
|
tableCellRightSmall: {
|
||||||
|
width: 40,
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: '#000000',
|
||||||
|
borderRightStyle: 'solid',
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
textAlign: 'right',
|
||||||
|
},
|
||||||
|
tableCellRightMedium: {
|
||||||
|
width: 60,
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: '#000000',
|
||||||
|
borderRightStyle: 'solid',
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
textAlign: 'right',
|
||||||
|
},
|
||||||
tableBorderBottom: {
|
tableBorderBottom: {
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderBottomColor: '#000000',
|
borderBottomColor: '#000000',
|
||||||
@@ -142,7 +295,7 @@ const pdfStyles = StyleSheet.create({
|
|||||||
borderRightWidth: 0,
|
borderRightWidth: 0,
|
||||||
},
|
},
|
||||||
allocationSection: {
|
allocationSection: {
|
||||||
marginBottom: 15,
|
marginBottom: 8,
|
||||||
},
|
},
|
||||||
allocationTable: {
|
allocationTable: {
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
|
|||||||
@@ -0,0 +1,185 @@
|
|||||||
|
/**
|
||||||
|
* Dummy data for ClosingFinance
|
||||||
|
* Generated from: closing_keuangan.json
|
||||||
|
*
|
||||||
|
* This file is auto-generated. Do not edit manually.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ClosingFinance } from '../../types/api/closing';
|
||||||
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
|
|
||||||
|
const DUMMY_DATA: ClosingFinance = {
|
||||||
|
project_flock_id: 1,
|
||||||
|
period: 1,
|
||||||
|
project_type: 'LAYING',
|
||||||
|
volume_base: {
|
||||||
|
total_birds: 254435,
|
||||||
|
total_weight_kg: 499961,
|
||||||
|
},
|
||||||
|
hpp_purchases: {
|
||||||
|
title: 'Pembelian HPP Budgeting dan HPP Realisasi',
|
||||||
|
hpp: [
|
||||||
|
{
|
||||||
|
group_name: 'hpp dan pengeluaran',
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
type: 'Pembelian PULLET LAYER',
|
||||||
|
budgeting: {
|
||||||
|
rp_per_bird: 7458.82,
|
||||||
|
rp_per_kg: 3795.866,
|
||||||
|
amount: 1897784868,
|
||||||
|
},
|
||||||
|
realization: {
|
||||||
|
rp_per_bird: 7292.414,
|
||||||
|
rp_per_kg: 3711.18,
|
||||||
|
amount: 1855445430,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Pembelian OVK',
|
||||||
|
budgeting: {
|
||||||
|
rp_per_bird: 385.681,
|
||||||
|
rp_per_kg: 196.277,
|
||||||
|
amount: 98130789,
|
||||||
|
},
|
||||||
|
realization: {
|
||||||
|
rp_per_bird: 424.097,
|
||||||
|
rp_per_kg: 215.827,
|
||||||
|
amount: 107905006,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Pembelian Pakan',
|
||||||
|
budgeting: {
|
||||||
|
rp_per_bird: 23002.545,
|
||||||
|
rp_per_kg: 11706.218,
|
||||||
|
amount: 5852652652,
|
||||||
|
},
|
||||||
|
realization: {
|
||||||
|
rp_per_bird: 25193.973,
|
||||||
|
rp_per_kg: 12821.457,
|
||||||
|
amount: 6410228456,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
group_name: 'hpp dan bahan baku',
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
type: 'Pengeluaran Overhead',
|
||||||
|
budgeting: {
|
||||||
|
rp_per_bird: 6165.894,
|
||||||
|
rp_per_kg: 3137.883,
|
||||||
|
amount: 1568819297,
|
||||||
|
},
|
||||||
|
realization: {
|
||||||
|
rp_per_bird: 5975.831,
|
||||||
|
rp_per_kg: 3041.158,
|
||||||
|
amount: 1520460611,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Beban Ekspedisi',
|
||||||
|
budgeting: {
|
||||||
|
rp_per_bird: 304.218,
|
||||||
|
rp_per_kg: 154.819,
|
||||||
|
amount: 77403605,
|
||||||
|
},
|
||||||
|
realization: {
|
||||||
|
rp_per_bird: 237.466,
|
||||||
|
rp_per_kg: 120.849,
|
||||||
|
amount: 60419779,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
summary_hpp: {
|
||||||
|
label: 'HPP',
|
||||||
|
budgeting: {
|
||||||
|
rp_per_bird: 37317.158,
|
||||||
|
rp_per_kg: 18991.064,
|
||||||
|
amount: 9494791211,
|
||||||
|
},
|
||||||
|
realization: {
|
||||||
|
rp_per_bird: 39123.781,
|
||||||
|
rp_per_kg: 19910.472,
|
||||||
|
amount: 9954459282,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
profit_loss: {
|
||||||
|
title: 'Laba Rugi Perusahaan',
|
||||||
|
data: {
|
||||||
|
penjualan: [
|
||||||
|
{
|
||||||
|
type: 'Penjualan Telur dan Ayam Afkir',
|
||||||
|
rp_per_bird: 37551.535,
|
||||||
|
rp_per_kg: 19110.34,
|
||||||
|
amount: 9554424729,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pembelian: [
|
||||||
|
{
|
||||||
|
type: 'Pembelian Sapronak Supplier',
|
||||||
|
rp_per_bird: 27629.158,
|
||||||
|
rp_per_kg: 14060.746,
|
||||||
|
amount: 7029824870,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Pengeluaran Overhead',
|
||||||
|
rp_per_bird: 5975.831,
|
||||||
|
rp_per_kg: 3041.158,
|
||||||
|
amount: 1520460611,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Beban Ekspedisi',
|
||||||
|
rp_per_bird: 237.466,
|
||||||
|
rp_per_kg: 120.849,
|
||||||
|
amount: 60419779,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
summary: {
|
||||||
|
gross_profit: {
|
||||||
|
label: 'LABA RUGI BRUTTO',
|
||||||
|
rp_per_bird: 9922.376,
|
||||||
|
rp_per_kg: 5049.594,
|
||||||
|
amount: 2524599859,
|
||||||
|
},
|
||||||
|
sub_total: {
|
||||||
|
label: 'SUB TOTAL',
|
||||||
|
rp_per_bird: 3709.079,
|
||||||
|
rp_per_kg: 1887.586,
|
||||||
|
amount: 943719469,
|
||||||
|
},
|
||||||
|
net_profit: {
|
||||||
|
label: 'LABA RUGI NETTO',
|
||||||
|
rp_per_bird: 3709.079,
|
||||||
|
rp_per_kg: 1887.586,
|
||||||
|
amount: 943719469,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get dummy ClosingFinance data
|
||||||
|
* @param id - Optional ID parameter
|
||||||
|
* @returns Promise with BaseApiResponse containing ClosingFinance
|
||||||
|
*/
|
||||||
|
export async function dummyGetOneClosingFinance(
|
||||||
|
id?: number
|
||||||
|
): Promise<BaseApiResponse<ClosingFinance>> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve({
|
||||||
|
code: 200,
|
||||||
|
status: 'success',
|
||||||
|
message: 'Data retrieved successfully',
|
||||||
|
data: DUMMY_DATA,
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -406,7 +406,6 @@ export const dummyReportExpenses: ReportExpense[] = [
|
|||||||
supplier: dummySuppliers[0],
|
supplier: dummySuppliers[0],
|
||||||
realization_date: today,
|
realization_date: today,
|
||||||
transaction_date: yesterday,
|
transaction_date: yesterday,
|
||||||
location: dummyLocations[0],
|
|
||||||
pengajuan: dummyPengajuans[0],
|
pengajuan: dummyPengajuans[0],
|
||||||
realisasi: dummyRealisasis[0],
|
realisasi: dummyRealisasis[0],
|
||||||
kandang: dummyKandangs[0],
|
kandang: dummyKandangs[0],
|
||||||
@@ -431,7 +430,6 @@ export const dummyReportExpenses: ReportExpense[] = [
|
|||||||
supplier: dummySuppliers[0],
|
supplier: dummySuppliers[0],
|
||||||
realization_date: today,
|
realization_date: today,
|
||||||
transaction_date: yesterday,
|
transaction_date: yesterday,
|
||||||
location: dummyLocations[1],
|
|
||||||
pengajuan: dummyPengajuans[1],
|
pengajuan: dummyPengajuans[1],
|
||||||
realisasi: dummyRealisasis[1],
|
realisasi: dummyRealisasis[1],
|
||||||
kandang: dummyKandangs[1],
|
kandang: dummyKandangs[1],
|
||||||
@@ -456,7 +454,6 @@ export const dummyReportExpenses: ReportExpense[] = [
|
|||||||
supplier: dummySuppliers[1],
|
supplier: dummySuppliers[1],
|
||||||
realization_date: lastWeek,
|
realization_date: lastWeek,
|
||||||
transaction_date: lastWeek,
|
transaction_date: lastWeek,
|
||||||
location: dummyLocations[2],
|
|
||||||
pengajuan: dummyPengajuans[2],
|
pengajuan: dummyPengajuans[2],
|
||||||
realisasi: dummyRealisasis[2],
|
realisasi: dummyRealisasis[2],
|
||||||
kandang: dummyKandangs[2],
|
kandang: dummyKandangs[2],
|
||||||
@@ -481,7 +478,6 @@ export const dummyReportExpenses: ReportExpense[] = [
|
|||||||
supplier: dummySuppliers[2],
|
supplier: dummySuppliers[2],
|
||||||
realization_date: today,
|
realization_date: today,
|
||||||
transaction_date: yesterday,
|
transaction_date: yesterday,
|
||||||
location: dummyLocations[0],
|
|
||||||
pengajuan: dummyPengajuans[3],
|
pengajuan: dummyPengajuans[3],
|
||||||
realisasi: dummyRealisasis[3],
|
realisasi: dummyRealisasis[3],
|
||||||
kandang: dummyKandangs[0],
|
kandang: dummyKandangs[0],
|
||||||
@@ -506,7 +502,6 @@ export const dummyReportExpenses: ReportExpense[] = [
|
|||||||
supplier: dummySuppliers[1],
|
supplier: dummySuppliers[1],
|
||||||
realization_date: yesterday,
|
realization_date: yesterday,
|
||||||
transaction_date: lastWeek,
|
transaction_date: lastWeek,
|
||||||
location: dummyLocations[1],
|
|
||||||
pengajuan: dummyPengajuans[4],
|
pengajuan: dummyPengajuans[4],
|
||||||
realisasi: dummyRealisasis[4],
|
realisasi: dummyRealisasis[4],
|
||||||
kandang: dummyKandangs[1],
|
kandang: dummyKandangs[1],
|
||||||
@@ -531,7 +526,6 @@ export const dummyReportExpenses: ReportExpense[] = [
|
|||||||
supplier: dummySuppliers[0],
|
supplier: dummySuppliers[0],
|
||||||
realization_date: lastMonth,
|
realization_date: lastMonth,
|
||||||
transaction_date: lastMonth,
|
transaction_date: lastMonth,
|
||||||
location: dummyLocations[2],
|
|
||||||
pengajuan: {
|
pengajuan: {
|
||||||
id: 6,
|
id: 6,
|
||||||
expense_id: 6,
|
expense_id: 6,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import axios from 'axios';
|
|||||||
import { BaseApiService } from '@/services/api/base';
|
import { BaseApiService } from '@/services/api/base';
|
||||||
import {
|
import {
|
||||||
Closing,
|
Closing,
|
||||||
|
ClosingFinance,
|
||||||
ClosingGeneralInformation,
|
ClosingGeneralInformation,
|
||||||
ClosingIncomingSapronak,
|
ClosingIncomingSapronak,
|
||||||
ClosingOutgoingSapronak,
|
ClosingOutgoingSapronak,
|
||||||
@@ -21,6 +22,7 @@ import {
|
|||||||
} 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';
|
||||||
|
import { dummyGetOneClosingFinance } from '@/dummy/json/closing-finance.dummy';
|
||||||
|
|
||||||
export class ClosingApiService extends BaseApiService<Closing, null, null> {
|
export class ClosingApiService extends BaseApiService<Closing, null, null> {
|
||||||
constructor(basePath: string) {
|
constructor(basePath: string) {
|
||||||
@@ -193,6 +195,26 @@ export class ClosingApiService extends BaseApiService<Closing, null, null> {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getFinance(
|
||||||
|
id: number
|
||||||
|
): Promise<BaseApiResponse<ClosingFinance> | undefined> {
|
||||||
|
// TODO: Remove this block when backend is ready
|
||||||
|
// return dummyGetOneClosingFinance(id);
|
||||||
|
|
||||||
|
// Uncomment this when backend is ready
|
||||||
|
try {
|
||||||
|
const path = `${this.basePath}/${id}/finance`;
|
||||||
|
return await httpClient<BaseApiResponse<ClosingFinance>>(path, {
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (axios.isAxiosError<BaseApiResponse<ClosingFinance>>(error)) {
|
||||||
|
return error.response?.data;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ClosingApi = new ClosingApiService('/closings');
|
export const ClosingApi = new ClosingApiService('/closings');
|
||||||
|
|||||||
@@ -23,27 +23,6 @@ export class ReportExpenseApiService extends BaseApiService<
|
|||||||
// Uncomment this when backend is ready
|
// Uncomment this when backend is ready
|
||||||
return await httpClientFetcher<BaseApiResponse<ReportExpense[]>>(endpoint);
|
return await httpClientFetcher<BaseApiResponse<ReportExpense[]>>(endpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSingle(
|
|
||||||
id: number
|
|
||||||
): Promise<BaseApiResponse<ReportExpense> | undefined> {
|
|
||||||
// TODO: Remove this block when backend is ready
|
|
||||||
const { dummyGetSingle } = await import('@/dummy/report/expense.dummy');
|
|
||||||
return await dummyGetSingle(id);
|
|
||||||
|
|
||||||
// Uncomment this when backend is ready
|
|
||||||
// try {
|
|
||||||
// const getSinglePath = `${this.basePath}/${id}`;
|
|
||||||
// const getSingleRes =
|
|
||||||
// await httpClient<BaseApiResponse<ReportExpense>>(getSinglePath);
|
|
||||||
// return getSingleRes;
|
|
||||||
// } catch (error) {
|
|
||||||
// if (axios.isAxiosError<BaseApiResponse<ReportExpense>>(error)) {
|
|
||||||
// return error.response?.data;
|
|
||||||
// }
|
|
||||||
// return undefined;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ReportExpenseApi = new ReportExpenseApiService('/report/expense');
|
export const ReportExpenseApi = new ReportExpenseApiService('/reports/expense');
|
||||||
|
|||||||
Vendored
+75
@@ -142,3 +142,78 @@ export type OverheadTotal = {
|
|||||||
cost_per_bird: number;
|
cost_per_bird: number;
|
||||||
};
|
};
|
||||||
export type ClosingSales = BaseMetadata & BaseClosingSales;
|
export type ClosingSales = BaseMetadata & BaseClosingSales;
|
||||||
|
|
||||||
|
// ====== FINANCE ======
|
||||||
|
export interface ClosingFinance {
|
||||||
|
project_flock_id: number;
|
||||||
|
period: number;
|
||||||
|
project_type: string;
|
||||||
|
volume_base: ClosingFinanceVolumeBase;
|
||||||
|
hpp_purchases: ClosingFinanceHppPurchases;
|
||||||
|
profit_loss: ClosingFinanceProfitLoss;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClosingFinanceProfitLoss {
|
||||||
|
title: string;
|
||||||
|
data: ProfitLossData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClosingFinanceHppPurchases {
|
||||||
|
title: string;
|
||||||
|
hpp: GroupHppPurchase[];
|
||||||
|
summary_hpp: HppPurchasesSummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClosingFinanceVolumeBase {
|
||||||
|
total_birds: number;
|
||||||
|
total_weight_kg: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProfitLossData {
|
||||||
|
penjualan: ProfitLossDataAmount[];
|
||||||
|
pembelian: ProfitLossDataAmount[];
|
||||||
|
summary: ProfitLossDataSummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GroupHppPurchase {
|
||||||
|
group_name: string;
|
||||||
|
data: HppPurchaseData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProfitLossDataSummary {
|
||||||
|
gross_profit: DataSummarySubTotal;
|
||||||
|
sub_total: DataSummarySubTotal;
|
||||||
|
net_profit: DataSummarySubTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProfitLossDataAmount {
|
||||||
|
type: string;
|
||||||
|
rp_per_bird: number;
|
||||||
|
rp_per_kg: number;
|
||||||
|
amount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HppPurchasesSummary {
|
||||||
|
label: string;
|
||||||
|
budgeting: HppPurchaseDataAmount;
|
||||||
|
realization: HppPurchaseDataAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HppPurchaseData {
|
||||||
|
type: string;
|
||||||
|
budgeting: HppPurchaseDataAmount;
|
||||||
|
realization: HppPurchaseDataAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HppPurchaseDataAmount {
|
||||||
|
rp_per_bird: number;
|
||||||
|
rp_per_kg: number;
|
||||||
|
amount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DataSummarySubTotal {
|
||||||
|
label: string;
|
||||||
|
rp_per_bird: number;
|
||||||
|
rp_per_kg: number;
|
||||||
|
amount: number;
|
||||||
|
}
|
||||||
|
|||||||
+2
-4
@@ -35,7 +35,6 @@ export type ReportExpense = {
|
|||||||
supplier: Supplier;
|
supplier: Supplier;
|
||||||
realization_date: string;
|
realization_date: string;
|
||||||
transaction_date: string;
|
transaction_date: string;
|
||||||
location: Location;
|
|
||||||
pengajuan: Pengajuan;
|
pengajuan: Pengajuan;
|
||||||
realisasi: Realisasi;
|
realisasi: Realisasi;
|
||||||
kandang: Kandang;
|
kandang: Kandang;
|
||||||
@@ -49,9 +48,8 @@ export type ReportExpenseSearchParams = {
|
|||||||
locationId: string | null;
|
locationId: string | null;
|
||||||
supplierId: string | null;
|
supplierId: string | null;
|
||||||
kandangId: string | null;
|
kandangId: string | null;
|
||||||
startDate: string | null;
|
nonstockId: string | null;
|
||||||
endDate: string | null;
|
realizationDate: string | null;
|
||||||
category: string | null;
|
category: string | null;
|
||||||
period: string | number;
|
|
||||||
search: string;
|
search: string;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user