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:
Claude
2025-11-07 18:34:30 +00:00
parent 84d38842ab
commit 28136cf198
9 changed files with 619 additions and 152 deletions

View File

@@ -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>