117 lines
3.9 KiB
TypeScript
117 lines
3.9 KiB
TypeScript
'use client';
|
|
import { FunctionComponent } from 'react';
|
|
import Box from '@mui/material/Box';
|
|
import Skeleton from '@mui/material/Skeleton';
|
|
import Stack from '@mui/material/Stack';
|
|
import { AppIconButton, SurfaceCard } from '@/components/common';
|
|
import PatientHeader from '@/components/PatientHeader';
|
|
import type { Patient } from '@/services/patients/types';
|
|
|
|
export interface PatientCardProps {
|
|
patient: Patient;
|
|
/** Translated relation label; omitted when the patient has no relation set. */
|
|
relationLabel?: string;
|
|
/** Translated gender label. */
|
|
genderLabel: string;
|
|
/** Translated age label (e.g. "70 yrs"); omitted when the birth date is unknown. */
|
|
ageLabel?: string;
|
|
/** Translated condition labels; empty renders the "no conditions" line. */
|
|
conditionLabels: string[];
|
|
noConditionsLabel: string;
|
|
onEdit: () => void;
|
|
onArchive: () => void;
|
|
editLabel: string;
|
|
archiveLabel: string;
|
|
/** When provided, tapping the identity area opens the patient's record (E2). The action buttons are unaffected. */
|
|
onOpen?: () => void;
|
|
/** Accessible label for the tappable identity area (required when `onOpen` is set). */
|
|
openLabel?: string;
|
|
}
|
|
|
|
/**
|
|
* Patient summary card for the E1 list — the shared `PatientHeader` (relation + name, age/gender, condition
|
|
* chips) plus edit and archive actions. When `onOpen` is passed the identity area becomes a button that opens
|
|
* the E2 record viewer; the edit/archive icon buttons keep their own handlers. All display text is translated
|
|
* by the caller.
|
|
* @component PatientCard
|
|
*/
|
|
const PatientCard: FunctionComponent<PatientCardProps> = ({
|
|
patient,
|
|
relationLabel,
|
|
genderLabel,
|
|
ageLabel,
|
|
conditionLabels,
|
|
noConditionsLabel,
|
|
onEdit,
|
|
onArchive,
|
|
editLabel,
|
|
archiveLabel,
|
|
onOpen,
|
|
openLabel,
|
|
}) => {
|
|
const header = (
|
|
<PatientHeader
|
|
displayName={patient.displayName}
|
|
relationLabel={relationLabel}
|
|
genderLabel={genderLabel}
|
|
ageLabel={ageLabel}
|
|
conditionLabels={conditionLabels}
|
|
noConditionsLabel={noConditionsLabel}
|
|
/>
|
|
);
|
|
|
|
return (
|
|
<SurfaceCard padding="sm">
|
|
<Stack direction="row" sx={{ alignItems: 'flex-start', gap: 1 }}>
|
|
{onOpen ? (
|
|
<Box
|
|
component="button"
|
|
type="button"
|
|
onClick={onOpen}
|
|
aria-label={openLabel}
|
|
sx={{
|
|
flexGrow: 1,
|
|
minWidth: 0,
|
|
textAlign: 'start',
|
|
background: 'none',
|
|
border: 'none',
|
|
p: 0,
|
|
cursor: 'pointer',
|
|
font: 'inherit',
|
|
color: 'inherit',
|
|
}}
|
|
>
|
|
{header}
|
|
</Box>
|
|
) : (
|
|
<Box sx={{ flexGrow: 1, minWidth: 0 }}>{header}</Box>
|
|
)}
|
|
|
|
<Stack direction="row" sx={{ flexShrink: 0 }}>
|
|
<AppIconButton icon="edit" title={editLabel} aria-label={editLabel} size="small" onClick={onEdit} />
|
|
<AppIconButton icon="archive" title={archiveLabel} aria-label={archiveLabel} size="small" onClick={onArchive} />
|
|
</Stack>
|
|
</Stack>
|
|
</SurfaceCard>
|
|
);
|
|
};
|
|
|
|
/** Matches the real card's anatomy (identity block + two icon-button actions) for the E1 patient list. */
|
|
const PatientCardSkeleton: FunctionComponent = () => (
|
|
<SurfaceCard padding="sm">
|
|
<Stack direction="row" sx={{ alignItems: 'flex-start', gap: 1 }}>
|
|
<Stack sx={{ gap: 0.75, flexGrow: 1 }}>
|
|
<Skeleton variant="text" width="45%" height={26} />
|
|
<Skeleton variant="text" width="65%" height={20} />
|
|
<Skeleton variant="rounded" width={90} height={22} />
|
|
</Stack>
|
|
<Stack direction="row" sx={{ flexShrink: 0, gap: 0.5 }}>
|
|
<Skeleton variant="circular" width={32} height={32} />
|
|
<Skeleton variant="circular" width={32} height={32} />
|
|
</Stack>
|
|
</Stack>
|
|
</SurfaceCard>
|
|
);
|
|
|
|
export default Object.assign(PatientCard, { Skeleton: PatientCardSkeleton });
|