mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
feat(FE-364): create DailyMarketingReportContent component
This commit is contained in:
@@ -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;
|
||||||
Reference in New Issue
Block a user