Files
baya-monorepo/client/src/components/PatientCard/PatientCard.tsx
T
2026-07-17 17:10:39 +03:30

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 });