header rekated fixes
This commit is contained in:
@@ -1,25 +1,27 @@
|
||||
/**
|
||||
* Standardized Page Header Component
|
||||
* Simplified version - Site/sector selector moved to AppHeader
|
||||
* Just shows: breadcrumb (inline), page title with badge, description
|
||||
* Simplified version - Title shown in AppHeader
|
||||
* Shows: page title with parent badge, description
|
||||
*/
|
||||
import React, { ReactNode, useEffect, useRef } from 'react';
|
||||
import React, { ReactNode, useEffect, useRef, useMemo } from 'react';
|
||||
import { useSiteStore } from '../../store/siteStore';
|
||||
import { useSectorStore } from '../../store/sectorStore';
|
||||
import { trackLoading } from './LoadingStateMonitor';
|
||||
import { useErrorHandler } from '../../hooks/useErrorHandler';
|
||||
import { usePageContext } from '../../context/PageContext';
|
||||
|
||||
interface PageHeaderProps {
|
||||
title: string;
|
||||
description?: string;
|
||||
breadcrumb?: string;
|
||||
parent?: string; // Parent module name (e.g., "Planner", "Writer")
|
||||
breadcrumb?: string; // Deprecated - use parent instead
|
||||
lastUpdated?: Date;
|
||||
showRefresh?: boolean;
|
||||
onRefresh?: () => void;
|
||||
className?: string;
|
||||
badge?: {
|
||||
icon: ReactNode;
|
||||
color: 'blue' | 'green' | 'purple' | 'orange' | 'red' | 'indigo';
|
||||
color: 'blue' | 'green' | 'purple' | 'orange' | 'red' | 'indigo' | 'yellow' | 'pink' | 'emerald' | 'cyan' | 'amber' | 'teal';
|
||||
};
|
||||
hideSiteSector?: boolean;
|
||||
navigation?: ReactNode; // Kept for backwards compat but not rendered
|
||||
@@ -31,7 +33,8 @@ interface PageHeaderProps {
|
||||
export default function PageHeader({
|
||||
title,
|
||||
description,
|
||||
breadcrumb,
|
||||
parent,
|
||||
breadcrumb, // Deprecated alias for parent
|
||||
lastUpdated,
|
||||
showRefresh = false,
|
||||
onRefresh,
|
||||
@@ -43,8 +46,19 @@ export default function PageHeader({
|
||||
const { activeSite } = useSiteStore();
|
||||
const { loadSectorsForSite } = useSectorStore();
|
||||
const { addError } = useErrorHandler('PageHeader');
|
||||
const { setPageInfo } = usePageContext();
|
||||
const lastSiteId = useRef<number | null>(null);
|
||||
const isLoadingSector = useRef(false);
|
||||
|
||||
// Resolve parent from either prop
|
||||
const parentModule = parent || breadcrumb;
|
||||
|
||||
// Update page context with title and badge info for AppHeader
|
||||
const pageInfoKey = useMemo(() => `${title}|${parentModule}`, [title, parentModule]);
|
||||
useEffect(() => {
|
||||
setPageInfo({ title, parent: parentModule, badge });
|
||||
return () => setPageInfo(null);
|
||||
}, [pageInfoKey, badge?.color]);
|
||||
|
||||
// Load sectors when active site changes
|
||||
useEffect(() => {
|
||||
@@ -83,56 +97,27 @@ export default function PageHeader({
|
||||
}, [activeSite?.id, activeSite?.is_active, hideSiteSector, loadSectorsForSite, addError]);
|
||||
|
||||
const badgeColors = {
|
||||
blue: 'bg-blue-600 dark:bg-blue-500',
|
||||
green: 'bg-green-600 dark:bg-green-500',
|
||||
purple: 'bg-purple-600 dark:bg-purple-500',
|
||||
orange: 'bg-orange-600 dark:bg-orange-500',
|
||||
red: 'bg-red-600 dark:bg-red-500',
|
||||
indigo: 'bg-indigo-600 dark:bg-indigo-500',
|
||||
blue: { bg: 'bg-blue-600 dark:bg-blue-500', light: 'bg-blue-100 text-blue-700 dark:bg-blue-500/20 dark:text-blue-300' },
|
||||
green: { bg: 'bg-green-600 dark:bg-green-500', light: 'bg-green-100 text-green-700 dark:bg-green-500/20 dark:text-green-300' },
|
||||
purple: { bg: 'bg-purple-600 dark:bg-purple-500', light: 'bg-purple-100 text-purple-700 dark:bg-purple-500/20 dark:text-purple-300' },
|
||||
orange: { bg: 'bg-orange-600 dark:bg-orange-500', light: 'bg-orange-100 text-orange-700 dark:bg-orange-500/20 dark:text-orange-300' },
|
||||
red: { bg: 'bg-red-600 dark:bg-red-500', light: 'bg-red-100 text-red-700 dark:bg-red-500/20 dark:text-red-300' },
|
||||
indigo: { bg: 'bg-indigo-600 dark:bg-indigo-500', light: 'bg-indigo-100 text-indigo-700 dark:bg-indigo-500/20 dark:text-indigo-300' },
|
||||
yellow: { bg: 'bg-yellow-600 dark:bg-yellow-500', light: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-500/20 dark:text-yellow-300' },
|
||||
pink: { bg: 'bg-pink-600 dark:bg-pink-500', light: 'bg-pink-100 text-pink-700 dark:bg-pink-500/20 dark:text-pink-300' },
|
||||
emerald: { bg: 'bg-emerald-600 dark:bg-emerald-500', light: 'bg-emerald-100 text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-300' },
|
||||
cyan: { bg: 'bg-cyan-600 dark:bg-cyan-500', light: 'bg-cyan-100 text-cyan-700 dark:bg-cyan-500/20 dark:text-cyan-300' },
|
||||
amber: { bg: 'bg-amber-600 dark:bg-amber-500', light: 'bg-amber-100 text-amber-700 dark:bg-amber-500/20 dark:text-amber-300' },
|
||||
teal: { bg: 'bg-teal-600 dark:bg-teal-500', light: 'bg-teal-100 text-teal-700 dark:bg-teal-500/20 dark:text-teal-300' },
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`flex items-center justify-between gap-4 ${className}`}>
|
||||
{/* Left: Breadcrumb + Badge + Title */}
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
{breadcrumb && (
|
||||
<>
|
||||
<span className="text-sm text-gray-400 dark:text-gray-500 whitespace-nowrap">{breadcrumb}</span>
|
||||
<span className="text-gray-300 dark:text-gray-600">/</span>
|
||||
</>
|
||||
)}
|
||||
{badge && (
|
||||
<div className={`flex items-center justify-center w-8 h-8 rounded-lg ${badgeColors[badge.color]} flex-shrink-0`}>
|
||||
{badge.icon && typeof badge.icon === 'object' && 'type' in badge.icon
|
||||
? React.cloneElement(badge.icon as React.ReactElement, { className: 'text-white size-4' })
|
||||
: badge.icon}
|
||||
</div>
|
||||
)}
|
||||
<div className="min-w-0">
|
||||
<h1 className="text-xl font-semibold text-gray-800 dark:text-white truncate">{title}</h1>
|
||||
{description && (
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 truncate">{description}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right: Actions */}
|
||||
<div className="flex items-center gap-3 flex-shrink-0">
|
||||
{lastUpdated && (
|
||||
<span className="text-xs text-gray-400 dark:text-gray-500 hidden sm:block">
|
||||
Updated {lastUpdated.toLocaleTimeString()}
|
||||
</span>
|
||||
)}
|
||||
{showRefresh && onRefresh && (
|
||||
<button
|
||||
onClick={onRefresh}
|
||||
className="px-3 py-1.5 text-sm font-medium text-brand-500 hover:text-brand-600 border border-brand-200 rounded-lg hover:bg-brand-50 dark:border-brand-800 dark:hover:bg-brand-500/10 transition-colors"
|
||||
>
|
||||
Refresh
|
||||
</button>
|
||||
)}
|
||||
{actions}
|
||||
</div>
|
||||
<div className={`${className}`}>
|
||||
{/* Title now shown in AppHeader - this component only triggers the context update */}
|
||||
{/* Show description if provided - can be used for additional context */}
|
||||
{description && (
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mb-2">{description}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user