From dbcf46912334db250a9baab4ca86c70f3b0f7021 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 12 Feb 2026 13:44:22 +0700 Subject: [PATCH] refactor(FE): Remove DailyMarketingsTable component and refactor related files --- .../report/marketing/DailyMarketingsTable.tsx | 289 ---- .../pages/report/marketing/MarketingTabs.tsx | 2 +- .../marketing/tab/DailyMarketingTab.tsx | 1311 +++++++++++------ 3 files changed, 887 insertions(+), 715 deletions(-) delete mode 100644 src/components/pages/report/marketing/DailyMarketingsTable.tsx diff --git a/src/components/pages/report/marketing/DailyMarketingsTable.tsx b/src/components/pages/report/marketing/DailyMarketingsTable.tsx deleted file mode 100644 index 4904ef16..00000000 --- a/src/components/pages/report/marketing/DailyMarketingsTable.tsx +++ /dev/null @@ -1,289 +0,0 @@ -'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, - formatVechicleNumber, -} 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; - 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([]); - - const dailyMarketingColumns: ColumnDef[] = [ - { - 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: 'realization_date', - header: 'Tanggal Realisasi', - cell: (props) => - formatDate(props.row.original.realization_date, 'DD-MMM-YYYY'), - }, - { - accessorKey: 'aging_days', - header: 'Aging', - cell: (props) => `${props.row.original.aging_days} hari`, - }, - { - accessorKey: 'warehouse', - header: 'Gudang', - cell: ({ row }) => row.original.warehouse.name, - }, - { - accessorKey: 'customer', - header: 'Pelanggan', - cell: ({ row }) => row.original.customer.name, - }, - { - accessorKey: 'do_number', - header: 'No. DO', - enableSorting: false, - }, - { - accessorKey: 'sales_person', - header: 'Sales/Marketing', - cell: (props) => props.row.original.sales.name, - }, - { - accessorKey: 'vehicle_number', - header: 'No. Polisi', - cell: (props) => ( - - {formatVechicleNumber(props.row.original.vehicle_number)} - - ), - }, - { - accessorKey: 'marketing_type', - header: 'Marketing Type', - enableSorting: false, - }, - { - accessorKey: 'product', - header: 'Produk', - cell: ({ row }) => row.original.product.name, - }, - { - accessorKey: 'qty', - header: 'Kuantitas', - cell: (props) => formatNumber(props.row.original.qty), - footer: () => { - const totalQty = isResponseSuccess(dailyMarketings) - ? dailyMarketings?.total?.total_qty - : 0; - - return totalQty ? formatNumber(totalQty) : '-'; - }, - }, - { - accessorKey: 'average_weight', - header: 'Bobot Rata-Rata (Kg)', - cell: (props) => formatNumber(props.row.original.average_weight_kg), - footer: () => { - const totalAverageWeightKg = isResponseSuccess(dailyMarketings) - ? dailyMarketings?.total?.average_weight_kg - : 0; - - return totalAverageWeightKg ? formatNumber(totalAverageWeightKg) : '-'; - }, - }, - { - accessorKey: 'total_weight', - header: 'Bobot Total (Kg)', - cell: (props) => formatNumber(props.row.original.total_weight_kg), - footer: () => { - const totalWeightKg = isResponseSuccess(dailyMarketings) - ? dailyMarketings?.total?.total_weight_kg - : 0; - - return totalWeightKg ? formatNumber(totalWeightKg) : '-'; - }, - }, - { - accessorKey: 'sales_price', - header: 'Harga Jual (Rp)', - cell: (props) => formatCurrency(props.row.original.sales_price_per_kg), - footer: () => { - const totalSalesPrice = isResponseSuccess(dailyMarketings) - ? dailyMarketings?.total?.average_sales_price - : 0; - - return totalSalesPrice ? formatNumber(totalSalesPrice) : '-'; - }, - }, - { - accessorKey: 'hpp_price', - header: 'HPP (Rp)', - 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', - header: 'Total (Rp)', - cell: (props) => formatCurrency(props.row.original.sales_amount), - footer: () => { - const totalSalesAmount = isResponseSuccess(dailyMarketings) - ? dailyMarketings?.total?.total_sales_amount - : 0; - - return totalSalesAmount ? 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.length > 0 - : false - ); - } - }, [dailyMarketings, isResponseSuccess]); - - return ( - - -
Penjualan Harian
- - - - } - className='w-full!' - titleClassName='w-full p-0!' - > -
-
-
- -
-
- - - data={ - isResponseSuccess(dailyMarketings) ? dailyMarketings?.data : [] - } - 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?.length === 0, - }), - }} - /> -
-
-
- ); -}; - -export default DailyMarketingsTable; diff --git a/src/components/pages/report/marketing/MarketingTabs.tsx b/src/components/pages/report/marketing/MarketingTabs.tsx index 8a814689..de449f9c 100644 --- a/src/components/pages/report/marketing/MarketingTabs.tsx +++ b/src/components/pages/report/marketing/MarketingTabs.tsx @@ -14,7 +14,7 @@ const MarketingReportContent = () => { { id: '1', label: 'Penjualan Harian', - content: , + content: , }, { id: '2', diff --git a/src/components/pages/report/marketing/tab/DailyMarketingTab.tsx b/src/components/pages/report/marketing/tab/DailyMarketingTab.tsx index cedf979f..5fcc630f 100644 --- a/src/components/pages/report/marketing/tab/DailyMarketingTab.tsx +++ b/src/components/pages/report/marketing/tab/DailyMarketingTab.tsx @@ -1,472 +1,933 @@ -'use client'; - -import { ChangeEventHandler, useEffect, useState } from 'react'; +import { useState, useMemo, useCallback } from 'react'; +import useSWR from 'swr'; +import { useSelect } from '@/components/input/SelectInput'; +import DateInput from '@/components/input/DateInput'; +import DebouncedTextInput from '@/components/input/DebouncedTextInput'; +import { AreaApi } from '@/services/api/master-data'; +import { LocationApi } from '@/services/api/master-data'; +import { WarehouseApi } from '@/services/api/master-data'; +import { CustomerApi } from '@/services/api/master-data'; +import { MarketingReportApi } from '@/services/api/report/marketing-report'; +import Table from '@/components/Table'; +import { ColumnDef } from '@tanstack/react-table'; +import { + formatCurrency, + formatNumber, + formatDate, + formatVechicleNumber, +} from '@/lib/helper'; +import { + DailyMarketingRow, + DailyMarketingReportResponse, +} from '@/types/api/report/marketing'; +import { isResponseSuccess } from '@/lib/api-helper'; +import Button from '@/components/Button'; +import Dropdown from '@/components/Dropdown'; +import MenuItem from '@/components/menu/MenuItem'; +import Menu from '@/components/menu/Menu'; +import DailyMarketingReportPDF from '@/components/pages/report/marketing/export/DailyMarketingExportPDF'; 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/marketing/DailyMarketingsTable'; -import { useTableFilter } from '@/services/hooks/useTableFilter'; -import DailyMarketingReportPDF from '@/components/pages/report/marketing/export/DailyMarketingExportPDF'; - -import { Area } from '@/types/api/master-data/area'; +import { useFormik } from 'formik'; 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'; + DailyMarketingReportFilterSchema, + DailyMarketingReportFilterType, +} from '@/components/pages/report/marketing/filter/DailyMarketingFilter'; +import SelectInput from '@/components/input/SelectInput'; +import Modal, { useModal } from '@/components/Modal'; +import { cn } from '@/lib/helper'; +import { useMarketingTabStore } from '@/stores/marketing-tab/marketing-tab.store'; +import DailyMarketingReportSkeleton from '@/components/pages/report/marketing/skeleton/DailyMarketingSkeleton'; +import { useEffect as useEffectHook } from 'react'; +import { httpClient } from '@/services/http/client'; +import { isResponseError } from '@/lib/api-helper'; import { MARKETING_DATE_FILTER_TYPE_OPTIONS, MARKETING_TYPE_OPTIONS, } from '@/config/constant'; -import { httpClient } from '@/services/http/client'; -import { BaseApiResponse } from '@/types/api/api-general'; -import { - DailyMarketingReport, - DailyMarketingReportResponse, -} from '@/types/api/report/marketing'; -import { isResponseError } from '@/lib/api-helper'; +import Badge from '@/components/Badge'; -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: '', +interface DailyMarketingTabProps { + tabId: string; +} + +interface FilterParams { + area_id?: string; + location_id?: string; + warehouse_id?: string; + customer_id?: string; + start_date?: string; + end_date?: string; + filter_by?: string; + marketing_type?: string; + sort_by?: string; +} + +const DailyMarketingTab = ({ tabId }: DailyMarketingTabProps) => { + // ===== STATE MANAGEMENT ===== + const [isPdfExportLoading, setIsPdfExportLoading] = useState(false); + const [isExcelExportLoading, setIsExcelExportLoading] = useState(false); + const isAnyExportLoading = isPdfExportLoading || isExcelExportLoading; + + // ===== SUBMISSION STATE ===== + const [isSubmitted, setIsSubmitted] = useState(false); + + // ===== SEARCH STATE ===== + const [searchValue, setSearchValue] = useState(''); + + // ===== FILTER STATE ===== + const [filterParams, setFilterParams] = useState({}); + + const filterModal = useModal(); + + // ===== OPTIONS ===== + const { options: areaOptions, isLoadingOptions: isLoadingAreas } = useSelect( + AreaApi.basePath, + 'id', + 'name', + 'search' + ); + + const { options: locationOptions, isLoadingOptions: isLoadingLocations } = + useSelect(LocationApi.basePath, 'id', 'name', 'search'); + + const { options: warehouseOptions, isLoadingOptions: isLoadingWarehouses } = + useSelect(WarehouseApi.basePath, 'id', 'name', 'search'); + + const { options: customerOptions, isLoadingOptions: isLoadingCustomers } = + useSelect(CustomerApi.basePath, 'id', 'name', 'search'); + + const handleFilterModalOpen = () => { + filterModal.openModal(); + formik.validateForm(); + }; + + // ===== FORMIK SETUP ===== + const formik = useFormik({ + initialValues: { + search: null, + area_id: null, + location_id: null, + warehouse_id: null, + customer_id: null, + start_date: null, + end_date: null, + filter_by: null, + marketing_type: null, + sort_by: null, }, - 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', + validationSchema: DailyMarketingReportFilterSchema, + onSubmit: (values, { setSubmitting }) => { + setFilterParams({ + area_id: values.area_id || undefined, + location_id: values.location_id || undefined, + warehouse_id: values.warehouse_id || undefined, + customer_id: values.customer_id || undefined, + start_date: values.start_date || undefined, + end_date: values.end_date || undefined, + filter_by: values.filter_by || undefined, + marketing_type: values.marketing_type || undefined, + sort_by: values.sort_by || undefined, + }); + filterModal.closeModal(); + setIsSubmitted(true); + setSubmitting(false); + }, + onReset: () => { + setFilterParams({}); + setIsSubmitted(false); }, }); - const dailyMarketingsReportUrl = `${MarketingReportApi.basePath}${getTableFilterQueryString()}`; - - const [isLoadingExportingToExcel, setIsLoadingExportingToExcel] = - useState(false); - const [isLoadingExportingToPdf, setIsLoadingExportingToPdf] = useState(false); - - const [selectedArea, setSelectedArea] = useState(null); - const { - setInputValue: setAreaInputValue, - options: areaOptions, - isLoadingOptions: isLoadingAreaOptions, - loadMore: loadMoreAreas, - } = useSelect(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( - null + // ===== SEARCH CHANGE HANDLER ===== + const searchChangeHandler = useCallback( + (e: React.ChangeEvent) => { + setSearchValue(e.target.value); + }, + [] ); - const { - setInputValue: setLocationInputValue, - options: locationOptions, - isLoadingOptions: isLoadingLocationOptions, - loadMore: loadMoreLocations, - } = useSelect(LocationApi.basePath, 'id', 'name'); - const locationChangeHandler = (val: OptionType | OptionType[] | null) => { - setSelectedLocation(val as OptionType); - updateFilter( - 'location_id', - val ? ((val as OptionType).value as string) : '' + // ===== DERIVED VALUES ===== + const areaValue = useMemo(() => { + if (!formik.values.area_id) return null; + return ( + areaOptions.find((opt) => String(opt.value) === formik.values.area_id) || + null ); - }; + }, [formik.values.area_id, areaOptions]); - const [selectedWarehouse, setSelectedWarehouse] = useState( - null + const locationValue = useMemo(() => { + if (!formik.values.location_id) return null; + return ( + locationOptions.find( + (opt) => String(opt.value) === formik.values.location_id + ) || null + ); + }, [formik.values.location_id, locationOptions]); + + const warehouseValue = useMemo(() => { + if (!formik.values.warehouse_id) return null; + return ( + warehouseOptions.find( + (opt) => String(opt.value) === formik.values.warehouse_id + ) || null + ); + }, [formik.values.warehouse_id, warehouseOptions]); + + const customerValue = useMemo(() => { + if (!formik.values.customer_id) return null; + return ( + customerOptions.find( + (opt) => String(opt.value) === formik.values.customer_id + ) || null + ); + }, [formik.values.customer_id, customerOptions]); + + const filterByValue = useMemo(() => { + if (!formik.values.filter_by) return null; + return ( + MARKETING_DATE_FILTER_TYPE_OPTIONS.find( + (opt) => opt.value === formik.values.filter_by + ) || null + ); + }, [formik.values.filter_by]); + + const marketingTypeValue = useMemo(() => { + if (!formik.values.marketing_type) return null; + return ( + MARKETING_TYPE_OPTIONS.find( + (opt) => opt.value === formik.values.marketing_type + ) || null + ); + }, [formik.values.marketing_type]); + + // ===== ACTIVE FILTERS COUNT ===== + const activeFiltersCount = useMemo(() => { + let count = 0; + + if (filterParams.area_id) { + count += 1; + } + + if (filterParams.location_id) { + count += 1; + } + + if (filterParams.warehouse_id) { + count += 1; + } + + if (filterParams.customer_id) { + count += 1; + } + + if (filterParams.start_date || filterParams.end_date) { + count += 1; + } + + if (filterParams.filter_by) { + count += 1; + } + + if (filterParams.marketing_type) { + count += 1; + } + + if (filterParams.sort_by) { + count += 1; + } + + return count; + }, [filterParams]); + + const hasFilters = activeFiltersCount > 0; + + // ===== DATA FETCHING ===== + const { data: dailyMarketings, isLoading } = useSWR( + isSubmitted + ? () => { + const params = new URLSearchParams(); + + if (searchValue) params.set('search', searchValue); + if (filterParams.area_id) params.set('area_id', filterParams.area_id); + if (filterParams.location_id) + params.set('location_id', filterParams.location_id); + if (filterParams.warehouse_id) + params.set('warehouse_id', filterParams.warehouse_id); + if (filterParams.customer_id) + params.set('customer_id', filterParams.customer_id); + if (filterParams.start_date) + params.set('start_date', filterParams.start_date); + if (filterParams.end_date) + params.set('end_date', filterParams.end_date); + if (filterParams.filter_by) + params.set('filter_by', filterParams.filter_by); + if (filterParams.marketing_type) + params.set('marketing_type', filterParams.marketing_type); + if (filterParams.sort_by) params.set('sort_by', filterParams.sort_by); + + return ['daily-marketing-report', params.toString()]; + } + : null, + ([, params]) => + MarketingReportApi.getAllDailyMarketingFetcher( + `${MarketingReportApi.basePath}?${params}` + ) ); - const { - setInputValue: setWarehouseInputValue, - options: warehouseOptions, - isLoadingOptions: isLoadingWarehouseOptions, - loadMore: loadMoreWarehouses, - } = useSelect(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( - null + const data: DailyMarketingRow[] = useMemo( + () => + isResponseSuccess(dailyMarketings) + ? (dailyMarketings?.data as DailyMarketingRow[]) || [] + : [], + [dailyMarketings] ); - const { - setInputValue: setCustomerInputValue, - options: customerOptions, - isLoadingOptions: isLoadingCustomerOptions, - loadMore: loadMoreCustomers, - } = useSelect(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 summaryTotal = useMemo( + () => + isResponseSuccess(dailyMarketings) && dailyMarketings?.total + ? dailyMarketings.total + : undefined, + [dailyMarketings] + ); - const startDateChangeHandler = (e: React.ChangeEvent) => { - updateFilter('start_date', e.target.value ? e.target.value : ''); - }; - - const endDateChangeHandler = (e: React.ChangeEvent) => { - updateFilter('end_date', e.target.value ? e.target.value : ''); - }; - - const [selectedMarketingDateFilterType, setSelectedMarketingDateFilterType] = - useState(null); - const marketingDateFilterTypeChangeHandler = ( - val: OptionType | OptionType[] | null - ) => { - setSelectedMarketingDateFilterType(val as OptionType); - updateFilter('filter_by', val ? ((val as OptionType).value as string) : ''); - }; - - const [selectedMarketingType, setSelectedMarketingType] = - useState(null); - const marketingTypeChangeHandler = ( - val: OptionType | OptionType[] | null - ) => { - setSelectedMarketingType(val as OptionType); - updateFilter( - 'marketing_type', - val ? ((val as OptionType).value as string) : '' - ); - }; - - const searchChangeHandler: ChangeEventHandler = (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()); + // ===== EXPORT DATA FETCHER ===== + const dailyMarketingsExport = useCallback(async (): Promise< + DailyMarketingRow[] | null + > => { + const params = new URLSearchParams(); + if (searchValue) params.set('search', searchValue); + if (filterParams.area_id) params.set('area_id', filterParams.area_id); + if (filterParams.location_id) + params.set('location_id', filterParams.location_id); + if (filterParams.warehouse_id) + params.set('warehouse_id', filterParams.warehouse_id); + if (filterParams.customer_id) + params.set('customer_id', filterParams.customer_id); + if (filterParams.start_date) + params.set('start_date', filterParams.start_date); + if (filterParams.end_date) params.set('end_date', filterParams.end_date); + if (filterParams.filter_by) params.set('filter_by', filterParams.filter_by); + if (filterParams.marketing_type) + params.set('marketing_type', filterParams.marketing_type); + if (filterParams.sort_by) params.set('sort_by', filterParams.sort_by); params.set('limit', '9999999'); const queryString = `?${params.toString()}`; try { - const dailyMarketingsReport = - await httpClient( - `${MarketingReportApi.basePath}${queryString}` - ); + const response = await httpClient( + `${MarketingReportApi.basePath}${queryString}` + ); - if (isResponseError(dailyMarketingsReport)) { - toast.error('Gagal melakukan export penjualan harian! Coba lagi.'); + if (isResponseError(response)) { + return null; + } + + return response.data || []; + } catch { + return null; + } + }, [filterParams, searchValue]); + + // ===== EXPORT HANDLERS ===== + const handleExportExcel = useCallback(async () => { + setIsExcelExportLoading(true); + try { + const queryString = new URLSearchParams(); + + if (searchValue) queryString.set('search', searchValue); + if (filterParams.area_id) + queryString.set('area_id', filterParams.area_id); + if (filterParams.location_id) + queryString.set('location_id', filterParams.location_id); + if (filterParams.warehouse_id) + queryString.set('warehouse_id', filterParams.warehouse_id); + if (filterParams.customer_id) + queryString.set('customer_id', filterParams.customer_id); + if (filterParams.start_date) + queryString.set('start_date', filterParams.start_date); + if (filterParams.end_date) + queryString.set('end_date', filterParams.end_date); + if (filterParams.filter_by) + queryString.set('filter_by', filterParams.filter_by); + if (filterParams.marketing_type) + queryString.set('marketing_type', filterParams.marketing_type); + if (filterParams.sort_by) + queryString.set('sort_by', filterParams.sort_by); + + await MarketingReportApi.exportDailyMarketingToExcel( + `?${queryString.toString()}` + ); + + toast.success('Excel berhasil dibuat dan diunduh.'); + } catch { + toast.error('Gagal membuat Excel. Silakan coba lagi.'); + } finally { + setIsExcelExportLoading(false); + } + }, [filterParams, searchValue]); + + const handleExportPDF = useCallback(async () => { + setIsPdfExportLoading(true); + try { + const allDataForExport = await dailyMarketingsExport(); + + if (!allDataForExport || allDataForExport.length === 0) { + toast.error('Tidak ada data untuk diekspor.'); return; } - const openPdf = async () => { - const dailyMarketingReportPdfBlob = await pdf( - - ).toBlob(); + const dailyMarketingReportPdfBlob = await pdf( + + ).toBlob(); - const dailyMarketingReportPdfUrl = URL.createObjectURL( - dailyMarketingReportPdfBlob - ); - window.open(dailyMarketingReportPdfUrl, '_blank'); - }; + const dailyMarketingReportPdfUrl = URL.createObjectURL( + dailyMarketingReportPdfBlob + ); + window.open(dailyMarketingReportPdfUrl, '_blank'); - const downloadPdf = async () => { - const blob = await pdf( - - ).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.'); + toast.success('PDF berhasil dibuat.'); + } catch { + toast.error('Gagal membuat PDF. Silakan coba lagi.'); + } finally { + setIsPdfExportLoading(false); } + }, [dailyMarketingsExport, summaryTotal]); - setIsLoadingExportingToPdf(false); + // ===== REGISTER TAB ACTIONS TO STORE ===== + const setTabActions = useMarketingTabStore((state) => state.setTabActions); + const clearTabActions = useMarketingTabStore( + (state) => state.clearTabActions + ); + + useEffectHook(() => { + setTabActions( + tabId, +
+ + } + className={{ + wrapper: 'w-full min-w-48 max-w-3xs', + inputWrapper: 'rounded-xl! shadow-button-soft', + input: 'placeholder:font-semibold placeholder:text-base-content/50', + }} + /> + + + + +
+ + + Export + +
+ + +
+ + } + > + + + +
+ ); + }, [ + tabId, + searchValue, + hasFilters, + activeFiltersCount, + isAnyExportLoading, + filterModal.open, + setTabActions, + ]); + + useEffectHook(() => { + return () => { + clearTabActions(tabId); + }; + }, [tabId, clearTabActions]); + + const getTableColumns = (): ColumnDef[] => { + const tableColumns: ColumnDef[] = [ + { + id: 'no', + header: 'No', + cell: (props) => props.row.index + 1, + footer: () =>
TOTAL
, + }, + { + id: 'so_date', + header: 'Tanggal Jual', + accessorKey: 'so_date', + cell: (props) => formatDate(props.row.original.so_date, 'DD-MMM-YYYY'), + footer: () =>
ALL
, + }, + { + id: 'realization_date', + header: 'Tanggal Realisasi', + accessorKey: 'realization_date', + cell: (props) => + formatDate(props.row.original.realization_date, 'DD-MMM-YYYY'), + footer: () =>
-
, + }, + { + id: 'aging_days', + header: 'Aging', + accessorKey: 'aging_days', + cell: (props) => `${props.row.original.aging_days} hari`, + footer: () =>
-
, + }, + { + id: 'warehouse', + header: 'Gudang', + accessorKey: 'warehouse', + cell: ({ row }) => row.original.warehouse.name, + footer: () =>
-
, + }, + { + id: 'customer', + header: 'Pelanggan', + accessorKey: 'customer', + cell: ({ row }) => row.original.customer.name, + footer: () =>
-
, + }, + { + id: 'do_number', + header: 'No. DO', + accessorKey: 'do_number', + footer: () =>
-
, + }, + { + id: 'sales_person', + header: 'Sales/Marketing', + accessorKey: 'sales', + cell: (props) => props.row.original.sales.name, + footer: () =>
-
, + }, + { + id: 'vehicle_number', + header: 'No. Polisi', + accessorKey: 'vehicle_number', + cell: (props) => ( + + {formatVechicleNumber(props.row.original.vehicle_number)} + + ), + footer: () =>
-
, + }, + { + id: 'marketing_type', + header: 'Marketing Type', + accessorKey: 'marketing_type', + footer: () =>
-
, + }, + { + id: 'product', + header: 'Produk', + accessorKey: 'product', + cell: ({ row }) => row.original.product.name, + footer: () =>
-
, + }, + { + id: 'qty', + header: 'Kuantitas', + accessorKey: 'qty', + cell: (props) => formatNumber(props.row.original.qty), + footer: () => ( +
+ {summaryTotal?.total_qty + ? formatNumber(summaryTotal.total_qty) + : '-'} +
+ ), + }, + { + id: 'average_weight', + header: 'Bobot Rata-Rata (Kg)', + accessorKey: 'average_weight_kg', + cell: (props) => formatNumber(props.row.original.average_weight_kg), + footer: () => ( +
+ {summaryTotal?.average_weight_kg + ? formatNumber(summaryTotal.average_weight_kg) + : '-'} +
+ ), + }, + { + id: 'total_weight', + header: 'Bobot Total (Kg)', + accessorKey: 'total_weight_kg', + cell: (props) => formatNumber(props.row.original.total_weight_kg), + footer: () => ( +
+ {summaryTotal?.total_weight_kg + ? formatNumber(summaryTotal.total_weight_kg) + : '-'} +
+ ), + }, + { + id: 'sales_price', + header: 'Harga Jual (Rp)', + accessorKey: 'sales_price_per_kg', + cell: (props) => formatCurrency(props.row.original.sales_price_per_kg), + footer: () => ( +
+ {summaryTotal?.average_sales_price + ? formatNumber(summaryTotal.average_sales_price) + : '-'} +
+ ), + }, + { + id: 'hpp_price', + header: 'HPP (Rp)', + accessorKey: 'hpp_price_per_kg', + cell: (props) => formatCurrency(props.row.original.hpp_price_per_kg), + footer: () => ( +
+ {summaryTotal?.total_hpp_price_per_kg + ? formatCurrency(summaryTotal.total_hpp_price_per_kg) + : '-'} +
+ ), + }, + { + id: 'sales_amount', + header: 'Total (Rp)', + accessorKey: 'sales_amount', + cell: (props) => formatCurrency(props.row.original.sales_amount), + footer: () => ( +
+ {summaryTotal?.total_sales_amount + ? formatCurrency(summaryTotal.total_sales_amount) + : '-'} +
+ ), + }, + ]; + return tableColumns; }; - const handleReset = () => { - setSelectedArea(null); - setSelectedLocation(null); - setSelectedWarehouse(null); - setSelectedCustomer(null); - setSelectedMarketingType(null); - resetFilter(); - }; - - useEffect(() => { - if ( - tableFilterState.filter_by === 'realization_date' || - tableFilterState.filter_by === 'so_date' - ) { - setSelectedMarketingDateFilterType({ - label: - tableFilterState.filter_by === 'realization_date' - ? 'Tanggal Realisasi' - : 'Tanggal SO', - value: tableFilterState.filter_by, - }); - } else { - setSelectedMarketingDateFilterType(null); - } - }, [tableFilterState.filter_by]); - return ( -
-
-

Penjualan Harian

+ <> +
+ {!isSubmitted ? ( + + } + title='No Filters Selected' + subtitle='Please choose filters to narrow down your results and make your search easier.' + /> + ) : isLoading ? ( + + } + title='Memuat Data Penjualan Harian' + subtitle='Silakan tunggu sebentar...' + /> + ) : data.length === 0 ? ( + + } + title='Data Not Yet Available' + subtitle='Please change your filters to get the data.' + /> + ) : ( + 0} + className={{ + containerClassName: cn('p-3', { + 'w-full mb-20': data.length === 0, + }), + headerColumnClassName: 'text-nowrap', + }} + /> + )} - {/* Filters */} -
-
- - - - - - - - - - - -
- -
- - - - -
- - - - - - Export{' '} - - - } - > - - - - - + {/* Filter Modal */} + + {/* Modal Header */} +
+
+ +

