Files
bakery-ia/frontend/src/pages/app/DashboardPage.tsx
Claude 449c231af0 fix: Update dashboard to match backend structure and support dark mode
Frontend changes:
- Updated API endpoints to call /tenants/{id}/dashboard/*
  (removed /orchestrator/ prefix to match backend router)
- Updated DashboardPage to use CSS theme variables instead of hardcoded colors
- Replaced bg-white, text-gray-*, bg-blue-* with var(--bg-primary), var(--text-primary), etc.
- Quick action buttons now use color accents with left border for visual distinction

This ensures:
1. Frontend calls the correct backend endpoints (fixes 404 errors)
2. Dashboard respects theme (light/dark mode)
3. Consistent styling with rest of application
2025-11-07 21:44:24 +00:00

246 lines
9.1 KiB
TypeScript

// ================================================================
// frontend/src/pages/app/NewDashboardPage.tsx
// ================================================================
/**
* JTBD-Aligned Dashboard Page
*
* Complete redesign based on Jobs To Be Done methodology.
* Focused on answering: "What requires my attention right now?"
*
* Key principles:
* - Automation-first (show what system did)
* - Action-oriented (prioritize tasks)
* - Progressive disclosure (show 20% that matters 80%)
* - Mobile-first (one-handed operation)
* - Trust-building (explain system reasoning)
*/
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { RefreshCw, ExternalLink } from 'lucide-react';
import { useTenant } from '../../stores/tenant.store';
import {
useBakeryHealthStatus,
useOrchestrationSummary,
useActionQueue,
useProductionTimeline,
useInsights,
useApprovePurchaseOrder,
useStartProductionBatch,
usePauseProductionBatch,
} from '../../api/hooks/newDashboard';
import { HealthStatusCard } from '../../components/dashboard/HealthStatusCard';
import { ActionQueueCard } from '../../components/dashboard/ActionQueueCard';
import { OrchestrationSummaryCard } from '../../components/dashboard/OrchestrationSummaryCard';
import { ProductionTimelineCard } from '../../components/dashboard/ProductionTimelineCard';
import { InsightsGrid } from '../../components/dashboard/InsightsGrid';
export function NewDashboardPage() {
const navigate = useNavigate();
const { currentTenant } = useTenant();
const tenantId = currentTenant?.id || '';
// Data fetching
const {
data: healthStatus,
isLoading: healthLoading,
refetch: refetchHealth,
} = useBakeryHealthStatus(tenantId);
const {
data: orchestrationSummary,
isLoading: orchestrationLoading,
refetch: refetchOrchestration,
} = useOrchestrationSummary(tenantId);
const {
data: actionQueue,
isLoading: actionQueueLoading,
refetch: refetchActionQueue,
} = useActionQueue(tenantId);
const {
data: productionTimeline,
isLoading: timelineLoading,
refetch: refetchTimeline,
} = useProductionTimeline(tenantId);
const {
data: insights,
isLoading: insightsLoading,
refetch: refetchInsights,
} = useInsights(tenantId);
// Mutations
const approvePO = useApprovePurchaseOrder();
const startBatch = useStartProductionBatch();
const pauseBatch = usePauseProductionBatch();
// Handlers
const handleApprove = async (actionId: string) => {
try {
await approvePO.mutateAsync({ tenantId, poId: actionId });
// Refetch to update UI
refetchActionQueue();
refetchHealth();
} catch (error) {
console.error('Error approving PO:', error);
}
};
const handleViewDetails = (actionId: string) => {
// Navigate to appropriate detail page based on action type
navigate(`/app/operations/procurement`);
};
const handleModify = (actionId: string) => {
navigate(`/app/operations/procurement`);
};
const handleStartBatch = async (batchId: string) => {
try {
await startBatch.mutateAsync({ tenantId, batchId });
refetchTimeline();
refetchHealth();
} catch (error) {
console.error('Error starting batch:', error);
}
};
const handlePauseBatch = async (batchId: string) => {
try {
await pauseBatch.mutateAsync({ tenantId, batchId });
refetchTimeline();
refetchHealth();
} catch (error) {
console.error('Error pausing batch:', error);
}
};
const handleRefreshAll = () => {
refetchHealth();
refetchOrchestration();
refetchActionQueue();
refetchTimeline();
refetchInsights();
};
return (
<div className="min-h-screen pb-20 md:pb-8" style={{ backgroundColor: 'var(--bg-secondary)' }}>
{/* Mobile-optimized container */}
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<div>
<h1 className="text-3xl md:text-4xl font-bold" style={{ color: 'var(--text-primary)' }}>Panel de Control</h1>
<p className="mt-1" style={{ color: 'var(--text-secondary)' }}>Your bakery at a glance</p>
</div>
<button
onClick={handleRefreshAll}
className="flex items-center gap-2 px-4 py-2 rounded-lg font-semibold transition-colors duration-200"
style={{
backgroundColor: 'var(--bg-primary)',
borderColor: 'var(--border-primary)',
border: '1px solid',
color: 'var(--text-secondary)'
}}
>
<RefreshCw className="w-5 h-5" />
<span className="hidden sm:inline">Refresh</span>
</button>
</div>
{/* Main Dashboard Layout */}
<div className="space-y-6">
{/* SECTION 1: Bakery Health Status */}
{healthStatus && <HealthStatusCard healthStatus={healthStatus} loading={healthLoading} />}
{/* SECTION 2: What Needs Your Attention (Action Queue) */}
{actionQueue && (
<ActionQueueCard
actionQueue={actionQueue}
loading={actionQueueLoading}
onApprove={handleApprove}
onViewDetails={handleViewDetails}
onModify={handleModify}
/>
)}
{/* SECTION 3: What the System Did for You (Orchestration Summary) */}
{orchestrationSummary && (
<OrchestrationSummaryCard
summary={orchestrationSummary}
loading={orchestrationLoading}
/>
)}
{/* SECTION 4: Today's Production Timeline */}
{productionTimeline && (
<ProductionTimelineCard
timeline={productionTimeline}
loading={timelineLoading}
onStart={handleStartBatch}
onPause={handlePauseBatch}
/>
)}
{/* SECTION 5: Quick Insights Grid */}
{insights && (
<div>
<h2 className="text-2xl font-bold mb-4" style={{ color: 'var(--text-primary)' }}>Key Metrics</h2>
<InsightsGrid insights={insights} loading={insightsLoading} />
</div>
)}
{/* SECTION 6: Quick Action Links */}
<div className="rounded-xl shadow-md p-6" style={{ backgroundColor: 'var(--bg-primary)' }}>
<h2 className="text-xl font-bold mb-4" style={{ color: 'var(--text-primary)' }}>Quick Actions</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<button
onClick={() => navigate('/app/operations/procurement')}
className="flex items-center justify-between p-4 rounded-lg transition-colors duration-200 group"
style={{ backgroundColor: 'var(--bg-tertiary)', borderLeft: '4px solid var(--color-info)' }}
>
<span className="font-semibold" style={{ color: 'var(--text-primary)' }}>View Orders</span>
<ExternalLink className="w-5 h-5 group-hover:translate-x-1 transition-transform duration-200" style={{ color: 'var(--color-info)' }} />
</button>
<button
onClick={() => navigate('/app/operations/production')}
className="flex items-center justify-between p-4 rounded-lg transition-colors duration-200 group"
style={{ backgroundColor: 'var(--bg-tertiary)', borderLeft: '4px solid var(--color-success)' }}
>
<span className="font-semibold" style={{ color: 'var(--text-primary)' }}>Production</span>
<ExternalLink className="w-5 h-5 group-hover:translate-x-1 transition-transform duration-200" style={{ color: 'var(--color-success)' }} />
</button>
<button
onClick={() => navigate('/app/database/inventory')}
className="flex items-center justify-between p-4 rounded-lg transition-colors duration-200 group"
style={{ backgroundColor: 'var(--bg-tertiary)', borderLeft: '4px solid var(--color-secondary)' }}
>
<span className="font-semibold" style={{ color: 'var(--text-primary)' }}>Inventory</span>
<ExternalLink className="w-5 h-5 group-hover:translate-x-1 transition-transform duration-200" style={{ color: 'var(--color-secondary)' }} />
</button>
<button
onClick={() => navigate('/app/database/suppliers')}
className="flex items-center justify-between p-4 rounded-lg transition-colors duration-200 group"
style={{ backgroundColor: 'var(--bg-tertiary)', borderLeft: '4px solid var(--color-warning)' }}
>
<span className="font-semibold" style={{ color: 'var(--text-primary)' }}>Suppliers</span>
<ExternalLink className="w-5 h-5 group-hover:translate-x-1 transition-transform duration-200" style={{ color: 'var(--color-warning)' }} />
</button>
</div>
</div>
</div>
</div>
{/* Mobile-friendly bottom padding */}
<div className="h-20 md:hidden"></div>
</div>
);
}
export default NewDashboardPage;