From 34f93f8dcccba81677783debee4d15a723bac18c Mon Sep 17 00:00:00 2001 From: randy-ar Date: Wed, 28 Jan 2026 17:54:55 +0700 Subject: [PATCH] fix(FE): refactor UI Dashboard pixel perfect figma --- src/components/Card.tsx | 7 +- src/components/Navbar.tsx | 6 +- src/components/helper/ButtonFilter.tsx | 12 +- .../pages/dashboard/DashboardProduction.tsx | 298 +++++++++++++----- .../dashboard/chart/DashboardLineChart.tsx | 138 +++++--- .../pages/dashboard/chart/DashboardStats.tsx | 57 ++-- .../DashboardExportCharts.tsx} | 14 +- .../dashboard/export/DashboardExportStats.tsx | 201 ++++++++++++ .../pages/dashboard/export/DashboardPDF.ts | 58 ++-- .../skeleton/DashboardLineChartSkeleton.tsx | 138 ++++---- src/stores/ui/slices/navbar.slice.ts | 22 ++ src/stores/ui/ui.store.ts | 2 + src/types/stores.d.ts | 12 +- 13 files changed, 716 insertions(+), 249 deletions(-) rename src/components/pages/dashboard/{chart/DashboardAllCharts.tsx => export/DashboardExportCharts.tsx} (97%) create mode 100644 src/components/pages/dashboard/export/DashboardExportStats.tsx create mode 100644 src/stores/ui/slices/navbar.slice.ts diff --git a/src/components/Card.tsx b/src/components/Card.tsx index e04fa4c7..c78766e1 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -22,6 +22,7 @@ export interface CardProps onCollapsedChange?: (collapsed: boolean) => void; className?: { wrapper?: string; + wrapperContent?: string; image?: string; body?: string; title?: string; @@ -144,6 +145,10 @@ const Card = ({ return cn('border-t border-base-300 mt-4 pt-4', className?.footer); }; + const getWrapperContentClasses = () => { + return cn('space-y-4', className?.wrapperContent); + }; + const renderCardContent = () => { const hasContent = children || actions || footer; @@ -177,7 +182,7 @@ const Card = ({ ); const cardContent = ( -
+
{children} {actions &&
{actions}
} {footer &&
{footer}
} diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 4998ca66..ce17f6b8 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -12,6 +12,7 @@ import PopoverContent from '@/components/popover/PopoverContent'; import { useAuth } from '@/services/hooks/useAuth'; import { AuthApi } from '@/services/api/auth'; import { isResponseError } from '@/lib/api-helper'; +import { useUiStore } from '@/stores/ui/ui.store'; interface NavbarProps { toggleSidebar?: () => void; @@ -21,6 +22,7 @@ const Navbar = ({ toggleSidebar }: NavbarProps) => { const { setUser } = useAuth(); const router = useRouter(); const pathname = usePathname(); + const navbarActions = useUiStore((state) => state.navbarActions); const logoutClickHandler = async () => { const logoutRes = await AuthApi.logout(); @@ -53,7 +55,9 @@ const Navbar = ({ toggleSidebar }: NavbarProps) => {
-
+
+ {/* Page-specific actions */} + {navbarActions &&
{navbarActions}
} void; }; +// 'bg-gradient-to-t from-blue-50 to-blue-100 border-blue-500 text-blue-600 hover:from-blue-100 hover:to-blue-200 + const ButtonFilter = ({ values, onClick, ...props }: ButtonFilterProps) => { return ( + } + className={{ + content: 'w-full', + }} + > + + + + +
+ ); + }, [formik.values, exporting, setNavbarActions]); + + // Cleanup only on unmount + useEffect(() => { + return () => { + clearNavbarActions(); + }; + }, [clearNavbarActions]); + if (isLoadingDashboardProductionData) { return (
@@ -210,48 +273,59 @@ const DashboardProduction = () => { return ( <> -
-
-
- -
- filterModal.openModal()} - /> - - - Export - - - } - className={{ - content: 'w-full', - }} - > - - - - -
+
+
+ openFilterModalRef.current()} + /> + + + Export +
+ +
+ + } + className={{ + content: 'w-full', + }} + > + + + +
- {/* Dashboard Stats */} -
+
{/* Use DashboardLineChart component or skeleton */} -
+
{isLoadingDashboardProductionData ? ( ) : dashboardProductionData && @@ -287,28 +361,46 @@ const DashboardProduction = () => { {/* Hidden container for all charts (used for PDF export in OVERVIEW mode) */} {dashboardProductionData && ( -
- + {/* Export Stats Charts */} +
+ +
+ + {/* Export ALL Charts */} +
+ -
+ } + /> +
+ )}
@@ -319,30 +411,32 @@ const DashboardProduction = () => { modalBox: 'p-0 rounded-xl', }} > -
+
{/* Modal Header */} -
-
+
+

Filter Data

{/* Rentang Waktu */} -
- +
+
{ errorMessage={formik.errors.startDate} onChange={formik.handleChange} className={{ - inputWrapper: 'rounded-lg', + inputWrapper: + 'rounded-[8px] px-[12px] py-[10px] text-sm border-[#18181B]/10', }} isError={ Boolean(formik.errors.startDate) && Boolean(formik.touched.startDate) } /> -
+
+ — +
{ errorMessage={formik.errors.endDate} onChange={formik.handleChange} className={{ - inputWrapper: 'rounded-lg', + inputWrapper: + 'rounded-[8px] px-[12px] py-[10px] text-sm border-[#18181B]/10', }} isError={ Boolean(formik.errors.endDate) && @@ -377,8 +475,10 @@ const DashboardProduction = () => {
{/* Analysis Mode */} -
- +
+ { }} color='primary' className={{ - wrapper: 'w-full my-6 font-semibold text-neutral-500', + wrapper: + 'w-full flex flex-row items-center min-h-[45px] font-medium text-[#18181B]/50', }} > {
{formik.values.analysisMode === 'COMPARISON' && ( -
+
{ Boolean(formik.errors.comparisonType) && Boolean(formik.touched.comparisonType) } + className={{ + label: 'mb-[8px] text-xs font-semibold', + select: 'rounded-[8px] text-sm border-[#18181B]/10', + }} />
)} {/* Location */} -
+
{comparisonTypeOptions.find( (option) => option.value === formik.values.comparisonType )?.value === 'FARM' ? ( @@ -465,6 +570,10 @@ const DashboardProduction = () => { Boolean(formik.errors.location) && Boolean(formik.touched.location) } + className={{ + label: 'mb-[8px] text-xs font-semibold', + select: 'rounded-[8px] text-sm border-[#18181B]/10', + }} /> ) : ( { Boolean(formik.errors.location) && Boolean(formik.touched.location) } + className={{ + label: 'mb-[8px] text-xs font-semibold', + select: 'rounded-[8px] text-sm border-[#18181B]/10', + }} /> )}
@@ -505,7 +618,7 @@ const DashboardProduction = () => { formik.values.comparisonType === 'KANDANG' ) ) && ( -
+
{comparisonTypeOptions.find( (option) => option.value === formik.values.comparisonType )?.value === 'FLOCK' ? ( @@ -530,6 +643,10 @@ const DashboardProduction = () => { Boolean(formik.errors.flock) && Boolean(formik.touched.flock) } + className={{ + label: 'mb-[8px] text-xs font-semibold', + select: 'rounded-[8px] text-sm border-[#18181B]/10', + }} /> ) : ( { Boolean(formik.errors.flock) && Boolean(formik.touched.flock) } + className={{ + label: 'mb-[8px] text-xs font-semibold', + select: 'rounded-[8px] text-sm border-[#18181B]/10', + }} /> )}
@@ -563,7 +684,7 @@ const DashboardProduction = () => { formik.values.analysisMode === 'COMPARISON' && !(formik.values.comparisonType === 'KANDANG') ) && ( -
+
{comparisonTypeOptions.find( (option) => option.value === formik.values.comparisonType )?.value === 'KANDANG' ? ( @@ -588,6 +709,10 @@ const DashboardProduction = () => { Boolean(formik.errors.kandang) && Boolean(formik.touched.kandang) } + className={{ + label: 'mb-[8px] text-xs font-semibold', + select: 'rounded-[8px] text-sm border-[#18181B]/10', + }} /> ) : ( { Boolean(formik.errors.kandang) && Boolean(formik.touched.kandang) } + className={{ + label: 'mb-[8px] text-xs font-semibold', + select: 'rounded-[8px] text-sm border-[#18181B]/10', + }} /> )}
@@ -621,16 +750,19 @@ const DashboardProduction = () => {
{/* Action Buttons */} -
+
-
diff --git a/src/components/pages/dashboard/chart/DashboardLineChart.tsx b/src/components/pages/dashboard/chart/DashboardLineChart.tsx index f2449795..6a419cb4 100644 --- a/src/components/pages/dashboard/chart/DashboardLineChart.tsx +++ b/src/components/pages/dashboard/chart/DashboardLineChart.tsx @@ -151,7 +151,7 @@ const DashboardLineChart = ({ }} variant='bordered' > -
+
Performance{' '} setOpen(!open)} > {chartTypeLabels[chartData]}{' '} -
- +
+ +
} className={{ - content: 'w-52 mt-3', + content: '', }} controlled={open} > { setChartData('body_weight'); setOpen(!open); @@ -192,6 +194,7 @@ const DashboardLineChart = ({ /> { setChartData('performance'); setOpen(!open); @@ -199,6 +202,7 @@ const DashboardLineChart = ({ /> { setChartData('fcr'); setOpen(!open); @@ -206,6 +210,7 @@ const DashboardLineChart = ({ /> { setChartData('quality_control'); setOpen(!open); @@ -213,6 +218,7 @@ const DashboardLineChart = ({ /> { setChartData('deplesi'); setOpen(!open); @@ -248,8 +254,8 @@ const DashboardLineChart = ({ .includes('std'); return ( - + ); }); })()} @@ -335,20 +343,54 @@ const DashboardLineChart = ({ { // Calculate dynamic domain based on visible data let seriesData: DashboardChartsSeries[] = []; @@ -399,14 +441,12 @@ const DashboardLineChart = ({ })()} ticks={(() => { // Calculate dynamic ticks based on domain - let seriesData: DashboardChartsSeries[] = []; let dataset: DashboardChartsDataset[] = []; if ( analysisMode === 'OVERVIEW' && isOverviewCharts(data.charts) ) { - seriesData = data.charts[chartData]?.series || []; dataset = data.charts[chartData]?.dataset || []; } else if ( analysisMode === 'COMPARISON' && @@ -416,7 +456,6 @@ const DashboardLineChart = ({ data.charts.farm || data.charts.flock || data.charts.kandang; - seriesData = comparisonChart?.series || []; dataset = comparisonChart?.dataset || []; } @@ -436,6 +475,20 @@ const DashboardLineChart = ({ const minValue = Math.min(...allValues); const maxValue = Math.max(...allValues); + + // Handle edge case where min equals max + if (minValue === maxValue) { + const value = Math.round(minValue); + const padding = Math.max(10, Math.abs(value) * 0.2); + return [ + Math.floor(value - padding), + Math.floor(value - padding / 2), + value, + Math.ceil(value + padding / 2), + Math.ceil(value + padding), + ]; + } + const padding = (maxValue - minValue) * 0.1; const domainMin = Math.floor(Math.max(0, minValue - padding)); const domainMax = Math.ceil(maxValue + padding); @@ -444,13 +497,16 @@ const DashboardLineChart = ({ const range = domainMax - domainMin; const step = range / 4; - return [ + // Use Set to ensure unique values + const tickSet = new Set([ domainMin, Math.round(domainMin + step), Math.round(domainMin + step * 2), Math.round(domainMin + step * 3), domainMax, - ]; + ]); + + return Array.from(tickSet).sort((a, b) => a - b); })()} /> `Week ${value}`} content={(props) => { return ( -
-

+

+

{analysisMode === 'OVERVIEW' ? selectedKandang ? selectedKandang.label || 'Overview Performance' @@ -506,12 +562,12 @@ const DashboardLineChart = ({ return (

  • -

    +

    Week {props.label}

    @@ -598,7 +654,7 @@ const DashboardLineChart = ({ return ( {/* Chart icon */} -
    - +
    +
    + +
    {/* Empty state text */} diff --git a/src/components/pages/dashboard/chart/DashboardStats.tsx b/src/components/pages/dashboard/chart/DashboardStats.tsx index dcb0707f..13167ba3 100644 --- a/src/components/pages/dashboard/chart/DashboardStats.tsx +++ b/src/components/pages/dashboard/chart/DashboardStats.tsx @@ -48,7 +48,7 @@ const DashboardStats = ({ data }: DashboardStatsProps) => { icon: isPositive ? 'heroicons:arrow-trending-up' : 'heroicons:arrow-trending-down', - color: isPositive ? 'text-success' : 'text-error', + color: isPositive ? 'text-[#008000]' : 'text-[#FF3A3A]', value: Math.abs(percent), }; }; @@ -60,14 +60,16 @@ const DashboardStats = ({ data }: DashboardStatsProps) => { {prefix} {formatNumber(value)} {suffix && ( - {suffix} + + {suffix} + )} ); }; return ( -
    +
    {CARD_CONFIG.map((config) => { // Find matching data from API const cardData = data.find((item) => item.label === config.key); @@ -78,35 +80,42 @@ const DashboardStats = ({ data }: DashboardStatsProps) => { -
    +
    +
    From last month
    -
    +
    Filter Required
    } > -
    - +
    +
    -

    +

    {config.key}

    -

    +

    ********

    @@ -121,17 +130,21 @@ const DashboardStats = ({ data }: DashboardStatsProps) => { -
    +
    +
    From last month
    {trend.value}% @@ -143,15 +156,15 @@ const DashboardStats = ({ data }: DashboardStatsProps) => { - +
    -

    +

    {cardData.label}

    -

    +

    {formatValue(cardData.value, config.prefix, config.suffix)}

    diff --git a/src/components/pages/dashboard/chart/DashboardAllCharts.tsx b/src/components/pages/dashboard/export/DashboardExportCharts.tsx similarity index 97% rename from src/components/pages/dashboard/chart/DashboardAllCharts.tsx rename to src/components/pages/dashboard/export/DashboardExportCharts.tsx index fe0db0a7..c89014d8 100644 --- a/src/components/pages/dashboard/chart/DashboardAllCharts.tsx +++ b/src/components/pages/dashboard/export/DashboardExportCharts.tsx @@ -17,12 +17,12 @@ import { YAxis, } from 'recharts'; -type DashboardAllChartsProps = { +type DashboardExportChartsProps = { data: Dashboard; analysisMode: string; }; -export type DashboardAllChartsRef = { +export type DashboardExportChartsRef = { getChartRefs: () => { key: string; ref: HTMLDivElement | null; @@ -99,9 +99,9 @@ const chartTypeLabels: Record = { deplesi: 'Deplesi', }; -const DashboardAllCharts = forwardRef< - DashboardAllChartsRef, - DashboardAllChartsProps +const DashboardExportCharts = forwardRef< + DashboardExportChartsRef, + DashboardExportChartsProps >(({ data, analysisMode }, ref) => { // Create refs for charts - use string keys for flexibility const chartRefs = useRef<{ @@ -338,6 +338,6 @@ const DashboardAllCharts = forwardRef< ); }); -DashboardAllCharts.displayName = 'DashboardAllCharts'; +DashboardExportCharts.displayName = 'DashboardExportCharts'; -export default DashboardAllCharts; +export default DashboardExportCharts; diff --git a/src/components/pages/dashboard/export/DashboardExportStats.tsx b/src/components/pages/dashboard/export/DashboardExportStats.tsx new file mode 100644 index 00000000..9d783313 --- /dev/null +++ b/src/components/pages/dashboard/export/DashboardExportStats.tsx @@ -0,0 +1,201 @@ +import Alert from '@/components/Alert'; +import Card from '@/components/Card'; +import { formatNumber } from '@/lib/helper'; +import { + DashboardOverviewCharts, + DashboardStatisticsData, +} from '@/types/api/dashboard/dashboard'; +import { Icon } from '@iconify/react'; +import { forwardRef, useImperativeHandle, useRef } from 'react'; +interface DashboardStatsProps { + data: DashboardStatisticsData[]; +} +export type DashboardExportStatsRef = { + getStatsRefs: () => { + key: string; + ref: HTMLDivElement | null; + label: string; + }[]; + getContainerRef: () => HTMLDivElement | null; +}; + +// Konfigurasi untuk setiap kartu +const CARD_CONFIG = [ + { + key: 'HPP Global', + icon: 'heroicons:banknotes', + alertColor: 'warning' as const, + suffix: ' /Kg', + prefix: 'RP ', + }, + { + key: 'Avg. Selling Price', + icon: 'heroicons:document-currency-dollar', + alertColor: 'success' as const, + suffix: ' /Kg', + prefix: '', + }, + { + key: 'FCR', + icon: 'heroicons:clipboard-document-list', + alertColor: 'info' as const, + suffix: '', + prefix: '', + }, + { + key: 'Mortality', + icon: 'heroicons:exclamation-triangle', + alertColor: 'error' as const, + suffix: ' %', + prefix: '', + }, +]; + +const DashboardExportStats = forwardRef< + DashboardExportStatsRef, + DashboardStatsProps +>(({ data }, ref) => { + const containerRef = useRef(null); + // Helper to get trend icon and color + const getTrendDisplay = (percent: number) => { + const isPositive = percent >= 0; + return { + icon: isPositive + ? 'heroicons:arrow-trending-up' + : 'heroicons:arrow-trending-down', + color: isPositive ? 'text-[#008000]' : 'text-[#FF3A3A]', + value: Math.abs(percent), + }; + }; + + // Helper to format value + const formatValue = (value: number, prefix: string, suffix: string) => { + return ( + <> + {prefix} + {formatNumber(value)} + {suffix && ( + + {suffix} + + )} + + ); + }; + + // Expose container ref through imperative handle + useImperativeHandle(ref, () => ({ + getStatsRefs: () => [], + getContainerRef: () => containerRef.current, + })); + + return ( +
    + {CARD_CONFIG.map((config) => { + // Find matching data from API + const cardData = data.find((item) => item.label === config.key); + + if (!cardData) { + // Show placeholder card for missing data (FCR & Mortality) + return ( + +
    + From last month +
    +
    + Filter Required +
    +
    + } + > +
    + + + +
    +

    + {config.key} +

    +

    + ******** +

    +
    +
    + + ); + } + + const trend = getTrendDisplay(cardData.percent_last_month); + + return ( + +
    + From last month +
    +
    + + {trend.value}% +
    +
    + } + > +
    + + + +
    +

    + {cardData.label} +

    +

    + {formatValue(cardData.value, config.prefix, config.suffix)} +

    +
    +
    + + ); + })} +
    + ); +}); + +DashboardExportStats.displayName = 'DashboardExportStats'; + +export default DashboardExportStats; diff --git a/src/components/pages/dashboard/export/DashboardPDF.ts b/src/components/pages/dashboard/export/DashboardPDF.ts index 17c5bde4..8b4c7e6a 100644 --- a/src/components/pages/dashboard/export/DashboardPDF.ts +++ b/src/components/pages/dashboard/export/DashboardPDF.ts @@ -3,18 +3,19 @@ import { toPng } from 'html-to-image'; import toast from 'react-hot-toast'; import { formatDate } from '@/lib/helper'; import { DashboardFilterType } from '@/components/pages/dashboard/filter/DashboardProductionFilter.schema'; -import { DashboardAllChartsRef } from '@/components/pages/dashboard/chart/DashboardAllCharts'; +import { DashboardExportChartsRef } from '@/components/pages/dashboard/export/DashboardExportCharts'; +import { DashboardExportStatsRef } from '@/components/pages/dashboard/export/DashboardExportStats'; interface DashboardPDFExportParams { filterValues: DashboardFilterType; - statsRef: React.RefObject; - allChartsRef: React.RefObject; + allStatsRef: React.RefObject; + allChartsRef: React.RefObject; setExporting: (value: boolean) => void; } export const generateDashboardPDF = async ({ filterValues, - statsRef, + allStatsRef, allChartsRef, setExporting, }: DashboardPDFExportParams): Promise => { @@ -168,31 +169,34 @@ export const generateDashboardPDF = async ({ yPosition += 10; // Capture and add stats if available - if (statsRef.current) { - const statsImage = await toPng(statsRef.current, { - quality: 1, - pixelRatio: 2, - }); - const statsImgProps = pdf.getImageProperties(statsImage); - const statsWidth = pageWidth - 2 * margin; - const statsHeight = - (statsImgProps.height * statsWidth) / statsImgProps.width; + if (allStatsRef.current) { + const statsContainer = allStatsRef.current.getContainerRef(); + if (statsContainer) { + const statsImage = await toPng(statsContainer, { + quality: 1, + pixelRatio: 2, + }); + const statsImgProps = pdf.getImageProperties(statsImage); + const statsWidth = pageWidth - 2 * margin; + const statsHeight = + (statsImgProps.height * statsWidth) / statsImgProps.width; - // Check if we need a new page - if (yPosition + statsHeight > pageHeight - margin) { - pdf.addPage(); - yPosition = margin; + // Check if we need a new page + if (yPosition + statsHeight > pageHeight - margin) { + pdf.addPage(); + yPosition = margin; + } + + pdf.addImage( + statsImage, + 'PNG', + margin, + yPosition, + statsWidth, + statsHeight + ); + yPosition += statsHeight + 10; } - - pdf.addImage( - statsImage, - 'PNG', - margin, - yPosition, - statsWidth, - statsHeight - ); - yPosition += statsHeight + 10; } if (allChartsRef.current) { diff --git a/src/components/pages/dashboard/skeleton/DashboardLineChartSkeleton.tsx b/src/components/pages/dashboard/skeleton/DashboardLineChartSkeleton.tsx index b479eced..d2999544 100644 --- a/src/components/pages/dashboard/skeleton/DashboardLineChartSkeleton.tsx +++ b/src/components/pages/dashboard/skeleton/DashboardLineChartSkeleton.tsx @@ -3,94 +3,104 @@ import { DashboardMeta } from '@/types/api/dashboard/dashboard'; const DashboardLineChartSkeleton = ({ meta }: { meta?: DashboardMeta }) => { return ( -
    +
    {/* Header with title skeleton */} -
    +
    Performance{' '}
    {/* Chart area with axes skeleton */} -
    - {/* Main chart container */} -
    - {/* Y-axis skeleton (left side) */} -
    - {[1, 2, 3, 4, 5, 6].map((item) => ( -
    - ))} -
    - - {/* Chart content area */} -
    - {/* Empty state centered in chart area */} -
    - {!meta?.filters && ( - <> - {/* Filter icon */} -
    - +
    + {/* Chart content area */} +
    + {/* Empty state centered in chart area */} +
    + {!meta?.filters && ( + <> + {/* Filter icon */} +
    +
    +
    + +
    +
    - {/* Empty state text */} -

    - No Filters Selected -

    -

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

    - - )} - {meta?.filters && ( - <> - {/* Filter icon */} -
    + {/* Empty state text */} +

    + No Filters Selected +

    +

    + 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. -

    - - )} + {/* Empty state text */} +

    + Data Not Yet Available +

    +

    + Please change your filters to get the data. +

    + + )} +
    + +
    +
    +
    - - {/* Placeholder for chart height */} -
    - - {/* X-axis skeleton (bottom) */} -
    - {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((item) => ( +
    + {[1, 2, 3, 4].map((item) => (
    + className='flex items-center w-full h-[16px] gap-[16px]' + > +
    +
    +
    ))}
    + + {/* X-axis skeleton (bottom) */} +
    + {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((item) => ( +
    + ))} +
    +
    +
    +
    diff --git a/src/stores/ui/slices/navbar.slice.ts b/src/stores/ui/slices/navbar.slice.ts new file mode 100644 index 00000000..2db6f547 --- /dev/null +++ b/src/stores/ui/slices/navbar.slice.ts @@ -0,0 +1,22 @@ +'use client'; + +import { ReactNode } from 'react'; +import { StateCreator } from 'zustand'; +import { UIStore } from '@/types/stores'; + +type NavbarActionsSlice = { + navbarActions: ReactNode | null; + setNavbarActions: (actions: ReactNode) => void; + clearNavbarActions: () => void; +}; + +export const createNavbarActionsSlice: StateCreator< + UIStore, + [], + [], + NavbarActionsSlice +> = (set) => ({ + navbarActions: null, + setNavbarActions: (actions) => set({ navbarActions: actions }), + clearNavbarActions: () => set({ navbarActions: null }), +}); diff --git a/src/stores/ui/ui.store.ts b/src/stores/ui/ui.store.ts index 05adbb9b..46c70283 100644 --- a/src/stores/ui/ui.store.ts +++ b/src/stores/ui/ui.store.ts @@ -7,6 +7,7 @@ import { UIStore } from '@/types/stores'; import { createMainUiSlice } from '@/stores/ui/slices/main.slice'; import { createDrawerUISlice } from '@/stores/ui/slices/drawer.slice'; import { createTableUISlice } from '@/stores/ui/slices/table.slice'; +import { createNavbarActionsSlice } from '@/stores/ui/slices/navbar.slice'; export const useUiStore = create()( devtools( @@ -15,6 +16,7 @@ export const useUiStore = create()( ...createMainUiSlice(...args), ...createDrawerUISlice(...args), ...createTableUISlice(...args), + ...createNavbarActionsSlice(...args), }), { name: 'ui-cache', diff --git a/src/types/stores.d.ts b/src/types/stores.d.ts index 5b0be6f3..47d2c1fd 100644 --- a/src/types/stores.d.ts +++ b/src/types/stores.d.ts @@ -32,7 +32,17 @@ type TableUISlice = { resetSearchValue: () => void; }; -export type UIStore = MainUiSlice & DrawerUISlice & TableUISlice; +// Navbar Actions Slice +type NavbarActionsSlice = { + navbarActions: ReactNode | null; + setNavbarActions: (actions: ReactNode) => void; + clearNavbarActions: () => void; +}; + +export type UIStore = MainUiSlice & + DrawerUISlice & + TableUISlice & + NavbarActionsSlice; type ProductionStandardFormSlice = { formData: {