feat(FE-364): create DailyMarketingsTable component

This commit is contained in:
ValdiANS
2025-12-18 16:09:34 +07:00
parent 61d85154fd
commit 33d8d2aa2a
@@ -0,0 +1,255 @@
'use client';
import { ChangeEventHandler, useEffect, useState } from 'react';
import useSWR from 'swr';
import { ColumnDef, SortingState } from '@tanstack/react-table';
import { Icon } from '@iconify/react';
import Table from '@/components/Table';
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
import Card from '@/components/Card';
import Collapse from '@/components/Collapse';
import { cn, formatCurrency, formatDate, formatNumber } from '@/lib/helper';
import { isResponseSuccess } from '@/lib/api-helper';
import { DailyMarketingRow } from '@/types/api/report/marketing';
import { MarketingReportApi } from '@/services/api/report/marketing-report';
interface DailyMarketingsTableProps {
dailyMarketingsReportUrl: string;
onSetPage: (page: number) => void;
pageSize: number;
onSetPageSize: (pageSize: number) => void;
searchValue: string;
onSearchChange: ChangeEventHandler<HTMLInputElement>;
onFilterByChange: (filterBy: string) => void;
onSortByChange: (sort: 'asc' | 'desc' | '') => void;
}
const DailyMarketingsTable = ({
dailyMarketingsReportUrl,
onSetPage,
pageSize,
onSetPageSize,
searchValue,
onSearchChange,
onFilterByChange,
onSortByChange,
}: DailyMarketingsTableProps) => {
const { data: dailyMarketings, isLoading: isLoadingDailyMarketings } = useSWR(
dailyMarketingsReportUrl,
MarketingReportApi.getAllDailyMarketingFetcher,
{
keepPreviousData: true,
}
);
const [open, setOpen] = useState(true);
const [sorting, setSorting] = useState<SortingState>([]);
const dailyMarketingColumns: ColumnDef<DailyMarketingRow>[] = [
{
header: 'No',
cell: (props) => props.row.index + 1,
},
{
accessorKey: 'so_date',
header: 'Tanggal Jual',
cell: (props) => formatDate(props.row.original.so_date, 'DD-MMM-YYYY'),
footer: 'Total',
},
{
accessorKey: 'do_date',
header: 'Tanggal DO',
cell: (props) => formatDate(props.row.original.do_date, 'DD-MMM-YYYY'),
},
{
accessorKey: 'aging_days',
header: 'Aging',
cell: (props) => `${props.row.original.aging_days} hari`,
},
{
accessorKey: 'warehouse.name',
header: 'Gudang',
},
{
accessorKey: 'customer.name',
header: 'Pelanggan',
},
{
accessorKey: 'do_number',
header: 'No. DO',
},
{
accessorKey: 'sales',
header: 'Sales/Marketing',
},
{
accessorKey: 'vehicle_number',
header: 'No. Polisi',
cell: (props) => (
<span className='text-nowrap'>{props.row.original.vehicle_number}</span>
),
},
{
accessorKey: 'marketing_type',
header: 'Marketing Type',
},
{
accessorKey: 'product.name',
header: 'Produk',
},
{
accessorKey: 'qty',
header: 'Kuantitas',
cell: (props) => formatNumber(props.row.original.qty),
footer: () => {
const totalQty = isResponseSuccess(dailyMarketings)
? dailyMarketings.data.summary.total_qty
: 0;
return formatNumber(totalQty);
},
},
{
accessorKey: 'average_weight_kg',
header: 'Bobot Rata-Rata (Kg)',
cell: (props) => formatNumber(props.row.original.average_weight_kg),
},
{
accessorKey: 'total_weight_kg',
header: 'Bobot Total (Kg)',
cell: (props) => formatNumber(props.row.original.total_weight_kg),
footer: () => {
const totalWeightKg = isResponseSuccess(dailyMarketings)
? dailyMarketings.data.summary.total_weight_kg
: 0;
return formatNumber(totalWeightKg);
},
},
{
accessorKey: 'sales_price_per_kg',
header: 'Harga Jual (Rp)',
cell: (props) => formatCurrency(props.row.original.sales_price_per_kg),
},
{
accessorKey: 'hpp_price_per_kg',
header: 'HPP (Rp)',
cell: (props) => formatCurrency(props.row.original.hpp_price_per_kg),
},
{
accessorKey: 'sales_amount',
header: 'Total (Rp)',
cell: (props) => formatCurrency(props.row.original.sales_amount),
footer: () => {
const totalSalesAmount = isResponseSuccess(dailyMarketings)
? dailyMarketings.data.summary.total_sales_amount
: 0;
return formatCurrency(totalSalesAmount);
},
},
];
useEffect(() => {
if (sorting.length === 1) {
onFilterByChange(sorting[0].id);
onSortByChange(sorting[0].desc ? 'desc' : 'asc');
} else {
onFilterByChange('');
onSortByChange('');
}
}, [sorting]);
useEffect(() => {
if (!open) {
setOpen(
isResponseSuccess(dailyMarketings)
? dailyMarketings.data.rows.length > 0
: false
);
}
}, [dailyMarketings, isResponseSuccess]);
return (
<Card
className={{
wrapper: 'w-full',
body: 'p-4 shadow',
}}
>
<Collapse
open={open}
onOpenChange={setOpen}
title={
<div className='card-actions p-4 justify-between items-center w-full'>
<div className='card-title'>Penjualan Harian</div>
<Icon
icon='material-symbols:keyboard-arrow-down'
width={24}
height={24}
className={cn('text-primary transition-transform', {
'-rotate-180': open,
})}
/>
</div>
}
className='w-full!'
titleClassName='w-full p-0!'
>
<div className='w-full p-0'>
<div className='flex flex-col gap-2 mb-4'>
<div className='w-full flex flex-col sm:flex-row justify-start items-end sm:items-center gap-4'>
<DebouncedTextInput
name='search'
placeholder='Cari Penjualan Harian'
value={searchValue}
onChange={onSearchChange}
className={{ wrapper: 'sm:max-w-3xs' }}
/>
</div>
</div>
<Table<DailyMarketingRow>
data={
isResponseSuccess(dailyMarketings)
? dailyMarketings?.data.rows
: []
}
columns={dailyMarketingColumns}
pageSize={pageSize}
onPageSizeChange={onSetPageSize}
rowOptions={[10, 20, 50, 100]}
page={
isResponseSuccess(dailyMarketings)
? dailyMarketings?.meta?.page
: 0
}
totalItems={
isResponseSuccess(dailyMarketings)
? dailyMarketings?.meta?.total_results
: 0
}
onPageChange={onSetPage}
isLoading={isLoadingDailyMarketings}
sorting={sorting}
setSorting={setSorting}
renderFooter={true}
className={{
containerClassName: cn({
'w-full mb-20':
isResponseSuccess(dailyMarketings) &&
dailyMarketings?.data?.rows.length === 0,
}),
}}
/>
</div>
</Collapse>
</Card>
);
};
export default DailyMarketingsTable;