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', + } + ) +);