fix: Add comprehensive null guards to all dashboard components
This commit fixes React Error #306 (text content mismatch) by ensuring no component ever tries to render undefined values as text content. Changes made across all dashboard components: 1. **Component Entry Guards**: - Added early returns for null/undefined data - Changed `if (loading)` to `if (loading || !data)` pattern - Components now show loading skeleton when data is undefined 2. **Property Access Guards**: - Added `|| fallback` operators for all rendered text - Added optional chaining for nested property access - Added conditional rendering for optional/array fields 3. **Specific Component Fixes**: **HealthStatusCard.tsx**: - Added early return for !healthStatus - Added fallback for status: `healthStatus.status || 'green'` - Added conditional rendering for nextScheduledRun - Added conditional rendering for checklistItems array - Added text fallback: `item.text || ''` **InsightsGrid.tsx**: - Added null check: `if (!insights || !insights.savings || ...)` - Added fallbacks: `insights.savings?.label || 'Savings'` - Added fallbacks for all 4 insight cards **OrchestrationSummaryCard.tsx**: - Added early return for !summary - Added fallback: `summary.message || ''` - Added fallback: `summary.runNumber || 0` - Added array checks: `summary.purchaseOrdersSummary && ...` - Added fallbacks for PO items: `po.supplierName || 'Unknown Supplier'` - Added fallbacks for batch items: `batch.quantity || 0` - Added safe date parsing: `batch.readyByTime ? new Date(...) : 'TBD'` **ProductionTimelineCard.tsx**: - Added early return for !timeline - Added array check: `!timeline.timeline || timeline.timeline.length === 0` - Added fallbacks for all item properties: - `item.statusIcon || '🔵'` - `item.productName || 'Product'` - `item.quantity || 0` - `item.unit || 'units'` - `item.batchNumber || 'N/A'` - `item.priority || 'NORMAL'` - `item.statusText || 'Status'` - `item.progress || 0` **ActionQueueCard.tsx**: - Added early return for !actionQueue - Added safe config lookup with fallback to normal urgency - Added array check: `!actionQueue.actions || ...` - Added property fallbacks: - `action.title || 'Action Required'` - `action.urgency || 'normal'` - `action.subtitle || ''` - `action.estimatedTimeMinutes || 5` - Added array guard: `(action.actions || []).map(...)` - Added fallbacks for counts: `actionQueue.totalActions || 0` **useReasoningTranslation.ts**: - Enhanced formatPOAction with explicit fallbacks - Enhanced formatBatchAction with explicit fallbacks - All translation functions return strings with || operators **DashboardPage.tsx**: - Removed conditional rendering operators (&&) - Components now always render and handle their own null states - Pattern: `<Component data={data} loading={loading} />` 4. **Testing Strategy**: - All components gracefully handle undefined data - Loading states shown when data is undefined - No undefined values can be rendered as text content - Multiple fallback layers ensure strings are always returned This comprehensive fix addresses the root cause of React Error #306 by making all components bulletproof against undefined values.
This commit is contained in:
@@ -35,7 +35,7 @@ export function OrchestrationSummaryCard({ summary, loading }: OrchestrationSumm
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const { t } = useTranslation('reasoning');
|
||||
|
||||
if (loading) {
|
||||
if (loading || !summary) {
|
||||
return (
|
||||
<div className="bg-white rounded-xl shadow-md p-6">
|
||||
<div className="animate-pulse space-y-4">
|
||||
@@ -62,7 +62,7 @@ export function OrchestrationSummaryCard({ summary, loading }: OrchestrationSumm
|
||||
<h3 className="text-lg font-bold text-blue-900 mb-2">
|
||||
{t('jtbd.orchestration_summary.ready_to_plan')}
|
||||
</h3>
|
||||
<p className="text-blue-700 mb-4">{summary.message}</p>
|
||||
<p className="text-blue-700 mb-4">{summary.message || ''}</p>
|
||||
<button className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-semibold transition-colors duration-200">
|
||||
{t('jtbd.orchestration_summary.run_planning')}
|
||||
</button>
|
||||
@@ -90,7 +90,7 @@ export function OrchestrationSummaryCard({ summary, loading }: OrchestrationSumm
|
||||
<div className="flex items-center gap-2 text-sm text-gray-600">
|
||||
<Clock className="w-4 h-4" />
|
||||
<span>
|
||||
{t('jtbd.orchestration_summary.run_info', { runNumber: summary.runNumber })} • {runTime}
|
||||
{t('jtbd.orchestration_summary.run_info', { runNumber: summary.runNumber || 0 })} • {runTime}
|
||||
</span>
|
||||
{summary.durationSeconds && (
|
||||
<span className="text-gray-400">
|
||||
@@ -111,16 +111,16 @@ export function OrchestrationSummaryCard({ summary, loading }: OrchestrationSumm
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{summary.purchaseOrdersSummary.length > 0 && (
|
||||
{summary.purchaseOrdersSummary && summary.purchaseOrdersSummary.length > 0 && (
|
||||
<ul className="space-y-2 ml-8">
|
||||
{summary.purchaseOrdersSummary.map((po, index) => (
|
||||
<li key={index} className="text-sm text-gray-700">
|
||||
<span className="font-medium">{po.supplierName}</span>
|
||||
<span className="font-medium">{po.supplierName || 'Unknown Supplier'}</span>
|
||||
{' • '}
|
||||
{po.itemCategories.slice(0, 2).join(', ')}
|
||||
{po.itemCategories.length > 2 && ` +${po.itemCategories.length - 2} more`}
|
||||
{(po.itemCategories || []).slice(0, 2).join(', ') || 'Items'}
|
||||
{(po.itemCategories || []).length > 2 && ` +${po.itemCategories.length - 2} more`}
|
||||
{' • '}
|
||||
<span className="font-semibold">€{po.totalAmount.toFixed(2)}</span>
|
||||
<span className="font-semibold">€{(po.totalAmount || 0).toFixed(2)}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
@@ -140,25 +140,25 @@ export function OrchestrationSummaryCard({ summary, loading }: OrchestrationSumm
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{summary.productionBatchesSummary.length > 0 && (
|
||||
{summary.productionBatchesSummary && summary.productionBatchesSummary.length > 0 && (
|
||||
<ul className="space-y-2 ml-8">
|
||||
{summary.productionBatchesSummary.slice(0, expanded ? undefined : 3).map((batch, index) => (
|
||||
<li key={index} className="text-sm text-gray-700">
|
||||
<span className="font-semibold">{batch.quantity}</span> {batch.productName}
|
||||
<span className="font-semibold">{batch.quantity || 0}</span> {batch.productName || 'Product'}
|
||||
{' • '}
|
||||
<span className="text-gray-500">
|
||||
ready by {new Date(batch.readyByTime).toLocaleTimeString('en-US', {
|
||||
ready by {batch.readyByTime ? new Date(batch.readyByTime).toLocaleTimeString('en-US', {
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
hour12: true,
|
||||
})}
|
||||
}) : 'TBD'}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{summary.productionBatchesSummary.length > 3 && (
|
||||
{summary.productionBatchesSummary && summary.productionBatchesSummary.length > 3 && (
|
||||
<button
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
className="ml-8 mt-2 flex items-center gap-1 text-sm text-purple-600 hover:text-purple-800 font-medium"
|
||||
|
||||
Reference in New Issue
Block a user