improvement: v1 standalone treatment/case creation made possible.
This commit is contained in:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user