feat(frontend): full FR i18n via react-i18next + 2-tab engagement detail (sprint 12) #14
@@ -1,4 +1,4 @@
|
|||||||
import { useParams } from 'react-router-dom';
|
import { useParams, Link } from 'react-router-dom';
|
||||||
import { extractApiError } from '@/api/client';
|
import { extractApiError } from '@/api/client';
|
||||||
import { useAuth } from '@/hooks/useAuth';
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
import { useEngagement } from '@/hooks/useEngagements';
|
import { useEngagement } from '@/hooks/useEngagements';
|
||||||
@@ -11,9 +11,8 @@ import { SimulationList } from '@/components/SimulationList';
|
|||||||
import { ExportEngagementButton } from '@/components/ExportEngagementButton';
|
import { ExportEngagementButton } from '@/components/ExportEngagementButton';
|
||||||
import { BackLink } from '@/components/BackLink';
|
import { BackLink } from '@/components/BackLink';
|
||||||
import { Tabs } from '@/components/Tabs';
|
import { Tabs } from '@/components/Tabs';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
type TabId = 'schedule' | 'description' | 'simulations';
|
type TabId = 'description' | 'simulations';
|
||||||
|
|
||||||
export function EngagementDetailPage(): JSX.Element {
|
export function EngagementDetailPage(): JSX.Element {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
@@ -23,7 +22,7 @@ export function EngagementDetailPage(): JSX.Element {
|
|||||||
const detail = useEngagement(numericId);
|
const detail = useEngagement(numericId);
|
||||||
const simsQuery = useEngagementSimulations(numericId);
|
const simsQuery = useEngagementSimulations(numericId);
|
||||||
|
|
||||||
const [activeTabRaw, setActiveTab] = useHashTab('schedule');
|
const [activeTabRaw, setActiveTab] = useHashTab('description');
|
||||||
const activeTab = activeTabRaw as TabId;
|
const activeTab = activeTabRaw as TabId;
|
||||||
|
|
||||||
if (detail.isLoading) return <LoadingState label="Loading engagement…" />;
|
if (detail.isLoading) return <LoadingState label="Loading engagement…" />;
|
||||||
@@ -41,7 +40,6 @@ export function EngagementDetailPage(): JSX.Element {
|
|||||||
const simCount = simsQuery.data?.length;
|
const simCount = simsQuery.data?.length;
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ id: 'schedule', label: 'Schedule' },
|
|
||||||
{ id: 'description', label: 'Description' },
|
{ id: 'description', label: 'Description' },
|
||||||
{ id: 'simulations', label: 'Simulations', count: simCount },
|
{ id: 'simulations', label: 'Simulations', count: simCount },
|
||||||
];
|
];
|
||||||
@@ -60,12 +58,7 @@ export function EngagementDetailPage(): JSX.Element {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{canEditEngagements ? (
|
{canEditEngagements ? (
|
||||||
<div className="flex items-center gap-sm">
|
|
||||||
<ExportEngagementButton engagementId={eng.id} />
|
<ExportEngagementButton engagementId={eng.id} />
|
||||||
<Link to={`/engagements/${eng.id}/edit`} className="btn-outline">
|
|
||||||
Edit
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
) : null}
|
) : null}
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -76,25 +69,27 @@ export function EngagementDetailPage(): JSX.Element {
|
|||||||
id={`tabpanel-${activeTab}`}
|
id={`tabpanel-${activeTab}`}
|
||||||
aria-labelledby={`tab-${activeTab}`}
|
aria-labelledby={`tab-${activeTab}`}
|
||||||
>
|
>
|
||||||
{activeTab === 'schedule' && (
|
|
||||||
<div className="card-product">
|
|
||||||
<h2 className="text-[20px] font-medium mb-md">Schedule</h2>
|
|
||||||
<dl className="grid grid-cols-2 gap-md text-[14px]">
|
|
||||||
<dt className="text-graphite">Start date</dt>
|
|
||||||
<dd className="text-ink font-mono">{eng.start_date}</dd>
|
|
||||||
<dt className="text-graphite">End date</dt>
|
|
||||||
<dd className="text-ink font-mono">{eng.end_date ?? '—'}</dd>
|
|
||||||
<dt className="text-graphite">Status</dt>
|
|
||||||
<dd className="text-ink capitalize">{eng.status}</dd>
|
|
||||||
<dt className="text-graphite">Created at</dt>
|
|
||||||
<dd className="text-ink font-mono">{eng.created_at}</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeTab === 'description' && (
|
{activeTab === 'description' && (
|
||||||
<div className="card-product">
|
<div className="card-product flex flex-col gap-md">
|
||||||
<h2 className="text-[20px] font-medium mb-md">Description</h2>
|
<header className="flex items-start justify-between gap-md">
|
||||||
|
<dl className="grid grid-cols-2 gap-x-lg gap-y-xxs text-caption-md">
|
||||||
|
<dt className="text-graphite">Start date</dt>
|
||||||
|
<dd className="font-mono">{eng.start_date}</dd>
|
||||||
|
<dt className="text-graphite">End date</dt>
|
||||||
|
<dd className="font-mono">{eng.end_date ?? '—'}</dd>
|
||||||
|
<dt className="text-graphite">Status</dt>
|
||||||
|
<dd><StatusBadge status={eng.status} /></dd>
|
||||||
|
<dt className="text-graphite">Created at</dt>
|
||||||
|
<dd className="font-mono">{eng.created_at}</dd>
|
||||||
|
</dl>
|
||||||
|
{canEditEngagements ? (
|
||||||
|
<Link to={`/engagements/${eng.id}/edit`} className="btn-outline shrink-0">
|
||||||
|
Edit
|
||||||
|
</Link>
|
||||||
|
) : null}
|
||||||
|
</header>
|
||||||
|
<hr className="border-hairline" />
|
||||||
|
<h2 className="text-display-sm">Description</h2>
|
||||||
<p className="text-[16px] text-charcoal whitespace-pre-line">
|
<p className="text-[16px] text-charcoal whitespace-pre-line">
|
||||||
{eng.description?.trim() ? eng.description : 'No description provided.'}
|
{eng.description?.trim() ? eng.description : 'No description provided.'}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user