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,35 +99,60 @@ 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">
<button {/* State Selector */}
onClick={() => navigate('/admin/intelligence/pricing')} <div className="dropdown dropdown-end">
className="btn btn-sm btn-outline gap-1" <button tabIndex={0} className="btn btn-sm gap-2 bg-emerald-50 border-emerald-200 hover:bg-emerald-100">
> {stateLabel}
<DollarSign className="w-4 h-4" /> <ChevronDown className="w-4 h-4" />
Pricing </button>
</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">
<button <li>
onClick={() => navigate('/admin/intelligence/stores')} <a onClick={() => setSelectedState(null)} className={isAllStates ? 'active bg-emerald-100' : ''}>
className="btn btn-sm btn-outline gap-1" All States
> </a>
<MapPin className="w-4 h-4" /> </li>
Stores <div className="divider my-1"></div>
</button> {availableStates.map((state) => (
<button <li key={state}>
onClick={loadBrands} <a onClick={() => setSelectedState(state)} className={selectedState === state ? 'active bg-emerald-100' : ''}>
className="btn btn-sm btn-outline gap-2" {state}
> </a>
<RefreshCw className="w-4 h-4" /> </li>
Refresh ))}
</button> </ul>
</div>
{/* Page Navigation */}
<div className="flex gap-1">
<button
className="btn btn-sm gap-1 bg-emerald-600 text-white hover:bg-emerald-700 border-emerald-600"
>
<Building2 className="w-4 h-4" />
<span>Brands</span>
</button>
<button
onClick={() => navigate('/admin/intelligence/stores')}
className="btn btn-sm btn-outline gap-1"
>
<MapPin className="w-4 h-4" />
<span>Stores</span>
</button>
<button
onClick={() => navigate('/admin/intelligence/pricing')}
className="btn btn-sm btn-outline gap-1"
>
<DollarSign className="w-4 h-4" />
<span>Pricing</span>
</button>
</div>
</div> </div>
</div> </div>
@@ -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) => {
<div key={brand.brandName} className="flex items-center gap-3"> const barWidth = Math.min((brand.storeCount / maxStoreCount) * 100, 100);
<span className="text-sm text-gray-500 w-6">{idx + 1}.</span> return (
<span className="text-sm font-medium w-40 truncate" title={brand.brandName}> <div key={brand.brandName} className="flex items-center gap-3">
{brand.brandName} <span className="text-sm font-medium w-28 truncate shrink-0" title={brand.brandName}>
</span> {brand.brandName}
<div className="flex-1 bg-gray-100 rounded-full h-4 relative"> </span>
<div <div className="flex-1 min-w-0">
className="bg-blue-500 rounded-full h-4" <div className="bg-gray-100 rounded h-5 overflow-hidden">
style={{ width: `${(brand.storeCount / maxStoreCount) * 100}%` }} <div
/> className="bg-gradient-to-r from-emerald-400 to-emerald-500 h-5 rounded transition-all"
style={{ width: `${barWidth}%` }}
/>
</div>
</div>
<span className="text-sm font-mono font-semibold text-emerald-600 w-16 text-right shrink-0">
{brand.storeCount}
</span>
</div> </div>
<span className="text-sm text-gray-600 w-16 text-right"> );
{brand.storeCount} stores })}
</span>
</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,56 +86,60 @@ 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>
<button
onClick={() => navigate('/admin/intelligence/brands')} {/* Page Navigation */}
className="btn btn-sm btn-outline gap-1" <div className="flex gap-1">
> <button
<Building2 className="w-4 h-4" /> onClick={() => navigate('/admin/intelligence/brands')}
Brands className="btn btn-sm btn-outline gap-1"
</button> >
<button <Building2 className="w-4 h-4" />
onClick={() => navigate('/admin/intelligence/stores')} <span>Brands</span>
className="btn btn-sm btn-outline gap-1" </button>
> <button
<MapPin className="w-4 h-4" /> onClick={() => navigate('/admin/intelligence/stores')}
Stores className="btn btn-sm btn-outline gap-1"
</button> >
<button <MapPin className="w-4 h-4" />
onClick={loadPricing} <span>Stores</span>
className="btn btn-sm btn-outline gap-2" </button>
> <button
<RefreshCw className="w-4 h-4" /> className="btn btn-sm gap-1 bg-emerald-600 text-white hover:bg-emerald-700 border-emerald-600"
Refresh >
</button> <DollarSign className="w-4 h-4" />
<span>Pricing</span>
</button>
</div>
</div> </div>
</div> </div>

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,35 +111,60 @@ 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">
<button {/* State Selector */}
onClick={() => navigate('/admin/intelligence/brands')} <div className="dropdown dropdown-end">
className="btn btn-sm btn-outline gap-1" <button tabIndex={0} className="btn btn-sm gap-2 bg-emerald-50 border-emerald-200 hover:bg-emerald-100">
> {stateLabel}
<Building2 className="w-4 h-4" /> <ChevronDown className="w-4 h-4" />
Brands </button>
</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">
<button <li>
onClick={() => navigate('/admin/intelligence/pricing')} <a onClick={() => setSelectedState(null)} className={isAllStates ? 'active bg-emerald-100' : ''}>
className="btn btn-sm btn-outline gap-1" All States
> </a>
<DollarSign className="w-4 h-4" /> </li>
Pricing <div className="divider my-1"></div>
</button> {availableStates.map((state) => (
<button <li key={state}>
onClick={loadStores} <a onClick={() => setSelectedState(state)} className={selectedState === state ? 'active bg-emerald-100' : ''}>
className="btn btn-sm btn-outline gap-2" {state}
> </a>
<RefreshCw className="w-4 h-4" /> </li>
Refresh ))}
</button> </ul>
</div>
{/* Page Navigation */}
<div className="flex gap-1">
<button
onClick={() => navigate('/admin/intelligence/brands')}
className="btn btn-sm btn-outline gap-1"
>
<Building2 className="w-4 h-4" />
<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
onClick={() => navigate('/admin/intelligence/pricing')}
className="btn btn-sm btn-outline gap-1"
>
<DollarSign className="w-4 h-4" />
<span>Pricing</span>
</button>
</div>
</div> </div>
</div> </div>