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 mt-1 p-0', + }} + > + + + + +
+ ); + }, [formik.values, exporting, setNavbarActions]); + + // Cleanup only on unmount + useEffect(() => { + return () => { + clearNavbarActions(); + }; + }, [clearNavbarActions]); + if (isLoadingDashboardProductionData) { return (
@@ -210,48 +272,62 @@ const DashboardProduction = () => { return ( <> -
-
-
- -
- filterModal.openModal()} - /> - - - Export - - - } - className={{ - content: 'w-full', - }} +
+
+ openFilterModalRef.current()} + /> + + + Export +
+ +
+ + } + className={{ + content: + 'w-full mt-1 p-0 shadow-button-soft border border-base-content/10 rounded-lg', + }} + > + - - - - -
+ + +
- {/* Dashboard Stats */} -
+
{/* Use DashboardLineChart component or skeleton */} -
+
{isLoadingDashboardProductionData ? ( ) : dashboardProductionData && @@ -287,28 +363,46 @@ const DashboardProduction = () => { {/* Hidden container for all charts (used for PDF export in OVERVIEW mode) */} {dashboardProductionData && ( -
- + {/* Export Stats Charts */} +
+ +
+ + {/* Export ALL Charts */} +
+ -
+ } + /> +
+ )}
@@ -316,102 +410,106 @@ const DashboardProduction = () => { ref={filterModal.ref} className={{ modal: 'p-0', - modalBox: 'p-0 rounded-xl', + modalBox: 'p-0 rounded-[0.875rem]', }} > -
+
{/* Modal Header */} -
-
+
+
-

Filter Data

+

Filter Data

- {/* Rentang Waktu */} -
- -
- -
- +
+ {/* Rentang Waktu */} +
+ +
+ +
+ +
-
- {/* Analysis Mode */} -
- - { - formik.handleChange(e); - setAnalysisMode(e.target.value as 'OVERVIEW' | 'COMPARISON'); - // Reset all dependent fields when analysis mode changes - formik.setFieldValue('location', []); - formik.setFieldValue('flock', []); - formik.setFieldValue('kandang', []); - formik.setFieldValue('comparisonType', ''); - setSelectedLocationIds([]); - }} - color='primary' - className={{ - wrapper: 'w-full my-6 font-semibold text-neutral-500', - }} - > - + + { + formik.handleChange(e); + setAnalysisMode( + e.target.value as 'OVERVIEW' | 'COMPARISON' + ); + // Reset all dependent fields when analysis mode changes + formik.setFieldValue('location', []); + formik.setFieldValue('flock', []); + formik.setFieldValue('kandang', []); + formik.setFieldValue('comparisonType', ''); + setSelectedLocationIds([]); + }} color='primary' - value='OVERVIEW' - label='Performance Overview' - /> - - -
+ className={{ + wrapper: + 'w-full flex flex-row items-center font-medium text-base-content/50', + radioWrapper: 'w-full grid grid-cols-2 gap-0 p-0', + }} + > + + + +
- {formik.values.analysisMode === 'COMPARISON' && ( -
+ {formik.values.analysisMode === 'COMPARISON' && ( { Boolean(formik.errors.comparisonType) && Boolean(formik.touched.comparisonType) } + className={{ + select: 'rounded-lg text-sm border-base-content/10', + }} /> -
- )} + )} - {/* Location */} -
+ {/* Location */} {comparisonTypeOptions.find( (option) => option.value === formik.values.comparisonType )?.value === 'FARM' ? ( @@ -465,6 +564,9 @@ const DashboardProduction = () => { Boolean(formik.errors.location) && Boolean(formik.touched.location) } + className={{ + select: 'rounded-lg text-sm border-base-content/10', + }} /> ) : ( { Boolean(formik.errors.location) && Boolean(formik.touched.location) } + className={{ + select: 'rounded-lg text-sm border-base-content/10', + }} /> )} + + {/* Flock */} + {!( + formik.values.analysisMode === 'COMPARISON' && + !( + formik.values.comparisonType === 'FLOCK' || + formik.values.comparisonType === 'KANDANG' + ) + ) && ( + <> + {comparisonTypeOptions.find( + (option) => option.value === formik.values.comparisonType + )?.value === 'FLOCK' ? ( + + formik.setFieldValue('flock', selected) + } + errorMessage={formik.errors.flock as string} + onInputChange={setInputValueFlock} + onMenuScrollToBottom={loadMoreFlock} + options={flockOptions} + isLoading={isLoadingFlockOptions} + isError={ + Boolean(formik.errors.flock) && + Boolean(formik.touched.flock) + } + className={{ + select: 'rounded-lg text-sm border-base-content/10', + }} + /> + ) : ( + + formik.setFieldValue('flock', selected) + } + errorMessage={formik.errors.flock as string} + onInputChange={setInputValueFlock} + onMenuScrollToBottom={loadMoreFlock} + options={flockOptions} + isLoading={isLoadingFlockOptions} + isError={ + Boolean(formik.errors.flock) && + Boolean(formik.touched.flock) + } + className={{ + select: 'rounded-lg text-sm border-base-content/10', + }} + /> + )} + + )} + + {/* Kandang */} + {!( + formik.values.analysisMode === 'COMPARISON' && + !(formik.values.comparisonType === 'KANDANG') + ) && ( + <> + {comparisonTypeOptions.find( + (option) => option.value === formik.values.comparisonType + )?.value === 'KANDANG' ? ( + + formik.setFieldValue('kandang', selected) + } + errorMessage={formik.errors.kandang as string} + onInputChange={setInputValueKandang} + onMenuScrollToBottom={loadMoreKandang} + options={kandangOptions} + isLoading={isLoadingKandangOptions} + isError={ + Boolean(formik.errors.kandang) && + Boolean(formik.touched.kandang) + } + className={{ + select: 'rounded-lg text-sm border-base-content/10', + }} + /> + ) : ( + + formik.setFieldValue('kandang', selected) + } + errorMessage={formik.errors.kandang as string} + onInputChange={setInputValueKandang} + onMenuScrollToBottom={loadMoreKandang} + options={kandangOptions} + isLoading={isLoadingKandangOptions} + isError={ + Boolean(formik.errors.kandang) && + Boolean(formik.touched.kandang) + } + className={{ + select: 'rounded-lg text-sm border-base-content/10', + }} + /> + )} + + )} + + {formErrorList.length > 0 && ( +
+ +
+ )}
- {/* Flock */} - {!( - formik.values.analysisMode === 'COMPARISON' && - !( - formik.values.comparisonType === 'FLOCK' || - formik.values.comparisonType === 'KANDANG' - ) - ) && ( -
- {comparisonTypeOptions.find( - (option) => option.value === formik.values.comparisonType - )?.value === 'FLOCK' ? ( - - formik.setFieldValue('flock', selected) - } - errorMessage={formik.errors.flock as string} - onInputChange={setInputValueFlock} - onMenuScrollToBottom={loadMoreFlock} - options={flockOptions} - isLoading={isLoadingFlockOptions} - isError={ - Boolean(formik.errors.flock) && - Boolean(formik.touched.flock) - } - /> - ) : ( - - formik.setFieldValue('flock', selected) - } - errorMessage={formik.errors.flock as string} - onInputChange={setInputValueFlock} - onMenuScrollToBottom={loadMoreFlock} - options={flockOptions} - isLoading={isLoadingFlockOptions} - isError={ - Boolean(formik.errors.flock) && - Boolean(formik.touched.flock) - } - /> - )} -
- )} - - {/* Kandang */} - {!( - formik.values.analysisMode === 'COMPARISON' && - !(formik.values.comparisonType === 'KANDANG') - ) && ( -
- {comparisonTypeOptions.find( - (option) => option.value === formik.values.comparisonType - )?.value === 'KANDANG' ? ( - - formik.setFieldValue('kandang', selected) - } - errorMessage={formik.errors.kandang as string} - onInputChange={setInputValueKandang} - onMenuScrollToBottom={loadMoreKandang} - options={kandangOptions} - isLoading={isLoadingKandangOptions} - isError={ - Boolean(formik.errors.kandang) && - Boolean(formik.touched.kandang) - } - /> - ) : ( - - formik.setFieldValue('kandang', selected) - } - errorMessage={formik.errors.kandang as string} - onInputChange={setInputValueKandang} - onMenuScrollToBottom={loadMoreKandang} - options={kandangOptions} - isLoading={isLoadingKandangOptions} - isError={ - Boolean(formik.errors.kandang) && - Boolean(formik.touched.kandang) - } - /> - )} -
- )} - -
- -
- {/* Action Buttons */} -
+
-
diff --git a/src/components/pages/dashboard/chart/DashboardLineChart.tsx b/src/components/pages/dashboard/chart/DashboardLineChart.tsx index f2449795..092e4bca 100644 --- a/src/components/pages/dashboard/chart/DashboardLineChart.tsx +++ b/src/components/pages/dashboard/chart/DashboardLineChart.tsx @@ -147,11 +147,12 @@ const DashboardLineChart = ({ return ( -
+
Performance{' '} setOpen(!open)} > {chartTypeLabels[chartData]}{' '} -
- +
+ +
} - className={{ - content: 'w-52 mt-3', - }} controlled={open} > - + { setChartData('body_weight'); setOpen(!open); @@ -192,6 +195,7 @@ const DashboardLineChart = ({ /> { setChartData('performance'); setOpen(!open); @@ -199,6 +203,7 @@ const DashboardLineChart = ({ /> { setChartData('fcr'); setOpen(!open); @@ -206,6 +211,7 @@ const DashboardLineChart = ({ /> { setChartData('quality_control'); setOpen(!open); @@ -213,6 +219,7 @@ const DashboardLineChart = ({ /> { setChartData('deplesi'); setOpen(!open); @@ -248,8 +255,8 @@ const DashboardLineChart = ({ .includes('std'); return ( - + ); }); })()} @@ -335,20 +344,68 @@ const DashboardLineChart = ({ { // Calculate dynamic domain based on visible data let seriesData: DashboardChartsSeries[] = []; @@ -399,14 +456,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 +471,6 @@ const DashboardLineChart = ({ data.charts.farm || data.charts.flock || data.charts.kandang; - seriesData = comparisonChart?.series || []; dataset = comparisonChart?.dataset || []; } @@ -436,6 +490,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,21 +512,25 @@ 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); })()} + tickFormatter={(value) => formatNumber(Number(value))} /> `Week ${value}`} content={(props) => { return ( -
-

+

+

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

  • - +
    -

    +

    Week {props.label}

    @@ -598,7 +670,7 @@ const DashboardLineChart = ({ return ( {/* Chart icon */} -
    - +
    +
    + +
    {/* Empty state text */} -

    +

    Data Not Yet Available

    -

    +

    Please change your filters to get the data.

    diff --git a/src/components/pages/dashboard/chart/DashboardStats.tsx b/src/components/pages/dashboard/chart/DashboardStats.tsx index dcb0707f..331547b0 100644 --- a/src/components/pages/dashboard/chart/DashboardStats.tsx +++ b/src/components/pages/dashboard/chart/DashboardStats.tsx @@ -21,7 +21,7 @@ const CARD_CONFIG = [ key: 'Avg. Selling Price', icon: 'heroicons:document-currency-dollar', alertColor: 'success' as const, - suffix: ' /Kg', + suffix: ' /Kg Telur', prefix: '', }, { @@ -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,41 @@ const DashboardStats = ({ data }: DashboardStatsProps) => { -
    +
    +
    From last month
    -
    +
    Filter Required
    } > -
    - +
    +
    -

    +

    {config.key}

    -

    +

    ********

    @@ -121,17 +129,20 @@ const DashboardStats = ({ data }: DashboardStatsProps) => { -
    +
    From last month
    {trend.value}% @@ -143,15 +154,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 96% rename from src/components/pages/dashboard/chart/DashboardAllCharts.tsx rename to src/components/pages/dashboard/export/DashboardExportCharts.tsx index fe0db0a7..ba0b2fe2 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<{ @@ -189,7 +189,8 @@ const DashboardAllCharts = forwardRef< > @@ -338,6 +339,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..aebdc751 --- /dev/null +++ b/src/components/pages/dashboard/export/DashboardExportStats.tsx @@ -0,0 +1,197 @@ +import Alert from '@/components/Alert'; +import Card from '@/components/Card'; +import { formatNumber } from '@/lib/helper'; +import { 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..6e4e4b97 100644 --- a/src/components/pages/dashboard/skeleton/DashboardLineChartSkeleton.tsx +++ b/src/components/pages/dashboard/skeleton/DashboardLineChartSkeleton.tsx @@ -3,94 +3,101 @@ 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) => ( +
    +
    +
    +
    ))}
    + + {/* 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: {