Merge branch 'fix/marketing-report' into 'development'

[FIX/FE] Daily Marketing Report

See merge request mbugroup/lti-web-client!162
This commit is contained in:
Rivaldi A N S
2026-01-13 03:27:14 +00:00
5 changed files with 88 additions and 46 deletions
@@ -31,7 +31,10 @@ import { MarketingReportApi } from '@/services/api/report/marketing-report';
import { MARKETING_TYPE_OPTIONS } from '@/config/constant'; import { MARKETING_TYPE_OPTIONS } from '@/config/constant';
import { httpClient } from '@/services/http/client'; import { httpClient } from '@/services/http/client';
import { BaseApiResponse } from '@/types/api/api-general'; import { BaseApiResponse } from '@/types/api/api-general';
import { DailyMarketingReport } from '@/types/api/report/marketing'; import {
DailyMarketingReport,
DailyMarketingReportResponse,
} from '@/types/api/report/marketing';
import { isResponseError } from '@/lib/api-helper'; import { isResponseError } from '@/lib/api-helper';
const DailyMarketingReportContent = () => { const DailyMarketingReportContent = () => {
@@ -191,9 +194,10 @@ const DailyMarketingReportContent = () => {
const queryString = `?${params.toString()}`; const queryString = `?${params.toString()}`;
try { try {
const dailyMarketingsReport = await httpClient< const dailyMarketingsReport =
BaseApiResponse<DailyMarketingReport> await httpClient<DailyMarketingReportResponse>(
>(`${MarketingReportApi.basePath}${queryString}`); `${MarketingReportApi.basePath}${queryString}`
);
if (isResponseError(dailyMarketingsReport)) { if (isResponseError(dailyMarketingsReport)) {
toast.error('Gagal melakukan export penjualan harian! Coba lagi.'); toast.error('Gagal melakukan export penjualan harian! Coba lagi.');
@@ -202,7 +206,10 @@ const DailyMarketingReportContent = () => {
const openPdf = async () => { const openPdf = async () => {
const dailyMarketingReportPdfBlob = await pdf( const dailyMarketingReportPdfBlob = await pdf(
<DailyMarketingReportPDF data={dailyMarketingsReport.data} /> <DailyMarketingReportPDF
data={dailyMarketingsReport.data}
total={dailyMarketingsReport.total}
/>
).toBlob(); ).toBlob();
const dailyMarketingReportPdfUrl = URL.createObjectURL( const dailyMarketingReportPdfUrl = URL.createObjectURL(
@@ -213,7 +220,10 @@ const DailyMarketingReportContent = () => {
const downloadPdf = async () => { const downloadPdf = async () => {
const blob = await pdf( const blob = await pdf(
<DailyMarketingReportPDF data={dailyMarketingsReport.data} /> <DailyMarketingReportPDF
data={dailyMarketingsReport.data}
total={dailyMarketingsReport.total}
/>
).toBlob(); ).toBlob();
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
@@ -9,11 +9,15 @@ import {
View, View,
} from '@react-pdf/renderer'; } from '@react-pdf/renderer';
import { DailyMarketingReport } from '@/types/api/report/marketing'; import {
DailyMarketingReport,
SalesSummary,
} from '@/types/api/report/marketing';
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper'; import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
interface DailyMarketingReportPDFProps { interface DailyMarketingReportPDFProps {
data?: DailyMarketingReport; data?: DailyMarketingReport;
total?: SalesSummary;
} }
const DailyMarketingReportPDFStyle = StyleSheet.create({ const DailyMarketingReportPDFStyle = StyleSheet.create({
@@ -267,9 +271,12 @@ const DailyMarketingReportPDFStyle = StyleSheet.create({
}, },
}); });
const DailyMarketingReportPDF = ({ data }: DailyMarketingReportPDFProps) => { const DailyMarketingReportPDF = ({
const rows = data?.rows || []; data,
const summary = data?.summary; total,
}: DailyMarketingReportPDFProps) => {
const rows = data || [];
const summary = total;
return ( return (
<Document> <Document>
@@ -409,7 +416,7 @@ const DailyMarketingReportPDF = ({ data }: DailyMarketingReportPDFProps) => {
</View> </View>
<View style={DailyMarketingReportPDFStyle.colDoDate}> <View style={DailyMarketingReportPDFStyle.colDoDate}>
<Text style={DailyMarketingReportPDFStyle.cellText}> <Text style={DailyMarketingReportPDFStyle.cellText}>
{formatDate(row.do_date, 'DD/MM/YYYY')} {formatDate(row.realization_date, 'DD/MM/YYYY')}
</Text> </Text>
</View> </View>
<View style={DailyMarketingReportPDFStyle.colAging}> <View style={DailyMarketingReportPDFStyle.colAging}>
@@ -429,7 +436,7 @@ const DailyMarketingReportPDF = ({ data }: DailyMarketingReportPDFProps) => {
</View> </View>
<View style={DailyMarketingReportPDFStyle.colSales}> <View style={DailyMarketingReportPDFStyle.colSales}>
<Text style={DailyMarketingReportPDFStyle.cellText}> <Text style={DailyMarketingReportPDFStyle.cellText}>
{row.sales} {row.sales.name}
</Text> </Text>
</View> </View>
<View style={DailyMarketingReportPDFStyle.colProduct}> <View style={DailyMarketingReportPDFStyle.colProduct}>
@@ -518,6 +525,19 @@ const DailyMarketingReportPDF = ({ data }: DailyMarketingReportPDFProps) => {
{formatCurrency(summary?.total_sales_amount ?? 0)} {formatCurrency(summary?.total_sales_amount ?? 0)}
</Text> </Text>
</View> </View>
<View
style={[
DailyMarketingReportPDFStyle.summaryRow,
{ borderBottomWidth: 0 },
]}
>
<Text style={DailyMarketingReportPDFStyle.summaryLabel}>
Total HPP Per KG:
</Text>
<Text style={DailyMarketingReportPDFStyle.summaryValue}>
{formatCurrency(summary?.total_hpp_price_per_kg ?? 0)}
</Text>
</View>
<View <View
style={[ style={[
DailyMarketingReportPDFStyle.summaryRow, DailyMarketingReportPDFStyle.summaryRow,
@@ -60,9 +60,10 @@ const DailyMarketingsTable = ({
footer: 'Total', footer: 'Total',
}, },
{ {
accessorKey: 'do_date', accessorKey: 'realization_date',
header: 'Tanggal DO', header: 'Tanggal Realisasi',
cell: (props) => formatDate(props.row.original.do_date, 'DD-MMM-YYYY'), cell: (props) =>
formatDate(props.row.original.realization_date, 'DD-MMM-YYYY'),
}, },
{ {
accessorKey: 'aging_days', accessorKey: 'aging_days',
@@ -106,10 +107,10 @@ const DailyMarketingsTable = ({
cell: (props) => formatNumber(props.row.original.qty), cell: (props) => formatNumber(props.row.original.qty),
footer: () => { footer: () => {
const totalQty = isResponseSuccess(dailyMarketings) const totalQty = isResponseSuccess(dailyMarketings)
? dailyMarketings.data.summary.total_qty ? dailyMarketings?.total?.total_qty
: 0; : 0;
return formatNumber(totalQty); return totalQty ? formatNumber(totalQty) : '-';
}, },
}, },
{ {
@@ -123,10 +124,10 @@ const DailyMarketingsTable = ({
cell: (props) => formatNumber(props.row.original.total_weight_kg), cell: (props) => formatNumber(props.row.original.total_weight_kg),
footer: () => { footer: () => {
const totalWeightKg = isResponseSuccess(dailyMarketings) const totalWeightKg = isResponseSuccess(dailyMarketings)
? dailyMarketings.data.summary.total_weight_kg ? dailyMarketings?.total?.total_weight_kg
: 0; : 0;
return formatNumber(totalWeightKg); return totalWeightKg ? formatNumber(totalWeightKg) : '-';
}, },
}, },
{ {
@@ -138,6 +139,13 @@ const DailyMarketingsTable = ({
accessorKey: 'hpp_price_per_kg', accessorKey: 'hpp_price_per_kg',
header: 'HPP (Rp)', header: 'HPP (Rp)',
cell: (props) => formatCurrency(props.row.original.hpp_price_per_kg), cell: (props) => formatCurrency(props.row.original.hpp_price_per_kg),
footer: () => {
const totalHppPricePerKg = isResponseSuccess(dailyMarketings)
? dailyMarketings?.total?.total_hpp_price_per_kg
: 0;
return totalHppPricePerKg ? formatCurrency(totalHppPricePerKg) : '-';
},
}, },
{ {
accessorKey: 'sales_amount', accessorKey: 'sales_amount',
@@ -145,10 +153,10 @@ const DailyMarketingsTable = ({
cell: (props) => formatCurrency(props.row.original.sales_amount), cell: (props) => formatCurrency(props.row.original.sales_amount),
footer: () => { footer: () => {
const totalSalesAmount = isResponseSuccess(dailyMarketings) const totalSalesAmount = isResponseSuccess(dailyMarketings)
? dailyMarketings.data.summary.total_sales_amount ? dailyMarketings?.total?.total_sales_amount
: 0; : 0;
return formatCurrency(totalSalesAmount); return totalSalesAmount ? formatCurrency(totalSalesAmount) : '-';
}, },
}, },
]; ];
@@ -167,7 +175,7 @@ const DailyMarketingsTable = ({
if (!open) { if (!open) {
setOpen( setOpen(
isResponseSuccess(dailyMarketings) isResponseSuccess(dailyMarketings)
? dailyMarketings.data.rows.length > 0 ? dailyMarketings.data.length > 0
: false : false
); );
} }
@@ -215,9 +223,7 @@ const DailyMarketingsTable = ({
<Table<DailyMarketingRow> <Table<DailyMarketingRow>
data={ data={
isResponseSuccess(dailyMarketings) isResponseSuccess(dailyMarketings) ? dailyMarketings?.data : []
? dailyMarketings?.data.rows
: []
} }
columns={dailyMarketingColumns} columns={dailyMarketingColumns}
pageSize={pageSize} pageSize={pageSize}
@@ -242,7 +248,7 @@ const DailyMarketingsTable = ({
containerClassName: cn({ containerClassName: cn({
'w-full mb-20': 'w-full mb-20':
isResponseSuccess(dailyMarketings) && isResponseSuccess(dailyMarketings) &&
dailyMarketings?.data?.rows.length === 0, dailyMarketings?.data?.length === 0,
}), }),
}} }}
/> />
+15 -11
View File
@@ -2,11 +2,14 @@ import * as XLSX from 'xlsx';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import { BaseApiService } from '@/services/api/base'; import { BaseApiService } from '@/services/api/base';
import { httpClient, httpClientFetcher } from '@/services/http/client'; import { httpClientFetcher } from '@/services/http/client';
import { BaseApiResponse } from '@/types/api/api-general'; import { BaseApiResponse } from '@/types/api/api-general';
import { DailyMarketingReport } from '@/types/api/report/marketing'; import {
DailyMarketingReport,
DailyMarketingReportResponse,
} from '@/types/api/report/marketing';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { formatDate, sleep } from '@/lib/helper'; import { formatDate } from '@/lib/helper';
export class MarketingReportApiService extends BaseApiService< export class MarketingReportApiService extends BaseApiService<
DailyMarketingReport, DailyMarketingReport,
@@ -19,10 +22,8 @@ export class MarketingReportApiService extends BaseApiService<
async getAllDailyMarketingFetcher( async getAllDailyMarketingFetcher(
endpoint: string endpoint: string
): Promise<BaseApiResponse<DailyMarketingReport>> { ): Promise<DailyMarketingReportResponse> {
return await httpClientFetcher<BaseApiResponse<DailyMarketingReport>>( return await httpClientFetcher<DailyMarketingReportResponse>(endpoint);
endpoint
);
} }
async exportDailyMarketingToExcel(initialQueryString: string) { async exportDailyMarketingToExcel(initialQueryString: string) {
@@ -42,16 +43,19 @@ export class MarketingReportApiService extends BaseApiService<
return; return;
} }
const rows = dailyMarketingsReport.data.rows; const rows = dailyMarketingsReport.data;
const formattedRows = []; const formattedRows = [];
for (let i = 0; i < rows.length; i++) { for (let i = 0; i < rows.length; i++) {
formattedRows.push({ formattedRows.push({
...rows[i], ...rows[i],
created_user: rows[i].created_user.name, // created_user: rows[i].created_user.name,
created_at: formatDate(rows[i].created_at, 'YYYY-MM-DD'), // created_at: formatDate(rows[i].created_at, 'YYYY-MM-DD'),
updated_at: formatDate(rows[i].updated_at, 'YYYY-MM-DD'), // updated_at: formatDate(rows[i].updated_at, 'YYYY-MM-DD'),
so_date: formatDate(rows[i].so_date, 'YYYY-MM-DD'),
realization_date: formatDate(rows[i].realization_date, 'YYYY-MM-DD'),
sales: rows[i].sales.name,
warehouse: rows[i].warehouse.name, warehouse: rows[i].warehouse.name,
customer: rows[i].customer.name, customer: rows[i].customer.name,
product: rows[i].product.name, product: rows[i].product.name,
+11 -9
View File
@@ -1,4 +1,4 @@
import { BaseMetadata } from '@/types/api/api-general'; import { BaseApiResponse, BaseMetadata } from '@/types/api/api-general';
import { BaseCustomer, Customer } from '@/types/api/master-data/customer'; import { BaseCustomer, Customer } from '@/types/api/master-data/customer';
import { import {
BaseWarehouseArea, BaseWarehouseArea,
@@ -9,16 +9,17 @@ import {
import { Location } from '@/types/api/master-data/location'; import { Location } from '@/types/api/master-data/location';
import { Area } from '@/types/api/master-data/area'; import { Area } from '@/types/api/master-data/area';
import { BaseProduct } from '@/types/api/master-data/product'; import { BaseProduct } from '@/types/api/master-data/product';
import { BaseUser } from '@/types/api/user';
export type BaseDailyMarketingRow = { export type BaseDailyMarketingRow = {
no: number; id: number;
so_date: string; // e.g. "01-Dec-2025" so_date: string;
do_date: string; // e.g. "08-Dec-2025" realization_date: string;
aging_days: number; aging_days: number;
warehouse: BaseWarehouseArea | BaseWarehouseLocation | BaseWarehouseKandang; warehouse: BaseWarehouseArea | BaseWarehouseLocation | BaseWarehouseKandang;
customer: BaseCustomer; customer: BaseCustomer;
sales: string; sales: BaseUser;
product: BaseProduct; product: BaseProduct;
do_number: string; do_number: string;
@@ -43,12 +44,13 @@ export interface SalesSummary {
total_weight_kg: number; total_weight_kg: number;
total_sales_amount: number; total_sales_amount: number;
total_hpp_amount: number; total_hpp_amount: number;
total_hpp_price_per_kg: number;
} }
export type DailyMarketingReport = { export type DailyMarketingReport = DailyMarketingRow[];
rows: DailyMarketingRow[];
summary: SalesSummary; export type DailyMarketingReportResponse =
}; BaseApiResponse<DailyMarketingReport> & { total: SalesSummary };
export type MarketingReportFilters = { export type MarketingReportFilters = {
area_id?: number; area_id?: number;