mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 15:55:48 +00:00
feat(FE-338): Slicing UI Halaman Reporting BOP & API integration & refactor debounce input: adding useEffect for sync value
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
import SuspenseHelper from '@/components/helper/SuspenseHelper';
|
||||||
|
|
||||||
|
const Layout = ({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) => {
|
||||||
|
return <SuspenseHelper>{children}</SuspenseHelper>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
const ReportExpenseDetail = () => {
|
||||||
|
return <div>ReportExpenseDetail</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReportExpenseDetail;
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import useSWR from 'swr';
|
||||||
|
import ReportExpenseTable from '@/components/pages/report/expense/ReportExpenseTable';
|
||||||
|
import { ReportExpenseApi } from '@/services/api/report';
|
||||||
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
import { ReportExpenseSearchParams } from '@/types/api/report/report-expense';
|
||||||
|
|
||||||
|
const ReportExpense = () => {
|
||||||
|
const [params, setParams] = useState<ReportExpenseSearchParams>({
|
||||||
|
locationId: null,
|
||||||
|
supplierId: null,
|
||||||
|
kandangId: null,
|
||||||
|
startDate: null,
|
||||||
|
endDate: null,
|
||||||
|
category: null,
|
||||||
|
period: '',
|
||||||
|
search: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const reportUrl = `${ReportExpenseApi.basePath}?${new URLSearchParams({
|
||||||
|
location_id: params.locationId ?? '',
|
||||||
|
supplier_id: params.supplierId ?? '',
|
||||||
|
kandang_id: params.kandangId ?? '',
|
||||||
|
start_date: params.startDate ?? '',
|
||||||
|
end_date: params.endDate ?? '',
|
||||||
|
category: params.category ?? '',
|
||||||
|
period: params.period.toString(),
|
||||||
|
search: params.search,
|
||||||
|
})}`;
|
||||||
|
const { data: reportExpenses } = useSWR(reportUrl, () =>
|
||||||
|
ReportExpenseApi.getAllFetcher(reportUrl)
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSearch = (searchParams: ReportExpenseSearchParams) => {
|
||||||
|
setParams(searchParams);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='w-full p-4'>
|
||||||
|
<ReportExpenseTable
|
||||||
|
reportExpenses={
|
||||||
|
isResponseSuccess(reportExpenses) ? reportExpenses.data : []
|
||||||
|
}
|
||||||
|
onSearch={onSearch}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReportExpense;
|
||||||
@@ -24,6 +24,11 @@ const DebouncedTextInput = (props: DebouncedTextInputProps) => {
|
|||||||
setInternalChangeEvent(e);
|
setInternalChangeEvent(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Sync internal value with external value prop changes (e.g., from reset)
|
||||||
|
useEffect(() => {
|
||||||
|
setInternalValue(props.value);
|
||||||
|
}, [props.value]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (debouncedChangeEvent) {
|
if (debouncedChangeEvent) {
|
||||||
onChange?.(debouncedChangeEvent);
|
onChange?.(debouncedChangeEvent);
|
||||||
|
|||||||
@@ -0,0 +1,346 @@
|
|||||||
|
import Badge from '@/components/Badge';
|
||||||
|
import Button from '@/components/Button';
|
||||||
|
import Card from '@/components/Card';
|
||||||
|
import DateInput from '@/components/input/DateInput';
|
||||||
|
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
|
||||||
|
import NumberInput from '@/components/input/NumberInput';
|
||||||
|
import SelectInput, {
|
||||||
|
OptionType,
|
||||||
|
useSelect,
|
||||||
|
} from '@/components/input/SelectInput';
|
||||||
|
import ExpenseStatusBadge from '@/components/pages/expense/ExpenseStatusBadge';
|
||||||
|
import RealizationStatusBadge from '@/components/pages/expense/RealizationStatusBadge';
|
||||||
|
import Table, { TABLE_DEFAULT_STYLING } from '@/components/Table';
|
||||||
|
import { cn, formatCurrency, formatDate } from '@/lib/helper';
|
||||||
|
import { ReportExpense } from '@/types/api/report/report-expense';
|
||||||
|
import { Icon } from '@iconify/react';
|
||||||
|
import { ColumnDef } from '@tanstack/react-table';
|
||||||
|
import { useMemo, useState } from 'react';
|
||||||
|
import ReportExpenseExport from '@/components/pages/report/expense/pdf/ReportExpenseExport';
|
||||||
|
|
||||||
|
const ReportExpenseTable = ({
|
||||||
|
reportExpenses,
|
||||||
|
onSearch,
|
||||||
|
}: {
|
||||||
|
reportExpenses: ReportExpense[];
|
||||||
|
onSearch: (params: {
|
||||||
|
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>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const [selectedSupplier, setSelectedSupplier] = useState<OptionType | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const [selectedCategory, setSelectedCategory] = useState<OptionType | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const [selectedKandang, setSelectedKandang] = useState<OptionType | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const [search, setSearch] = useState('');
|
||||||
|
const [startDate, setStartDate] = useState<string | null>(null);
|
||||||
|
const [endDate, setEndDate] = useState<string | null>(null);
|
||||||
|
const [period, setPeriod] = useState<number | string>('');
|
||||||
|
|
||||||
|
const { options: optionsLocation, isLoadingOptions: isLoadingLocation } =
|
||||||
|
useSelect(`/master-data/locations`, 'id', 'name');
|
||||||
|
const { options: optionsSupplier, isLoadingOptions: isLoadingSupplier } =
|
||||||
|
useSelect(`/master-data/suppliers`, 'id', 'name');
|
||||||
|
const { options: optionsKandang, isLoadingOptions: isLoadingKandang } =
|
||||||
|
useSelect(`/master-data/kandangs`, 'id', 'name', '', {
|
||||||
|
location_id: selectedLocation?.value.toString() || '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const columns = useMemo((): ColumnDef<ReportExpense>[] => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
header: 'No',
|
||||||
|
accessorFn: (_, index) => index + 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'No. PO',
|
||||||
|
accessorKey: 'po_number',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'No. Referensi',
|
||||||
|
accessorKey: 'reference_number',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Tanggal Realisasi',
|
||||||
|
accessorKey: 'realization_date',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return formatDate(row.original.realization_date, 'DD MMM, YYYY');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Tanggal Transaksi',
|
||||||
|
accessorKey: 'transaction_date',
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return formatDate(row.original.transaction_date, 'DD MMM, YYYY');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Kategori',
|
||||||
|
accessorKey: 'category',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Supplier',
|
||||||
|
accessorFn: (row) => row.supplier.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Lokasi',
|
||||||
|
accessorFn: (row) => row.location.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Kandang',
|
||||||
|
accessorFn: (row) => row.kandang.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Pengajuan',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
header: 'Qty',
|
||||||
|
id: 'qty_pengajuan',
|
||||||
|
accessorFn: (row) => row.pengajuan.qty,
|
||||||
|
cell: ({ row }) =>
|
||||||
|
row.original.pengajuan.qty.toLocaleString('id-ID'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Harga',
|
||||||
|
id: 'harga_pengajuan',
|
||||||
|
accessorFn: (row) => row.pengajuan.price,
|
||||||
|
cell: ({ row }) => formatCurrency(row.original.pengajuan.price),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Total',
|
||||||
|
id: 'total_pengajuan',
|
||||||
|
accessorFn: (row) => row.pengajuan.qty * row.pengajuan.price,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const total =
|
||||||
|
row.original.pengajuan.qty * row.original.pengajuan.price;
|
||||||
|
return formatCurrency(total);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Realisasi',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
header: 'Qty',
|
||||||
|
id: 'qty_realisasi',
|
||||||
|
accessorFn: (row) => row.realisasi.qty,
|
||||||
|
cell: ({ row }) =>
|
||||||
|
row.original.realisasi.qty.toLocaleString('id-ID'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Harga',
|
||||||
|
id: 'harga_realisasi',
|
||||||
|
accessorFn: (row) => row.realisasi.price,
|
||||||
|
cell: ({ row }) => formatCurrency(row.original.realisasi.price),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Total',
|
||||||
|
id: 'total_realisasi',
|
||||||
|
accessorFn: (row) => row.realisasi.qty * row.realisasi.price,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const total =
|
||||||
|
row.original.realisasi.qty * row.original.realisasi.price;
|
||||||
|
return formatCurrency(total);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Status Pencairan',
|
||||||
|
cell: (props) => (
|
||||||
|
<RealizationStatusBadge
|
||||||
|
approval={props.row.original.latest_approval}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: 'Status BOP',
|
||||||
|
cell: (props) => (
|
||||||
|
<ExpenseStatusBadge approval={props.row.original.latest_approval} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Handle Search
|
||||||
|
const handleSearch = () => {
|
||||||
|
onSearch({
|
||||||
|
search,
|
||||||
|
period,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
locationId: selectedLocation?.value.toString() ?? '',
|
||||||
|
kandangId: selectedKandang?.value.toString() ?? '',
|
||||||
|
supplierId: selectedSupplier?.value.toString() ?? '',
|
||||||
|
category: selectedCategory?.value.toString() ?? '',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const handleSearchInput = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
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 = () => {
|
||||||
|
setSearch('');
|
||||||
|
setPeriod('');
|
||||||
|
setStartDate('');
|
||||||
|
setEndDate('');
|
||||||
|
setSelectedLocation(null);
|
||||||
|
setSelectedKandang(null);
|
||||||
|
setSelectedSupplier(null);
|
||||||
|
setSelectedCategory(null);
|
||||||
|
onSearch({
|
||||||
|
search: '',
|
||||||
|
period: '',
|
||||||
|
startDate: '',
|
||||||
|
endDate: '',
|
||||||
|
locationId: '',
|
||||||
|
kandangId: '',
|
||||||
|
supplierId: '',
|
||||||
|
category: '',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col gap-4'>
|
||||||
|
<Card
|
||||||
|
title='Laporan Biaya Operasional'
|
||||||
|
variant='bordered'
|
||||||
|
className={{
|
||||||
|
wrapper: 'w-full',
|
||||||
|
}}
|
||||||
|
footer={
|
||||||
|
<div className='flex flex-row items-center justify-between gap-2'>
|
||||||
|
<div>
|
||||||
|
<ReportExpenseExport data={reportExpenses} />
|
||||||
|
</div>
|
||||||
|
<div className='flex flex-row items-center gap-2'>
|
||||||
|
<Button className='min-w-24' onClick={handleSearch}>
|
||||||
|
Cari
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className='min-w-24'
|
||||||
|
color='warning'
|
||||||
|
onClick={handleReset}
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className='grid grid-cols-2 md:grid-cols-4 gap-4'>
|
||||||
|
<SelectInput
|
||||||
|
isClearable
|
||||||
|
label='Lokasi'
|
||||||
|
options={optionsLocation}
|
||||||
|
isLoading={isLoadingLocation}
|
||||||
|
placeholder='Lokasi'
|
||||||
|
value={selectedLocation}
|
||||||
|
onChange={(option) => {
|
||||||
|
setSelectedLocation(option as OptionType);
|
||||||
|
setSelectedKandang(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<SelectInput
|
||||||
|
isClearable
|
||||||
|
label='Kandang'
|
||||||
|
options={optionsKandang}
|
||||||
|
isLoading={isLoadingKandang}
|
||||||
|
placeholder='Kandang'
|
||||||
|
value={selectedKandang}
|
||||||
|
onChange={(option) => setSelectedKandang(option as OptionType)}
|
||||||
|
/>
|
||||||
|
<SelectInput
|
||||||
|
isClearable
|
||||||
|
label='Supplier'
|
||||||
|
options={optionsSupplier}
|
||||||
|
isLoading={isLoadingSupplier}
|
||||||
|
placeholder='Supplier'
|
||||||
|
value={selectedSupplier}
|
||||||
|
onChange={(option) => setSelectedSupplier(option as OptionType)}
|
||||||
|
/>
|
||||||
|
<SelectInput
|
||||||
|
isClearable
|
||||||
|
label='Kategori'
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
value: 'BOP',
|
||||||
|
label: 'BOP',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'NON BOP',
|
||||||
|
label: 'Non BOP',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
placeholder='Kategori'
|
||||||
|
value={selectedCategory}
|
||||||
|
onChange={(option) => setSelectedCategory(option as OptionType)}
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
label='Periode'
|
||||||
|
value={period}
|
||||||
|
onChange={(e) => setPeriod(e.target.value)}
|
||||||
|
name='periode'
|
||||||
|
placeholder='Periode'
|
||||||
|
/>
|
||||||
|
<DateInput
|
||||||
|
label='Tanggal Mulai'
|
||||||
|
value={startDate as string}
|
||||||
|
onChange={(e) => setStartDate(e.target.value)}
|
||||||
|
name='start_date'
|
||||||
|
placeholder='Tanggal Mulai'
|
||||||
|
/>
|
||||||
|
<DateInput
|
||||||
|
label='Tanggal Selesai'
|
||||||
|
value={endDate as string}
|
||||||
|
onChange={(e) => setEndDate(e.target.value)}
|
||||||
|
name='end_date'
|
||||||
|
placeholder='Tanggal Selesai'
|
||||||
|
/>
|
||||||
|
<DebouncedTextInput
|
||||||
|
label='Cari'
|
||||||
|
name='search'
|
||||||
|
value={search}
|
||||||
|
onChange={handleSearchInput}
|
||||||
|
placeholder='Cari'
|
||||||
|
startAdornment={<Icon icon='mdi:magnify' width={24} height={24} />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
<Table<ReportExpense>
|
||||||
|
columns={columns}
|
||||||
|
data={reportExpenses}
|
||||||
|
className={{
|
||||||
|
headerRowClassName: cn(TABLE_DEFAULT_STYLING, 'whitespace-nowrap'),
|
||||||
|
bodyRowClassName: cn(TABLE_DEFAULT_STYLING, 'whitespace-nowrap'),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReportExpenseTable;
|
||||||
@@ -0,0 +1,420 @@
|
|||||||
|
import Button from '@/components/Button';
|
||||||
|
import { ReportExpense } from '@/types/api/report/report-expense';
|
||||||
|
import { Icon } from '@iconify/react';
|
||||||
|
import { Document, Image, Page, pdf, Text, View } from '@react-pdf/renderer';
|
||||||
|
import { useMemo, useState } from 'react';
|
||||||
|
import { formatCurrency, formatDate } from '@/lib/helper';
|
||||||
|
import pdfStyles from '@/components/pages/report/expense/pdf/styles/ReportExpenseStyles';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
|
interface ReportExpenseExportProps {
|
||||||
|
data: ReportExpense[];
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReportExpenseExport = ({ data }: ReportExpenseExportProps) => {
|
||||||
|
const [isGeneratingPDF, setIsGeneratingPDF] = useState(false);
|
||||||
|
|
||||||
|
const handleDownloadPDF = async () => {
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
toast.error('No report expense data available');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsGeneratingPDF(true);
|
||||||
|
try {
|
||||||
|
const blob = await pdf(<PDFDocument data={data} />).toBlob();
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.download = `Laporan-BOP-${formatDate(new Date(), 'DD-MMM-YYYY')}.pdf`;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
} catch (error) {
|
||||||
|
toast.error('Failed to generate PDF. Please try again.');
|
||||||
|
return error;
|
||||||
|
} finally {
|
||||||
|
setIsGeneratingPDF(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
color='error'
|
||||||
|
className='min-w-32'
|
||||||
|
onClick={handleDownloadPDF}
|
||||||
|
isLoading={isGeneratingPDF}
|
||||||
|
>
|
||||||
|
<Icon icon='mdi:file-pdf-box' width={20} height={20} />
|
||||||
|
Export PDF
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReportExpenseExport;
|
||||||
|
|
||||||
|
const PDFDocument = ({ data }: { data: ReportExpense[] }) => {
|
||||||
|
// Group data by supplier
|
||||||
|
const groupedBySupplier = useMemo(() => {
|
||||||
|
const groups: Record<string, ReportExpense[]> = {};
|
||||||
|
data.forEach((item) => {
|
||||||
|
const supplierName = item.supplier.name;
|
||||||
|
if (!groups[supplierName]) {
|
||||||
|
groups[supplierName] = [];
|
||||||
|
}
|
||||||
|
groups[supplierName].push(item);
|
||||||
|
});
|
||||||
|
return groups;
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
// Calculate grand totals
|
||||||
|
const grandTotals = useMemo(() => {
|
||||||
|
return data.reduce(
|
||||||
|
(acc, item) => {
|
||||||
|
const pengajuanTotal = item.pengajuan.qty * item.pengajuan.price;
|
||||||
|
const realisasiTotal = item.realisasi.qty * item.realisasi.price;
|
||||||
|
return {
|
||||||
|
pengajuan: acc.pengajuan + pengajuanTotal,
|
||||||
|
realisasi: acc.realisasi + realisasiTotal,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{ pengajuan: 0, realisasi: 0 }
|
||||||
|
);
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Document>
|
||||||
|
<Page size='A4' orientation='landscape' style={pdfStyles.page}>
|
||||||
|
{/* Header Section */}
|
||||||
|
<View style={pdfStyles.header}>
|
||||||
|
<Image
|
||||||
|
src={'https://placehold.co/120x30/png'}
|
||||||
|
style={pdfStyles.logo}
|
||||||
|
id={'mbu-logo'}
|
||||||
|
/>
|
||||||
|
<Text style={pdfStyles.companyInfo}>PT LUMBUNG TELUR INDONESIA</Text>
|
||||||
|
<Text style={pdfStyles.address}>
|
||||||
|
SOHO Building Lt.3 (Paris Van Java), Jalan Karang Tinggal, Kel.
|
||||||
|
Cipedes, Kec. Sukajadi, Kota Bandung 40162
|
||||||
|
</Text>
|
||||||
|
<View style={pdfStyles.divider} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Report Title */}
|
||||||
|
<View style={pdfStyles.titleSection}>
|
||||||
|
<Text style={pdfStyles.title}>LAPORAN BIAYA OPERASIONAL</Text>
|
||||||
|
<View style={pdfStyles.poInfo}>
|
||||||
|
<Text>Tanggal Cetak: {formatDate(new Date(), 'DD MMM YYYY')}</Text>
|
||||||
|
<Text>Total Data: {data.length} transaksi</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Grouped Tables by Supplier */}
|
||||||
|
{Object.entries(groupedBySupplier).map(
|
||||||
|
([supplierName, items], groupIndex) => {
|
||||||
|
const supplierTotals = items.reduce(
|
||||||
|
(acc, item) => {
|
||||||
|
const pengajuanTotal =
|
||||||
|
item.pengajuan.qty * item.pengajuan.price;
|
||||||
|
const realisasiTotal =
|
||||||
|
item.realisasi.qty * item.realisasi.price;
|
||||||
|
return {
|
||||||
|
pengajuan: acc.pengajuan + pengajuanTotal,
|
||||||
|
realisasi: acc.realisasi + realisasiTotal,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{ pengajuan: 0, realisasi: 0 }
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View key={groupIndex} style={pdfStyles.allocationSection}>
|
||||||
|
{/* Supplier Header */}
|
||||||
|
<Text style={pdfStyles.sectionTitle}>{supplierName}</Text>
|
||||||
|
|
||||||
|
{/* Table */}
|
||||||
|
<View style={pdfStyles.table}>
|
||||||
|
{/* Table Header */}
|
||||||
|
<View style={[pdfStyles.tableRow, pdfStyles.tableHeader]}>
|
||||||
|
<View style={pdfStyles.tableCellHeader}>
|
||||||
|
<Text>No</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellHeader}>
|
||||||
|
<Text>No. PO</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellHeader}>
|
||||||
|
<Text>No. Referensi</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellHeader}>
|
||||||
|
<Text>Tgl Realisasi</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellHeader}>
|
||||||
|
<Text>Tgl Transaksi</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellHeader}>
|
||||||
|
<Text>Kategori</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellHeader}>
|
||||||
|
<Text>Lokasi</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellHeader}>
|
||||||
|
<Text>Kandang</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellHeader}>
|
||||||
|
<Text>Qty Pengajuan</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellHeader}>
|
||||||
|
<Text>Harga Pengajuan</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellHeader}>
|
||||||
|
<Text>Total Pengajuan</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellHeader}>
|
||||||
|
<Text>Qty Realisasi</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellHeader}>
|
||||||
|
<Text>Harga Realisasi</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellHeader}>
|
||||||
|
<Text>Total Realisasi</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellHeader}>
|
||||||
|
<Text>Status Pencairan</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellHeaderLast}>
|
||||||
|
<Text>Status BOP</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Table Body */}
|
||||||
|
{items.map((item, index) => {
|
||||||
|
const pengajuanTotal =
|
||||||
|
item.pengajuan.qty * item.pengajuan.price;
|
||||||
|
const realisasiTotal =
|
||||||
|
item.realisasi.qty * item.realisasi.price;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View key={index} style={pdfStyles.tableRow}>
|
||||||
|
<View style={pdfStyles.tableCell}>
|
||||||
|
<Text>{index + 1}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCell}>
|
||||||
|
<Text>{item.po_number}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCell}>
|
||||||
|
<Text>{item.reference_number}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCell}>
|
||||||
|
<Text>
|
||||||
|
{formatDate(item.realization_date, 'DD MMM YY')}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCell}>
|
||||||
|
<Text>
|
||||||
|
{formatDate(item.transaction_date, 'DD MMM YY')}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCell}>
|
||||||
|
<Text>{item.category}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCell}>
|
||||||
|
<Text>{item.location.name}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCell}>
|
||||||
|
<Text>{item.kandang.name}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellRight}>
|
||||||
|
<Text>
|
||||||
|
{item.pengajuan.qty.toLocaleString('id-ID')}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellRight}>
|
||||||
|
<Text>{formatCurrency(item.pengajuan.price)}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellRight}>
|
||||||
|
<Text>{formatCurrency(pengajuanTotal)}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellRight}>
|
||||||
|
<Text>
|
||||||
|
{item.realisasi.qty.toLocaleString('id-ID')}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellRight}>
|
||||||
|
<Text>{formatCurrency(item.realisasi.price)}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellRight}>
|
||||||
|
<Text>{formatCurrency(realisasiTotal)}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCell}>
|
||||||
|
<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
|
||||||
|
style={{
|
||||||
|
fontSize: 7,
|
||||||
|
backgroundColor:
|
||||||
|
item.latest_approval.action === 'APPROVED'
|
||||||
|
? '#dcfce7'
|
||||||
|
: item.latest_approval.action === 'REJECTED'
|
||||||
|
? '#fee2e2'
|
||||||
|
: '#fef3c7',
|
||||||
|
padding: 2,
|
||||||
|
borderRadius: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.latest_approval.action}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* Supplier Subtotal Row */}
|
||||||
|
<View style={pdfStyles.grandTotalRow}>
|
||||||
|
<View
|
||||||
|
style={[pdfStyles.tableCell, { borderRightWidth: 0 }]}
|
||||||
|
>
|
||||||
|
<Text></Text>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={[pdfStyles.tableCell, { borderRightWidth: 0 }]}
|
||||||
|
>
|
||||||
|
<Text></Text>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={[pdfStyles.tableCell, { borderRightWidth: 0 }]}
|
||||||
|
>
|
||||||
|
<Text></Text>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={[pdfStyles.tableCell, { borderRightWidth: 0 }]}
|
||||||
|
>
|
||||||
|
<Text></Text>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={[pdfStyles.tableCell, { borderRightWidth: 0 }]}
|
||||||
|
>
|
||||||
|
<Text></Text>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={[pdfStyles.tableCell, { borderRightWidth: 0 }]}
|
||||||
|
>
|
||||||
|
<Text></Text>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={[pdfStyles.tableCell, { borderRightWidth: 0 }]}
|
||||||
|
>
|
||||||
|
<Text></Text>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={[pdfStyles.tableCell, { borderRightWidth: 0 }]}
|
||||||
|
>
|
||||||
|
<Text></Text>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={[pdfStyles.tableCell, { borderRightWidth: 0 }]}
|
||||||
|
>
|
||||||
|
<Text></Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellRight}>
|
||||||
|
<Text style={{ fontWeight: 'bold' }}>Subtotal</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellRight}>
|
||||||
|
<Text style={{ fontWeight: 'bold' }}>
|
||||||
|
{formatCurrency(supplierTotals.pengajuan)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={[pdfStyles.tableCell, { borderRightWidth: 0 }]}
|
||||||
|
>
|
||||||
|
<Text></Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellRight}>
|
||||||
|
<Text style={{ fontWeight: 'bold' }}>Subtotal</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellRight}>
|
||||||
|
<Text style={{ fontWeight: 'bold' }}>
|
||||||
|
{formatCurrency(supplierTotals.realisasi)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={[pdfStyles.tableCell, { borderRightWidth: 0 }]}
|
||||||
|
>
|
||||||
|
<Text></Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCellLast}>
|
||||||
|
<Text></Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Grand Total Section */}
|
||||||
|
<View style={pdfStyles.allocationSection}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
{
|
||||||
|
width: '30%',
|
||||||
|
borderTopWidth: 1,
|
||||||
|
borderTopColor: '#000000',
|
||||||
|
borderTopStyle: 'solid',
|
||||||
|
borderLeftWidth: 1,
|
||||||
|
borderLeftColor: '#000000',
|
||||||
|
borderLeftStyle: 'solid',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<View style={pdfStyles.innerRow}>
|
||||||
|
<View style={pdfStyles.tableCell}>
|
||||||
|
<Text style={{ fontWeight: 'bold' }}>
|
||||||
|
GRAND TOTAL PENGAJUAN
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCell}>
|
||||||
|
<Text style={{ fontWeight: 'bold' }}>
|
||||||
|
{formatCurrency(grandTotals.pengajuan)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.innerRow}>
|
||||||
|
<View style={pdfStyles.tableCell}>
|
||||||
|
<Text style={{ fontWeight: 'bold' }}>
|
||||||
|
GRAND TOTAL REALISASI
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={pdfStyles.tableCell}>
|
||||||
|
<Text style={{ fontWeight: 'bold' }}>
|
||||||
|
{formatCurrency(grandTotals.realisasi)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<View style={pdfStyles.footer}>
|
||||||
|
<View style={pdfStyles.footerCompany}>
|
||||||
|
<Text>PT LUMBUNG TELUR INDONESIA</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Page>
|
||||||
|
</Document>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
import { StyleSheet } from '@react-pdf/renderer';
|
||||||
|
|
||||||
|
const pdfStyles = StyleSheet.create({
|
||||||
|
page: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontFamily: 'Helvetica',
|
||||||
|
padding: 20,
|
||||||
|
backgroundColor: '#FFFFFF',
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
logo: {
|
||||||
|
width: 120,
|
||||||
|
height: 30,
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
companyInfo: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
marginBottom: 4,
|
||||||
|
color: '#1f74bf',
|
||||||
|
},
|
||||||
|
address: {
|
||||||
|
fontSize: 7,
|
||||||
|
color: '#666666',
|
||||||
|
maxWidth: 400,
|
||||||
|
marginBottom: 10,
|
||||||
|
},
|
||||||
|
divider: {
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#000000',
|
||||||
|
borderBottomStyle: 'solid',
|
||||||
|
marginBottom: 15,
|
||||||
|
},
|
||||||
|
titleSection: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
marginBottom: 20,
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
flex: 3,
|
||||||
|
color: '#1f74bf',
|
||||||
|
},
|
||||||
|
poInfo: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: 7,
|
||||||
|
textAlign: 'right',
|
||||||
|
},
|
||||||
|
sectionTitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
marginBottom: 8,
|
||||||
|
color: '#1f74bf',
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#000000',
|
||||||
|
marginBottom: 15,
|
||||||
|
},
|
||||||
|
tableRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
},
|
||||||
|
tableHeader: {
|
||||||
|
backgroundColor: '#F5F5F5',
|
||||||
|
},
|
||||||
|
tableCell: {
|
||||||
|
flex: 1,
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: '#000000',
|
||||||
|
borderRightStyle: 'solid',
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
},
|
||||||
|
tableCellLast: {
|
||||||
|
flex: 1,
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
},
|
||||||
|
tableCellHeader: {
|
||||||
|
flex: 1,
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: '#000000',
|
||||||
|
borderRightStyle: 'solid',
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
backgroundColor: '#F5F5F5',
|
||||||
|
},
|
||||||
|
tableCellHeaderLast: {
|
||||||
|
flex: 1,
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
backgroundColor: '#F5F5F5',
|
||||||
|
},
|
||||||
|
tableCellRight: {
|
||||||
|
flex: 1,
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: '#000000',
|
||||||
|
borderRightStyle: 'solid',
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
textAlign: 'right',
|
||||||
|
},
|
||||||
|
tableCellRightLast: {
|
||||||
|
flex: 1,
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
textAlign: 'right',
|
||||||
|
},
|
||||||
|
tableBorderBottom: {
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#000000',
|
||||||
|
borderBottomStyle: 'solid',
|
||||||
|
},
|
||||||
|
grandTotalRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
borderTopWidth: 1,
|
||||||
|
borderTopColor: '#000000',
|
||||||
|
borderTopStyle: 'solid',
|
||||||
|
},
|
||||||
|
grandTotalLabel: {
|
||||||
|
flex: 3,
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
textAlign: 'right',
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: '#000000',
|
||||||
|
borderRightStyle: 'solid',
|
||||||
|
},
|
||||||
|
grandTotalValue: {
|
||||||
|
flex: 1,
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
textAlign: 'right',
|
||||||
|
borderRightWidth: 0,
|
||||||
|
},
|
||||||
|
allocationSection: {
|
||||||
|
marginBottom: 15,
|
||||||
|
},
|
||||||
|
allocationTable: {
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#000000',
|
||||||
|
},
|
||||||
|
innerTable: {
|
||||||
|
marginTop: 5,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#000000',
|
||||||
|
},
|
||||||
|
innerRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#000000',
|
||||||
|
borderBottomStyle: 'solid',
|
||||||
|
},
|
||||||
|
innerCell: {
|
||||||
|
flex: 1,
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: '#000000',
|
||||||
|
borderRightStyle: 'solid',
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
},
|
||||||
|
innerCellLast: {
|
||||||
|
flex: 1,
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
},
|
||||||
|
innerCellRight: {
|
||||||
|
flex: 1,
|
||||||
|
borderRightWidth: 1,
|
||||||
|
borderRightColor: '#000000',
|
||||||
|
borderRightStyle: 'solid',
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
textAlign: 'right',
|
||||||
|
},
|
||||||
|
innerCellRightLast: {
|
||||||
|
flex: 1,
|
||||||
|
padding: 3,
|
||||||
|
fontSize: 7,
|
||||||
|
textAlign: 'right',
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
marginTop: 30,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
},
|
||||||
|
footerCompany: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
textAlign: 'right',
|
||||||
|
flex: 1,
|
||||||
|
color: '#1f74bf',
|
||||||
|
},
|
||||||
|
specialInstructionTable: {
|
||||||
|
width: '60%',
|
||||||
|
maxWidth: 300,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#000000',
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default pdfStyles;
|
||||||
@@ -6,6 +6,17 @@ export const MAIN_DRAWER_LINKS: SidebarMenuItem[] = [
|
|||||||
link: '/dashboard',
|
link: '/dashboard',
|
||||||
icon: 'heroicons-outline:chart-bar-square',
|
icon: 'heroicons-outline:chart-bar-square',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: 'Laporan',
|
||||||
|
link: '/report',
|
||||||
|
icon: 'heroicons-outline:clipboard',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
text: 'Biaya Operasional',
|
||||||
|
link: '/report/expense',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
text: 'Produksi',
|
text: 'Produksi',
|
||||||
link: '/production',
|
link: '/production',
|
||||||
|
|||||||
@@ -0,0 +1,627 @@
|
|||||||
|
/**
|
||||||
|
* Dummy Data untuk Report Expense API
|
||||||
|
*
|
||||||
|
* File ini berisi dummy data untuk testing Report Expense API sebelum backend siap.
|
||||||
|
*
|
||||||
|
* Struktur data mengikuti tipe yang didefinisikan di @/types/api/report/report-expense.d.ts
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Menggunakan getAllFetcher dengan SWR:
|
||||||
|
* import useSWR from 'swr';
|
||||||
|
* import { ReportExpenseApi } from '@/services/api/report';
|
||||||
|
*
|
||||||
|
* const { data, error, isLoading } = useSWR(
|
||||||
|
* ReportExpenseApi.basePath,
|
||||||
|
* ReportExpenseApi.getAllFetcher
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* if (data?.status === 'success') {
|
||||||
|
* console.log(data.data); // Array of ReportExpense objects
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @see {@link /home/sweetpotet/Documents/projects/lti-web-client/src/types/api/report/report-expense.d.ts}
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { format } from 'date-fns';
|
||||||
|
import {
|
||||||
|
Pengajuan,
|
||||||
|
Realisasi,
|
||||||
|
ReportExpense,
|
||||||
|
} from '@/types/api/report/report-expense';
|
||||||
|
import { BaseApiResponse, CreatedUser } from '@/types/api/api-general';
|
||||||
|
import { Supplier } from '@/types/api/master-data/supplier';
|
||||||
|
import { Location } from '@/types/api/master-data/location';
|
||||||
|
import { Nonstock } from '@/types/api/master-data/nonstock';
|
||||||
|
import { Kandang } from '@/types/api/master-data/kandang';
|
||||||
|
|
||||||
|
// Waktu saat ini untuk created_at/updated_at
|
||||||
|
const now = format(new Date(), 'yyyy-MM-dd HH:mm:ss');
|
||||||
|
const today = format(new Date(), 'yyyy-MM-dd');
|
||||||
|
const yesterday = format(
|
||||||
|
new Date(new Date().setDate(new Date().getDate() - 1)),
|
||||||
|
'yyyy-MM-dd'
|
||||||
|
);
|
||||||
|
const lastWeek = format(
|
||||||
|
new Date(new Date().setDate(new Date().getDate() - 7)),
|
||||||
|
'yyyy-MM-dd'
|
||||||
|
);
|
||||||
|
const lastMonth = format(
|
||||||
|
new Date(new Date().setMonth(new Date().getMonth() - 1)),
|
||||||
|
'yyyy-MM-dd'
|
||||||
|
);
|
||||||
|
|
||||||
|
// ======================
|
||||||
|
// 👤 Created User
|
||||||
|
// ======================
|
||||||
|
const createdUser: CreatedUser = {
|
||||||
|
id: 1,
|
||||||
|
id_user: 1,
|
||||||
|
email: 'admin@example.com',
|
||||||
|
name: 'Admin Utama',
|
||||||
|
};
|
||||||
|
|
||||||
|
// ======================
|
||||||
|
// 🏢 Supplier Dummy Data
|
||||||
|
// ======================
|
||||||
|
const dummySuppliers: Supplier[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'PT. Mitra Pakan Sejahtera',
|
||||||
|
alias: 'MPS',
|
||||||
|
pic: 'Budi Santoso',
|
||||||
|
type: 'Pakan',
|
||||||
|
category: 'Supplier Utama',
|
||||||
|
hatchery: '-',
|
||||||
|
phone: '022-1234567',
|
||||||
|
email: 'info@mitrapakan.com',
|
||||||
|
address: 'Jl. Raya Industri No. 123, Bandung',
|
||||||
|
npwp: '01.234.567.8-901.000',
|
||||||
|
account_number: '1234567890',
|
||||||
|
due_date: 30,
|
||||||
|
created_user: createdUser,
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'CV. Sumber Ternak Jaya',
|
||||||
|
alias: 'STJ',
|
||||||
|
pic: 'Siti Rahayu',
|
||||||
|
type: 'DOC',
|
||||||
|
category: 'Supplier Utama',
|
||||||
|
hatchery: 'Hatchery Jaya',
|
||||||
|
phone: '021-9876543',
|
||||||
|
email: 'contact@sumberternak.com',
|
||||||
|
address: 'Jl. Peternakan No. 45, Jakarta',
|
||||||
|
npwp: '02.345.678.9-012.000',
|
||||||
|
account_number: '0987654321',
|
||||||
|
due_date: 45,
|
||||||
|
created_user: createdUser,
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'PT. Agro Veteriner Indonesia',
|
||||||
|
alias: 'AVI',
|
||||||
|
pic: 'Dr. Ahmad Fauzi',
|
||||||
|
type: 'OVK',
|
||||||
|
category: 'Supplier Utama',
|
||||||
|
hatchery: '-',
|
||||||
|
phone: '031-5555666',
|
||||||
|
email: 'sales@agroveteriner.co.id',
|
||||||
|
address: 'Jl. Kesehatan Hewan No. 78, Surabaya',
|
||||||
|
npwp: '03.456.789.0-123.000',
|
||||||
|
account_number: '5678901234',
|
||||||
|
due_date: 60,
|
||||||
|
created_user: createdUser,
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// ======================
|
||||||
|
// 📍 Location Dummy Data
|
||||||
|
// ======================
|
||||||
|
const dummyLocations: Location[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Farm Sukajadi',
|
||||||
|
address: 'Jl. Sukajadi No. 100, Bandung',
|
||||||
|
area: {
|
||||||
|
id: 1,
|
||||||
|
name: 'Bandung Barat',
|
||||||
|
},
|
||||||
|
created_user: createdUser,
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'Farm Cihampelas',
|
||||||
|
address: 'Jl. Cihampelas No. 200, Bandung',
|
||||||
|
area: {
|
||||||
|
id: 1,
|
||||||
|
name: 'Bandung Barat',
|
||||||
|
},
|
||||||
|
created_user: createdUser,
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'Farm Pasteur',
|
||||||
|
address: 'Jl. Pasteur No. 300, Bandung',
|
||||||
|
area: {
|
||||||
|
id: 2,
|
||||||
|
name: 'Bandung Timur',
|
||||||
|
},
|
||||||
|
created_user: createdUser,
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// ======================
|
||||||
|
// 📦 Nonstock Dummy Data
|
||||||
|
// ======================
|
||||||
|
const dummyNonstocks: Nonstock[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Listrik',
|
||||||
|
uom_id: 1,
|
||||||
|
uom: { id: 1, name: 'kWh' },
|
||||||
|
suppliers: [],
|
||||||
|
flags: [],
|
||||||
|
created_user: createdUser,
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'Air',
|
||||||
|
uom_id: 2,
|
||||||
|
uom: { id: 2, name: 'm³' },
|
||||||
|
suppliers: [],
|
||||||
|
flags: [],
|
||||||
|
created_user: createdUser,
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'Bahan Bakar',
|
||||||
|
uom_id: 3,
|
||||||
|
uom: { id: 3, name: 'Liter' },
|
||||||
|
suppliers: [],
|
||||||
|
flags: [],
|
||||||
|
created_user: createdUser,
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: 'Pemeliharaan Kandang',
|
||||||
|
uom_id: 4,
|
||||||
|
uom: { id: 4, name: 'Unit' },
|
||||||
|
suppliers: [],
|
||||||
|
flags: [],
|
||||||
|
created_user: createdUser,
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: 'Transportasi',
|
||||||
|
uom_id: 5,
|
||||||
|
uom: { id: 5, name: 'Trip' },
|
||||||
|
suppliers: [],
|
||||||
|
flags: [],
|
||||||
|
created_user: createdUser,
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// ======================
|
||||||
|
// 🏠 Kandang Dummy Data
|
||||||
|
// ======================
|
||||||
|
const dummyKandangs: Kandang[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Kandang A1',
|
||||||
|
status: 'Aktif',
|
||||||
|
location: dummyLocations[0],
|
||||||
|
capacity: 5000,
|
||||||
|
pic: {
|
||||||
|
id_user: 1,
|
||||||
|
id: 1,
|
||||||
|
name: 'Budi Kandang',
|
||||||
|
email: 'budi@example.com',
|
||||||
|
},
|
||||||
|
created_user: createdUser,
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'Kandang B1',
|
||||||
|
status: 'Aktif',
|
||||||
|
location: dummyLocations[1],
|
||||||
|
capacity: 4000,
|
||||||
|
pic: {
|
||||||
|
id_user: 2,
|
||||||
|
id: 2,
|
||||||
|
name: 'Siti Kandang',
|
||||||
|
email: 'siti@example.com',
|
||||||
|
},
|
||||||
|
created_user: createdUser,
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'Kandang C1',
|
||||||
|
status: 'Aktif',
|
||||||
|
location: dummyLocations[2],
|
||||||
|
capacity: 6000,
|
||||||
|
pic: {
|
||||||
|
id_user: 3,
|
||||||
|
id: 3,
|
||||||
|
name: 'Ahmad Kandang',
|
||||||
|
email: 'ahmad@example.com',
|
||||||
|
},
|
||||||
|
created_user: createdUser,
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// ======================
|
||||||
|
// 📋 Pengajuan Dummy Data
|
||||||
|
// ======================
|
||||||
|
const dummyPengajuans: Pengajuan[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
expense_id: 1,
|
||||||
|
project_flock_kandang_id: 1,
|
||||||
|
kandang_id: 1,
|
||||||
|
nonstock_id: 1,
|
||||||
|
qty: 1000,
|
||||||
|
price: 1500,
|
||||||
|
notes: 'Pengajuan biaya listrik bulan ini',
|
||||||
|
nonstock: dummyNonstocks[0],
|
||||||
|
created_at: now,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
expense_id: 2,
|
||||||
|
project_flock_kandang_id: 2,
|
||||||
|
kandang_id: 2,
|
||||||
|
nonstock_id: 2,
|
||||||
|
qty: 500,
|
||||||
|
price: 5000,
|
||||||
|
notes: 'Pengajuan biaya air bulan ini',
|
||||||
|
nonstock: dummyNonstocks[1],
|
||||||
|
created_at: now,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
expense_id: 3,
|
||||||
|
project_flock_kandang_id: 3,
|
||||||
|
kandang_id: 3,
|
||||||
|
nonstock_id: 3,
|
||||||
|
qty: 200,
|
||||||
|
price: 15000,
|
||||||
|
notes: 'Pengajuan biaya bahan bakar',
|
||||||
|
nonstock: dummyNonstocks[2],
|
||||||
|
created_at: now,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
expense_id: 4,
|
||||||
|
project_flock_kandang_id: 1,
|
||||||
|
kandang_id: 1,
|
||||||
|
nonstock_id: 4,
|
||||||
|
qty: 1,
|
||||||
|
price: 5000000,
|
||||||
|
notes: 'Pengajuan biaya pemeliharaan kandang',
|
||||||
|
nonstock: dummyNonstocks[3],
|
||||||
|
created_at: now,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
expense_id: 5,
|
||||||
|
project_flock_kandang_id: 2,
|
||||||
|
kandang_id: 2,
|
||||||
|
nonstock_id: 5,
|
||||||
|
qty: 10,
|
||||||
|
price: 500000,
|
||||||
|
notes: 'Pengajuan biaya transportasi',
|
||||||
|
nonstock: dummyNonstocks[4],
|
||||||
|
created_at: now,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// ======================
|
||||||
|
// 💰 Realisasi Dummy Data
|
||||||
|
// ======================
|
||||||
|
const dummyRealisasis: Realisasi[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
expense_nonstock_id: 1,
|
||||||
|
qty: 950,
|
||||||
|
price: 1500,
|
||||||
|
notes: 'Realisasi biaya listrik aktual',
|
||||||
|
nonstock: dummyNonstocks[0],
|
||||||
|
created_at: now,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
expense_nonstock_id: 2,
|
||||||
|
qty: 480,
|
||||||
|
price: 5000,
|
||||||
|
notes: 'Realisasi biaya air aktual',
|
||||||
|
nonstock: dummyNonstocks[1],
|
||||||
|
created_at: now,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
expense_nonstock_id: 3,
|
||||||
|
qty: 195,
|
||||||
|
price: 15000,
|
||||||
|
notes: 'Realisasi biaya bahan bakar aktual',
|
||||||
|
nonstock: dummyNonstocks[2],
|
||||||
|
created_at: now,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
expense_nonstock_id: 4,
|
||||||
|
qty: 1,
|
||||||
|
price: 4800000,
|
||||||
|
notes: 'Realisasi biaya pemeliharaan kandang',
|
||||||
|
nonstock: dummyNonstocks[3],
|
||||||
|
created_at: now,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
expense_nonstock_id: 5,
|
||||||
|
qty: 9,
|
||||||
|
price: 500000,
|
||||||
|
notes: 'Realisasi biaya transportasi',
|
||||||
|
nonstock: dummyNonstocks[4],
|
||||||
|
created_at: now,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// ======================
|
||||||
|
// 📊 Report Expense Dummy Data
|
||||||
|
// ======================
|
||||||
|
export const dummyReportExpenses: ReportExpense[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
reference_number: 'EXP-2025-001',
|
||||||
|
po_number: 'PO-2025-001',
|
||||||
|
category: 'Utilitas',
|
||||||
|
supplier: dummySuppliers[0],
|
||||||
|
realization_date: today,
|
||||||
|
transaction_date: yesterday,
|
||||||
|
location: dummyLocations[0],
|
||||||
|
pengajuan: dummyPengajuans[0],
|
||||||
|
realisasi: dummyRealisasis[0],
|
||||||
|
kandang: dummyKandangs[0],
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now,
|
||||||
|
created_user: createdUser,
|
||||||
|
latest_approval: {
|
||||||
|
id: 1,
|
||||||
|
step_number: 1,
|
||||||
|
step_name: 'Manager Approval',
|
||||||
|
action: 'PENDING',
|
||||||
|
notes: '',
|
||||||
|
action_by: createdUser,
|
||||||
|
action_at: now,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
reference_number: 'EXP-2025-002',
|
||||||
|
po_number: 'PO-2025-002',
|
||||||
|
category: 'Utilitas',
|
||||||
|
supplier: dummySuppliers[0],
|
||||||
|
realization_date: today,
|
||||||
|
transaction_date: yesterday,
|
||||||
|
location: dummyLocations[1],
|
||||||
|
pengajuan: dummyPengajuans[1],
|
||||||
|
realisasi: dummyRealisasis[1],
|
||||||
|
kandang: dummyKandangs[1],
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now,
|
||||||
|
created_user: createdUser,
|
||||||
|
latest_approval: {
|
||||||
|
id: 2,
|
||||||
|
step_number: 2,
|
||||||
|
step_name: 'Finance Approval',
|
||||||
|
action: 'APPROVED',
|
||||||
|
notes: 'Disetujui oleh finance',
|
||||||
|
action_by: createdUser,
|
||||||
|
action_at: now,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
reference_number: 'EXP-2025-003',
|
||||||
|
po_number: 'PO-2025-003',
|
||||||
|
category: 'Operasional',
|
||||||
|
supplier: dummySuppliers[1],
|
||||||
|
realization_date: lastWeek,
|
||||||
|
transaction_date: lastWeek,
|
||||||
|
location: dummyLocations[2],
|
||||||
|
pengajuan: dummyPengajuans[2],
|
||||||
|
realisasi: dummyRealisasis[2],
|
||||||
|
kandang: dummyKandangs[2],
|
||||||
|
created_at: lastWeek,
|
||||||
|
updated_at: lastWeek,
|
||||||
|
created_user: createdUser,
|
||||||
|
latest_approval: {
|
||||||
|
id: 3,
|
||||||
|
step_number: 3,
|
||||||
|
step_name: 'Director Approval',
|
||||||
|
action: 'APPROVED',
|
||||||
|
notes: 'Disetujui oleh direktur',
|
||||||
|
action_by: createdUser,
|
||||||
|
action_at: lastWeek,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
reference_number: 'EXP-2025-004',
|
||||||
|
po_number: 'PO-2025-004',
|
||||||
|
category: 'Maintenance',
|
||||||
|
supplier: dummySuppliers[2],
|
||||||
|
realization_date: today,
|
||||||
|
transaction_date: yesterday,
|
||||||
|
location: dummyLocations[0],
|
||||||
|
pengajuan: dummyPengajuans[3],
|
||||||
|
realisasi: dummyRealisasis[3],
|
||||||
|
kandang: dummyKandangs[0],
|
||||||
|
created_at: now,
|
||||||
|
updated_at: now,
|
||||||
|
created_user: createdUser,
|
||||||
|
latest_approval: {
|
||||||
|
id: 4,
|
||||||
|
step_number: 1,
|
||||||
|
step_name: 'Manager Approval',
|
||||||
|
action: 'REJECTED',
|
||||||
|
notes: 'Biaya terlalu tinggi, perlu revisi',
|
||||||
|
action_by: createdUser,
|
||||||
|
action_at: now,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
reference_number: 'EXP-2025-005',
|
||||||
|
po_number: 'PO-2025-005',
|
||||||
|
category: 'Operasional',
|
||||||
|
supplier: dummySuppliers[1],
|
||||||
|
realization_date: yesterday,
|
||||||
|
transaction_date: lastWeek,
|
||||||
|
location: dummyLocations[1],
|
||||||
|
pengajuan: dummyPengajuans[4],
|
||||||
|
realisasi: dummyRealisasis[4],
|
||||||
|
kandang: dummyKandangs[1],
|
||||||
|
created_at: lastWeek,
|
||||||
|
updated_at: yesterday,
|
||||||
|
created_user: createdUser,
|
||||||
|
latest_approval: {
|
||||||
|
id: 5,
|
||||||
|
step_number: 2,
|
||||||
|
step_name: 'Finance Approval',
|
||||||
|
action: 'PENDING',
|
||||||
|
notes: '',
|
||||||
|
action_by: createdUser,
|
||||||
|
action_at: yesterday,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
reference_number: 'EXP-2025-006',
|
||||||
|
po_number: 'PO-2025-006',
|
||||||
|
category: 'Utilitas',
|
||||||
|
supplier: dummySuppliers[0],
|
||||||
|
realization_date: lastMonth,
|
||||||
|
transaction_date: lastMonth,
|
||||||
|
location: dummyLocations[2],
|
||||||
|
pengajuan: {
|
||||||
|
id: 6,
|
||||||
|
expense_id: 6,
|
||||||
|
project_flock_kandang_id: 3,
|
||||||
|
kandang_id: 3,
|
||||||
|
nonstock_id: 1,
|
||||||
|
qty: 1200,
|
||||||
|
price: 1500,
|
||||||
|
notes: 'Pengajuan biaya listrik bulan lalu',
|
||||||
|
nonstock: dummyNonstocks[0],
|
||||||
|
created_at: lastMonth,
|
||||||
|
},
|
||||||
|
realisasi: {
|
||||||
|
id: 6,
|
||||||
|
expense_nonstock_id: 6,
|
||||||
|
qty: 1150,
|
||||||
|
price: 1500,
|
||||||
|
notes: 'Realisasi biaya listrik bulan lalu',
|
||||||
|
nonstock: dummyNonstocks[0],
|
||||||
|
created_at: lastMonth,
|
||||||
|
},
|
||||||
|
kandang: dummyKandangs[2],
|
||||||
|
created_at: lastMonth,
|
||||||
|
updated_at: lastMonth,
|
||||||
|
created_user: createdUser,
|
||||||
|
latest_approval: {
|
||||||
|
id: 6,
|
||||||
|
step_number: 3,
|
||||||
|
step_name: 'Director Approval',
|
||||||
|
action: 'APPROVED',
|
||||||
|
notes: 'Selesai diproses',
|
||||||
|
action_by: createdUser,
|
||||||
|
action_at: lastMonth,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// ======================
|
||||||
|
// 🔧 Fetcher Functions
|
||||||
|
// ======================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dummy fetcher untuk mendapatkan semua data report expense
|
||||||
|
* @returns Promise dengan BaseApiResponse berisi array ReportExpense
|
||||||
|
*/
|
||||||
|
export async function dummyGetAllFetcher(): Promise<
|
||||||
|
BaseApiResponse<ReportExpense[]>
|
||||||
|
> {
|
||||||
|
// Simulasi delay network
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
status: 'success',
|
||||||
|
message: 'Data report expense berhasil diambil',
|
||||||
|
data: dummyReportExpenses,
|
||||||
|
meta: {
|
||||||
|
page: 1,
|
||||||
|
limit: 10,
|
||||||
|
total_results: dummyReportExpenses.length,
|
||||||
|
total_pages: 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dummy fetcher untuk mendapatkan single data report expense berdasarkan ID
|
||||||
|
* @param id - ID dari report expense yang ingin diambil
|
||||||
|
* @returns Promise dengan BaseApiResponse berisi single ReportExpense
|
||||||
|
*/
|
||||||
|
export async function dummyGetSingle(
|
||||||
|
id: number
|
||||||
|
): Promise<BaseApiResponse<ReportExpense>> {
|
||||||
|
// Simulasi delay network
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||||
|
|
||||||
|
const reportExpense = dummyReportExpenses.find((item) => item.id === id);
|
||||||
|
|
||||||
|
if (!reportExpense) {
|
||||||
|
return {
|
||||||
|
code: 404,
|
||||||
|
status: 'error',
|
||||||
|
message: `Report expense dengan ID ${id} tidak ditemukan`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
status: 'success',
|
||||||
|
message: 'Data report expense berhasil diambil',
|
||||||
|
data: reportExpense,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { BaseApiService } from '@/services/api/base';
|
||||||
|
import { httpClient, httpClientFetcher } from '@/services/http/client';
|
||||||
|
import { BaseApiResponse } from '@/types/api/api-general';
|
||||||
|
import { ReportExpense } from '@/types/api/report/report-expense';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export class ReportExpenseApiService extends BaseApiService<
|
||||||
|
ReportExpense,
|
||||||
|
unknown,
|
||||||
|
unknown
|
||||||
|
> {
|
||||||
|
constructor(basePath: string) {
|
||||||
|
super(basePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllFetcher(
|
||||||
|
endpoint: string
|
||||||
|
): Promise<BaseApiResponse<ReportExpense[]>> {
|
||||||
|
// TODO: Remove this block when backend is ready
|
||||||
|
// const { dummyGetAllFetcher } = await import('@/dummy/report/expense.dummy');
|
||||||
|
// return await dummyGetAllFetcher();
|
||||||
|
|
||||||
|
// Uncomment this when backend is ready
|
||||||
|
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');
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
import { BaseApproval, CreatedUser } from '@/types/api/api-general';
|
||||||
|
import { Supplier } from '@/types/api/master-data/supplier';
|
||||||
|
import { Location } from '@/types/api/master-data/location';
|
||||||
|
import { Nonstock } from '@/types/api/master-data/nonstock';
|
||||||
|
import { Kandang } from '@/types/api/master-data/kandang';
|
||||||
|
|
||||||
|
export type Pengajuan = {
|
||||||
|
id: number;
|
||||||
|
expense_id: number;
|
||||||
|
project_flock_kandang_id: number;
|
||||||
|
kandang_id: number;
|
||||||
|
nonstock_id: number;
|
||||||
|
qty: number;
|
||||||
|
price: number;
|
||||||
|
notes: string;
|
||||||
|
nonstock: Nonstock;
|
||||||
|
created_at: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Realisasi = {
|
||||||
|
id: number;
|
||||||
|
expense_nonstock_id: number;
|
||||||
|
qty: number;
|
||||||
|
price: number;
|
||||||
|
notes: string;
|
||||||
|
nonstock: Nonstock;
|
||||||
|
created_at: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ReportExpense = {
|
||||||
|
id: number;
|
||||||
|
reference_number: string;
|
||||||
|
po_number: string;
|
||||||
|
category: string;
|
||||||
|
supplier: Supplier;
|
||||||
|
realization_date: string;
|
||||||
|
transaction_date: string;
|
||||||
|
location: Location;
|
||||||
|
pengajuan: Pengajuan;
|
||||||
|
realisasi: Realisasi;
|
||||||
|
kandang: Kandang;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
created_user: CreatedUser;
|
||||||
|
latest_approval: BaseApproval;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ReportExpenseSearchParams = {
|
||||||
|
locationId: string | null;
|
||||||
|
supplierId: string | null;
|
||||||
|
kandangId: string | null;
|
||||||
|
startDate: string | null;
|
||||||
|
endDate: string | null;
|
||||||
|
category: string | null;
|
||||||
|
period: string | number;
|
||||||
|
search: string;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user