Files
liqa/client/src/pages/EnvironmentDetailPage.tsx
T
ars9 18177cac52 feat(client): add create/edit environment pages and hoverable cards
- add CreateEnvironmentPage and EditEnvironmentPage with pre-filled form
- add /environments/new and /environments/:id/edit routes
- add Edit option to card context menu and detail page toolbar
- move env id pill to card footer (left), timestamp stays right
- make env cards clickable (navigate to detail); cog stops propagation
- add hoverable Card variant with primary border+shadow on hover
- add link color tokens and global anchor styles for both themes
- fix Timestamp pill visibility in dark mode with border token
- add DescriptionList truncate prop with ellipsis and title tooltip
- stack DescriptionList dd below dt with 8px gap between items
2026-04-09 17:22:01 +03:00

102 lines
3.2 KiB
TypeScript

import { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Pencil, 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 && (
<div className={styles.toolbarActions}>
<Button variant="secondary" size="sm" onClick={() => navigate(`/environments/${env.id}/edit`)}>
<Pencil size={14} />
{t('environments.action_edit')}
</Button>
<Button variant="danger" size="sm" onClick={handleDelete}>
<Trash2 size={14} />
{t('environments.action_delete')}
</Button>
</div>
)}
</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>
);
}