fix(admin): Consistent navigation across Intelligence pages

- Add state selector dropdown to all three Intelligence pages (Brands, Stores, Pricing)
- Use consistent emerald-styled page navigation badges with current page highlighted
- Remove Refresh buttons from all Intelligence pages
- Update chart styling to use emerald gradient bars (matching Pricing page)
- Load all available states from orchestrator API instead of extracting from local data
- Fix z-index and styling on state dropdown for better visibility

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Kelly
2025-12-10 23:32:57 -07:00
parent c091d2316b
commit 5b34b5a78c
3 changed files with 161 additions and 127 deletions

View File

@@ -9,7 +9,6 @@ import {
MapPin, MapPin,
Package, Package,
DollarSign, DollarSign,
RefreshCw,
Search, Search,
TrendingUp, TrendingUp,
BarChart3, BarChart3,
@@ -100,37 +99,62 @@ export function IntelligenceBrands() {
<Layout> <Layout>
<div className="space-y-6"> <div className="space-y-6">
{/* Header */} {/* Header */}
<div className="flex items-center justify-between"> <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div> <div>
<h1 className="text-2xl font-bold text-gray-900">Brands Intelligence</h1> <h1 className="text-2xl font-bold text-gray-900">Brands Intelligence</h1>
<p className="text-sm text-gray-600 mt-1"> <p className="text-sm text-gray-600 mt-1">
Brand penetration and pricing analytics across markets Brand penetration and pricing analytics across markets
</p> </p>
</div> </div>
<div className="flex gap-2"> <div className="flex flex-wrap gap-2 items-center">
{/* State Selector */}
<div className="dropdown dropdown-end">
<button tabIndex={0} className="btn btn-sm gap-2 bg-emerald-50 border-emerald-200 hover:bg-emerald-100">
{stateLabel}
<ChevronDown className="w-4 h-4" />
</button>
<ul tabIndex={0} className="dropdown-content z-50 menu p-2 shadow-lg bg-white rounded-box w-44 max-h-60 overflow-y-auto border border-gray-200">
<li>
<a onClick={() => setSelectedState(null)} className={isAllStates ? 'active bg-emerald-100' : ''}>
All States
</a>
</li>
<div className="divider my-1"></div>
{availableStates.map((state) => (
<li key={state}>
<a onClick={() => setSelectedState(state)} className={selectedState === state ? 'active bg-emerald-100' : ''}>
{state}
</a>
</li>
))}
</ul>
</div>
{/* Page Navigation */}
<div className="flex gap-1">
<button <button
onClick={() => navigate('/admin/intelligence/pricing')} className="btn btn-sm gap-1 bg-emerald-600 text-white hover:bg-emerald-700 border-emerald-600"
className="btn btn-sm btn-outline gap-1"
> >
<DollarSign className="w-4 h-4" /> <Building2 className="w-4 h-4" />
Pricing <span>Brands</span>
</button> </button>
<button <button
onClick={() => navigate('/admin/intelligence/stores')} onClick={() => navigate('/admin/intelligence/stores')}
className="btn btn-sm btn-outline gap-1" className="btn btn-sm btn-outline gap-1"
> >
<MapPin className="w-4 h-4" /> <MapPin className="w-4 h-4" />
Stores <span>Stores</span>
</button> </button>
<button <button
onClick={loadBrands} onClick={() => navigate('/admin/intelligence/pricing')}
className="btn btn-sm btn-outline gap-2" className="btn btn-sm btn-outline gap-1"
> >
<RefreshCw className="w-4 h-4" /> <DollarSign className="w-4 h-4" />
Refresh <span>Pricing</span>
</button> </button>
</div> </div>
</div> </div>
</div>
{/* Summary Cards */} {/* Summary Cards */}
<div className="grid grid-cols-4 gap-4"> <div className="grid grid-cols-4 gap-4">
@@ -180,51 +204,32 @@ export function IntelligenceBrands() {
{/* Top Brands Chart */} {/* Top Brands Chart */}
<div className="bg-white rounded-lg border border-gray-200 p-4"> <div className="bg-white rounded-lg border border-gray-200 p-4">
<div className="flex items-center justify-between mb-4"> <h3 className="text-lg font-semibold text-gray-900 flex items-center gap-2 mb-4">
<h3 className="text-lg font-semibold text-gray-900 flex items-center gap-2"> <BarChart3 className="w-5 h-5 text-emerald-500" />
<BarChart3 className="w-5 h-5 text-blue-500" />
Top 10 Brands by Store Count Top 10 Brands by Store Count
</h3> </h3>
<div className="dropdown dropdown-end">
<button tabIndex={0} className="btn btn-sm btn-outline gap-2">
{stateLabel}
<ChevronDown className="w-4 h-4" />
</button>
<ul tabIndex={0} className="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-40 max-h-60 overflow-y-auto">
<li>
<a onClick={() => setSelectedState(null)} className={isAllStates ? 'active' : ''}>
All States
</a>
</li>
<li className="divider"></li>
{availableStates.map((state) => (
<li key={state}>
<a onClick={() => setSelectedState(state)} className={selectedState === state ? 'active' : ''}>
{state}
</a>
</li>
))}
</ul>
</div>
</div>
<div className="space-y-2"> <div className="space-y-2">
{topBrands.map((brand, idx) => ( {topBrands.map((brand) => {
const barWidth = Math.min((brand.storeCount / maxStoreCount) * 100, 100);
return (
<div key={brand.brandName} className="flex items-center gap-3"> <div key={brand.brandName} className="flex items-center gap-3">
<span className="text-sm text-gray-500 w-6">{idx + 1}.</span> <span className="text-sm font-medium w-28 truncate shrink-0" title={brand.brandName}>
<span className="text-sm font-medium w-40 truncate" title={brand.brandName}>
{brand.brandName} {brand.brandName}
</span> </span>
<div className="flex-1 bg-gray-100 rounded-full h-4 relative"> <div className="flex-1 min-w-0">
<div className="bg-gray-100 rounded h-5 overflow-hidden">
<div <div
className="bg-blue-500 rounded-full h-4" className="bg-gradient-to-r from-emerald-400 to-emerald-500 h-5 rounded transition-all"
style={{ width: `${(brand.storeCount / maxStoreCount) * 100}%` }} style={{ width: `${barWidth}%` }}
/> />
</div> </div>
<span className="text-sm text-gray-600 w-16 text-right"> </div>
{brand.storeCount} stores <span className="text-sm font-mono font-semibold text-emerald-600 w-16 text-right shrink-0">
{brand.storeCount}
</span> </span>
</div> </div>
))} );
})}
</div> </div>
</div> </div>

View File

@@ -8,7 +8,6 @@ import {
Building2, Building2,
MapPin, MapPin,
Package, Package,
RefreshCw,
TrendingUp, TrendingUp,
TrendingDown, TrendingDown,
BarChart3, BarChart3,
@@ -87,58 +86,62 @@ export function IntelligencePricing() {
<Layout> <Layout>
<div className="space-y-6"> <div className="space-y-6">
{/* Header */} {/* Header */}
<div className="flex items-center justify-between"> <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div> <div>
<h1 className="text-2xl font-bold text-gray-900">Pricing Intelligence</h1> <h1 className="text-2xl font-bold text-gray-900">Pricing Intelligence</h1>
<p className="text-sm text-gray-600 mt-1"> <p className="text-sm text-gray-600 mt-1">
Price distribution and trends by category Price distribution and trends by category
</p> </p>
</div> </div>
<div className="flex gap-2"> <div className="flex flex-wrap gap-2 items-center">
{/* State Selector */}
<div className="dropdown dropdown-end"> <div className="dropdown dropdown-end">
<button tabIndex={0} className="btn btn-sm btn-outline gap-2"> <button tabIndex={0} className="btn btn-sm gap-2 bg-emerald-50 border-emerald-200 hover:bg-emerald-100">
{stateLabel} {stateLabel}
<ChevronDown className="w-4 h-4" /> <ChevronDown className="w-4 h-4" />
</button> </button>
<ul tabIndex={0} className="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-40 max-h-60 overflow-y-auto"> <ul tabIndex={0} className="dropdown-content z-50 menu p-2 shadow-lg bg-white rounded-box w-44 max-h-60 overflow-y-auto border border-gray-200">
<li> <li>
<a onClick={() => setSelectedState(null)} className={isAllStates ? 'active' : ''}> <a onClick={() => setSelectedState(null)} className={isAllStates ? 'active bg-emerald-100' : ''}>
All States All States
</a> </a>
</li> </li>
<li className="divider"></li> <div className="divider my-1"></div>
{availableStates.map((state) => ( {availableStates.map((state) => (
<li key={state}> <li key={state}>
<a onClick={() => setSelectedState(state)} className={selectedState === state ? 'active' : ''}> <a onClick={() => setSelectedState(state)} className={selectedState === state ? 'active bg-emerald-100' : ''}>
{state} {state}
</a> </a>
</li> </li>
))} ))}
</ul> </ul>
</div> </div>
{/* Page Navigation */}
<div className="flex gap-1">
<button <button
onClick={() => navigate('/admin/intelligence/brands')} onClick={() => navigate('/admin/intelligence/brands')}
className="btn btn-sm btn-outline gap-1" className="btn btn-sm btn-outline gap-1"
> >
<Building2 className="w-4 h-4" /> <Building2 className="w-4 h-4" />
Brands <span>Brands</span>
</button> </button>
<button <button
onClick={() => navigate('/admin/intelligence/stores')} onClick={() => navigate('/admin/intelligence/stores')}
className="btn btn-sm btn-outline gap-1" className="btn btn-sm btn-outline gap-1"
> >
<MapPin className="w-4 h-4" /> <MapPin className="w-4 h-4" />
Stores <span>Stores</span>
</button> </button>
<button <button
onClick={loadPricing} className="btn btn-sm gap-1 bg-emerald-600 text-white hover:bg-emerald-700 border-emerald-600"
className="btn btn-sm btn-outline gap-2"
> >
<RefreshCw className="w-4 h-4" /> <DollarSign className="w-4 h-4" />
Refresh <span>Pricing</span>
</button> </button>
</div> </div>
</div> </div>
</div>
{/* Overall Stats */} {/* Overall Stats */}
{overall && ( {overall && (

View File

@@ -8,7 +8,6 @@ import {
Building2, Building2,
DollarSign, DollarSign,
Package, Package,
RefreshCw,
Search, Search,
Clock, Clock,
Activity, Activity,
@@ -34,12 +33,19 @@ export function IntelligenceStores() {
const [stores, setStores] = useState<StoreActivity[]>([]); const [stores, setStores] = useState<StoreActivity[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const [localStates, setLocalStates] = useState<string[]>([]); const [availableStates, setAvailableStates] = useState<string[]>([]);
useEffect(() => { useEffect(() => {
loadStores(); loadStores();
}, [selectedState]); }, [selectedState]);
useEffect(() => {
// Load available states from orchestrator API
api.getOrchestratorStates().then(data => {
setAvailableStates(data.states?.map((s: any) => s.state) || []);
}).catch(console.error);
}, []);
const loadStores = async () => { const loadStores = async () => {
try { try {
setLoading(true); setLoading(true);
@@ -47,12 +53,7 @@ export function IntelligenceStores() {
state: stateParam, state: stateParam,
limit: 500, limit: 500,
}); });
const storeList = data.stores || []; setStores(data.stores || []);
setStores(storeList);
// Extract unique states from response for dropdown counts
const uniqueStates = [...new Set(storeList.map((s: StoreActivity) => s.state))].filter(Boolean).sort() as string[];
setLocalStates(uniqueStates);
} catch (error) { } catch (error) {
console.error('Failed to load stores:', error); console.error('Failed to load stores:', error);
} finally { } finally {
@@ -110,37 +111,62 @@ export function IntelligenceStores() {
<Layout> <Layout>
<div className="space-y-6"> <div className="space-y-6">
{/* Header */} {/* Header */}
<div className="flex items-center justify-between"> <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div> <div>
<h1 className="text-2xl font-bold text-gray-900">Store Activity</h1> <h1 className="text-2xl font-bold text-gray-900">Store Activity</h1>
<p className="text-sm text-gray-600 mt-1"> <p className="text-sm text-gray-600 mt-1">
Per-store SKU counts, snapshots, and crawl frequency Per-store SKU counts, snapshots, and crawl frequency
</p> </p>
</div> </div>
<div className="flex gap-2"> <div className="flex flex-wrap gap-2 items-center">
{/* State Selector */}
<div className="dropdown dropdown-end">
<button tabIndex={0} className="btn btn-sm gap-2 bg-emerald-50 border-emerald-200 hover:bg-emerald-100">
{stateLabel}
<ChevronDown className="w-4 h-4" />
</button>
<ul tabIndex={0} className="dropdown-content z-50 menu p-2 shadow-lg bg-white rounded-box w-44 max-h-60 overflow-y-auto border border-gray-200">
<li>
<a onClick={() => setSelectedState(null)} className={isAllStates ? 'active bg-emerald-100' : ''}>
All States
</a>
</li>
<div className="divider my-1"></div>
{availableStates.map((state) => (
<li key={state}>
<a onClick={() => setSelectedState(state)} className={selectedState === state ? 'active bg-emerald-100' : ''}>
{state}
</a>
</li>
))}
</ul>
</div>
{/* Page Navigation */}
<div className="flex gap-1">
<button <button
onClick={() => navigate('/admin/intelligence/brands')} onClick={() => navigate('/admin/intelligence/brands')}
className="btn btn-sm btn-outline gap-1" className="btn btn-sm btn-outline gap-1"
> >
<Building2 className="w-4 h-4" /> <Building2 className="w-4 h-4" />
Brands <span>Brands</span>
</button>
<button
className="btn btn-sm gap-1 bg-emerald-600 text-white hover:bg-emerald-700 border-emerald-600"
>
<MapPin className="w-4 h-4" />
<span>Stores</span>
</button> </button>
<button <button
onClick={() => navigate('/admin/intelligence/pricing')} onClick={() => navigate('/admin/intelligence/pricing')}
className="btn btn-sm btn-outline gap-1" className="btn btn-sm btn-outline gap-1"
> >
<DollarSign className="w-4 h-4" /> <DollarSign className="w-4 h-4" />
Pricing <span>Pricing</span>
</button>
<button
onClick={loadStores}
className="btn btn-sm btn-outline gap-2"
>
<RefreshCw className="w-4 h-4" />
Refresh
</button> </button>
</div> </div>
</div> </div>
</div>
{/* Summary Cards - Responsive: 2→4 columns */} {/* Summary Cards - Responsive: 2→4 columns */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4"> <div className="grid grid-cols-2 md:grid-cols-4 gap-4">