Imporve the UI 4
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { LucideIcon } from 'lucide-react';
|
import { LucideIcon } from 'lucide-react';
|
||||||
import { Card } from '../Card';
|
import { Card } from '../Card';
|
||||||
import { Button } from '../Button';
|
|
||||||
import { ProgressBar } from '../ProgressBar';
|
import { ProgressBar } from '../ProgressBar';
|
||||||
import { statusColors } from '../../../styles/colors';
|
import { statusColors } from '../../../styles/colors';
|
||||||
import { TextOverflowPrevention, overflowClasses, getScreenSize, safeText } from '../../../utils/textUtils';
|
import { TextOverflowPrevention, overflowClasses, getScreenSize, safeText } from '../../../utils/textUtils';
|
||||||
@@ -121,12 +120,12 @@ export const StatusCard: React.FC<StatusCardProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className={`
|
className={`
|
||||||
p-5 sm:p-6 transition-all duration-200 border-l-4 hover:shadow-lg
|
p-3 sm:p-5 lg:p-6 transition-all duration-200 border-l-4 hover:shadow-lg
|
||||||
${hasInteraction ? 'hover:shadow-xl cursor-pointer hover:scale-[1.01]' : ''}
|
${hasInteraction ? 'hover:shadow-xl cursor-pointer hover:scale-[1.01]' : ''}
|
||||||
${statusIndicator.isCritical
|
${statusIndicator.isCritical
|
||||||
? 'ring-2 ring-red-200 shadow-md border-l-6 sm:border-l-8'
|
? 'ring-2 ring-red-200 shadow-md sm:border-l-6 lg:border-l-8'
|
||||||
: statusIndicator.isHighlight
|
: statusIndicator.isHighlight
|
||||||
? 'ring-1 ring-yellow-200 border-l-6'
|
? 'ring-1 ring-yellow-200 sm:border-l-6'
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
${className}
|
${className}
|
||||||
@@ -141,33 +140,33 @@ export const StatusCard: React.FC<StatusCardProps> = ({
|
|||||||
}}
|
}}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
<div className="space-y-4">
|
<div className="space-y-3 sm:space-y-4">
|
||||||
{/* Header with status indicator */}
|
{/* Header with status indicator */}
|
||||||
<div className="flex items-start justify-between gap-4">
|
<div className="flex items-start justify-between gap-2 sm:gap-4">
|
||||||
<div className="flex items-start gap-3 flex-1 min-w-0">
|
<div className="flex items-start gap-2 sm:gap-3 flex-1 min-w-0">
|
||||||
<div
|
<div
|
||||||
className={`flex-shrink-0 p-2.5 sm:p-3 rounded-lg shadow-sm ${
|
className={`flex-shrink-0 p-1.5 sm:p-2.5 lg:p-3 rounded-lg shadow-sm ${
|
||||||
statusIndicator.isCritical ? 'ring-2 ring-white' : ''
|
statusIndicator.isCritical ? 'ring-2 ring-white' : ''
|
||||||
}`}
|
}`}
|
||||||
style={{ backgroundColor: `${statusIndicator.color}20` }}
|
style={{ backgroundColor: `${statusIndicator.color}20` }}
|
||||||
>
|
>
|
||||||
{StatusIcon && (
|
{StatusIcon && (
|
||||||
<StatusIcon
|
<StatusIcon
|
||||||
className="w-5 h-5 sm:w-6 sm:h-6"
|
className="w-4 h-4 sm:w-5 sm:h-5 lg:w-6 lg:h-6"
|
||||||
style={{ color: statusIndicator.color }}
|
style={{ color: statusIndicator.color }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div
|
<div
|
||||||
className={`font-semibold text-[var(--text-primary)] text-base sm:text-lg leading-tight mb-2 ${overflowClasses.truncate}`}
|
className={`font-semibold text-[var(--text-primary)] text-sm sm:text-base lg:text-lg leading-tight mb-1 sm:mb-2 ${overflowClasses.truncate}`}
|
||||||
title={title}
|
title={title}
|
||||||
>
|
>
|
||||||
{truncationEngine.title(title)}
|
{truncationEngine.title(title)}
|
||||||
</div>
|
</div>
|
||||||
{subtitle && (
|
{subtitle && (
|
||||||
<div
|
<div
|
||||||
className={`text-sm text-[var(--text-secondary)] mb-2 ${overflowClasses.truncate}`}
|
className={`text-xs sm:text-sm text-[var(--text-secondary)] mb-1 sm:mb-2 ${overflowClasses.truncate}`}
|
||||||
title={subtitle}
|
title={subtitle}
|
||||||
>
|
>
|
||||||
{truncationEngine.subtitle(subtitle)}
|
{truncationEngine.subtitle(subtitle)}
|
||||||
@@ -175,13 +174,13 @@ export const StatusCard: React.FC<StatusCardProps> = ({
|
|||||||
)}
|
)}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div
|
<div
|
||||||
className={`inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium transition-all ${
|
className={`inline-flex items-center px-2 py-1 sm:px-3 sm:py-1.5 rounded-full text-xs font-medium transition-all ${
|
||||||
statusIndicator.isCritical
|
statusIndicator.isCritical
|
||||||
? 'bg-red-100 text-red-800 ring-2 ring-red-300 shadow-sm animate-pulse'
|
? 'bg-red-100 text-red-800 ring-2 ring-red-300 shadow-sm animate-pulse'
|
||||||
: statusIndicator.isHighlight
|
: statusIndicator.isHighlight
|
||||||
? 'bg-yellow-100 text-yellow-800 ring-1 ring-yellow-300 shadow-sm'
|
? 'bg-yellow-100 text-yellow-800 ring-1 ring-yellow-300 shadow-sm'
|
||||||
: 'ring-1 shadow-sm'
|
: 'ring-1 shadow-sm'
|
||||||
} max-w-[200px] sm:max-w-[220px]`}
|
} max-w-full sm:max-w-[220px]`}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: statusIndicator.isCritical || statusIndicator.isHighlight
|
backgroundColor: statusIndicator.isCritical || statusIndicator.isHighlight
|
||||||
? undefined
|
? undefined
|
||||||
@@ -208,16 +207,16 @@ export const StatusCard: React.FC<StatusCardProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right flex-shrink-0 min-w-0 max-w-[130px] sm:max-w-[160px]">
|
<div className="text-right flex-shrink-0 min-w-[60px] sm:min-w-[80px] max-w-[100px] sm:max-w-[140px] lg:max-w-[160px]">
|
||||||
<div
|
<div
|
||||||
className={`text-2xl sm:text-3xl font-bold text-[var(--text-primary)] leading-none mb-1 ${overflowClasses.truncate}`}
|
className={`text-xl sm:text-2xl lg:text-3xl font-bold text-[var(--text-primary)] leading-none mb-0.5 sm:mb-1 ${overflowClasses.truncate}`}
|
||||||
title={primaryValue?.toString()}
|
title={primaryValue?.toString()}
|
||||||
>
|
>
|
||||||
{safeText(primaryValue?.toString(), '0', isMobile ? 12 : 18)}
|
{safeText(primaryValue?.toString(), '0', isMobile ? 10 : 18)}
|
||||||
</div>
|
</div>
|
||||||
{primaryValueLabel && (
|
{primaryValueLabel && (
|
||||||
<div
|
<div
|
||||||
className={`text-xs text-[var(--text-tertiary)] uppercase tracking-wide ${overflowClasses.truncate}`}
|
className={`text-[10px] sm:text-xs text-[var(--text-tertiary)] uppercase tracking-wide ${overflowClasses.truncate}`}
|
||||||
title={primaryValueLabel}
|
title={primaryValueLabel}
|
||||||
>
|
>
|
||||||
{truncationEngine.primaryValueLabel(primaryValueLabel)}
|
{truncationEngine.primaryValueLabel(primaryValueLabel)}
|
||||||
@@ -228,9 +227,9 @@ export const StatusCard: React.FC<StatusCardProps> = ({
|
|||||||
|
|
||||||
{/* Secondary info - Mobile optimized */}
|
{/* Secondary info - Mobile optimized */}
|
||||||
{secondaryInfo && (
|
{secondaryInfo && (
|
||||||
<div className="flex items-center justify-between text-sm gap-2 min-w-0">
|
<div className="flex items-center justify-between text-xs sm:text-sm gap-2 min-w-0">
|
||||||
<span
|
<span
|
||||||
className={`text-[var(--text-secondary)] flex-shrink-0 ${overflowClasses.truncate} max-w-[100px] sm:max-w-[120px]`}
|
className={`text-[var(--text-secondary)] flex-shrink-0 ${overflowClasses.truncate} max-w-[80px] sm:max-w-[120px]`}
|
||||||
title={secondaryInfo.label}
|
title={secondaryInfo.label}
|
||||||
>
|
>
|
||||||
{truncationEngine.secondaryLabel(secondaryInfo.label)}
|
{truncationEngine.secondaryLabel(secondaryInfo.label)}
|
||||||
@@ -246,7 +245,7 @@ export const StatusCard: React.FC<StatusCardProps> = ({
|
|||||||
|
|
||||||
{/* Progress indicator */}
|
{/* Progress indicator */}
|
||||||
{progress && (
|
{progress && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-2 sm:space-y-3">
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
value={progress.percentage}
|
value={progress.percentage}
|
||||||
max={100}
|
max={100}
|
||||||
@@ -265,19 +264,19 @@ export const StatusCard: React.FC<StatusCardProps> = ({
|
|||||||
|
|
||||||
{/* Metadata - Improved mobile layout with overflow prevention */}
|
{/* Metadata - Improved mobile layout with overflow prevention */}
|
||||||
{metadata.length > 0 && (
|
{metadata.length > 0 && (
|
||||||
<div className="text-xs text-[var(--text-secondary)] space-y-1">
|
<div className="text-[10px] sm:text-xs text-[var(--text-secondary)] space-y-0.5 sm:space-y-1">
|
||||||
{metadata.slice(0, isMobile ? 3 : 4).map((item, index) => (
|
{metadata.slice(0, isMobile ? 2 : 4).map((item, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className={`${overflowClasses.truncate} ${overflowClasses.breakWords}`}
|
className={`${overflowClasses.truncate} ${overflowClasses.breakWords} leading-tight`}
|
||||||
title={item}
|
title={item}
|
||||||
>
|
>
|
||||||
{truncationEngine.metadataItem(item)}
|
{truncationEngine.metadataItem(item)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{metadata.length > (isMobile ? 3 : 4) && (
|
{metadata.length > (isMobile ? 2 : 4) && (
|
||||||
<div className="text-[var(--text-tertiary)] italic">
|
<div className="text-[var(--text-tertiary)] italic text-[10px]">
|
||||||
+{metadata.length - (isMobile ? 3 : 4)} más...
|
+{metadata.length - (isMobile ? 2 : 4)} más...
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -285,9 +284,9 @@ export const StatusCard: React.FC<StatusCardProps> = ({
|
|||||||
|
|
||||||
{/* Simplified Action System - Mobile optimized */}
|
{/* Simplified Action System - Mobile optimized */}
|
||||||
{actions.length > 0 && (
|
{actions.length > 0 && (
|
||||||
<div className="pt-4 border-t border-[var(--border-primary)]">
|
<div className="pt-2 sm:pt-4 border-t border-[var(--border-primary)]">
|
||||||
{/* All actions in a clean horizontal layout */}
|
{/* All actions in a clean horizontal layout */}
|
||||||
<div className="flex items-center justify-between gap-3 flex-wrap">
|
<div className="flex items-center justify-between gap-2 sm:gap-3 flex-wrap">
|
||||||
|
|
||||||
{/* Primary action as a subtle text button */}
|
{/* Primary action as a subtle text button */}
|
||||||
{primaryActions.length > 0 && (
|
{primaryActions.length > 0 && (
|
||||||
@@ -300,8 +299,8 @@ export const StatusCard: React.FC<StatusCardProps> = ({
|
|||||||
}}
|
}}
|
||||||
disabled={primaryActions[0].disabled}
|
disabled={primaryActions[0].disabled}
|
||||||
className={`
|
className={`
|
||||||
flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-lg
|
flex items-center gap-1.5 sm:gap-2 px-2 py-1.5 sm:px-3 sm:py-2 text-xs sm:text-sm font-medium rounded-lg
|
||||||
transition-all duration-200 hover:scale-105 active:scale-95 flex-shrink-0 max-w-[140px] sm:max-w-[160px]
|
transition-all duration-200 hover:scale-105 active:scale-95 flex-shrink-0 max-w-[120px] sm:max-w-[160px]
|
||||||
${primaryActions[0].disabled
|
${primaryActions[0].disabled
|
||||||
? 'opacity-50 cursor-not-allowed'
|
? 'opacity-50 cursor-not-allowed'
|
||||||
: primaryActions[0].destructive
|
: primaryActions[0].destructive
|
||||||
@@ -311,7 +310,7 @@ export const StatusCard: React.FC<StatusCardProps> = ({
|
|||||||
`}
|
`}
|
||||||
title={primaryActions[0].label}
|
title={primaryActions[0].label}
|
||||||
>
|
>
|
||||||
{primaryActions[0].icon && React.createElement(primaryActions[0].icon, { className: "w-4 h-4 flex-shrink-0" })}
|
{primaryActions[0].icon && React.createElement(primaryActions[0].icon, { className: "w-3.5 h-3.5 sm:w-4 sm:h-4 flex-shrink-0" })}
|
||||||
<span className={`${overflowClasses.truncate} flex-1`}>
|
<span className={`${overflowClasses.truncate} flex-1`}>
|
||||||
{truncationEngine.actionLabel(primaryActions[0].label)}
|
{truncationEngine.actionLabel(primaryActions[0].label)}
|
||||||
</span>
|
</span>
|
||||||
@@ -319,7 +318,7 @@ export const StatusCard: React.FC<StatusCardProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Action icons for secondary actions */}
|
{/* Action icons for secondary actions */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-1.5 sm:gap-2">
|
||||||
{secondaryActions.map((action, index) => (
|
{secondaryActions.map((action, index) => (
|
||||||
<button
|
<button
|
||||||
key={`action-${index}`}
|
key={`action-${index}`}
|
||||||
@@ -332,7 +331,7 @@ export const StatusCard: React.FC<StatusCardProps> = ({
|
|||||||
disabled={action.disabled}
|
disabled={action.disabled}
|
||||||
title={action.label}
|
title={action.label}
|
||||||
className={`
|
className={`
|
||||||
p-2 rounded-lg transition-all duration-200 hover:scale-110 active:scale-95 hover:shadow-sm
|
p-1.5 sm:p-2 rounded-lg transition-all duration-200 hover:scale-110 active:scale-95 hover:shadow-sm
|
||||||
${action.disabled
|
${action.disabled
|
||||||
? 'opacity-50 cursor-not-allowed'
|
? 'opacity-50 cursor-not-allowed'
|
||||||
: action.destructive
|
: action.destructive
|
||||||
@@ -343,7 +342,7 @@ export const StatusCard: React.FC<StatusCardProps> = ({
|
|||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{action.icon && React.createElement(action.icon, { className: "w-4 h-4" })}
|
{action.icon && React.createElement(action.icon, { className: "w-3.5 h-3.5 sm:w-4 sm:h-4" })}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
@@ -360,7 +359,7 @@ export const StatusCard: React.FC<StatusCardProps> = ({
|
|||||||
disabled={action.disabled}
|
disabled={action.disabled}
|
||||||
title={action.label}
|
title={action.label}
|
||||||
className={`
|
className={`
|
||||||
p-2 rounded-lg transition-all duration-200 hover:scale-110 active:scale-95 hover:shadow-sm
|
p-1.5 sm:p-2 rounded-lg transition-all duration-200 hover:scale-110 active:scale-95 hover:shadow-sm
|
||||||
${action.disabled
|
${action.disabled
|
||||||
? 'opacity-50 cursor-not-allowed'
|
? 'opacity-50 cursor-not-allowed'
|
||||||
: action.destructive
|
: action.destructive
|
||||||
@@ -369,7 +368,7 @@ export const StatusCard: React.FC<StatusCardProps> = ({
|
|||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{action.icon && React.createElement(action.icon, { className: "w-4 h-4" })}
|
{action.icon && React.createElement(action.icon, { className: "w-3.5 h-3.5 sm:w-4 sm:h-4" })}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -102,7 +102,12 @@
|
|||||||
"quality_checks": "Quality Checks",
|
"quality_checks": "Quality Checks",
|
||||||
"manage_quality": "Manage quality control processes",
|
"manage_quality": "Manage quality control processes",
|
||||||
"quality_management": "Quality Management",
|
"quality_management": "Quality Management",
|
||||||
"event_message": "Message"
|
"event_message": "Message",
|
||||||
|
"batches_today": "batches today",
|
||||||
|
"batches_late": "late",
|
||||||
|
"late_to_start": "Late to Start",
|
||||||
|
"running": "Currently Running",
|
||||||
|
"pending_today": "Pending Today"
|
||||||
},
|
},
|
||||||
"po_approvals": {
|
"po_approvals": {
|
||||||
"title": "What purchase orders need approval?",
|
"title": "What purchase orders need approval?",
|
||||||
|
|||||||
@@ -102,7 +102,12 @@
|
|||||||
"quality_checks": "Controles de Calidad",
|
"quality_checks": "Controles de Calidad",
|
||||||
"manage_quality": "Gestionar procesos de control de calidad",
|
"manage_quality": "Gestionar procesos de control de calidad",
|
||||||
"quality_management": "Gestión de Calidad",
|
"quality_management": "Gestión de Calidad",
|
||||||
"event_message": "Mensaje"
|
"event_message": "Mensaje",
|
||||||
|
"batches_today": "lotes hoy",
|
||||||
|
"batches_late": "atrasado",
|
||||||
|
"late_to_start": "Atrasados para Empezar",
|
||||||
|
"running": "En Ejecución",
|
||||||
|
"pending_today": "Pendientes Hoy"
|
||||||
},
|
},
|
||||||
"po_approvals": {
|
"po_approvals": {
|
||||||
"title": "¿Qué órdenes debo aprobar?",
|
"title": "¿Qué órdenes debo aprobar?",
|
||||||
|
|||||||
@@ -69,9 +69,43 @@
|
|||||||
"items_needed": "elementu behar dira"
|
"items_needed": "elementu behar dira"
|
||||||
},
|
},
|
||||||
"production": {
|
"production": {
|
||||||
"title": "Zer ekoiztu behar da gaur?",
|
"title": "Ekoizpen Egoera",
|
||||||
"empty": "Ez dago ekoizpen programaturik gaur",
|
"empty": "Ez dago ekoizpen programaturik gaur",
|
||||||
"batches_pending": "sortak zain"
|
"batches_pending": "sortak zain",
|
||||||
|
"equipment_status": "Ekipamenduaren Egoera",
|
||||||
|
"temperature": "Tenperatura",
|
||||||
|
"utilization": "Erabilera",
|
||||||
|
"next_maintenance": "Hurrengo Mantentze-Lanak",
|
||||||
|
"last_maintenance": "Azken Mantentze-Lanak",
|
||||||
|
"view_details": "Xehetasunak Ikusi",
|
||||||
|
"status_critical": "Kritikoa",
|
||||||
|
"status_warning": "Abisua",
|
||||||
|
"status_normal": "Normala",
|
||||||
|
"no_equipment": "Ez dago ekipamendurik erabilgarri",
|
||||||
|
"efficiency_metrics": "Ekoizpen Eraginkortasunaren Metrikak",
|
||||||
|
"on_time_start_rate": "Denboraren Arabera Hasieraren Tasa",
|
||||||
|
"batches_started_on_time": "Denboraren arabera hasitako sortak",
|
||||||
|
"efficiency_rate": "Eraginkortasun Tasa",
|
||||||
|
"overall_efficiency": "Ekoizpen eraginkortasun orokorra",
|
||||||
|
"active_alerts": "Alerta Aktiboak",
|
||||||
|
"issues_require_attention": "Arreta behar duten arazoak",
|
||||||
|
"ai_prevented": "ADk Saihestutako Arazoak",
|
||||||
|
"problems_prevented": "ADk saihestutako arazoak",
|
||||||
|
"quick_actions": "Ekintza Azkarrak",
|
||||||
|
"create_batch": "Sortu Ekoizpen Sorta",
|
||||||
|
"create_batch_description": "Sortu ekoizpen sorta berria sarearentzat",
|
||||||
|
"maintenance": "Ekipamenduen Mantentze-Lanak",
|
||||||
|
"schedule_maintenance": "Programatu mantentze-lanak ekoizpen ekipamendurako",
|
||||||
|
"manage_equipment": "Kudeatu Ekipamendua",
|
||||||
|
"quality_checks": "Kalitate Egiaztapenak",
|
||||||
|
"manage_quality": "Kudeatu kalitate kontrol prozesuak",
|
||||||
|
"quality_management": "Kalitate Kudeaketa",
|
||||||
|
"event_message": "Mezua",
|
||||||
|
"batches_today": "lote gaur",
|
||||||
|
"batches_late": "atzeratuta",
|
||||||
|
"late_to_start": "Hasteko Atzeratua",
|
||||||
|
"running": "Martxan",
|
||||||
|
"pending_today": "Gaur Zain"
|
||||||
},
|
},
|
||||||
"po_approvals": {
|
"po_approvals": {
|
||||||
"title": "Zein erosketa agindu onartu behar ditut?",
|
"title": "Zein erosketa agindu onartu behar ditut?",
|
||||||
|
|||||||
@@ -247,17 +247,21 @@ const EnterpriseDashboardPage: React.FC<EnterpriseDashboardPageProps> = ({ tenan
|
|||||||
|
|
||||||
// Error boundary fallback
|
// Error boundary fallback
|
||||||
const ErrorFallback = ({ error, resetErrorBoundary }: { error: Error; resetErrorBoundary: () => void }) => (
|
const ErrorFallback = ({ error, resetErrorBoundary }: { error: Error; resetErrorBoundary: () => void }) => (
|
||||||
<div className="p-6 text-center">
|
<div className="px-4 sm:px-6 lg:px-8 py-6">
|
||||||
<AlertTriangle className="mx-auto h-12 w-12 text-red-500 mb-4" />
|
<Card className="border-[var(--color-error)] bg-[var(--color-error-50)]">
|
||||||
<h3 className="text-lg font-medium text-gray-900 mb-2">Something went wrong</h3>
|
<CardContent className="p-6 text-center">
|
||||||
<p className="text-gray-500 mb-4">{error.message}</p>
|
<AlertTriangle className="mx-auto h-12 w-12 text-[var(--color-error)] mb-4" />
|
||||||
<Button onClick={resetErrorBoundary}>Try again</Button>
|
<h3 className="text-lg font-medium text-[var(--text-primary)] mb-2">Something went wrong</h3>
|
||||||
|
<p className="text-[var(--text-secondary)] mb-4">{error.message}</p>
|
||||||
|
<Button onClick={resetErrorBoundary} variant="primary">Try again</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isNetworkSummaryLoading || isChildrenPerformanceLoading || isDistributionLoading || isForecastLoading) {
|
if (isNetworkSummaryLoading || isChildrenPerformanceLoading || isDistributionLoading || isForecastLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="p-6 min-h-screen">
|
<div className="px-4 sm:px-6 lg:px-8 py-6 min-h-screen">
|
||||||
<div className="flex items-center justify-center h-96">
|
<div className="flex items-center justify-center h-96">
|
||||||
<LoadingSpinner text={t('enterprise.loading')} />
|
<LoadingSpinner text={t('enterprise.loading')} />
|
||||||
</div>
|
</div>
|
||||||
@@ -267,69 +271,67 @@ const EnterpriseDashboardPage: React.FC<EnterpriseDashboardPageProps> = ({ tenan
|
|||||||
|
|
||||||
if (networkSummaryError || childrenPerformanceError || distributionError || forecastError) {
|
if (networkSummaryError || childrenPerformanceError || distributionError || forecastError) {
|
||||||
return (
|
return (
|
||||||
<div className="p-6 min-h-screen">
|
<div className="px-4 sm:px-6 lg:px-8 py-6 min-h-screen">
|
||||||
<div className="bg-red-50 border border-red-200 rounded-lg p-6 text-center">
|
<Card className="border-[var(--color-error)] bg-[var(--color-error-50)]">
|
||||||
<AlertTriangle className="mx-auto h-12 w-12 text-red-500 mb-4" />
|
<CardContent className="p-6 text-center">
|
||||||
<h3 className="text-lg font-medium text-red-800 mb-2">Error Loading Dashboard</h3>
|
<AlertTriangle className="mx-auto h-12 w-12 text-[var(--color-error)] mb-4" />
|
||||||
<p className="text-red-600">
|
<h3 className="text-lg font-medium text-[var(--text-primary)] mb-2">Error Loading Dashboard</h3>
|
||||||
{networkSummaryError?.message ||
|
<p className="text-[var(--text-secondary)]">
|
||||||
childrenPerformanceError?.message ||
|
{networkSummaryError?.message ||
|
||||||
distributionError?.message ||
|
childrenPerformanceError?.message ||
|
||||||
forecastError?.message}
|
distributionError?.message ||
|
||||||
</p>
|
forecastError?.message}
|
||||||
</div>
|
</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||||
<div className="px-4 sm:px-6 lg:px-8 py-6 min-h-screen bg-[var(--bg-secondary)]">
|
<div className="px-4 sm:px-6 lg:px-8 py-6 min-h-screen">
|
||||||
{/* Breadcrumb / Return to Network Banner */}
|
{/* Breadcrumb / Return to Network Banner */}
|
||||||
{enterpriseState.selectedOutletId && !enterpriseState.isNetworkView && (
|
{enterpriseState.selectedOutletId && !enterpriseState.isNetworkView && (
|
||||||
<div
|
<Card className="mb-6 border-2 border-[var(--color-info)] bg-[var(--color-info-50)]">
|
||||||
className="mb-6 rounded-lg p-4 border border-[var(--border-primary)]"
|
<CardContent className="p-4">
|
||||||
style={{
|
<div className="flex items-center justify-between">
|
||||||
backgroundColor: 'var(--color-info-50)',
|
<div className="flex items-center gap-3">
|
||||||
}}
|
<Network className="w-5 h-5 text-[var(--color-info)]" />
|
||||||
>
|
<div className="flex items-center gap-2 text-sm">
|
||||||
<div className="flex items-center justify-between">
|
<span className="font-medium text-[var(--color-info)]">Network Overview</span>
|
||||||
<div className="flex items-center gap-3">
|
<ChevronRight className="w-4 h-4 text-[var(--color-info-300)]" />
|
||||||
<Network className="w-5 h-5 text-[var(--color-info)]" />
|
<span className="text-[var(--text-primary)] font-semibold">{enterpriseState.selectedOutletName}</span>
|
||||||
<div className="flex items-center gap-2 text-sm">
|
</div>
|
||||||
<span className="font-medium text-[var(--color-info)]">Network Overview</span>
|
|
||||||
<ChevronRight className="w-4 h-4 text-[var(--color-info-300)]" />
|
|
||||||
<span className="text-[var(--text-primary)] font-semibold">{enterpriseState.selectedOutletName}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
<Button
|
||||||
|
onClick={handleReturnToNetwork}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="w-4 h-4" />
|
||||||
|
Return to Network View
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
{enterpriseState.networkMetrics && (
|
||||||
onClick={handleReturnToNetwork}
|
<div className="mt-3 pt-3 border-t border-[var(--border-primary)] grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 text-sm">
|
||||||
variant="outline"
|
<div>
|
||||||
size="sm"
|
<span className="text-[var(--color-info)]">Network Average Sales:</span>
|
||||||
className="flex items-center gap-2"
|
<span className="ml-2 font-semibold text-[var(--text-primary)]">{currencySymbol}{enterpriseState.networkMetrics.averageSales.toLocaleString()}</span>
|
||||||
>
|
</div>
|
||||||
<ArrowLeft className="w-4 h-4" />
|
<div>
|
||||||
Return to Network View
|
<span className="text-[var(--color-info)]">Total Outlets:</span>
|
||||||
</Button>
|
<span className="ml-2 font-semibold text-[var(--text-primary)]">{enterpriseState.networkMetrics.childCount}</span>
|
||||||
</div>
|
</div>
|
||||||
{enterpriseState.networkMetrics && (
|
<div>
|
||||||
<div className="mt-3 pt-3 border-t grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 text-sm"
|
<span className="text-[var(--color-info)]">Network Total:</span>
|
||||||
style={{ borderColor: 'var(--border-primary)' }}>
|
<span className="ml-2 font-semibold text-[var(--text-primary)]">{currencySymbol}{enterpriseState.networkMetrics.totalSales.toLocaleString()}</span>
|
||||||
<div>
|
</div>
|
||||||
<span className="text-[var(--color-info)]">Network Average Sales:</span>
|
|
||||||
<span className="ml-2 font-semibold text-[var(--text-primary)]">{currencySymbol}{enterpriseState.networkMetrics.averageSales.toLocaleString()}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
)}
|
||||||
<span className="text-[var(--color-info)]">Total Outlets:</span>
|
</CardContent>
|
||||||
<span className="ml-2 font-semibold text-[var(--text-primary)]">{enterpriseState.networkMetrics.childCount}</span>
|
</Card>
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span className="text-[var(--color-info)]">Network Total:</span>
|
|
||||||
<span className="ml-2 font-semibold text-[var(--text-primary)]">{currencySymbol}{enterpriseState.networkMetrics.totalSales.toLocaleString()}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Enhanced Header */}
|
{/* Enhanced Header */}
|
||||||
@@ -423,22 +425,22 @@ const EnterpriseDashboardPage: React.FC<EnterpriseDashboardPageProps> = ({ tenan
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{forecastSummary && forecastSummary.aggregated_forecasts ? (
|
{forecastSummary && forecastSummary.aggregated_forecasts ? (
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
{/* Total Demand Card */}
|
{/* Total Demand Card */}
|
||||||
<Card className="hover:shadow-lg transition-shadow duration-300">
|
<Card className="hover:shadow-xl hover:-translate-y-1 transition-all duration-300 border-[var(--border-primary)]">
|
||||||
<CardContent className="p-6">
|
<CardContent className="p-6">
|
||||||
<div className="flex items-center gap-3 mb-3">
|
<div className="flex items-center gap-3 mb-3">
|
||||||
<div
|
<div
|
||||||
className="w-10 h-10 rounded-lg flex items-center justify-center shadow-md"
|
className="w-12 h-12 rounded-xl flex items-center justify-center"
|
||||||
style={{ backgroundColor: 'var(--color-info-100)' }}
|
style={{ backgroundColor: 'var(--color-info-100)' }}
|
||||||
>
|
>
|
||||||
<Package className="w-5 h-5" style={{ color: 'var(--color-info-600)' }} />
|
<Package className="w-6 h-6" style={{ color: 'var(--color-info)' }} />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-semibold text-sm" style={{ color: 'var(--color-info-800)' }}>
|
<h3 className="font-semibold text-sm text-[var(--text-secondary)]">
|
||||||
{t('enterprise.total_demand')}
|
{t('enterprise.total_demand')}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-3xl font-bold" style={{ color: 'var(--color-info-900)' }}>
|
<p className="text-3xl font-bold text-[var(--text-primary)]">
|
||||||
{Object.values(forecastSummary.aggregated_forecasts).reduce((total: number, day: any) =>
|
{Object.values(forecastSummary.aggregated_forecasts).reduce((total: number, day: any) =>
|
||||||
total + Object.values(day).reduce((dayTotal: number, product: any) =>
|
total + Object.values(day).reduce((dayTotal: number, product: any) =>
|
||||||
dayTotal + (product.predicted_demand || 0), 0), 0
|
dayTotal + (product.predicted_demand || 0), 0), 0
|
||||||
@@ -448,40 +450,40 @@ const EnterpriseDashboardPage: React.FC<EnterpriseDashboardPageProps> = ({ tenan
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Days Forecast Card */}
|
{/* Days Forecast Card */}
|
||||||
<Card className="hover:shadow-lg transition-shadow duration-300">
|
<Card className="hover:shadow-xl hover:-translate-y-1 transition-all duration-300 border-[var(--border-primary)]">
|
||||||
<CardContent className="p-6">
|
<CardContent className="p-6">
|
||||||
<div className="flex items-center gap-3 mb-3">
|
<div className="flex items-center gap-3 mb-3">
|
||||||
<div
|
<div
|
||||||
className="w-10 h-10 rounded-lg flex items-center justify-center shadow-md"
|
className="w-12 h-12 rounded-xl flex items-center justify-center"
|
||||||
style={{ backgroundColor: 'var(--color-success-100)' }}
|
style={{ backgroundColor: 'var(--color-success-100)' }}
|
||||||
>
|
>
|
||||||
<Calendar className="w-5 h-5" style={{ color: 'var(--color-success-600)' }} />
|
<Calendar className="w-6 h-6" style={{ color: 'var(--color-success)' }} />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-semibold text-sm" style={{ color: 'var(--color-success-800)' }}>
|
<h3 className="font-semibold text-sm text-[var(--text-secondary)]">
|
||||||
{t('enterprise.days_forecast')}
|
{t('enterprise.days_forecast')}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-3xl font-bold" style={{ color: 'var(--color-success-900)' }}>
|
<p className="text-3xl font-bold text-[var(--text-primary)]">
|
||||||
{forecastSummary.days_forecast || 7}
|
{forecastSummary.days_forecast || 7}
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Average Daily Demand Card */}
|
{/* Average Daily Demand Card */}
|
||||||
<Card className="hover:shadow-lg transition-shadow duration-300">
|
<Card className="hover:shadow-xl hover:-translate-y-1 transition-all duration-300 border-[var(--border-primary)]">
|
||||||
<CardContent className="p-6">
|
<CardContent className="p-6">
|
||||||
<div className="flex items-center gap-3 mb-3">
|
<div className="flex items-center gap-3 mb-3">
|
||||||
<div
|
<div
|
||||||
className="w-10 h-10 rounded-lg flex items-center justify-center shadow-md"
|
className="w-12 h-12 rounded-xl flex items-center justify-center"
|
||||||
style={{ backgroundColor: 'var(--color-secondary-100)' }}
|
style={{ backgroundColor: 'var(--color-secondary-100)' }}
|
||||||
>
|
>
|
||||||
<Activity className="w-5 h-5" style={{ color: 'var(--color-secondary-600)' }} />
|
<Activity className="w-6 h-6" style={{ color: 'var(--color-secondary)' }} />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-semibold text-sm" style={{ color: 'var(--color-secondary-800)' }}>
|
<h3 className="font-semibold text-sm text-[var(--text-secondary)]">
|
||||||
{t('enterprise.avg_daily_demand')}
|
{t('enterprise.avg_daily_demand')}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-3xl font-bold" style={{ color: 'var(--color-secondary-900)' }}>
|
<p className="text-3xl font-bold text-[var(--text-primary)]">
|
||||||
{forecastSummary.aggregated_forecasts
|
{forecastSummary.aggregated_forecasts
|
||||||
? Math.round(Object.values(forecastSummary.aggregated_forecasts).reduce((total: number, day: any) =>
|
? Math.round(Object.values(forecastSummary.aggregated_forecasts).reduce((total: number, day: any) =>
|
||||||
total + Object.values(day).reduce((dayTotal: number, product: any) =>
|
total + Object.values(day).reduce((dayTotal: number, product: any) =>
|
||||||
@@ -494,20 +496,20 @@ const EnterpriseDashboardPage: React.FC<EnterpriseDashboardPageProps> = ({ tenan
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Last Updated Card */}
|
{/* Last Updated Card */}
|
||||||
<Card className="hover:shadow-lg transition-shadow duration-300">
|
<Card className="hover:shadow-xl hover:-translate-y-1 transition-all duration-300 border-[var(--border-primary)]">
|
||||||
<CardContent className="p-6">
|
<CardContent className="p-6">
|
||||||
<div className="flex items-center gap-3 mb-3">
|
<div className="flex items-center gap-3 mb-3">
|
||||||
<div
|
<div
|
||||||
className="w-10 h-10 rounded-lg flex items-center justify-center shadow-md"
|
className="w-12 h-12 rounded-xl flex items-center justify-center"
|
||||||
style={{ backgroundColor: 'var(--color-warning-100)' }}
|
style={{ backgroundColor: 'var(--color-warning-100)' }}
|
||||||
>
|
>
|
||||||
<Clock className="w-5 h-5" style={{ color: 'var(--color-warning-600)' }} />
|
<Clock className="w-6 h-6" style={{ color: 'var(--color-warning)' }} />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-semibold text-sm" style={{ color: 'var(--color-warning-800)' }}>
|
<h3 className="font-semibold text-sm text-[var(--text-secondary)]">
|
||||||
{t('enterprise.last_updated')}
|
{t('enterprise.last_updated')}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-lg font-semibold" style={{ color: 'var(--color-warning-900)' }}>
|
<p className="text-lg font-semibold text-[var(--text-primary)]">
|
||||||
{forecastSummary.last_updated ?
|
{forecastSummary.last_updated ?
|
||||||
new Date(forecastSummary.last_updated).toLocaleTimeString() :
|
new Date(forecastSummary.last_updated).toLocaleTimeString() :
|
||||||
'N/A'}
|
'N/A'}
|
||||||
|
|||||||
Reference in New Issue
Block a user