feat: Complete frontend i18n implementation for dashboard components
- Updated TypeScript types to support reasoning_data field - Integrated useReasoningTranslation hook in all dashboard components: * ActionQueueCard: Translates PO reasoning_data and UI text * ProductionTimelineCard: Translates batch reasoning_data and UI text * OrchestrationSummaryCard: Translates all hardcoded English text * HealthStatusCard: Translates all hardcoded English text - Added missing translation keys to all language files (EN, ES, EU): * health_status: never, critical_issues, actions_needed * action_queue: total, critical, important * orchestration_summary: ready_to_plan, run_info, took, show_more/less * production_timeline: Complete rebuild with new keys - Components now support fallback for deprecated text fields - Full multilingual support: English, Spanish, Basque Dashboard is now fully translatable and will display reasoning in user's language.
This commit is contained in:
@@ -21,6 +21,8 @@ import {
|
||||
ChevronUp,
|
||||
} from 'lucide-react';
|
||||
import { ActionItem, ActionQueue } from '../../api/hooks/newDashboard';
|
||||
import { useReasoningFormatter } from '../../hooks/useReasoningTranslation';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface ActionQueueCardProps {
|
||||
actionQueue: ActionQueue;
|
||||
@@ -65,6 +67,13 @@ function ActionItemCard({
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const config = urgencyConfig[action.urgency];
|
||||
const UrgencyIcon = config.icon;
|
||||
const { formatPOAction } = useReasoningFormatter();
|
||||
const { t } = useTranslation('reasoning');
|
||||
|
||||
// Translate reasoning_data (or fallback to deprecated text fields)
|
||||
const { reasoning, consequence, severity } = action.reasoning_data
|
||||
? formatPOAction(action.reasoning_data)
|
||||
: { reasoning: action.reasoning || '', consequence: action.consequence || '', severity: '' };
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -96,29 +105,42 @@ function ActionItemCard({
|
||||
|
||||
{/* Reasoning (always visible) */}
|
||||
<div className="bg-white rounded-md p-3 mb-3">
|
||||
<p className="text-sm font-medium text-gray-700 mb-1">Why this is needed:</p>
|
||||
<p className="text-sm text-gray-600">{action.reasoning}</p>
|
||||
<p className="text-sm font-medium text-gray-700 mb-1">
|
||||
{t('jtbd.action_queue.why_needed')}
|
||||
</p>
|
||||
<p className="text-sm text-gray-600">{reasoning}</p>
|
||||
</div>
|
||||
|
||||
{/* Consequence (expandable) */}
|
||||
<button
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
className="flex items-center gap-2 text-sm text-gray-600 hover:text-gray-900 transition-colors mb-3 w-full"
|
||||
>
|
||||
{expanded ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
|
||||
<span className="font-medium">What happens if I don't do this?</span>
|
||||
</button>
|
||||
{consequence && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
className="flex items-center gap-2 text-sm text-gray-600 hover:text-gray-900 transition-colors mb-3 w-full"
|
||||
>
|
||||
{expanded ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
|
||||
<span className="font-medium">{t('jtbd.action_queue.what_if_not')}</span>
|
||||
</button>
|
||||
|
||||
{expanded && (
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-md p-3 mb-3">
|
||||
<p className="text-sm text-amber-900">{action.consequence}</p>
|
||||
</div>
|
||||
{expanded && (
|
||||
<div className="bg-amber-50 border border-amber-200 rounded-md p-3 mb-3">
|
||||
<p className="text-sm text-amber-900">{consequence}</p>
|
||||
{severity && (
|
||||
<span className="text-xs font-semibold text-amber-900 mt-1 block">
|
||||
{severity}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Time Estimate */}
|
||||
<div className="flex items-center gap-2 text-xs text-gray-500 mb-4">
|
||||
<Clock className="w-4 h-4" />
|
||||
<span>Estimated time: {action.estimatedTimeMinutes} min</span>
|
||||
<span>
|
||||
{t('jtbd.action_queue.estimated_time')}: {action.estimatedTimeMinutes} min
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
@@ -167,6 +189,7 @@ export function ActionQueueCard({
|
||||
}: ActionQueueCardProps) {
|
||||
const [showAll, setShowAll] = useState(false);
|
||||
const displayedActions = showAll ? actionQueue.actions : actionQueue.actions.slice(0, 3);
|
||||
const { t } = useTranslation('reasoning');
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@@ -184,8 +207,10 @@ export function ActionQueueCard({
|
||||
return (
|
||||
<div className="bg-green-50 border-2 border-green-200 rounded-xl p-8 text-center">
|
||||
<CheckCircle2 className="w-16 h-16 text-green-600 mx-auto mb-4" />
|
||||
<h3 className="text-xl font-bold text-green-900 mb-2">All caught up!</h3>
|
||||
<p className="text-green-700">No actions requiring your attention right now.</p>
|
||||
<h3 className="text-xl font-bold text-green-900 mb-2">
|
||||
{t('jtbd.action_queue.all_caught_up')}
|
||||
</h3>
|
||||
<p className="text-green-700">{t('jtbd.action_queue.no_actions')}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -194,10 +219,10 @@ export function ActionQueueCard({
|
||||
<div className="bg-white rounded-xl shadow-md p-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-2xl font-bold text-gray-900">What Needs Your Attention</h2>
|
||||
<h2 className="text-2xl font-bold text-gray-900">{t('jtbd.action_queue.title')}</h2>
|
||||
{actionQueue.totalActions > 3 && (
|
||||
<span className="bg-red-100 text-red-800 px-3 py-1 rounded-full text-sm font-semibold">
|
||||
{actionQueue.totalActions} total
|
||||
{actionQueue.totalActions} {t('jtbd.action_queue.total')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -207,12 +232,12 @@ export function ActionQueueCard({
|
||||
<div className="flex flex-wrap gap-2 mb-6">
|
||||
{actionQueue.criticalCount > 0 && (
|
||||
<span className="bg-red-100 text-red-800 px-3 py-1 rounded-full text-sm font-semibold">
|
||||
{actionQueue.criticalCount} critical
|
||||
{actionQueue.criticalCount} {t('jtbd.action_queue.critical')}
|
||||
</span>
|
||||
)}
|
||||
{actionQueue.importantCount > 0 && (
|
||||
<span className="bg-amber-100 text-amber-800 px-3 py-1 rounded-full text-sm font-semibold">
|
||||
{actionQueue.importantCount} important
|
||||
{actionQueue.importantCount} {t('jtbd.action_queue.important')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -238,10 +263,8 @@ export function ActionQueueCard({
|
||||
className="w-full mt-4 py-3 bg-gray-100 hover:bg-gray-200 rounded-lg font-semibold text-gray-700 transition-colors duration-200"
|
||||
>
|
||||
{showAll
|
||||
? 'Show Less'
|
||||
: `Show ${actionQueue.totalActions - 3} More Action${
|
||||
actionQueue.totalActions - 3 !== 1 ? 's' : ''
|
||||
}`}
|
||||
? t('jtbd.action_queue.show_less')
|
||||
: t('jtbd.action_queue.show_more', { count: actionQueue.totalActions - 3 })}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user