refactor: Smart contextual headers - only when they add value

Implemented intelligent header rules based on user feedback.

Problem Analysis:
1. Dashboard submenu tabs already show page names (Overview, Revenue, Orders...)
2. Showing "Orders" header is ambiguous (Analytics or Management?)
3. Wasted vertical space for redundant information
4. FAB already handles actions on management pages

Solution: Headers ONLY When They Add Value

Rules Implemented:

1. Dashboard Pages: NO HEADERS
   - Submenu tabs are sufficient
   - Saves vertical space
   - No ambiguity

   Before:
   Dashboard → Overview  = "Dashboard" header (redundant!)
   Dashboard → Orders    = "Orders" header (confusing!)

   After:
   Dashboard → Overview  = No header (tabs show "Overview")
   Dashboard → Orders    = No header (tabs show "Orders")

2. Settings Pages: HEADERS ONLY WITH ACTIONS
   - Store Details + [Save] = Show header ✓
   - Payments + [Refresh]   = Show header ✓
   - Pages without actions  = No header (save space)

   Logic: If there is an action button, we need a place to put it → header
          If no action button, header is just wasting space → remove it

3. Management Pages: NO HEADERS
   - FAB handles actions ([+ Add Order])
   - No need for redundant header with action button

4. Payments Exception: REMOVED
   - Treat Payments like any other settings page
   - Has action (Refresh) = show header
   - Consistent with other pages

Implementation:

Dashboard Pages (7 files):
- Removed usePageHeader hook
- Removed useEffect for setting header
- Removed unused imports (useEffect, usePageHeader)
- Result: Clean, no headers, tabs are enough

PageHeader Component:
- Removed Payments special case detection
- Removed useLocation import
- Simplified logic: hideOnDesktop prop only

SettingsLayout Component:
- Changed logic: Only set header when onSave OR action exists
- If no action: clearPageHeader() instead of setPageHeader(title)
- Result: Headers only appear when needed

Benefits:

 Saves vertical space (no redundant headers)
 No ambiguity (Dashboard Orders vs Orders Management)
 Consistent logic (action = header, no action = no header)
 Cleaner UI (less visual clutter)
 FAB handles management page actions

Files Modified:
- Dashboard/index.tsx (Overview)
- Dashboard/Revenue.tsx
- Dashboard/Orders.tsx
- Dashboard/Products.tsx
- Dashboard/Customers.tsx
- Dashboard/Coupons.tsx
- Dashboard/Taxes.tsx
- PageHeader.tsx
- SettingsLayout.tsx

Result: Smart headers that only appear when they add value! 🎯
This commit is contained in:
dwindown
2025-11-06 23:11:59 +07:00
parent bc86a12c38
commit 0dace90597
9 changed files with 17 additions and 71 deletions

View File

