mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 21:41:57 +00:00
feat(FE-355,356,357): Implement HPP Per Kandang report tab
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import Tabs from '@/components/Tabs';
|
||||
import { HppPerKandangTab } from '@/components/pages/report/sale/tab/HppPerKandangTab';
|
||||
import HppPerKandangTab from '@/components/pages/report/sale/tab/HppPerKandangTab';
|
||||
|
||||
const SaleReportTabs = () => {
|
||||
const tabs = [
|
||||
|
||||
@@ -1,3 +1,744 @@
|
||||
export const HppPerKandangTab = () => {
|
||||
return <div>HPP Per Kandang Tab</div>;
|
||||
import { useState, useMemo, useCallback } from 'react';
|
||||
import { ChangeEventHandler } from 'react';
|
||||
import useSWR from 'swr';
|
||||
import Card from '@/components/Card';
|
||||
import SelectInput, {
|
||||
useSelect,
|
||||
OptionType,
|
||||
} from '@/components/input/SelectInput';
|
||||
import DateInput from '@/components/input/DateInput';
|
||||
import TextInput from '@/components/input/TextInput';
|
||||
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||
import { AreaApi } from '@/services/api/master-data';
|
||||
import { LocationApi } from '@/services/api/master-data';
|
||||
import { KandangApi } from '@/services/api/master-data';
|
||||
import { SaleReportApi } from '@/services/api/report/marketing-sale';
|
||||
import Table from '@/components/Table';
|
||||
import { ColumnDef } from '@tanstack/react-table';
|
||||
import { formatCurrency, formatNumber } from '@/lib/helper';
|
||||
import { HppPerKandangReport } from '@/types/api/report/hpp-per-kandang';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||
import Pagination from '@/components/Pagination';
|
||||
import Button from '@/components/Button';
|
||||
import Dropdown from '@/components/dropdown/Dropdown';
|
||||
import MenuItem from '@/components/menu/MenuItem';
|
||||
import Menu from '@/components/menu/Menu';
|
||||
import toast from 'react-hot-toast';
|
||||
import * as XLSX from 'xlsx';
|
||||
|
||||
interface Totals {
|
||||
total_remaining_chicken_birds: number;
|
||||
total_remaining_chicken_weight_kg: number;
|
||||
total_remaining_value_rp: number;
|
||||
}
|
||||
|
||||
const HppPerKandangTab = () => {
|
||||
// ===== STATE MANAGEMENT =====
|
||||
const [isPdfExportLoading, setIsPdfExportLoading] = useState(false);
|
||||
|
||||
// ===== PAGINATION STATE =====
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(10);
|
||||
|
||||
// ===== SUBMISSION STATE =====
|
||||
const [isSubmitted, setIsSubmitted] = useState(false);
|
||||
|
||||
// ===== TABLE FILTER STATE =====
|
||||
const { state: tableFilterState, updateFilter } = useTableFilter({
|
||||
initial: {
|
||||
area_id: '',
|
||||
location_id: '',
|
||||
kandang_id: '',
|
||||
weight_min: '',
|
||||
weight_max: '',
|
||||
period: '',
|
||||
sort_by: '',
|
||||
show_unrecorded: false,
|
||||
},
|
||||
paramMap: {
|
||||
page: 'page',
|
||||
pageSize: 'limit',
|
||||
},
|
||||
});
|
||||
|
||||
const { options: areaOptions, isLoadingOptions: isLoadingAreas } = useSelect(
|
||||
AreaApi.basePath,
|
||||
'id',
|
||||
'name',
|
||||
'search'
|
||||
);
|
||||
|
||||
const { options: locationOptions, isLoadingOptions: isLoadingLocations } =
|
||||
useSelect(LocationApi.basePath, 'id', 'name', 'search');
|
||||
|
||||
const { options: kandangOptions, isLoadingOptions: isLoadingKandangs } =
|
||||
useSelect(KandangApi.basePath, 'id', 'name', 'search');
|
||||
|
||||
const areaChangeHandler = useCallback(
|
||||
(val: OptionType | OptionType[] | null) => {
|
||||
const newVal = val as OptionType;
|
||||
updateFilter('area_id', newVal?.value ? String(newVal.value) : '');
|
||||
setIsSubmitted(false);
|
||||
},
|
||||
[updateFilter]
|
||||
);
|
||||
|
||||
const locationChangeHandler = useCallback(
|
||||
(val: OptionType | OptionType[] | null) => {
|
||||
const newVal = val as OptionType;
|
||||
updateFilter('location_id', newVal?.value ? String(newVal.value) : '');
|
||||
setIsSubmitted(false);
|
||||
},
|
||||
[updateFilter]
|
||||
);
|
||||
|
||||
const kandangChangeHandler = useCallback(
|
||||
(val: OptionType | OptionType[] | null) => {
|
||||
const newVal = val as OptionType;
|
||||
updateFilter('kandang_id', newVal?.value ? String(newVal.value) : '');
|
||||
setIsSubmitted(false);
|
||||
},
|
||||
[updateFilter]
|
||||
);
|
||||
|
||||
const weightMinChangeHandler = useCallback<
|
||||
ChangeEventHandler<HTMLInputElement>
|
||||
>(
|
||||
(e) => {
|
||||
const val = e.target.value;
|
||||
updateFilter('weight_min', val || '');
|
||||
setIsSubmitted(false);
|
||||
},
|
||||
[updateFilter]
|
||||
);
|
||||
|
||||
const weightMaxChangeHandler = useCallback<
|
||||
ChangeEventHandler<HTMLInputElement>
|
||||
>(
|
||||
(e) => {
|
||||
const val = e.target.value;
|
||||
updateFilter('weight_max', val || '');
|
||||
setIsSubmitted(false);
|
||||
},
|
||||
[updateFilter]
|
||||
);
|
||||
|
||||
const periodChangeHandler = useCallback<ChangeEventHandler<HTMLInputElement>>(
|
||||
(e) => {
|
||||
const val = e.target.value;
|
||||
updateFilter('period', val || '');
|
||||
setIsSubmitted(false);
|
||||
},
|
||||
[updateFilter]
|
||||
);
|
||||
|
||||
const showUnrecordedChangeHandler = useCallback<
|
||||
ChangeEventHandler<HTMLInputElement>
|
||||
>(
|
||||
(e) => {
|
||||
const checked = e.target.checked;
|
||||
updateFilter('show_unrecorded', checked);
|
||||
setIsSubmitted(false);
|
||||
},
|
||||
[updateFilter]
|
||||
);
|
||||
|
||||
const resetFilters = useCallback(() => {
|
||||
updateFilter('area_id', '');
|
||||
updateFilter('location_id', '');
|
||||
updateFilter('kandang_id', '');
|
||||
updateFilter('weight_min', '');
|
||||
updateFilter('weight_max', '');
|
||||
updateFilter('period', '');
|
||||
updateFilter('sort_by', '');
|
||||
updateFilter('show_unrecorded', false);
|
||||
setIsSubmitted(false);
|
||||
}, [updateFilter]);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
if (!tableFilterState.period) {
|
||||
toast.error('Periode wajib diisi');
|
||||
return;
|
||||
}
|
||||
setIsSubmitted(true);
|
||||
setCurrentPage(1);
|
||||
}, [tableFilterState.period]);
|
||||
|
||||
// ===== DATA FETCHING =====
|
||||
const { data: hppPerKandang, isLoading } = useSWR(
|
||||
isSubmitted
|
||||
? () => {
|
||||
const params = {
|
||||
area_id: tableFilterState.area_id
|
||||
? Number(tableFilterState.area_id)
|
||||
: undefined,
|
||||
location_id: tableFilterState.location_id
|
||||
? Number(tableFilterState.location_id)
|
||||
: undefined,
|
||||
kandang_id: tableFilterState.kandang_id
|
||||
? Number(tableFilterState.kandang_id)
|
||||
: undefined,
|
||||
weight_min: tableFilterState.weight_min
|
||||
? Number(tableFilterState.weight_min)
|
||||
: undefined,
|
||||
weight_max: tableFilterState.weight_max
|
||||
? Number(tableFilterState.weight_max)
|
||||
: undefined,
|
||||
period: tableFilterState.period || undefined,
|
||||
sort_by: tableFilterState.sort_by || undefined,
|
||||
show_unrecorded: tableFilterState.show_unrecorded,
|
||||
page: currentPage,
|
||||
limit: pageSize,
|
||||
};
|
||||
|
||||
return ['hpp-per-kandang-report', params];
|
||||
}
|
||||
: null,
|
||||
([, params]) =>
|
||||
SaleReportApi.getHppPerKandangReport(
|
||||
params.area_id,
|
||||
params.location_id,
|
||||
params.kandang_id,
|
||||
params.weight_min,
|
||||
params.weight_max,
|
||||
params.period,
|
||||
params.sort_by,
|
||||
params.show_unrecorded,
|
||||
params.page,
|
||||
params.limit
|
||||
)
|
||||
);
|
||||
|
||||
const data: HppPerKandangReport['rows'] = useMemo(
|
||||
() =>
|
||||
isResponseSuccess(hppPerKandang)
|
||||
? (hppPerKandang?.data?.rows as HppPerKandangReport['rows']) || []
|
||||
: [],
|
||||
[hppPerKandang]
|
||||
);
|
||||
|
||||
const summary =
|
||||
isResponseSuccess(hppPerKandang) && 'summary' in hppPerKandang
|
||||
? hppPerKandang.data.summary
|
||||
: undefined;
|
||||
|
||||
const period =
|
||||
isResponseSuccess(hppPerKandang) && 'period' in hppPerKandang
|
||||
? hppPerKandang.data.period
|
||||
: undefined;
|
||||
|
||||
const meta =
|
||||
isResponseSuccess(hppPerKandang) && 'meta' in hppPerKandang
|
||||
? hppPerKandang.meta
|
||||
: undefined;
|
||||
|
||||
const { data: allDataForExport } = useSWR(
|
||||
isSubmitted
|
||||
? () => {
|
||||
const params = {
|
||||
area_id: tableFilterState.area_id
|
||||
? Number(tableFilterState.area_id)
|
||||
: undefined,
|
||||
location_id: tableFilterState.location_id
|
||||
? Number(tableFilterState.location_id)
|
||||
: undefined,
|
||||
kandang_id: tableFilterState.kandang_id
|
||||
? Number(tableFilterState.kandang_id)
|
||||
: undefined,
|
||||
weight_min: tableFilterState.weight_min
|
||||
? Number(tableFilterState.weight_min)
|
||||
: undefined,
|
||||
weight_max: tableFilterState.weight_max
|
||||
? Number(tableFilterState.weight_max)
|
||||
: undefined,
|
||||
period: tableFilterState.period || undefined,
|
||||
sort_by: tableFilterState.sort_by || undefined,
|
||||
show_unrecorded: tableFilterState.show_unrecorded,
|
||||
limit: 10000,
|
||||
page: 1,
|
||||
};
|
||||
|
||||
return ['hpp-per-kandang-report-export', params];
|
||||
}
|
||||
: null,
|
||||
([, params]) =>
|
||||
SaleReportApi.getHppPerKandangReport(
|
||||
params.area_id,
|
||||
params.location_id,
|
||||
params.kandang_id,
|
||||
params.weight_min,
|
||||
params.weight_max,
|
||||
params.period,
|
||||
params.sort_by,
|
||||
params.show_unrecorded,
|
||||
params.page,
|
||||
params.limit
|
||||
)
|
||||
);
|
||||
|
||||
const allExportData: HppPerKandangReport['rows'] = useMemo(
|
||||
() =>
|
||||
isResponseSuccess(allDataForExport)
|
||||
? (allDataForExport?.data?.rows as HppPerKandangReport['rows']) || []
|
||||
: [],
|
||||
[allDataForExport]
|
||||
);
|
||||
|
||||
// ===== EXPORT HANDLERS =====
|
||||
const handleExportExcel = useCallback(() => {
|
||||
if (allExportData.length === 0) {
|
||||
toast.error('Tidak ada data untuk diekspor.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const totals = allExportData.reduce(
|
||||
(acc, item) => ({
|
||||
total_remaining_chicken_birds:
|
||||
acc.total_remaining_chicken_birds +
|
||||
(item.remaining_chicken_birds || 0),
|
||||
total_remaining_chicken_weight_kg:
|
||||
acc.total_remaining_chicken_weight_kg +
|
||||
(item.remaining_chicken_weight_kg || 0),
|
||||
total_remaining_value_rp:
|
||||
acc.total_remaining_value_rp + (item.remaining_value_rp || 0),
|
||||
}),
|
||||
{
|
||||
total_remaining_chicken_birds: 0,
|
||||
total_remaining_chicken_weight_kg: 0,
|
||||
total_remaining_value_rp: 0,
|
||||
}
|
||||
);
|
||||
|
||||
const excelData: { [key: string]: string | number }[] = allExportData.map(
|
||||
(item, index) => ({
|
||||
No: index + 1,
|
||||
Kandang: item.kandang?.name || '',
|
||||
'Rentang Bobot': item.weight_range
|
||||
? `${formatNumber(item.weight_range.weight_min)} - ${formatNumber(item.weight_range.weight_max)}`
|
||||
: '',
|
||||
'Sisa Ayam (Ekor)': item.remaining_chicken_birds || 0,
|
||||
'Sisa Ayam (KG)': item.remaining_chicken_weight_kg || 0,
|
||||
'Rata-Rata Bobot (KG)': item.avg_weight_kg || 0,
|
||||
'Feed (Supplier)':
|
||||
item.feed_suppliers?.map((s) => s.alias || s.name).join(' | ') ||
|
||||
'',
|
||||
'DOC (Supplier)':
|
||||
item.doc_suppliers?.map((s) => s.alias || s.name).join(' | ') || '',
|
||||
'Rata-Rata Harga DOC (RP)': item.average_doc_price_rp || 0,
|
||||
'HPP (RP)': item.hpp_rp || 0,
|
||||
'Nilai Nominal Sisa Ayam (RP)': item.remaining_value_rp || 0,
|
||||
})
|
||||
);
|
||||
|
||||
excelData.push({
|
||||
No: 'TOTAL',
|
||||
Kandang: 'ALL',
|
||||
'Rentang Bobot': '-',
|
||||
'Sisa Ayam (Ekor)': totals.total_remaining_chicken_birds,
|
||||
'Sisa Ayam (KG)': totals.total_remaining_chicken_weight_kg,
|
||||
'Rata-Rata Bobot (KG)': '',
|
||||
'Feed (Supplier)': '',
|
||||
'DOC (Supplier)': '',
|
||||
'Rata-Rata Harga DOC (RP)': '',
|
||||
'HPP (RP)': '',
|
||||
'Nilai Nominal Sisa Ayam (RP)': totals.total_remaining_value_rp,
|
||||
});
|
||||
|
||||
const worksheet = XLSX.utils.json_to_sheet(excelData);
|
||||
|
||||
const colWidths = [
|
||||
{ wch: 5 }, // No
|
||||
{ wch: 30 }, // Kandang
|
||||
{ wch: 15 }, // Rentang Bobot
|
||||
{ wch: 15 }, // Sisa Ayam (Ekor)
|
||||
{ wch: 15 }, // Sisa Ayam (KG)
|
||||
{ wch: 18 }, // Rata-Rata Bobot (KG)
|
||||
{ wch: 20 }, // Feed (Supplier)
|
||||
{ wch: 20 }, // DOC (Supplier)
|
||||
{ wch: 20 }, // Rata-Rata Harga DOC (RP)
|
||||
{ wch: 12 }, // HPP (RP)
|
||||
{ wch: 25 }, // Nilai Nominal Sisa Ayam (RP)
|
||||
];
|
||||
worksheet['!cols'] = colWidths;
|
||||
|
||||
const workbook = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, 'HPP Per Kandang');
|
||||
|
||||
const areaName = tableFilterState.area_id
|
||||
? areaOptions.find(
|
||||
(opt) => opt.value === Number(tableFilterState.area_id)
|
||||
)?.label || 'Semua Area'
|
||||
: 'Semua Area';
|
||||
|
||||
const locationName = tableFilterState.location_id
|
||||
? locationOptions.find(
|
||||
(opt) => opt.value === Number(tableFilterState.location_id)
|
||||
)?.label || 'Semua Lokasi'
|
||||
: 'Semua Lokasi';
|
||||
|
||||
const kandangName = tableFilterState.kandang_id
|
||||
? kandangOptions.find(
|
||||
(opt) => opt.value === Number(tableFilterState.kandang_id)
|
||||
)?.label || 'Semua Kandang'
|
||||
: 'Semua Kandang';
|
||||
|
||||
const filename = `Laporan_HPP_Per_Kandang_${areaName}_${locationName}_${kandangName}_${tableFilterState.period}.xlsx`;
|
||||
|
||||
XLSX.writeFile(workbook, filename);
|
||||
toast.success('Excel berhasil dibuat dan diunduh.');
|
||||
} catch {
|
||||
toast.error('Gagal membuat Excel. Silakan coba lagi.');
|
||||
}
|
||||
}, [
|
||||
allExportData,
|
||||
tableFilterState,
|
||||
areaOptions,
|
||||
locationOptions,
|
||||
kandangOptions,
|
||||
]);
|
||||
|
||||
// ===== PAGINATION HANDLERS =====
|
||||
const handlePageChange = (page: number) => {
|
||||
setCurrentPage(page);
|
||||
};
|
||||
|
||||
const handleRowChange = (pageSize: number) => {
|
||||
setPageSize(pageSize);
|
||||
};
|
||||
|
||||
const handleNextPage = () => {
|
||||
if (meta && currentPage < meta.total_pages) {
|
||||
setCurrentPage(currentPage + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePrevPage = () => {
|
||||
if (currentPage > 1) {
|
||||
setCurrentPage(currentPage - 1);
|
||||
}
|
||||
};
|
||||
|
||||
// ===== TABLE COLUMNS DEFINITION =====
|
||||
const totals: Totals = useMemo(() => {
|
||||
return {
|
||||
total_remaining_chicken_birds:
|
||||
summary?.total_remaining_chicken_birds || 0,
|
||||
total_remaining_chicken_weight_kg:
|
||||
summary?.total_remaining_chicken_weight_kg || 0,
|
||||
total_remaining_value_rp: summary?.total_remaining_value_rp || 0,
|
||||
};
|
||||
}, [summary]);
|
||||
|
||||
const getTableColumns = (): ColumnDef<HppPerKandangReport['rows'][0]>[] => {
|
||||
const tableColumns: ColumnDef<HppPerKandangReport['rows'][0]>[] = [
|
||||
{
|
||||
id: 'no',
|
||||
header: 'No',
|
||||
cell: (props) => props.row.index + 1,
|
||||
footer: () => <div className='font-semibold text-gray-900'>TOTAL</div>,
|
||||
},
|
||||
{
|
||||
id: 'kandang_name',
|
||||
header: 'Kandang',
|
||||
accessorKey: 'kandang.name',
|
||||
cell: (props) => {
|
||||
const kandang = props.row.original.kandang;
|
||||
return kandang?.name || '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'weight_range',
|
||||
header: 'Rentang Bobot',
|
||||
accessorKey: 'weight_range',
|
||||
cell: (props) => {
|
||||
const weightRange = props.row.original.weight_range;
|
||||
return weightRange
|
||||
? `${formatNumber(weightRange.weight_min)} - ${formatNumber(weightRange.weight_max)}`
|
||||
: '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'remaining_chicken_birds',
|
||||
header: 'Sisa Ayam (Ekor)',
|
||||
accessorKey: 'remaining_chicken_birds',
|
||||
cell: (props) => {
|
||||
const value = props.row.original.remaining_chicken_birds;
|
||||
return <div className='text-right'>{formatNumber(value)}</div>;
|
||||
},
|
||||
footer: () => (
|
||||
<div className='text-right font-semibold text-gray-900'>
|
||||
{formatNumber(totals.total_remaining_chicken_birds)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'remaining_chicken_weight_kg',
|
||||
header: 'Sisa Ayam (KG)',
|
||||
accessorKey: 'remaining_chicken_weight_kg',
|
||||
cell: (props) => {
|
||||
const value = props.row.original.remaining_chicken_weight_kg;
|
||||
return <div className='text-right'>{formatNumber(value)}</div>;
|
||||
},
|
||||
footer: () => (
|
||||
<div className='text-right font-semibold text-gray-900'>
|
||||
{formatNumber(totals.total_remaining_chicken_weight_kg)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'avg_weight_kg',
|
||||
header: 'Rata-Rata Bobot (KG)',
|
||||
accessorKey: 'avg_weight_kg',
|
||||
cell: (props) => {
|
||||
const value = props.row.original.avg_weight_kg;
|
||||
return <div className='text-right'>{formatNumber(value)}</div>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'feed_suppliers',
|
||||
header: 'Feed (Supplier)',
|
||||
accessorKey: 'feed_suppliers',
|
||||
cell: (props) => {
|
||||
const suppliers = props.row.original.feed_suppliers;
|
||||
return suppliers?.map((s) => s.alias || s.name).join(' | ') || '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'doc_suppliers',
|
||||
header: 'DOC (Supplier)',
|
||||
accessorKey: 'doc_suppliers',
|
||||
cell: (props) => {
|
||||
const suppliers = props.row.original.doc_suppliers;
|
||||
return suppliers?.map((s) => s.alias || s.name).join(' | ') || '-';
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'average_doc_price_rp',
|
||||
header: 'Rata-Rata Harga DOC (RP)',
|
||||
accessorKey: 'average_doc_price_rp',
|
||||
cell: (props) => {
|
||||
const value = props.row.original.average_doc_price_rp;
|
||||
return <div className='text-right'>{formatCurrency(value)}</div>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'hpp_rp',
|
||||
header: 'HPP (RP)',
|
||||
accessorKey: 'hpp_rp',
|
||||
cell: (props) => {
|
||||
const value = props.row.original.hpp_rp;
|
||||
return <div className='text-right'>{formatCurrency(value)}</div>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'remaining_value_rp',
|
||||
header: 'Nilai Nominal Sisa Ayam (RP)',
|
||||
accessorKey: 'remaining_value_rp',
|
||||
cell: (props) => {
|
||||
const value = props.row.original.remaining_value_rp;
|
||||
return <div className='text-right'>{formatCurrency(value)}</div>;
|
||||
},
|
||||
footer: () => (
|
||||
<div className='text-right font-semibold text-gray-900'>
|
||||
{formatCurrency(totals.total_remaining_value_rp)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
return tableColumns;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='w-full p-0 sm:p-4'>
|
||||
<Card
|
||||
subtitle={
|
||||
period
|
||||
? `Laporan > HPP Harian Kandang (${period})`
|
||||
: 'Laporan > HPP Harian Kandang'
|
||||
}
|
||||
className={{ wrapper: 'w-full', body: 'p-1!' }}
|
||||
>
|
||||
<div className='mb-4 flex justify-end gap-2 [&_button]:px-4'>
|
||||
<Button color='primary' onClick={handleSubmit}>
|
||||
Cari
|
||||
</Button>
|
||||
<Button color='warning' onClick={resetFilters}>
|
||||
Reset
|
||||
</Button>
|
||||
<Dropdown
|
||||
trigger={<Button color='success'>Export</Button>}
|
||||
align='end'
|
||||
>
|
||||
<Menu className='w-32'>
|
||||
<MenuItem title='Excel' onClick={handleExportExcel} />
|
||||
<MenuItem
|
||||
title='PDF'
|
||||
onClick={() => {
|
||||
alert('Fitur belum tersedia');
|
||||
}}
|
||||
/>
|
||||
</Menu>
|
||||
</Dropdown>
|
||||
</div>
|
||||
|
||||
<div className='grid md:grid-cols-3 grid-cols-1 gap-4'>
|
||||
<SelectInput
|
||||
label='Area'
|
||||
placeholder='Pilih Area'
|
||||
options={areaOptions}
|
||||
value={
|
||||
tableFilterState.area_id
|
||||
? areaOptions.find(
|
||||
(option) =>
|
||||
option.value === Number(tableFilterState.area_id)
|
||||
) || null
|
||||
: null
|
||||
}
|
||||
onChange={areaChangeHandler}
|
||||
isLoading={isLoadingAreas}
|
||||
isClearable
|
||||
/>
|
||||
<SelectInput
|
||||
label='Lokasi'
|
||||
placeholder='Pilih Lokasi'
|
||||
options={locationOptions}
|
||||
value={
|
||||
tableFilterState.location_id
|
||||
? locationOptions.find(
|
||||
(option) =>
|
||||
option.value === Number(tableFilterState.location_id)
|
||||
) || null
|
||||
: null
|
||||
}
|
||||
onChange={locationChangeHandler}
|
||||
isLoading={isLoadingLocations}
|
||||
isClearable
|
||||
/>
|
||||
<SelectInput
|
||||
label='Kandang'
|
||||
placeholder='Pilih Kandang'
|
||||
options={kandangOptions}
|
||||
value={
|
||||
tableFilterState.kandang_id
|
||||
? kandangOptions.find(
|
||||
(option) =>
|
||||
option.value === Number(tableFilterState.kandang_id)
|
||||
) || null
|
||||
: null
|
||||
}
|
||||
onChange={kandangChangeHandler}
|
||||
isLoading={isLoadingKandangs}
|
||||
isClearable
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='grid md:grid-cols-3 grid-cols-1 gap-4'>
|
||||
<TextInput
|
||||
label='Rentang Bobot Min (Kg)'
|
||||
name='weight_min'
|
||||
placeholder='Masukkan bobot minimum'
|
||||
value={tableFilterState.weight_min}
|
||||
onChange={weightMinChangeHandler}
|
||||
type='number'
|
||||
/>
|
||||
<TextInput
|
||||
label='Rentang Bobot Max (Kg)'
|
||||
name='weight_max'
|
||||
placeholder='Masukkan bobot maximum'
|
||||
value={tableFilterState.weight_max}
|
||||
onChange={weightMaxChangeHandler}
|
||||
type='number'
|
||||
/>
|
||||
<DateInput
|
||||
label='Periode'
|
||||
name='period'
|
||||
placeholder='Pilih Periode'
|
||||
value={tableFilterState.period}
|
||||
onChange={periodChangeHandler}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='grid md:grid-cols-1 grid-cols-1 gap-4'>
|
||||
<CheckboxInput
|
||||
label='Tampilkan Kandang Tanpa Recording'
|
||||
name='show_unrecorded'
|
||||
checked={tableFilterState.show_unrecorded}
|
||||
onChange={showUnrecordedChangeHandler}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{!isSubmitted ? (
|
||||
<div className='mt-6 text-center text-gray-500'>
|
||||
Silakan pilih filter dan klik tombol Cari untuk menampilkan data.
|
||||
</div>
|
||||
) : isLoading ? (
|
||||
<div className='w-full flex flex-row justify-center items-center p-4'>
|
||||
<span className='loading loading-spinner loading-xl' />
|
||||
</div>
|
||||
) : data.length === 0 ? (
|
||||
<div className='mt-6 text-center text-gray-500'>
|
||||
Tidak ada data yang dapat ditampilkan...
|
||||
</div>
|
||||
) : (
|
||||
<Card
|
||||
title={`Data HPP Per Kandang`}
|
||||
subtitle={
|
||||
summary
|
||||
? `Total Sisa Ayam: ${formatNumber(
|
||||
summary.total_remaining_chicken_birds
|
||||
)} ekor | Total Nilai Sisa: ${formatCurrency(
|
||||
summary.total_remaining_value_rp
|
||||
)}`
|
||||
: undefined
|
||||
}
|
||||
className={{ wrapper: 'w-full', body: 'py-6! p-0' }}
|
||||
>
|
||||
<Table
|
||||
data={data}
|
||||
columns={getTableColumns()}
|
||||
pageSize={pageSize}
|
||||
renderFooter={data.length > 0}
|
||||
className={{
|
||||
containerClassName: 'w-full',
|
||||
tableWrapperClassName: 'overflow-x-auto mt-4',
|
||||
tableClassName: 'w-full table-auto text-sm',
|
||||
headerRowClassName: 'border-b border-b-gray-200 bg-gray-50',
|
||||
headerColumnClassName:
|
||||
'px-4 py-3 text-xs font-semibold text-gray-700 text-left border border-gray-200',
|
||||
bodyRowClassName:
|
||||
'hover:bg-gray-50 transition-colors border-b border-l border-r border-b-gray-200 border-l-gray-200 border-r-gray-200',
|
||||
bodyColumnClassName:
|
||||
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
|
||||
tableFooterClassName:
|
||||
'bg-gray-100 font-semibold border border-gray-200',
|
||||
footerRowClassName: 'border-t-2 border-gray-300',
|
||||
footerColumnClassName:
|
||||
'px-4 py-3 text-xs text-gray-900 whitespace-nowrap',
|
||||
paginationClassName: 'hidden',
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
</Card>
|
||||
{meta && data.length > 0 && (
|
||||
<div className='mt-6'>
|
||||
<Pagination
|
||||
currentPage={meta.page}
|
||||
totalItems={meta.total_results}
|
||||
onPageChange={handlePageChange}
|
||||
onRowChange={handleRowChange}
|
||||
onNextPage={handleNextPage}
|
||||
onPrevPage={handlePrevPage}
|
||||
rowOptions={[10, 25, 50, 100]}
|
||||
itemsPerPage={meta.limit}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HppPerKandangTab;
|
||||
|
||||
Reference in New Issue
Block a user