Files
liqa/client/src/pages/EnvironmentDetailPage.tsx
T
ars9 89016b01d2 feat(client): add Card header/footer, DescriptionList, and portal ContextMenu
- add Card header prop (primary/secondary variants) and footer prop (sticks to bottom)
- add Breadcrumbs to all 5 pages; remove duplicate h1 headings
- add pageToolbar layout for breadcrumbs + danger button aligned right
- add DescriptionList component replacing raw dl/dt/dd markup; supports truncate+tooltip
- rewrite ContextMenu to render dropdown via React portal to avoid clip/overflow issues
- update EnvironmentCard to use Card header (primary), footer (Timestamp), and truncated DescriptionList
- fix cog button contrast, square sizing, and right-edge alignment on colored header
- add border to Timestamp pill for dark-mode visibility
- add id badge pill styling to environment card header
2026-04-09 14:29:16 +03:00

96 lines
2.9 KiB
TypeScript

import { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Trash2 } from 'lucide-react';
import { environments } from '../api';
import type { Environment } from '../api';
import { Breadcrumbs, Button, Card, DescriptionList, Timestamp } from '../ui';
import styles from './Page.module.css';
export function EnvironmentDetailPage() {
const { t } = useTranslation();
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const [env, setEnv] = useState<Environment | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
if (!id) return;
environments
.get(Number(id))
.then(setEnv)
.catch((err: Error) => setError(err.message))
.finally(() => setLoading(false));
}, [id]);
const handleDelete = async () => {
if (!env) return;
await environments.remove(env.id);
navigate('/environments');
};
return (
<div>
<div className={styles.pageToolbar}>
<Breadcrumbs
items={[
{ label: t('environments.title'), onClick: () => navigate('/environments') },
{ label: env?.name ?? `#${id}` },
]}
/>
{env && (
<Button variant="danger" size="sm" onClick={handleDelete}>
<Trash2 size={14} />
{t('environments.action_delete')}
</Button>
)}
</div>
{error && <p className={styles.error}>{error}</p>}
{loading && <p className={styles.muted}>{t('environments.loading')}</p>}
{env && (
<>
<div className={styles.detailMeta}>
<DescriptionList
items={[
{ term: t('environments.field_id'), detail: env.id },
{
term: t('environments.field_created'),
detail: <Timestamp value={env.createdAt} />,
},
{
term: t('environments.field_updated'),
detail: <Timestamp value={env.updatedAt} />,
},
]}
/>
</div>
<h2 className={styles.sectionHeading}>{t('environments.section_urls')}</h2>
<Card>
{Object.entries(env.urls).filter(([, v]) => v).length === 0 ? (
<p className={styles.muted}>{t('environments.no_urls')}</p>
) : (
<DescriptionList
items={Object.entries(env.urls)
.filter(([, v]) => v)
.map(([key, value]) => ({
term: key,
detail: (
<a href={value} target="_blank" rel="noopener noreferrer">
{value}
</a>
),
}))}
/>
)}
</Card>
</>
)}
</div>
);
}