Filter Data

+
-
+
+
+ {/* Area Filter */} + { + formik.setFieldValue( + 'area_id', + val && !Array.isArray(val) ? String(val.value) : null + ); + }} + isClearable + className={{ wrapper: 'w-full' }} + /> - -
+ {/* Location Filter */} + { + formik.setFieldValue( + 'location_id', + val && !Array.isArray(val) ? String(val.value) : null + ); + }} + isClearable + className={{ wrapper: 'w-full' }} + /> + + {/* Warehouse Filter */} + { + formik.setFieldValue( + 'warehouse_id', + val && !Array.isArray(val) ? String(val.value) : null + ); + }} + isClearable + className={{ wrapper: 'w-full' }} + /> + + {/* Customer Filter */} + { + formik.setFieldValue( + 'customer_id', + val && !Array.isArray(val) ? String(val.value) : null + ); + }} + isClearable + className={{ wrapper: 'w-full' }} + /> + + {/* Date Range Filter */} +
+ +
+ { + formik.setFieldValue('start_date', e.target.value || null); + }} + className={{ wrapper: 'w-full' }} + isError={ + !!formik.errors.start_date && formik.touched.start_date + } + /> + {formik.errors.start_date && formik.touched.start_date && ( +
+ {formik.errors.start_date} +
+ )} + { + formik.setFieldValue('end_date', e.target.value || null); + }} + className={{ wrapper: 'w-full' }} + isError={!!formik.errors.end_date && formik.touched.end_date} + /> + {formik.errors.end_date && formik.touched.end_date && ( +
+ {formik.errors.end_date} +
+ )} +
+
+ + {/* Filter By Date Type */} + { + formik.setFieldValue( + 'filter_by', + val && !Array.isArray(val) ? (val.value as string) : null + ); + }} + isClearable + className={{ wrapper: 'w-full' }} + /> + + {/* Marketing Type Filter */} + { + formik.setFieldValue( + 'marketing_type', + val && !Array.isArray(val) ? (val.value as string) : null + ); + }} + isClearable + className={{ wrapper: 'w-full' }} + /> +
+ + {/* Modal Footer */} +
+ + +
+ + + ); }; -export default DailyMarketingReportContent; +export default DailyMarketingTab;