frontend phase 13
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { ThemeProvider } from '../../theme';
|
||||
import VisitNoteCard from './VisitNoteCard';
|
||||
import type { VisitNote } from '@/services/patientRecords/types';
|
||||
|
||||
const NOTE: VisitNote = {
|
||||
id: 8100,
|
||||
bookingId: 5005,
|
||||
nurseProfileId: 1,
|
||||
nurseDisplayName: 'مریم رضایی',
|
||||
body: 'وضعیت پایدار بود؛ داروها داده شد.',
|
||||
taskResults: [
|
||||
{ label: 'دادن متفورمین', done: true },
|
||||
{ label: 'پیادهروی کوتاه', done: false },
|
||||
],
|
||||
recordedAt: '2026-06-01T09:00:00Z',
|
||||
};
|
||||
|
||||
function renderCard(note: VisitNote = NOTE) {
|
||||
return render(
|
||||
<ThemeProvider>
|
||||
<VisitNoteCard note={note} dateLabel="۱ خرداد ۱۴۰۵" authorFallback="پرستار" />
|
||||
</ThemeProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
describe('<VisitNoteCard/> component', () => {
|
||||
it('renders the nurse name, date and body', () => {
|
||||
renderCard();
|
||||
expect(screen.getByText('مریم رضایی')).toBeInTheDocument();
|
||||
expect(screen.getByText('۱ خرداد ۱۴۰۵')).toBeInTheDocument();
|
||||
expect(screen.getByText('وضعیت پایدار بود؛ داروها داده شد.')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders each ticked task as a chip', () => {
|
||||
renderCard();
|
||||
expect(screen.getByText('دادن متفورمین')).toBeInTheDocument();
|
||||
expect(screen.getByText('پیادهروی کوتاه')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('falls back to the provided author label when the nurse name is missing', () => {
|
||||
renderCard({ ...NOTE, nurseDisplayName: null });
|
||||
expect(screen.getByText('پرستار')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
'use client';
|
||||
import { FunctionComponent } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { AppIcon } from '@/components/common';
|
||||
import type { VisitNote } from '@/services/patientRecords/types';
|
||||
|
||||
export interface VisitNoteCardProps {
|
||||
/** One nurse-authored visit note (read-only from the client's perspective). */
|
||||
note: VisitNote;
|
||||
/** Pre-formatted Shamsi date/time — the caller owns locale. */
|
||||
dateLabel: string;
|
||||
/** Shown when the note has no recorded nurse name. */
|
||||
authorFallback: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* One read-only visit note in the longitudinal history — the nurse's display name, the Shamsi date, the note
|
||||
* body, and (if any) the checklist the nurse ticked as done/not-done chips. Purely presentational (the caller
|
||||
* formats the date and supplies the author fallback); reused by the customer سوابق tab and the nurse
|
||||
* continuity view. Clinical text is rendered verbatim and never logged.
|
||||
* @component VisitNoteCard
|
||||
*/
|
||||
const VisitNoteCard: FunctionComponent<VisitNoteCardProps> = ({ note, dateLabel, authorFallback }) => {
|
||||
return (
|
||||
<Paper elevation={0} sx={{ p: 2, border: '1px solid', borderColor: 'divider', borderRadius: 2 }}>
|
||||
<Stack direction="row" sx={{ alignItems: 'center', gap: 1, mb: 1, flexWrap: 'wrap' }}>
|
||||
<AppIcon icon="notes" size={18} color="var(--bal-primary)" />
|
||||
<Typography variant="body2" sx={{ fontWeight: 700 }}>
|
||||
{note.nurseDisplayName?.trim() || authorFallback}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: 'text.secondary', marginInlineStart: 'auto' }}>
|
||||
{dateLabel}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
<Typography variant="body2" sx={{ whiteSpace: 'pre-wrap' }}>
|
||||
{note.body}
|
||||
</Typography>
|
||||
|
||||
{note.taskResults.length > 0 ? (
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5, mt: 1 }}>
|
||||
{note.taskResults.map((task, index) => (
|
||||
<Chip
|
||||
key={`${task.label}-${index}`}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
icon={
|
||||
<AppIcon
|
||||
icon={task.done ? 'verified' : 'pending'}
|
||||
size={14}
|
||||
color={task.done ? 'var(--bal-success)' : 'var(--bal-text-secondary)'}
|
||||
/>
|
||||
}
|
||||
label={task.label}
|
||||
sx={{ color: task.done ? undefined : 'text.secondary' }}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
) : null}
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export default VisitNoteCard;
|
||||
@@ -0,0 +1,4 @@
|
||||
import VisitNoteCard from './VisitNoteCard';
|
||||
|
||||
export type { VisitNoteCardProps } from './VisitNoteCard';
|
||||
export { VisitNoteCard as default, VisitNoteCard };
|
||||
Reference in New Issue
Block a user