@@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import { useLocation } from 'react-router-dom';
import { usePageHeader } from '@/contexts/PageHeaderContext'; import { usePageHeader } from '@/contexts/PageHeaderContext';
interface PageHeaderProps { interface PageHeaderProps {
@@ -9,18 +8,14 @@ interface PageHeaderProps {
export function PageHeader({ fullscreen = false, hideOnDesktop = false }: PageHeaderProps) { export function PageHeader({ fullscreen = false, hideOnDesktop = false }: PageHeaderProps) {
const { title, action } = usePageHeader(); const { title, action } = usePageHeader();
const location = useLocation();
if (!title) return null; if (!title) return null;
// Special case: Payments page should hide header on desktop (has its own layout)
const isPaymentsPage = location.pathname.startsWith('/settings/payments');
const shouldHideOnDesktop = hideOnDesktop || isPaymentsPage;
// PageHeader is now ABOVE submenu in DOM order // PageHeader is now ABOVE submenu in DOM order
// z-20 ensures it stays on top when both are sticky // z-20 ensures it stays on top when both are sticky
// Only hide on desktop if explicitly requested (for mobile-only headers)
return ( return (
<div className={`sticky top-0 z-20 border-b bg-background ${shouldHideOnDesktop ? 'md:hidden' : ''}`}> <div className={`sticky top-0 z-20 border-b bg-background ${hideOnDesktop ? 'md:hidden' : ''}`}>
<div className="w-full max-w-5xl mx-auto px-4 py-3 flex items-center justify-between min-w-0"> <div className="w-full max-w-5xl mx-auto px-4 py-3 flex items-center justify-between min-w-0">
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<h1 className="text-lg font-semibold truncate">{title}</h1> <h1 className="text-lg font-semibold truncate">{title}</h1>

View File

@@ -1,4 +1,4 @@
import React, { useState, useMemo, useEffect } from 'react'; import React, { useState, useMemo } from 'react';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts';
import { Tag, DollarSign, TrendingUp, ShoppingCart } from 'lucide-react'; import { Tag, DollarSign, TrendingUp, ShoppingCart } from 'lucide-react';
import { __ } from '@/lib/i18n'; import { __ } from '@/lib/i18n';
@@ -12,18 +12,11 @@ import { ChartCard } from './components/ChartCard';
import { DataTable, Column } from './components/DataTable'; import { DataTable, Column } from './components/DataTable';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { DUMMY_COUPONS_DATA, CouponsData, CouponPerformance } from './data/dummyCoupons'; import { DUMMY_COUPONS_DATA, CouponsData, CouponPerformance } from './data/dummyCoupons';
import { usePageHeader } from '@/contexts/PageHeaderContext';
export default function CouponsReport() { export default function CouponsReport() {
const { setPageHeader, clearPageHeader } = usePageHeader();
const { period } = useDashboardPeriod(); const { period } = useDashboardPeriod();
const store = getStoreCurrency(); const store = getStoreCurrency();
useEffect(() => {
setPageHeader(__('Coupons'));
return () => clearPageHeader();
}, [setPageHeader, clearPageHeader]);
// Fetch real data or use dummy data based on toggle // Fetch real data or use dummy data based on toggle
const { data, isLoading, error, refetch } = useCouponsAnalytics(DUMMY_COUPONS_DATA); const { data, isLoading, error, refetch } = useCouponsAnalytics(DUMMY_COUPONS_DATA);

View File

@@ -1,4 +1,4 @@
import React, { useState, useMemo, useEffect } from 'react'; import React, { useState, useMemo } from 'react';
import { BarChart, Bar, LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts'; import { BarChart, Bar, LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts';
import { Users, TrendingUp, DollarSign, ShoppingCart, UserPlus, UserCheck, Info } from 'lucide-react'; import { Users, TrendingUp, DollarSign, ShoppingCart, UserPlus, UserCheck, Info } from 'lucide-react';
import { __ } from '@/lib/i18n'; import { __ } from '@/lib/i18n';
@@ -12,18 +12,11 @@ import { ChartCard } from './components/ChartCard';
import { DataTable, Column } from './components/DataTable'; import { DataTable, Column } from './components/DataTable';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { DUMMY_CUSTOMERS_DATA, CustomersData, TopCustomer } from './data/dummyCustomers'; import { DUMMY_CUSTOMERS_DATA, CustomersData, TopCustomer } from './data/dummyCustomers';
import { usePageHeader } from '@/contexts/PageHeaderContext';
export default function CustomersAnalytics() { export default function CustomersAnalytics() {
const { setPageHeader, clearPageHeader } = usePageHeader();
const { period } = useDashboardPeriod(); const { period } = useDashboardPeriod();
const store = getStoreCurrency(); const store = getStoreCurrency();
useEffect(() => {
setPageHeader(__('Customers'));
return () => clearPageHeader();
}, [setPageHeader, clearPageHeader]);
// Fetch real data or use dummy data based on toggle // Fetch real data or use dummy data based on toggle
const { data, isLoading, error, refetch } = useCustomersAnalytics(DUMMY_CUSTOMERS_DATA); const { data, isLoading, error, refetch } = useCustomersAnalytics(DUMMY_CUSTOMERS_DATA);
@@ -247,7 +240,7 @@ export default function CustomersAnalytics() {
]; ];
return ( return (
<div className="space-y-6 p-6"> <div className="space-y-6 lg:p-6 pb-6">
{/* Header */} {/* Header */}
<div className="mb-6"> <div className="mb-6">
<h1 className="text-2xl font-bold">{__('Customers Analytics')}</h1> <h1 className="text-2xl font-bold">{__('Customers Analytics')}</h1>

View File

@@ -1,4 +1,4 @@
import React, { useState, useMemo, useRef, useEffect } from 'react'; import React, { useState, useMemo, useRef } from 'react';
import { BarChart, Bar, LineChart, Line, AreaChart, Area, ComposedChart, PieChart, Pie, Cell, Label, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts'; import { BarChart, Bar, LineChart, Line, AreaChart, Area, ComposedChart, PieChart, Pie, Cell, Label, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts';
import { ShoppingCart, TrendingUp, Package, XCircle, DollarSign, CheckCircle, Clock } from 'lucide-react'; import { ShoppingCart, TrendingUp, Package, XCircle, DollarSign, CheckCircle, Clock } from 'lucide-react';
import { __ } from '@/lib/i18n'; import { __ } from '@/lib/i18n';
@@ -12,20 +12,13 @@ import { ChartCard } from './components/ChartCard';
import { DataTable, Column } from './components/DataTable'; import { DataTable, Column } from './components/DataTable';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { DUMMY_ORDERS_DATA, OrdersData } from './data/dummyOrders'; import { DUMMY_ORDERS_DATA, OrdersData } from './data/dummyOrders';
import { usePageHeader } from '@/contexts/PageHeaderContext';
export default function OrdersAnalytics() { export default function OrdersAnalytics() {
const { setPageHeader, clearPageHeader } = usePageHeader();
const { period } = useDashboardPeriod(); const { period } = useDashboardPeriod();
const store = getStoreCurrency(); const store = getStoreCurrency();
const [hoverIndex, setHoverIndex] = useState<number | undefined>(undefined); const [hoverIndex, setHoverIndex] = useState<number | undefined>(undefined);
const chartRef = useRef<any>(null); const chartRef = useRef<any>(null);
useEffect(() => {
setPageHeader(__('Orders'));
return () => clearPageHeader();
}, [setPageHeader, clearPageHeader]);
// Fetch real data or use dummy data based on toggle // Fetch real data or use dummy data based on toggle
const { data, isLoading, error, refetch } = useOrdersAnalytics(DUMMY_ORDERS_DATA); const { data, isLoading, error, refetch } = useOrdersAnalytics(DUMMY_ORDERS_DATA);
@@ -165,7 +158,7 @@ export default function OrdersAnalytics() {
}; };
return ( return (
<div className="space-y-6 p-6"> <div className="space-y-6 lg:p-6 pb-6">
{/* Header */} {/* Header */}
<div className="mb-6"> <div className="mb-6">
<h1 className="text-2xl font-bold">{__('Orders Analytics')}</h1> <h1 className="text-2xl font-bold">{__('Orders Analytics')}</h1>

View File

@@ -1,4 +1,4 @@
import React, { useState, useMemo, useEffect } from 'react'; import React, { useState, useMemo } from 'react';
import { Package, TrendingUp, DollarSign, AlertTriangle, XCircle } from 'lucide-react'; import { Package, TrendingUp, DollarSign, AlertTriangle, XCircle } from 'lucide-react';
import { __ } from '@/lib/i18n'; import { __ } from '@/lib/i18n';
import { formatMoney, getStoreCurrency } from '@/lib/currency'; import { formatMoney, getStoreCurrency } from '@/lib/currency';
@@ -12,18 +12,11 @@ import { DataTable, Column } from './components/DataTable';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { DUMMY_PRODUCTS_DATA, ProductsData, TopProduct, ProductByCategory, StockAnalysisProduct } from './data/dummyProducts'; import { DUMMY_PRODUCTS_DATA, ProductsData, TopProduct, ProductByCategory, StockAnalysisProduct } from './data/dummyProducts';
import { usePageHeader } from '@/contexts/PageHeaderContext';
export default function ProductsPerformance() { export default function ProductsPerformance() {
const { setPageHeader, clearPageHeader } = usePageHeader();
const { period } = useDashboardPeriod(); const { period } = useDashboardPeriod();
const store = getStoreCurrency(); const store = getStoreCurrency();
useEffect(() => {
setPageHeader(__('Products'));
return () => clearPageHeader();
}, [setPageHeader, clearPageHeader]);
// Fetch real data or use dummy data based on toggle // Fetch real data or use dummy data based on toggle
const { data, isLoading, error, refetch } = useProductsAnalytics(DUMMY_PRODUCTS_DATA); const { data, isLoading, error, refetch } = useProductsAnalytics(DUMMY_PRODUCTS_DATA);
@@ -192,7 +185,7 @@ export default function ProductsPerformance() {
]; ];
return ( return (
<div className="space-y-6 p-6"> <div className="space-y-6 lg:p-6 pb-6">
{/* Header */} {/* Header */}
<div className="mb-6"> <div className="mb-6">
<h1 className="text-2xl font-bold">{__('Products Performance')}</h1> <h1 className="text-2xl font-bold">{__('Products Performance')}</h1>

View File

@@ -1,4 +1,4 @@
import React, { useState, useMemo, useEffect } from 'react'; import React, { useState, useMemo } from 'react';
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts'; import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts';
import { DollarSign, TrendingUp, TrendingDown, CreditCard, Truck, RefreshCw } from 'lucide-react'; import { DollarSign, TrendingUp, TrendingDown, CreditCard, Truck, RefreshCw } from 'lucide-react';
import { __ } from '@/lib/i18n'; import { __ } from '@/lib/i18n';
@@ -13,19 +13,12 @@ import { DataTable, Column } from './components/DataTable';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { DUMMY_REVENUE_DATA, RevenueData, RevenueByProduct, RevenueByCategory, RevenueByPaymentMethod, RevenueByShippingMethod } from './data/dummyRevenue'; import { DUMMY_REVENUE_DATA, RevenueData, RevenueByProduct, RevenueByCategory, RevenueByPaymentMethod, RevenueByShippingMethod } from './data/dummyRevenue';
import { usePageHeader } from '@/contexts/PageHeaderContext';
export default function RevenueAnalytics() { export default function RevenueAnalytics() {
const { setPageHeader, clearPageHeader } = usePageHeader();
const { period } = useDashboardPeriod(); const { period } = useDashboardPeriod();
const [granularity, setGranularity] = useState<'day' | 'week' | 'month'>('day'); const [granularity, setGranularity] = useState<'day' | 'week' | 'month'>('day');
const store = getStoreCurrency(); const store = getStoreCurrency();
useEffect(() => {
setPageHeader(__('Revenue'));
return () => clearPageHeader();
}, [setPageHeader, clearPageHeader]);
// Fetch real data or use dummy data based on toggle // Fetch real data or use dummy data based on toggle
const { data, isLoading, error, refetch } = useRevenueAnalytics(DUMMY_REVENUE_DATA, granularity); const { data, isLoading, error, refetch } = useRevenueAnalytics(DUMMY_REVENUE_DATA, granularity);
@@ -343,7 +336,7 @@ export default function RevenueAnalytics() {
]; ];
return ( return (
<div className="space-y-6 p-6"> <div className="space-y-6 lg:p-6 pb-6">
{/* Header */} {/* Header */}
<div className="mb-6"> <div className="mb-6">
<h1 className="text-2xl font-bold">{__('Revenue Analytics')}</h1> <h1 className="text-2xl font-bold">{__('Revenue Analytics')}</h1>

View File

@@ -1,4 +1,4 @@
import React, { useState, useMemo, useEffect } from 'react'; import React, { useState, useMemo } from 'react';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
import { DollarSign, FileText, ShoppingCart, TrendingUp } from 'lucide-react'; import { DollarSign, FileText, ShoppingCart, TrendingUp } from 'lucide-react';
import { __ } from '@/lib/i18n'; import { __ } from '@/lib/i18n';
@@ -12,18 +12,11 @@ import { ChartCard } from './components/ChartCard';
import { DataTable, Column } from './components/DataTable'; import { DataTable, Column } from './components/DataTable';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { DUMMY_TAXES_DATA, TaxesData, TaxByRate, TaxByLocation } from './data/dummyTaxes'; import { DUMMY_TAXES_DATA, TaxesData, TaxByRate, TaxByLocation } from './data/dummyTaxes';
import { usePageHeader } from '@/contexts/PageHeaderContext';
export default function TaxesReport() { export default function TaxesReport() {
const { setPageHeader, clearPageHeader } = usePageHeader();
const { period } = useDashboardPeriod(); const { period } = useDashboardPeriod();
const store = getStoreCurrency(); const store = getStoreCurrency();
useEffect(() => {
setPageHeader(__('Taxes'));
return () => clearPageHeader();
}, [setPageHeader, clearPageHeader]);
// Fetch real data or use dummy data based on toggle // Fetch real data or use dummy data based on toggle
const { data, isLoading, error, refetch } = useTaxesAnalytics(DUMMY_TAXES_DATA); const { data, isLoading, error, refetch } = useTaxesAnalytics(DUMMY_TAXES_DATA);

View File

@@ -1,4 +1,4 @@
import React, { useState, useMemo, useRef, useEffect } from 'react'; import React, { useState, useMemo, useRef } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { AreaChart, Area, BarChart, Bar, LineChart, Line, ComposedChart, PieChart, Pie, Cell, Sector, Label, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts'; import { AreaChart, Area, BarChart, Bar, LineChart, Line, ComposedChart, PieChart, Pie, Cell, Sector, Label, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts';
import { TrendingUp, TrendingDown, ShoppingCart, DollarSign, Package, Users, AlertTriangle, ArrowUpRight } from 'lucide-react'; import { TrendingUp, TrendingDown, ShoppingCart, DollarSign, Package, Users, AlertTriangle, ArrowUpRight } from 'lucide-react';
@@ -12,7 +12,6 @@ import { useOverviewAnalytics } from '@/hooks/useAnalytics';
import { ErrorCard } from '@/components/ErrorCard'; import { ErrorCard } from '@/components/ErrorCard';
import { getPageLoadErrorMessage } from '@/lib/errorHandling'; import { getPageLoadErrorMessage } from '@/lib/errorHandling';
import { useFABConfig } from '@/hooks/useFABConfig'; import { useFABConfig } from '@/hooks/useFABConfig';
import { usePageHeader } from '@/contexts/PageHeaderContext';
// Dummy data for visualization // Dummy data for visualization
const DUMMY_DATA = { const DUMMY_DATA = {
@@ -161,19 +160,12 @@ function MetricCard({ title, value, change, icon: Icon, format = 'number', perio
export default function Dashboard() { export default function Dashboard() {
useFABConfig('dashboard'); // Add FAB for quick actions useFABConfig('dashboard'); // Add FAB for quick actions
const { setPageHeader, clearPageHeader } = usePageHeader();
const { period } = useDashboardPeriod(); const { period } = useDashboardPeriod();
const store = getStoreCurrency(); const store = getStoreCurrency();
const [hoverIndex, setHoverIndex] = useState<number | undefined>(undefined); const [hoverIndex, setHoverIndex] = useState<number | undefined>(undefined);
const [chartMetric, setChartMetric] = useState<'both' | 'revenue' | 'orders'>('both'); const [chartMetric, setChartMetric] = useState<'both' | 'revenue' | 'orders'>('both');
const chartRef = useRef<any>(null); const chartRef = useRef<any>(null);
// Set contextual header
useEffect(() => {
setPageHeader(__('Dashboard'));
return () => clearPageHeader();
}, [setPageHeader, clearPageHeader]);
// Fetch real data or use dummy data based on toggle // Fetch real data or use dummy data based on toggle
const { data, isLoading, error, refetch } = useOverviewAnalytics(DUMMY_DATA); const { data, isLoading, error, refetch } = useOverviewAnalytics(DUMMY_DATA);

View File

@@ -35,7 +35,8 @@ export function SettingsLayout({
} }
}; };
// Set page header when component mounts // Set page header ONLY when there's an action (Save button or custom action)
// This saves vertical space on pages without actions
useEffect(() => { useEffect(() => {
if (onSave) { if (onSave) {
setPageHeader( setPageHeader(
@@ -59,8 +60,8 @@ export function SettingsLayout({
// If there's a custom action, use it // If there's a custom action, use it
setPageHeader(title, action); setPageHeader(title, action);
} else { } else {
// Always set the title, even without action // No action = no header (save vertical space)
setPageHeader(title); clearPageHeader();
} }
return () => clearPageHeader(); return () => clearPageHeader();