improvement: treatment preview wizard optimized. comments component updated and unified accross the app.

This commit is contained in:
2026-07-17 12:30:43 +03:30
parent 58676f702e
commit 4b2349228a
19 changed files with 163 additions and 149 deletions

View File

@@ -45,19 +45,10 @@ interface LabCasesDispatchPanelProps {
canInviteLab?: boolean;
onInviteLab?: () => void;
sendBusyId: string | null;
onAddLabCase: () => void | Promise<void>;
onSendLabCase: (labCase: LabCaseDraft, comment?: string) => void | Promise<void>;
onCommentError?: (message: string) => void;
}
function sentDetailClientIds(labCases: LabCaseDraft[]): Set<string> {
const ids = new Set<string>();
for (const lc of labCases) {
if (lc.sentAt && lc.detailClientId) ids.add(lc.detailClientId);
}
return ids;
}
type ProsthesisGroupRow = {
groupId: string;
kind: 'connected' | 'single';
@@ -129,7 +120,6 @@ export function LabCasesDispatchPanel({
canInviteLab = false,
onInviteLab,
sendBusyId,
onAddLabCase,
onSendLabCase,
onCommentError,
}: LabCasesDispatchPanelProps) {
@@ -157,8 +147,6 @@ export function LabCasesDispatchPanel({
(activeLabCaseId ? labCases.find((lc) => lc.clientId === activeLabCaseId) : null);
const detailAlreadyInShipment = Boolean(labCaseForActiveDetail);
const canAddLabShipment =
!detailAlreadyInShipment && !sentDetailClientIds(labCases).has(activeDetailId);
const sent = Boolean(activeLabCase?.sentAt);
const activeDetailNumber = details.findIndex((d) => d.clientId === activeDetailId) + 1;
@@ -289,23 +277,14 @@ export function LabCasesDispatchPanel({
}
}
function renderShipmentCardHeader() {
return (
<div className="flex flex-col gap-3 border-b border-border/60 pb-3 sm:flex-row sm:items-start sm:justify-between">
<p className="text-xs font-semibold text-text-primary">{t('labShipmentIncludedDetails')}</p>
{renderDueDateField()}
</div>
);
}
function renderDueDateField() {
if (!activeLabCase) return null;
const inputValue = toDateInputValue(activeLabCase.dueDate);
const dueDateInputId = `lab-case-due-date-${activeLabCase.clientId}`;
return (
<div className="shrink-0 sm:text-end">
<div className="flex items-center gap-2 sm:justify-end">
<div className="w-full min-w-0 sm:w-auto sm:shrink-0 sm:text-end">
<div className="flex flex-wrap items-center gap-2 sm:justify-end">
<label
htmlFor={dueDateInputId}
className="text-xs font-medium text-text-secondary shrink-0"
@@ -323,7 +302,7 @@ export function LabCasesDispatchPanel({
onBlur={(committed) => {
if (sent) void handleSentDueDateBlur(committed);
}}
className={`${FORM_SELECT_CLASS} w-full max-w-[11rem] rounded-md py-1.5 text-sm`}
className={`${FORM_SELECT_CLASS} w-full min-w-0 max-w-full sm:max-w-[11rem] rounded-md py-1.5 text-sm`}
/>
</div>
{sent && caseFullyComplete && activeLabCase.dueDate ? (
@@ -335,7 +314,6 @@ export function LabCasesDispatchPanel({
function renderIncludedDetailSummary() {
if (!activeDetail) return null;
const typeLabel = treatmentTypeLabelFromCatalog(activeDetail.treatmentType, treatmentCatalog);
if (activeDetail.treatmentType !== 'prosthesis' || !activeLabCase) {
return (
@@ -375,9 +353,6 @@ export function LabCasesDispatchPanel({
return (
<div className="text-sm text-text-primary rounded-[var(--radius-sm)] border border-border/50 bg-background-secondary/50 px-3 py-2 space-y-1">
<p className="text-text-primary">
{t('detailLabel', { n: activeDetailNumber })} · {typeLabel}
</p>
{rows.map((g) => (
<p
key={g.groupId}
@@ -401,31 +376,19 @@ export function LabCasesDispatchPanel({
const activeDetailAttachments = activeDetail.attachmentMetas ?? [];
return (
<div className="surface-card p-4 space-y-4">
<div className="flex flex-wrap items-center justify-between gap-3">
<div>
<div className="surface-card p-3 sm:p-4 space-y-4 min-w-0 overflow-x-hidden">
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div className="min-w-0">
<h3 className="text-sm font-semibold text-text-primary">{t('labDispatchTitle')}</h3>
<p className="text-xs text-text-muted mt-0.5">
{t('labDispatchSubtitle')} {t('labDispatchSendHint')}
</p>
</div>
{canAddLabShipment && (
<Button
type="button"
variant="primary"
disabled={!canEdit || disabled}
onClick={onAddLabCase}
>
{t('addLabShipment')}
</Button>
)}
{detailAlreadyInShipment ? renderDueDateField() : null}
</div>
{!detailAlreadyInShipment ? (
<p className="text-xs text-text-muted">{t('labDispatchEmpty')}</p>
) : activeLabCase ? (
<div className="space-y-4 border border-border/60 rounded-[var(--radius-md)] p-3 sm:p-4 bg-background-secondary/30">
{renderShipmentCardHeader()}
{activeLabCase ? (
<div className="space-y-4 border border-border/60 rounded-[var(--radius-md)] p-3 sm:p-4 bg-background-secondary/30 min-w-0">
{sent ? (
<>
{renderIncludedDetailSummary()}
@@ -589,39 +552,38 @@ export function LabCasesDispatchPanel({
return (
<label
key={row.groupId}
className="block text-xs text-text-muted space-y-1 rounded-[var(--radius-sm)] border border-border/50 bg-background-secondary/40 px-3 py-2"
className="block text-xs text-text-muted rounded-[var(--radius-sm)] border border-border/50 bg-background-secondary/40 px-3 py-2 min-w-0"
>
<span className="flex flex-wrap items-center gap-2 text-text-secondary">
{row.kind === 'connected' ? <ConnectedSelectionBadge /> : null}
<span>
{row.kind === 'connected'
? t('prosthesisConnectedLabel')
: t('prosthesisColTooth')}
{': '}
<span className="text-text-primary">{row.teeth.join(', ')}</span>
</span>
<span className="text-text-muted">
· {t('detailLabel', { n: row.detailNumber })}
<span className="grid grid-cols-1 gap-2 md:grid-cols-2 md:items-center md:gap-3">
<span className="flex flex-wrap items-center gap-2 text-text-secondary min-w-0 break-words">
{row.kind === 'connected' ? <ConnectedSelectionBadge /> : null}
<span className="min-w-0">
{row.kind === 'connected'
? t('prosthesisConnectedLabel')
: t('prosthesisColTooth')}
{': '}
<span className="text-text-primary">{row.teeth.join(', ')}</span>
</span>
</span>
<select
value={current}
disabled={disabled}
onChange={(e) => setGroupProsthesis(row, e.target.value)}
className={`${FORM_SELECT_CLASS} w-full min-w-0`}
aria-label={
row.kind === 'connected'
? t('prosthesisConnectedLabel')
: t('prosthesisColType')
}
>
<option value="">{t('prosthesisSelectPlaceholder')}</option>
{prosthesisOptions.map((opt) => (
<option key={opt.code} value={opt.code}>
{opt.label}
</option>
))}
</select>
</span>
<select
value={current}
disabled={disabled}
onChange={(e) => setGroupProsthesis(row, e.target.value)}
className={`${FORM_SELECT_CLASS} w-full mt-1`}
aria-label={
row.kind === 'connected'
? t('prosthesisConnectedLabel')
: t('prosthesisColType')
}
>
<option value="">{t('prosthesisSelectPlaceholder')}</option>
{prosthesisOptions.map((opt) => (
<option key={opt.code} value={opt.code}>
{opt.label}
</option>
))}
</select>
</label>
);
})}