improvement: v1 standalone treatment/case creation made possible.

This commit is contained in:
2026-08-19 15:05:58 +03:30
parent e5c39c6b7e
commit 8bfa8c88fe
39 changed files with 3296 additions and 387 deletions

View File

@@ -8,6 +8,7 @@ import { useAuth } from '@/lib/hooks/useAuth';
import { useToast } from '@/lib/hooks/useToast';
import { canEditCases, canEditTasks } from '@/components/shared/permissions';
import { CaseDetailPanel, CaseTaskProgressBar } from '@/components/ui/lab/CaseDetailPanel';
import { CaseCreatePanel } from '@/components/ui/lab/CaseCreatePanel';
import { LabCaseProsthesisGroupsList } from '@/components/ui/lab/LabCaseProsthesisGroupsList';
import { LabCaseCommentsPanel } from '@/components/ui/lab/LabCaseCommentsPanel';
import {
@@ -79,6 +80,7 @@ export function CasesPage() {
const [assignableStaff, setAssignableStaff] = useState<AssignableTaskStaff[]>([]);
const [assigningTaskId, setAssigningTaskId] = useState<string | null>(null);
const [commentCount, setCommentCount] = useState(0);
const [creatingCase, setCreatingCase] = useState(false);
const canEdit = canEditCases(currentOrganization);
const canEditComments = canEditTasks(currentOrganization);
@@ -260,7 +262,7 @@ export function CasesPage() {
item.id === selectedCaseId ? { ...item, hasUnread: false } : item,
),
);
});
}).catch(() => undefined);
void tasksApi
.listComments(selectedCaseId)
.then((r) => setCommentCount(r.data.length))
@@ -272,6 +274,34 @@ export function CasesPage() {
// eslint-disable-next-line react-hooks/exhaustive-deps -- reload when selection changes
}, [selectedCaseId]);
async function handleAddCase() {
if (!canEdit || creatingCase) return;
setCreatingCase(true);
toast.setError('');
try {
const response = await casesApi.create();
setSelectedCase(response.data);
setSelectedCaseId(response.data.id);
setMobileDetailOpen(true);
setPage(1);
await loadCases({
q: search,
clinicOrganizationId: clinicId,
prosthesisTypeCode,
sentFrom,
sentTo,
page: 1,
});
} catch (error: unknown) {
toast.showError(getUserFacingError(error, tErrors, t('errorCreateCase')));
} finally {
setCreatingCase(false);
}
}
const isDraftCase = (item: { origin?: string; startedAt?: string | null }) =>
item.origin === 'LAB_INTERNAL' && !item.startedAt;
function scrollToComments() {
document.getElementById('case-comments')?.scrollIntoView({ behavior: 'smooth' });
}
@@ -365,15 +395,30 @@ export function CasesPage() {
mobileDetailOpen && selectedCaseId ? 'hidden lg:flex' : 'flex'
}`}
>
<SearchBar
embedded
value={search}
onChange={(value) => {
setSearch(value);
setPage(1);
}}
placeholder={t('searchPlaceholder')}
/>
<div className="flex items-center justify-between gap-2">
<div className="min-w-0 flex-1">
<SearchBar
embedded
value={search}
onChange={(value) => {
setSearch(value);
setPage(1);
}}
placeholder={t('searchPlaceholder')}
/>
</div>
{canEdit ? (
<Button
variant="primary"
size="sm"
className="shrink-0"
disabled={creatingCase}
onClick={() => void handleAddCase()}
>
{t('addCase')}
</Button>
) : null}
</div>
<div className="grid gap-2 sm:grid-cols-2">
<label className="space-y-1">
@@ -484,6 +529,11 @@ export function CasesPage() {
{t('importantLabel')}
</Badge>
) : null}
{isDraftCase(item) ? (
<Badge variant="default" fixedWidth={false}>
{t('draftBadge')}
</Badge>
) : null}
</div>
{item.hasUnread ? (
<span
@@ -500,7 +550,9 @@ export function CasesPage() {
/>
</div>
<div className="text-[10px] text-text-muted mt-1">
{formatCaseDateTime(item.sentAt, locale)}
{isDraftCase(item)
? t('draftBadge')
: formatCaseDateTime(item.startedAt ?? item.sentAt, locale)}
</div>
<div className="mt-1.5">
<CaseTaskProgressBar
@@ -557,6 +609,57 @@ export function CasesPage() {
<p className="text-sm text-text-muted">{t('emptyList')}</p>
) : loadingDetail || !selectedCase ? (
<p className="text-sm text-text-muted">{tCommon('loading')}</p>
) : isDraftCase(selectedCase) ? (
<CaseCreatePanel
key={selectedCase.id}
labCase={selectedCase}
canEdit={canEdit}
onSaved={(detail) => {
setSelectedCase(detail);
setCases((prev) =>
prev.map((item) =>
item.id === detail.id
? {
...item,
startedAt: detail.startedAt,
origin: detail.origin,
dueDate: detail.dueDate,
clinic: detail.clinic,
patient: detail.patient,
}
: item,
),
);
}}
onStarted={(detail) => {
setSelectedCase(detail);
setCases((prev) =>
prev.map((item) =>
item.id === detail.id
? {
...item,
startedAt: detail.startedAt,
origin: detail.origin,
dueDate: detail.dueDate,
clinic: detail.clinic,
patient: detail.patient,
prosthesisGroups: item.prosthesisGroups,
taskProgress: detail.taskProgress,
}
: item,
),
);
void loadCases({
q: search,
clinicOrganizationId: clinicId,
prosthesisTypeCode,
sentFrom,
sentTo,
page,
});
}}
onError={toast.showError}
/>
) : (
<CaseDetailPanel
labCase={selectedCase}