From c7818cefbb69f5cd1ec88950ecf32fb7f2f49d5e Mon Sep 17 00:00:00 2001 From: randy-ar Date: Thu, 29 Jan 2026 18:24:43 +0700 Subject: [PATCH 1/6] fix(FE): adjust ui debt supplier pixel perfect figma --- src/components/Card.tsx | 5 + src/components/Tabs.tsx | 35 ++-- src/components/helper/ButtonFilter.tsx | 8 +- .../pages/dashboard/DashboardProduction.tsx | 2 +- .../pages/report/finance/FinanceTabs.tsx | 25 ++- .../report/finance/tab/DebtSupplierTab.tsx | 172 +++++++++++------- src/stores/finance-tab/finance-tab.store.ts | 51 ++++++ 7 files changed, 214 insertions(+), 84 deletions(-) create mode 100644 src/stores/finance-tab/finance-tab.store.ts diff --git a/src/components/Card.tsx b/src/components/Card.tsx index c78766e1..ce7c1c57 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -123,6 +123,10 @@ const Card = ({ return cn(baseClasses, 'p-6', className?.body); }; + const getCollapsibleClasses = () => { + return cn('', className?.collapsible); + }; + const getTitleClasses = () => { const sizeClasses = { sm: 'text-lg', @@ -213,6 +217,7 @@ const Card = ({ titleClassName='w-full cursor-pointer' contentClassName='p-0' fullWidth={true} + className={getCollapsibleClasses()} > {cardContent} diff --git a/src/components/Tabs.tsx b/src/components/Tabs.tsx index 8f685452..8a06f9ed 100644 --- a/src/components/Tabs.tsx +++ b/src/components/Tabs.tsx @@ -25,8 +25,10 @@ export interface TabsProps wrapper?: string; tab?: string; content?: string; + tabHeaderWrapper?: string; }; onTabChange?: (tabId: string) => void; + sideContent?: ReactNode; } const Tabs = ({ @@ -38,6 +40,7 @@ const Tabs = ({ activeTabId: controlledActiveId, className, onTabChange, + sideContent, ...props }: TabsProps) => { // State internal hanya dipakai kalau `activeTabId` (controlled) tidak diset @@ -59,6 +62,7 @@ const Tabs = ({ wrapper: wrapperClassName, tab: tabClassName, content: contentClassName, + tabHeaderWrapper: tabHeaderWrapperClassName, } = typeof className === 'object' ? className : { wrapper: className, tab: undefined }; @@ -102,6 +106,10 @@ const Tabs = ({ tabClassName ); + const getSideContentClasses = () => { + return cn('flex flex-row', tabHeaderWrapperClassName); + }; + const activeContent = tabs.find((tab) => tab.id === activeTabId)?.content; return ( @@ -112,18 +120,21 @@ const Tabs = ({ typeof className === 'string' ? className : containerClassName )} > -
- {tabs.map(({ id, label, disabled }) => ( - - ))} +
+
+ {tabs.map(({ id, label, disabled }) => ( + + ))} +
+ {sideContent && sideContent}
{activeContent && ( diff --git a/src/components/helper/ButtonFilter.tsx b/src/components/helper/ButtonFilter.tsx index aca86a88..cff1d167 100644 --- a/src/components/helper/ButtonFilter.tsx +++ b/src/components/helper/ButtonFilter.tsx @@ -19,11 +19,11 @@ const ButtonFilter = ({ values, onClick, ...props }: ButtonFilterProps) => { variant='outline' color='none' className={cn( - 'padding-[12px] rounded-[8px] max-h-[40px] font-semibold text-[14px] gap-[6px]', + 'rounded-lg max-h-10 font-semibold text-sm gap-1.5', 'text-sm text-base-content/50 border border-base-content/10 shadow-button-soft', getFilledFormikValuesCount(values) > 0 - ? 'border-primary-gradient !rounded-[8px]' - : '!rounded-[8px]', + ? 'border-primary-gradient text-primary rounded-lg!' + : 'rounded-lg', props.className )} > @@ -37,7 +37,7 @@ const ButtonFilter = ({ values, onClick, ...props }: ButtonFilterProps) => { /> Filter {getFilledFormikValuesCount(values) > 0 && ( - + {getFilledFormikValuesCount(values)} )} diff --git a/src/components/pages/dashboard/DashboardProduction.tsx b/src/components/pages/dashboard/DashboardProduction.tsx index 2085d943..674f3719 100644 --- a/src/components/pages/dashboard/DashboardProduction.tsx +++ b/src/components/pages/dashboard/DashboardProduction.tsx @@ -226,7 +226,7 @@ const DashboardProduction = () => { variant='outline' color='none' className={cn( - 'p-2 rounded-lg font-semibold text-sm gap-1.5', + 'rounded-lg font-semibold text-sm gap-1.5', 'text-sm text-base-content/50 border border-base-content/10 shadow-button-soft' )} > diff --git a/src/components/pages/report/finance/FinanceTabs.tsx b/src/components/pages/report/finance/FinanceTabs.tsx index 58d1e78b..e7800f96 100644 --- a/src/components/pages/report/finance/FinanceTabs.tsx +++ b/src/components/pages/report/finance/FinanceTabs.tsx @@ -1,28 +1,43 @@ 'use client'; +import { useState } from 'react'; import Tabs from '@/components/Tabs'; import CustomerPaymentTab from '@/components/pages/report/finance/tab/CustomerPaymentTab'; import DebtSupplierTab from '@/components/pages/report/finance/tab/DebtSupplierTab'; +import { useFinanceTabStore } from '@/stores/finance-tab/finance-tab.store'; const FinanceTabs = () => { + const [activeTabId, setActiveTabId] = useState('1'); + const tabActions = useFinanceTabStore((state) => state.tabActions); + const tabs = [ { id: '1', label: 'Rekapitulasi Hutang Ke Supplier', - - content: , + content: , }, { id: '2', label: 'Kontrol Pembayaran Customer', - content: , }, ]; return ( -
- +
+
); }; diff --git a/src/components/pages/report/finance/tab/DebtSupplierTab.tsx b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx index c5065d29..aa66cf66 100644 --- a/src/components/pages/report/finance/tab/DebtSupplierTab.tsx +++ b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx @@ -22,7 +22,7 @@ import { generateDebtSupplierExcel } from '@/components/pages/report/finance/exp import { generateDebtSupplierPDF } from '@/components/pages/report/finance/export/DebtSupllierExportPDF'; import { Icon } from '@iconify/react'; import { ColumnDef } from '@tanstack/react-table'; -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import toast from 'react-hot-toast'; import useSWR from 'swr'; import { DebtSupplierApi } from '@/services/api/report/debt-supplier'; @@ -37,6 +37,7 @@ import { Color } from '@/types/theme'; import { Supplier } from '@/types/api/master-data/supplier'; import SelectInputCheckbox from '@/components/input/SelectInputCheckbox'; import SelectInputRadio from '@/components/input/SelectInputRadio'; +import { useFinanceTabStore } from '@/stores/finance-tab/finance-tab.store'; const dueStatus: Record = { 'Sudah Jatuh Tempo': 'error', @@ -75,7 +76,11 @@ const getPillBadge = ( ); }; -const DebtSupplierTab = () => { +interface DebtSupplierTabProps { + tabId: string; +} + +const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => { // ===== STATE MANAGEMENT ===== const [isPdfExportLoading, setIsPdfExportLoading] = useState(false); const [isExcelExportLoading, setIsExcelExportLoading] = useState(false); @@ -271,6 +276,77 @@ const DebtSupplierTab = () => { } }, [debtSupplierExport]); + // ===== REGISTER TAB ACTIONS TO STORE ===== + const setTabActions = useFinanceTabStore((state) => state.setTabActions); + const clearTabActions = useFinanceTabStore((state) => state.clearTabActions); + + useEffect(() => { + setTabActions( + tabId, +
+ + + + + Export +
+ +
+ + } + align='end' + className={{ + content: + 'mt-1 p-0 w-full shadow-button-soft border border-base-content/10 rounded-lg', + }} + > + + + + +
+
+ ); + }, [ + tabId, + formik.values, + isAnyExportLoading, + handleExportExcel, + handleExportPdf, + setTabActions, + ]); + + // Cleanup on unmount + useEffect(() => { + return () => { + clearTabActions(tabId); + }; + }, [tabId, clearTabActions]); + const getTableColumns = (supplier: DebtSupplier): ColumnDef[] => [ { id: 'no', @@ -478,41 +554,9 @@ const DebtSupplierTab = () => { ]; return ( <> -
- -
- - - - - Export - - } - align='end' - > - - - - - -
-
- +
{!isSubmitted ? ( -
+
Silakan klik tombol Filter untuk mengatur filter dan menampilkan data.
@@ -521,7 +565,7 @@ const DebtSupplierTab = () => {
) : data.length === 0 ? ( -
+
Tidak ada data yang dapat ditampilkan...
) : ( @@ -531,10 +575,11 @@ const DebtSupplierTab = () => { key={supplierReport.supplier.id} title={supplierReport.supplier.name} className={{ - wrapper: 'w-full !rounded-lg', - body: 'p-0 rounded-lg', + wrapper: 'w-full rounded-lg border-none', + body: 'p-0', title: - 'ps-2 pt-1 pb-1 font-normal text-md bg-primary text-white', + 'px-2 py-1.5 font-normal text-sm bg-primary text-white', + collapsible: 'rounded-lg', }} variant='bordered' collapsible={true} @@ -551,8 +596,9 @@ const DebtSupplierTab = () => { pageSize={supplierReport.rows.length + 1} renderFooter={supplierReport.rows.length > 0} className={{ - containerClassName: 'w-full', - tableWrapperClassName: 'overflow-x-auto', + containerClassName: 'w-full mb-0', + tableWrapperClassName: + 'overflow-x-auto rounded-tr-none rounded-tl-none', headerColumnClassName: cn( TABLE_DEFAULT_STYLING.headerColumnClassName, 'whitespace-nowrap' @@ -617,33 +663,34 @@ const DebtSupplierTab = () => { ref={filterModal.ref} className={{ modal: 'p-0', - modalBox: 'p-0 rounded-2xl xl:max-w-4/12 max-w-sm', + modalBox: 'p-0 rounded-[0.875rem] xl:max-w-4/12 max-w-sm', }} > -
+ {/* Modal Header */} -
+
-

Filter Data

+

Filter Data

-
-
-
+ + {/* Modal Body */} +
+
+ +
{ @@ -655,11 +702,8 @@ const DebtSupplierTab = () => { } errorMessage={formik.errors.startDate} /> -
- -
+
{ @@ -730,15 +774,19 @@ const DebtSupplierTab = () => {
{/* Action Buttons */} -
+
-
diff --git a/src/stores/finance-tab/finance-tab.store.ts b/src/stores/finance-tab/finance-tab.store.ts new file mode 100644 index 00000000..9b5cf096 --- /dev/null +++ b/src/stores/finance-tab/finance-tab.store.ts @@ -0,0 +1,51 @@ +'use client'; + +import { ReactNode } from 'react'; +import { create } from 'zustand'; +import { devtools } from 'zustand/middleware'; + +export type FinanceTabActionsSlice = { + // State - actions per tab ID + tabActions: Record; + + // Actions + setTabActions: (tabId: string, actions: ReactNode) => void; + clearTabActions: (tabId: string) => void; + clearAllTabActions: () => void; +}; + +export const useFinanceTabStore = create()( + devtools( + (set) => ({ + tabActions: {}, + + setTabActions: (tabId, actions) => + set( + (state) => ({ + tabActions: { + ...state.tabActions, + [tabId]: actions, + }, + }), + false, + 'setTabActions' + ), + + clearTabActions: (tabId) => + set( + (state) => { + const { [tabId]: _, ...rest } = state.tabActions; + return { tabActions: rest }; + }, + false, + 'clearTabActions' + ), + + clearAllTabActions: () => + set({ tabActions: {} }, false, 'clearAllTabActions'), + }), + { + name: 'FinanceTabStore', + } + ) +); From 0ed6c246b1289b4c496d0c19c66b8d25e479eef2 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Thu, 29 Jan 2026 20:18:42 +0700 Subject: [PATCH 2/6] fix(FE): fix badge and table pdf --- src/components/helper/StatusBadge.tsx | 2 + .../finance/export/DebtSupllierExportPDF.tsx | 59 ++++++++++++------- .../report/finance/tab/DebtSupplierTab.tsx | 21 +------ 3 files changed, 42 insertions(+), 40 deletions(-) diff --git a/src/components/helper/StatusBadge.tsx b/src/components/helper/StatusBadge.tsx index c4f99593..f9725fff 100644 --- a/src/components/helper/StatusBadge.tsx +++ b/src/components/helper/StatusBadge.tsx @@ -27,6 +27,7 @@ const StatusBadge = ({ 'bg-success/30': color === 'success', 'bg-error/20': color === 'error', 'bg-primary/20': color === 'info', + 'bg-[#FF9A20]/12': color === 'warning', }, className?.badge ), @@ -43,6 +44,7 @@ const StatusBadge = ({ 'text-[#008000]': color === 'success', 'text-error': color === 'error', 'text-primary': color === 'info', + 'text-[#FF9A20]': color === 'warning', })} > diff --git a/src/components/pages/report/finance/export/DebtSupllierExportPDF.tsx b/src/components/pages/report/finance/export/DebtSupllierExportPDF.tsx index 869430b0..edcd360f 100644 --- a/src/components/pages/report/finance/export/DebtSupllierExportPDF.tsx +++ b/src/components/pages/report/finance/export/DebtSupllierExportPDF.tsx @@ -281,16 +281,16 @@ const createPDFDocument = (params: DebtSupplierExportPDFParams) => { No - + No. PR - + No. PO - + Tgl Terima/Bayar - + Tgl PO @@ -320,7 +320,12 @@ const createPDFDocument = (params: DebtSupplierExportPDFParams) => { Status - + No. Perjalanan @@ -330,16 +335,16 @@ const createPDFDocument = (params: DebtSupplierExportPDFParams) => { {/* NO */} - + {/* No. PR */} - + {/* No. PO */} - + {/* Tgl Terima/Bayar */} - + {/* Tgl PO */} @@ -381,8 +386,13 @@ const createPDFDocument = (params: DebtSupplierExportPDFParams) => { {/* Status */} - - {/* No. Perjalanan */} + + @@ -400,13 +410,13 @@ const createPDFDocument = (params: DebtSupplierExportPDFParams) => { {index + 1} - + {item.pr_number || '-'} - + {item.po_number || '-'} - + {item.received_date ? item.received_date != '-' @@ -415,7 +425,7 @@ const createPDFDocument = (params: DebtSupplierExportPDFParams) => { : '-'} - + {item.po_date ? item.po_date != '-' @@ -526,7 +536,12 @@ const createPDFDocument = (params: DebtSupplierExportPDFParams) => { - )} - + {item.travel_number || '-'} @@ -538,18 +553,18 @@ const createPDFDocument = (params: DebtSupplierExportPDFParams) => { Total - - - - - - + + + + + + {formatNumber(supplierReport.total.aging)} Hari diff --git a/src/components/pages/report/finance/tab/DebtSupplierTab.tsx b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx index aa66cf66..811c739b 100644 --- a/src/components/pages/report/finance/tab/DebtSupplierTab.tsx +++ b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx @@ -2,10 +2,7 @@ import Button from '@/components/Button'; import Card from '@/components/Card'; import Dropdown from '@/components/Dropdown'; import DateInput from '@/components/input/DateInput'; -import SelectInput, { - OptionType, - useSelect, -} from '@/components/input/SelectInput'; +import { OptionType, useSelect } from '@/components/input/SelectInput'; import Menu from '@/components/menu/Menu'; import MenuItem from '@/components/menu/MenuItem'; import Modal, { useModal } from '@/components/Modal'; @@ -32,12 +29,12 @@ import { DebtSupplierFilterType, } from '@/components/pages/report/finance/filter/DebtSupplierFilter'; import ButtonFilter from '@/components/helper/ButtonFilter'; -import Badge from '@/components/Badge'; import { Color } from '@/types/theme'; import { Supplier } from '@/types/api/master-data/supplier'; import SelectInputCheckbox from '@/components/input/SelectInputCheckbox'; import SelectInputRadio from '@/components/input/SelectInputRadio'; import { useFinanceTabStore } from '@/stores/finance-tab/finance-tab.store'; +import StatusBadge from '@/components/helper/StatusBadge'; const dueStatus: Record = { 'Sudah Jatuh Tempo': 'error', @@ -61,19 +58,7 @@ const getPillBadge = ( ? dueStatus[statusText] || 'neutral' : paymentStatus[statusText] || 'neutral'; - return ( - - {statusText} - - ); + return ; }; interface DebtSupplierTabProps { From e980320d002c8a1220201f90f8edb33ce6c4cdf7 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Thu, 29 Jan 2026 20:59:16 +0700 Subject: [PATCH 3/6] fix(FE): fixing report finance filter ui --- src/components/input/DateInput.tsx | 27 +- .../pages/dashboard/DashboardProduction.tsx | 1 + .../pages/report/finance/FinanceTabs.tsx | 2 +- .../report/finance/tab/CustomerPaymentTab.tsx | 412 ++++++++++-------- .../report/finance/tab/DebtSupplierTab.tsx | 6 +- 5 files changed, 253 insertions(+), 195 deletions(-) diff --git a/src/components/input/DateInput.tsx b/src/components/input/DateInput.tsx index da1a4d81..7c0863c5 100644 --- a/src/components/input/DateInput.tsx +++ b/src/components/input/DateInput.tsx @@ -280,7 +280,7 @@ const DateInput = ({ ref={calendarModal.ref} className={{ modal: 'rounded', - modalBox: `!max-w-max min-h-${isRange ? '124' : '110'} flex flex-col`, + modalBox: `max-w-max flex flex-col`, }} closeOnBackdrop > @@ -296,7 +296,11 @@ const DateInput = ({ endMonth={maxDate ?? new Date(new Date().getFullYear() + 5, 11)} selected={selectedRange as DateRange} onSelect={handleSelectRange} - footer={
{displayValue}
} + footer={ +
+ {displayValue} +
+ } disabled={ [ minDate ? { before: minDate } : undefined, @@ -326,17 +330,26 @@ const DateInput = ({ )}
{isRange && ( - - Tekan dua kali untuk memilih tanggal awal + + Tekan dua kali untuk reset tanggal awal )} -
- {isRange && ( - )} diff --git a/src/components/pages/dashboard/DashboardProduction.tsx b/src/components/pages/dashboard/DashboardProduction.tsx index 674f3719..27cc208b 100644 --- a/src/components/pages/dashboard/DashboardProduction.tsx +++ b/src/components/pages/dashboard/DashboardProduction.tsx @@ -463,6 +463,7 @@ const DashboardProduction = () => { Boolean(formik.errors.endDate) && Boolean(formik.touched.endDate) } + isRange />
diff --git a/src/components/pages/report/finance/FinanceTabs.tsx b/src/components/pages/report/finance/FinanceTabs.tsx index e7800f96..ffb0d3f1 100644 --- a/src/components/pages/report/finance/FinanceTabs.tsx +++ b/src/components/pages/report/finance/FinanceTabs.tsx @@ -19,7 +19,7 @@ const FinanceTabs = () => { { id: '2', label: 'Kontrol Pembayaran Customer', - content: , + content: , }, ]; diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index dc1705ed..0ce0b904 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -1,4 +1,4 @@ -import { useState, useMemo, useCallback } from 'react'; +import { useState, useMemo, useCallback, useEffect } from 'react'; import useSWR from 'swr'; import { Icon } from '@iconify/react'; import Card from '@/components/Card'; @@ -11,7 +11,7 @@ import { FinanceApi } from '@/services/api/report/finance-report'; import { UserApi } from '@/services/api/user'; import Table from '@/components/Table'; import { ColumnDef } from '@tanstack/react-table'; -import { formatCurrency, formatDate, formatNumber } from '@/lib/helper'; +import { formatCurrency, formatDate, formatNumber, cn } from '@/lib/helper'; import { CustomerPaymentReport, CustomerPaymentSummary, @@ -26,8 +26,13 @@ import { useModal } from '@/components/Modal'; import toast from 'react-hot-toast'; import { generateCustomerPaymentExcel } from '@/components/pages/report/finance/export/CustomerPaymentExportXLSX'; import { generateCustomerPaymentPDF } from '@/components/pages/report/finance/export/CustomerPaymentExportPDF'; +import { useFinanceTabStore } from '@/stores/finance-tab/finance-tab.store'; -const CustomerPaymentTab = () => { +interface CustomerPaymentTabProps { + tabId: string; +} + +const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => { // ===== STATE MANAGEMENT ===== const [isPdfExportLoading, setIsPdfExportLoading] = useState(false); const [isExcelExportLoading, setIsExcelExportLoading] = useState(false); @@ -111,6 +116,10 @@ const CustomerPaymentTab = () => { }; // ===== FILTER HANDLERS ===== + const handleFilterModalOpen = useCallback(() => { + filterModal.openModal(); + }, [filterModal]); + const handleResetFilters = useCallback(() => { setIsSubmitted(false); setFilterCustomer([]); @@ -298,6 +307,92 @@ const CustomerPaymentTab = () => { } }, [customerPaymentExport]); + // ===== REGISTER TAB ACTIONS TO STORE ===== + const setTabActions = useFinanceTabStore((state) => state.setTabActions); + const clearTabActions = useFinanceTabStore((state) => state.clearTabActions); + + useEffect(() => { + setTabActions( + tabId, +
+ + + + + Export +
+ +
+ + } + align='end' + className={{ + content: + 'mt-1 p-0 w-full shadow-button-soft border border-base-content/10 rounded-lg', + }} + > + + + + +
+
+ ); + }, [ + tabId, + hasFilters, + activeFiltersCount, + isAnyExportLoading, + handleExportExcel, + handleExportPdf, + filterModal.open, + setTabActions, + ]); + + // Cleanup on unmount + useEffect(() => { + return () => { + clearTabActions(tabId); + }; + }, [tabId, clearTabActions]); + const getTableColumns = ( summary: CustomerPaymentSummary ): ColumnDef[] => { @@ -552,181 +647,10 @@ const CustomerPaymentTab = () => { }; return ( -
- -
- - - - - Export - - } - align='end' - > - - - - - -
- - {/* Filter Modal */} - -
- {/* Modal Header */} -
-
- -

Filter Data

-
- -
-
-
-
- { - setFilterStartDate(e.target.value); - }} - className={{ wrapper: 'w-full' }} - /> -
- -
- { - setFilterEndDate(e.target.value); - }} - className={{ wrapper: 'w-full' }} - /> -
-
- -
- { - setFilterCustomer( - Array.isArray(val) ? val : val ? [val] : [] - ); - }} - onInputChange={setCustomerInputValue} - isLoading={isLoadingCustomers} - isClearable - onMenuScrollToBottom={loadMoreCustomers} - className={{ wrapper: 'w-full' }} - /> -
- - {/* TODO: Uncomment when BE is ready */} - {/*
- { - setFilterSales(Array.isArray(val) ? val : val ? [val] : []); - }} - onInputChange={setSalesInputValue} - isLoading={isLoadingSales} - isClearable - onMenuScrollToBottom={loadMoreSales} - className={{ wrapper: 'w-full' }} - /> -
*/} - - {/* TODO: Uncomment when BE is ready */} - {/*
- -
*/} -
- - {/* Action Buttons */} -
- - -
-
-
- + <> +
{!isSubmitted ? ( -
+
Silakan klik tombol Filter untuk mengatur filter dan menampilkan data.
@@ -735,7 +659,7 @@ const CustomerPaymentTab = () => {
) : data.length === 0 ? ( -
+
Tidak ada data yang dapat ditampilkan...
) : ( @@ -757,15 +681,17 @@ const CustomerPaymentTab = () => { title={customerReport.customer.name} subtitle={`(${customerReport.customer.address})`} className={{ - wrapper: 'w-full rounded-2xl', + wrapper: 'w-full rounded-lg border-none', body: 'p-0', title: - 'py-1.5 px-3 bg-primary text-white text-lg font-normal', + 'px-2 py-1.5 font-normal text-sm bg-primary text-white', subtitle: - 'px-3 pb-1 bg-primary text-white text-sm font-normal', + 'px-2 pb-1.5 bg-primary text-white text-xs font-normal', + collapsible: 'rounded-lg', }} variant='bordered' collapsible={true} + defaultCollapsed={true} > { renderFooter={customerReport.rows.length > 0} className={{ containerClassName: 'w-full mb-0!', - tableWrapperClassName: 'overflow-x-auto', + tableWrapperClassName: + 'overflow-x-auto rounded-tr-none rounded-tl-none', tableClassName: 'w-full table-auto text-sm', headerRowClassName: 'border-b border-b-gray-200 bg-gray-50', headerColumnClassName: @@ -799,8 +726,8 @@ const CustomerPaymentTab = () => { if (row.index === 0) { return ( ))} diff --git a/src/components/helper/skeleton/DataStateSkeleton.tsx b/src/components/helper/skeleton/DataStateSkeleton.tsx new file mode 100644 index 00000000..cea68b82 --- /dev/null +++ b/src/components/helper/skeleton/DataStateSkeleton.tsx @@ -0,0 +1,26 @@ +import IconSkeleton from '@/components/helper/skeleton/IconSkeleton'; +import { Icon } from '@iconify/react'; + +const DataStateSkeleton = ({ + icon, + title, + description, +}: { + icon: React.ReactNode; + title: string; + description: string; +}) => { + return ( +
+ {icon} +

+ {title} +

+

+ {description} +

+
+ ); +}; + +export default DataStateSkeleton; diff --git a/src/components/helper/skeleton/IconSkeleton.tsx b/src/components/helper/skeleton/IconSkeleton.tsx new file mode 100644 index 00000000..0100f14b --- /dev/null +++ b/src/components/helper/skeleton/IconSkeleton.tsx @@ -0,0 +1,13 @@ +import { ReactNode } from 'react'; + +const IconSkeleton = ({ children }: { children: ReactNode }) => { + return ( +
+
+ {children} +
+
+ ); +}; + +export default IconSkeleton; diff --git a/src/components/pages/dashboard/DashboardProduction.tsx b/src/components/pages/dashboard/DashboardProduction.tsx index 27cc208b..674f3719 100644 --- a/src/components/pages/dashboard/DashboardProduction.tsx +++ b/src/components/pages/dashboard/DashboardProduction.tsx @@ -463,7 +463,6 @@ const DashboardProduction = () => { Boolean(formik.errors.endDate) && Boolean(formik.touched.endDate) } - isRange /> diff --git a/src/components/pages/dashboard/chart/DashboardLineChart.tsx b/src/components/pages/dashboard/chart/DashboardLineChart.tsx index 092e4bca..58749cf2 100644 --- a/src/components/pages/dashboard/chart/DashboardLineChart.tsx +++ b/src/components/pages/dashboard/chart/DashboardLineChart.tsx @@ -1,6 +1,7 @@ import Button from '@/components/Button'; import Card from '@/components/Card'; import Dropdown from '@/components/Dropdown'; +import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton'; import { OptionType } from '@/components/input/SelectInput'; import Menu from '@/components/menu/Menu'; import MenuItem from '@/components/menu/MenuItem'; @@ -721,24 +722,18 @@ const DashboardLineChart = ({ return (
{/* Chart icon */} -
-
+ -
-
- - {/* Empty state text */} -

- Data Not Yet Available -

-

- Please change your filters to get the data. -

+ } + title='Data Not Yet Available' + description='Please change your filters to get the data.' + />
); } diff --git a/src/components/pages/dashboard/skeleton/DashboardLineChartSkeleton.tsx b/src/components/pages/dashboard/skeleton/DashboardLineChartSkeleton.tsx index 6e4e4b97..7f19a6d0 100644 --- a/src/components/pages/dashboard/skeleton/DashboardLineChartSkeleton.tsx +++ b/src/components/pages/dashboard/skeleton/DashboardLineChartSkeleton.tsx @@ -1,5 +1,6 @@ import { Icon } from '@iconify/react'; import { DashboardMeta } from '@/types/api/dashboard/dashboard'; +import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton'; const DashboardLineChartSkeleton = ({ meta }: { meta?: DashboardMeta }) => { return ( @@ -24,50 +25,35 @@ const DashboardLineChartSkeleton = ({ meta }: { meta?: DashboardMeta }) => { {!meta?.filters && ( <> {/* Filter icon */} -
-
-
- -
-
-
- - {/* Empty state text */} -

- No Filters Selected -

-

- Please choose filters to narrow down your results and make - your search easier. -

+ + } + title='No Filters Selected' + description='Please choose filters to narrow down your results and make your search easier.' + /> )} {meta?.filters && ( <> {/* Filter icon */} -
-
+ -
-
- - {/* Empty state text */} -

- Data Not Yet Available -

-

- Please change your filters to get the data. -

+ } + title='Data Not Yet Available' + description='Please change your filters to get the data.' + /> )} diff --git a/src/components/pages/report/finance/skeleton/CustomerSupplierSkeleton.tsx b/src/components/pages/report/finance/skeleton/CustomerSupplierSkeleton.tsx new file mode 100644 index 00000000..8ab0ffd3 --- /dev/null +++ b/src/components/pages/report/finance/skeleton/CustomerSupplierSkeleton.tsx @@ -0,0 +1,37 @@ +import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton'; +import Table from '@/components/Table'; +import { CustomerPaymentRow } from '@/types/api/report/customer-payment'; +import { ColumnDef } from '@tanstack/react-table'; + +const CustomerSupplierSkeleton = ({ + columns, + icon, + title, + subtitle, +}: { + columns: ColumnDef[]; + icon: React.ReactNode; + title: string; + subtitle: string; +}) => { + return ( +
+
{ ); }) )} - - + + + {/* Filter Modal */} + + {/* Modal Header */} +
+
+ +

Filter Data

+
+ +
+
+
+ +
+ { + setFilterStartDate(e.target.value); + }} + className={{ wrapper: 'w-full' }} + isNestedModal + /> +
+ + { + setFilterEndDate(e.target.value); + }} + className={{ wrapper: 'w-full' }} + isNestedModal + /> +
+
+ + { + setFilterCustomer(Array.isArray(val) ? val : val ? [val] : []); + }} + onInputChange={setCustomerInputValue} + isLoading={isLoadingCustomers} + isClearable + onMenuScrollToBottom={loadMoreCustomers} + className={{ wrapper: 'w-full' }} + /> + + {/* TODO: Uncomment when BE is ready */} + {/*
+ { + setFilterSales(Array.isArray(val) ? val : val ? [val] : []); + }} + onInputChange={setSalesInputValue} + isLoading={isLoadingSales} + isClearable + onMenuScrollToBottom={loadMoreSales} + className={{ wrapper: 'w-full' }} + /> +
*/} + + {/* TODO: Uncomment when BE is ready */} + {/*
+ +
*/} + + {/* Action Buttons */} +
+
+ + +
+
+ ); }; diff --git a/src/components/pages/report/finance/tab/DebtSupplierTab.tsx b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx index 811c739b..ac53a7a3 100644 --- a/src/components/pages/report/finance/tab/DebtSupplierTab.tsx +++ b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx @@ -686,6 +686,7 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => { formik.touched.startDate && !!formik.errors.startDate } errorMessage={formik.errors.startDate} + isNestedModal />
{ className={{ wrapper: 'w-full' }} isError={formik.touched.endDate && !!formik.errors.endDate} errorMessage={formik.errors.endDate} + isNestedModal /> @@ -759,7 +761,7 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => { {/* Action Buttons */} -
+
+
+ +
+ + ); +}; + +export default CustomerSupplierSkeleton; diff --git a/src/components/pages/report/finance/skeleton/DebtSupplierSkeleton.tsx b/src/components/pages/report/finance/skeleton/DebtSupplierSkeleton.tsx new file mode 100644 index 00000000..b9397f8f --- /dev/null +++ b/src/components/pages/report/finance/skeleton/DebtSupplierSkeleton.tsx @@ -0,0 +1,38 @@ +import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton'; +import Table from '@/components/Table'; +import { DebtRow } from '@/types/api/report/debt-supplier'; +import { Icon } from '@iconify/react'; +import { ColumnDef } from '@tanstack/react-table'; + +const DebtSupplierSkeleton = ({ + columns, + icon, + title, + subtitle, +}: { + columns: ColumnDef[]; + icon: React.ReactNode; + title: string; + subtitle: string; +}) => { + return ( +
+
+
+ +
+ + ); +}; + +export default DebtSupplierSkeleton; diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index 0ce0b904..2987455a 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -14,6 +14,7 @@ import { ColumnDef } from '@tanstack/react-table'; import { formatCurrency, formatDate, formatNumber, cn } from '@/lib/helper'; import { CustomerPaymentReport, + CustomerPaymentRow, CustomerPaymentSummary, } from '@/types/api/report/customer-payment'; import { isResponseSuccess } from '@/lib/api-helper'; @@ -27,6 +28,7 @@ import toast from 'react-hot-toast'; import { generateCustomerPaymentExcel } from '@/components/pages/report/finance/export/CustomerPaymentExportXLSX'; import { generateCustomerPaymentPDF } from '@/components/pages/report/finance/export/CustomerPaymentExportPDF'; import { useFinanceTabStore } from '@/stores/finance-tab/finance-tab.store'; +import CustomerSupplierSkeleton from '@/components/pages/report/finance/skeleton/CustomerSupplierSkeleton'; interface CustomerPaymentTabProps { tabId: string; @@ -650,18 +652,37 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => { <>
{!isSubmitted ? ( -
- Silakan klik tombol Filter untuk mengatur filter dan menampilkan - data. -
+ + } + title='No Filters Selected' + subtitle='Please choose filters to narrow down your results and make your search easier.' + /> ) : isLoading ? (
) : data.length === 0 ? ( -
- Tidak ada data yang dapat ditampilkan... -
+ + } + title='Data Not Yet Available' + subtitle='Please change your filters to get the data.' + /> ) : ( data.map((customerReport) => { const summary = customerReport.summary || { diff --git a/src/components/pages/report/finance/tab/DebtSupplierTab.tsx b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx index ac53a7a3..646cfb15 100644 --- a/src/components/pages/report/finance/tab/DebtSupplierTab.tsx +++ b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx @@ -35,6 +35,8 @@ import SelectInputCheckbox from '@/components/input/SelectInputCheckbox'; import SelectInputRadio from '@/components/input/SelectInputRadio'; import { useFinanceTabStore } from '@/stores/finance-tab/finance-tab.store'; import StatusBadge from '@/components/helper/StatusBadge'; +import DebtSupplierSkeleton from '@/components/pages/report/finance/skeleton/DebtSupplierSkeleton'; +import DataStateSkeleton from '@/components/helper/skeleton/DataStateSkeleton'; const dueStatus: Record = { 'Sudah Jatuh Tempo': 'error', @@ -332,7 +334,7 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => { }; }, [tabId, clearTabActions]); - const getTableColumns = (supplier: DebtSupplier): ColumnDef[] => [ + const getTableColumns = (supplier?: DebtSupplier): ColumnDef[] => [ { id: 'no', header: 'No', @@ -398,8 +400,10 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => { return
{formatNumber(value)} Hari
; }, footer: () => { - const value = supplier.total.aging; - return
{formatNumber(value)} Hari
; + const value = supplier?.total.aging; + return ( +
{formatNumber(value || 0)} Hari
+ ); }, }, { @@ -460,10 +464,10 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => { ); }, footer: () => { - const value = supplier.total.total_price; + const value = supplier?.total.total_price; return ( -
- {formatCurrency(value)} +
+ {formatCurrency(value || 0)}
); }, @@ -482,10 +486,10 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => { ); }, footer: () => { - const value = supplier.total.payment_price; + const value = supplier?.total.payment_price; return ( -
- {formatCurrency(value)} +
+ {formatCurrency(value || 0)}
); }, @@ -504,10 +508,10 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => { ); }, footer: () => { - const value = supplier.total.debt_price; + const value = supplier?.total.debt_price; return ( -
- {formatCurrency(value)} +
+ {formatCurrency(value || 0)}
); }, @@ -541,18 +545,37 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => { <>
{!isSubmitted ? ( -
- Silakan klik tombol Filter untuk mengatur filter dan menampilkan - data. -
+ + } + title='No Filters Selected' + subtitle='Please choose filters to narrow down your results and make your search easier.' + /> ) : isLoading ? (
) : data.length === 0 ? ( -
- Tidak ada data yang dapat ditampilkan... -
+ + } + title='Data Not Yet Available' + subtitle='Please change your filters to get the data.' + /> ) : ( data.map((supplierReport) => { return ( From 8a647801354b5f27bb1c7b3443332818ef467fe2 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Fri, 30 Jan 2026 11:20:47 +0700 Subject: [PATCH 5/6] fix(FE): adding margin on data state skeleton --- .../helper/skeleton/DataStateSkeleton.tsx | 8 +++++- .../helper/skeleton/IconSkeleton.tsx | 26 ++++++++++++++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/components/helper/skeleton/DataStateSkeleton.tsx b/src/components/helper/skeleton/DataStateSkeleton.tsx index cea68b82..cd5474e0 100644 --- a/src/components/helper/skeleton/DataStateSkeleton.tsx +++ b/src/components/helper/skeleton/DataStateSkeleton.tsx @@ -12,7 +12,13 @@ const DataStateSkeleton = ({ }) => { return (
- {icon} + + {icon} +

{title}

diff --git a/src/components/helper/skeleton/IconSkeleton.tsx b/src/components/helper/skeleton/IconSkeleton.tsx index 0100f14b..7fde0cd8 100644 --- a/src/components/helper/skeleton/IconSkeleton.tsx +++ b/src/components/helper/skeleton/IconSkeleton.tsx @@ -1,9 +1,29 @@ +import { cn } from '@/lib/helper'; import { ReactNode } from 'react'; -const IconSkeleton = ({ children }: { children: ReactNode }) => { +const IconSkeleton = ({ + children, + className, +}: { + children: ReactNode; + className?: { + outer?: string; + inner?: string; + }; +}) => { return ( -
-
+
+
{children}
From 359cdb253462a14c4642230082f874940a48bcdb Mon Sep 17 00:00:00 2001 From: randy-ar Date: Fri, 30 Jan 2026 11:24:42 +0700 Subject: [PATCH 6/6] fix(FE): adding margin on data state skeleton --- src/components/helper/skeleton/IconSkeleton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/helper/skeleton/IconSkeleton.tsx b/src/components/helper/skeleton/IconSkeleton.tsx index 7fde0cd8..4c865719 100644 --- a/src/components/helper/skeleton/IconSkeleton.tsx +++ b/src/components/helper/skeleton/IconSkeleton.tsx @@ -14,7 +14,7 @@ const IconSkeleton = ({ return (