Files
baya-monorepo/client/src/components/PatientCard/PatientCard.tsx
T
2026-07-27 22:27:04 +03:30

132 lines
4.6 KiB
TypeScript

'use client';
import { FunctionComponent } from 'react';
import Box from '@mui/material/Box';
import ButtonBase from '@mui/material/ButtonBase';
import Skeleton from '@mui/material/Skeleton';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { AppIcon, 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;
/**
* Pre-formatted "آخرین ویزیت: …" teaser, best-effort from the cached bookings list. Omitted (never
* fabricated) when no cached booking can be matched to this patient — the server has no
* `lastVisitAt` field yet (filed as a REQ).
*/
lastVisitLabel?: 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,
lastVisitLabel,
}) => {
const header = (
<>
<PatientHeader
displayName={patient.displayName}
relationLabel={relationLabel}
genderLabel={genderLabel}
ageLabel={ageLabel}
conditionLabels={conditionLabels}
noConditionsLabel={noConditionsLabel}
/>
{lastVisitLabel ? (
<Typography variant="caption" sx={{ color: 'text.secondary', display: 'block', mt: 0.75 }}>
{lastVisitLabel}
</Typography>
) : null}
</>
);
return (
<SurfaceCard padding="sm">
<Stack direction="row" sx={{ alignItems: 'flex-start', gap: 1 }}>
{onOpen ? (
<ButtonBase
onClick={onOpen}
aria-label={openLabel}
sx={{
flexGrow: 1,
minWidth: 0,
justifyContent: 'flex-start',
textAlign: 'start',
borderRadius: 'var(--bal-radius-md)',
p: 1,
m: -1,
gap: 1,
'&:hover': { bgcolor: 'action.hover' },
}}
>
<Box sx={{ flexGrow: 1, minWidth: 0 }}>{header}</Box>
<AppIcon icon="forward" size={20} color="var(--bal-text-secondary)" />
</ButtonBase>
) : (
<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 });