feat(FE-364): create DailyMarketingReportContent component

This commit is contained in:
ValdiANS
2025-12-18 16:08:15 +07:00
parent 3a35c72e06
commit 466ea47121
@@ -0,0 +1,413 @@
'use client';
import { ChangeEventHandler, useState } from 'react';
import { pdf } from '@react-pdf/renderer';
import toast from 'react-hot-toast';
import { Icon } from '@iconify/react';
import Button from '@/components/Button';
import Dropdown from '@/components/dropdown/Dropdown';
import DateInput from '@/components/input/DateInput';
import SelectInput, {
OptionType,
useSelect,
} from '@/components/input/SelectInput';
import Menu from '@/components/menu/Menu';
import MenuItem from '@/components/menu/MenuItem';
import DailyMarketingsTable from '@/components/pages/report/DailyMarketingsTable';
import { useTableFilter } from '@/services/hooks/useTableFilter';
import DailyMarketingReportPDF from '@/components/pages/report/DailyMarketingReportPDF';
import { Area } from '@/types/api/master-data/area';
import {
AreaApi,
CustomerApi,
LocationApi,
WarehouseApi,
} from '@/services/api/master-data';
import { Warehouse } from '@/types/api/master-data/warehouse';
import { Customer } from '@/types/api/master-data/customer';
import { MarketingReportApi } from '@/services/api/report/marketing-report';
import { MARKETING_TYPE_OPTIONS } from '@/config/constant';
import { httpClient } from '@/services/http/client';
import { BaseApiResponse } from '@/types/api/api-general';
import { DailyMarketingReport } from '@/types/api/report/marketing';
import { isResponseError } from '@/lib/api-helper';
const DailyMarketingReportContent = () => {
const {
state: tableFilterState,
updateFilter,
setPage,
setPageSize,
toQueryString: getTableFilterQueryString,
reset: resetFilter,
} = useTableFilter({
initial: {
search: '',
area_id: '',
location_id: '',
warehouse_id: '',
customer_id: '',
start_date: '',
end_date: '',
marketing_type: '',
filter_by: '',
sort_by: '',
},
paramMap: {
page: 'page',
pageSize: 'limit',
area_id: 'area_id',
location_id: 'location_id',
warehouse_id: 'warehouse_id',
customer_id: 'customer_id',
start_date: 'start_date',
end_date: 'end_date',
marketing_type: 'marketing_type',
filter_by: 'filter_by',
sort_by: 'sort_by',
},
});
const dailyMarketingsReportUrl = `${MarketingReportApi.basePath}${getTableFilterQueryString()}`;
const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] =
useState(false);
const [isLoadingExportingToPdf, setIsLoadingExportingToPdf] = useState(false);
const [selectedArea, setSelectedArea] = useState<OptionType | null>(null);
const {
setInputValue: setAreaInputValue,
options: areaOptions,
isLoadingOptions: isLoadingAreaOptions,
} = useSelect<Area>(AreaApi.basePath, 'id', 'name');
const areaChangeHandler = (val: OptionType | OptionType[] | null) => {
setSelectedArea(val as OptionType);
updateFilter('area_id', val ? ((val as OptionType).value as string) : '');
};
const [selectedLocation, setSelectedLocation] = useState<OptionType | null>(
null
);
const {
setInputValue: setLocationInputValue,
options: locationOptions,
isLoadingOptions: isLoadingLocationOptions,
} = useSelect<Location>(LocationApi.basePath, 'id', 'name');
const locationChangeHandler = (val: OptionType | OptionType[] | null) => {
setSelectedLocation(val as OptionType);
updateFilter(
'location_id',
val ? ((val as OptionType).value as string) : ''
);
};
const [selectedWarehouse, setSelectedWarehouse] = useState<OptionType | null>(
null
);
const {
setInputValue: setWarehouseInputValue,
options: warehouseOptions,
isLoadingOptions: isLoadingWarehouseOptions,
} = useSelect<Warehouse>(WarehouseApi.basePath, 'id', 'name');
const warehouseChangeHandler = (val: OptionType | OptionType[] | null) => {
setSelectedWarehouse(val as OptionType);
updateFilter(
'warehouse_id',
val ? ((val as OptionType).value as string) : ''
);
};
const [selectedCustomer, setSelectedCustomer] = useState<OptionType | null>(
null
);
const {
setInputValue: setCustomerInputValue,
options: customerOptions,
isLoadingOptions: isLoadingCustomerOptions,
} = useSelect<Customer>(CustomerApi.basePath, 'id', 'name');
const customerChangeHandler = (val: OptionType | OptionType[] | null) => {
setSelectedCustomer(val as OptionType);
updateFilter(
'customer_id',
val ? ((val as OptionType).value as string) : ''
);
};
const startDateChangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
updateFilter('start_date', e.target.value ? e.target.value : '');
};
const endDateChangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
updateFilter('end_date', e.target.value ? e.target.value : '');
};
const [selectedMarketingType, setSelectedMarketingType] =
useState<OptionType | null>(null);
const marketingTypeChangeHandler = (
val: OptionType | OptionType[] | null
) => {
setSelectedMarketingType(val as OptionType);
updateFilter(
'marketing_type',
val ? ((val as OptionType).value as string) : ''
);
};
const searchChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
updateFilter('search', e.target.value);
};
const filterByChangeHandler = (filterBy: string) => {
updateFilter('filter_by', filterBy);
};
const sortByChangeHandler = (sort: 'asc' | 'desc' | '') => {
updateFilter('sort_by', sort);
};
const exportToExcelHandler = async () => {
setIsLoadingExportingToExcel(true);
await MarketingReportApi.exportDailyMarketingToExcel(
getTableFilterQueryString()
);
setIsLoadingExportingToExcel(false);
};
const exportToPdfHandler = async () => {
setIsLoadingExportingToPdf(true);
const params = new URLSearchParams(getTableFilterQueryString());
params.set('limit', '9999999');
const queryString = `?${params.toString()}`;
try {
const dailyMarketingsReport = await httpClient<
BaseApiResponse<DailyMarketingReport>
>(`${MarketingReportApi.basePath}${queryString}`);
if (isResponseError(dailyMarketingsReport)) {
toast.error('Gagal melakukan export penjualan harian! Coba lagi.');
return;
}
const openPdf = async () => {
const dailyMarketingReportPdfBlob = await pdf(
<DailyMarketingReportPDF data={dailyMarketingsReport.data} />
).toBlob();
const dailyMarketingReportPdfUrl = URL.createObjectURL(
dailyMarketingReportPdfBlob
);
window.open(dailyMarketingReportPdfUrl, '_blank');
};
const downloadPdf = async () => {
const blob = await pdf(
<DailyMarketingReportPDF data={dailyMarketingsReport.data} />
).toBlob();
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'laporan-penjualan-harian.pdf';
link.click();
URL.revokeObjectURL(url);
};
await openPdf();
} catch (error) {
toast.error('Gagal melakukan export penjualan harian! Coba lagi.');
}
setIsLoadingExportingToPdf(false);
};
const handleReset = () => {
setSelectedArea(null);
setSelectedLocation(null);
setSelectedWarehouse(null);
setSelectedCustomer(null);
setSelectedMarketingType(null);
resetFilter();
};
return (
<div className='w-full border border-gray-200 p-4'>
<div>
<h2 className='text-xl font-bold text-center'>Penjualan Harian</h2>
</div>
{/* Filters */}
<div className='flex flex-col gap-4 mb-6'>
<div className='grid grid-cols-12 gap-4'>
<SelectInput
label='Area'
placeholder='Pilih Area'
options={areaOptions}
isLoading={isLoadingAreaOptions}
value={selectedArea}
onChange={areaChangeHandler}
onInputChange={setAreaInputValue}
isClearable
className={{
wrapper: 'col-span-12 sm:col-span-6 lg:col-span-4',
}}
/>
<SelectInput
label='Lokasi'
placeholder='Pilih Lokasi'
options={locationOptions}
isLoading={isLoadingLocationOptions}
value={selectedLocation}
onChange={locationChangeHandler}
onInputChange={setLocationInputValue}
isClearable
className={{
wrapper: 'col-span-12 sm:col-span-6 lg:col-span-4',
}}
/>
<SelectInput
label='Gudang'
placeholder='Pilih Gudang'
options={warehouseOptions}
isLoading={isLoadingWarehouseOptions}
value={selectedWarehouse}
onChange={warehouseChangeHandler}
onInputChange={setWarehouseInputValue}
isClearable
className={{
wrapper: 'col-span-12 sm:col-span-6 lg:col-span-4',
}}
/>
<SelectInput
label='Customer'
placeholder='Pilih Customer'
options={customerOptions}
isLoading={isLoadingCustomerOptions}
value={selectedCustomer}
onChange={customerChangeHandler}
onInputChange={setCustomerInputValue}
isClearable
className={{
wrapper: 'col-span-12 sm:col-span-6 lg:col-span-4',
}}
/>
<DateInput
name='startDate'
label='Tanggal Awal'
placeholder='Tanggal Realisasi'
value={tableFilterState.start_date}
onChange={startDateChangeHandler}
className={{
wrapper: 'col-span-12 sm:col-span-6 lg:col-span-4',
}}
/>
<DateInput
name='endDate'
label='Tanggal Akhir'
placeholder='Tanggal Realisasi'
value={tableFilterState.end_date}
onChange={endDateChangeHandler}
className={{
wrapper: 'col-span-12 sm:col-span-6 lg:col-span-4',
}}
/>
</div>
<div className='grid grid-cols-12 gap-4'>
<SelectInput
label='Tipe Marketing'
placeholder='Pilih Tipe Marketing'
options={MARKETING_TYPE_OPTIONS}
value={selectedMarketingType}
onChange={marketingTypeChangeHandler}
isClearable
className={{
wrapper: 'col-span-12 sm:col-span-6 lg:col-span-4',
}}
/>
<div className='col-span-12 sm:col-span-6 lg:col-span-8 flex flex-wrap sm:justify-end items-end gap-2'>
<Button
color='primary'
// onClick={handleSearch}
className='flex-1 sm:flex-none'
>
<Icon icon='heroicons:magnifying-glass' width={20} height={20} />
Cari
</Button>
<Button
color='warning'
onClick={handleReset}
className='flex-1 sm:flex-none'
>
<Icon icon='heroicons-outline:refresh' width={20} height={20} />
Reset
</Button>
<Dropdown
align='end'
direction='bottom'
trigger={
<Button>
Export{' '}
<Icon
icon='heroicons-outline:download'
width={20}
height={20}
/>
</Button>
}
>
<Menu>
<MenuItem
title='Export to Excel'
icon='icon-park-outline:excel'
isLoading={isLoadingExportingToExcel}
onClick={exportToExcelHandler}
className='text-nowrap'
/>
<MenuItem
title='Export to PDF'
icon='icon-park-outline:file-pdf-one'
onClick={exportToPdfHandler}
className='text-nowrap'
/>
</Menu>
</Dropdown>
</div>
</div>
</div>
<DailyMarketingsTable
dailyMarketingsReportUrl={dailyMarketingsReportUrl}
onSetPage={setPage}
pageSize={tableFilterState.pageSize}
onSetPageSize={setPageSize}
searchValue={tableFilterState.search}
onSearchChange={searchChangeHandler}
onFilterByChange={filterByChangeHandler}
onSortByChange={sortByChangeHandler}
/>
</div>
);
};
export default DailyMarketingReportContent;