header rekated fixes

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-27 05:33:05 +00:00
parent fd6e7eb2dd
commit 726d945bda
15 changed files with 414 additions and 232 deletions

View File

@@ -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>
);
}