improvement: treatment UX fully overhauled.

This commit is contained in:
2026-07-13 01:03:26 +03:30
parent abf0371a5b
commit f28cd06615
27 changed files with 1370 additions and 212 deletions

View File

@@ -4,9 +4,9 @@ import { useEffect, useState } from 'react';
import { useTranslations } from 'next-intl';
import { Button } from '@/components/ui/shared/Button';
import { Checkbox } from '@/components/ui/shared/Checkbox';
import { Dropdown } from '@/components/ui/shared/Dropdown';
import { isDetailReadyForLabDispatch } from '@/components/treatment/treatmentDetailRules';
import { FORM_SELECT_CLASS } from '@/components/shared/formSelectStyles';
import { SearchBar } from '@/components/ui/shared/SearchBar';
import { LinkedOrganizationSearchCombobox } from '@/components/ui/treatment/LinkedOrganizationSearchCombobox';
import { CaseSentLabel } from '@/components/ui/treatment/CaseSentLabel';
import { LabCaseCommentsPanel } from '@/components/ui/lab/LabCaseCommentsPanel';
import { treatmentTypeLabelFromCatalog } from '@/components/shared/treatmentTypeDisplay';
@@ -30,6 +30,8 @@ interface LabCasesDispatchPanelProps {
onOrganizationSearchChange: (value: string) => void;
recentOrganizationIds: string[];
onRecentOrganizationPick: (orgId: string) => void;
canInviteLab?: boolean;
onInviteLab?: () => void;
sendBusyId: string | null;
onAddLabCase: () => void;
onSendLabCase: (labCase: LabCaseDraft, comment?: string) => void;
@@ -89,6 +91,8 @@ export function LabCasesDispatchPanel({
onOrganizationSearchChange,
recentOrganizationIds,
onRecentOrganizationPick,
canInviteLab = false,
onInviteLab,
sendBusyId,
onAddLabCase,
onSendLabCase,
@@ -100,18 +104,13 @@ export function LabCasesDispatchPanel({
const [pendingComment, setPendingComment] = useState('');
const activeLinkedOrganizations = orgs.filter((o) => o.active);
const filteredOrganizations = (() => {
const q = organizationSearch.trim().toLowerCase();
if (!q) return activeLinkedOrganizations;
return activeLinkedOrganizations.filter((o) => o.name.toLowerCase().includes(q));
})();
const recentOrganizations = recentOrganizationIds
.map((id) => activeLinkedOrganizations.find((o) => o.id === id))
.filter(Boolean) as LinkedOrganizationOption[];
const activeDetail = details.find((d) => d.clientId === activeDetailId) ?? null;
const isLabDependentDetail = Boolean(
activeDetail && labDependentCodes.has(activeDetail.treatmentType),
activeDetail && isDetailReadyForLabDispatch(activeDetail, labDependentCodes),
);
const labCaseForActiveDetail =
@@ -214,6 +213,14 @@ export function LabCasesDispatchPanel({
updateActiveLabCase({ attachmentIds: [...set] });
}
function handleSelectOrganization(org: LinkedOrganizationOption) {
updateActiveLabCase({
destinationOrganizationId: org.id,
toothProsthesis: [],
});
setApplyAllProsthesis('');
}
const activeDetailAttachments = activeDetail.attachmentMetas ?? [];
return (
@@ -340,11 +347,16 @@ export function LabCasesDispatchPanel({
<div className="space-y-2">
<p className="text-xs font-medium text-text-secondary">{t('selectLab')}</p>
<SearchBar
embedded
value={organizationSearch}
onChange={onOrganizationSearchChange}
placeholder={t('searchOrgsPlaceholder')}
<LinkedOrganizationSearchCombobox
search={organizationSearch}
onSearchChange={onOrganizationSearchChange}
organizations={activeLinkedOrganizations}
selectedOrganizationId={activeLabCase.destinationOrganizationId}
onSelectOrganization={handleSelectOrganization}
disabled={disabled}
canInviteLab={canInviteLab}
onInviteLab={onInviteLab}
noPermissionMessage={t('noOrgInvitePermission')}
/>
{recentOrganizations.length > 0 && (
<div className="flex flex-wrap items-center gap-2">
@@ -362,28 +374,6 @@ export function LabCasesDispatchPanel({
))}
</div>
)}
<Dropdown
value={activeLabCase.destinationOrganizationId ?? ''}
onChange={(e) => {
const nextOrgId = e.target.value || null;
updateActiveLabCase({
destinationOrganizationId: nextOrgId,
toothProsthesis: [],
});
setApplyAllProsthesis('');
}}
disabled={disabled || filteredOrganizations.length === 0}
>
<option value="">{t('selectLabPlaceholder')}</option>
{filteredOrganizations.map((o) => (
<option key={o.id} value={o.id}>
{o.name}
</option>
))}
</Dropdown>
{filteredOrganizations.length === 0 && (
<p className="text-xs text-text-muted">{t('noOrgMatch')}</p>
)}
</div>
{prosthesisRows.length > 0 && activeLabCase.destinationOrganizationId ? (
@@ -411,7 +401,7 @@ export function LabCasesDispatchPanel({
))}
</select>
</label>
<div className="overflow-x-auto">
<div className="overflow-x-auto overscroll-x-contain">
<table className="w-full text-sm">
<thead>
<tr className="text-left text-xs text-text-muted">