improvement: QRcode and shared link added to cases inorder to make it possible for staff users to share a case info with other staffs or other clinics.
This commit is contained in:
216
frontend/src/components/ui/lab/CaseTasksFocusView.tsx
Normal file
216
frontend/src/components/ui/lab/CaseTasksFocusView.tsx
Normal file
@@ -0,0 +1,216 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { TaskCaseGroupHeader } from '@/components/ui/lab/TaskCaseGroupHeader';
|
||||
import { TaskProsthesisGroupHeader } from '@/components/ui/lab/TaskProsthesisGroupHeader';
|
||||
import { TaskRow } from '@/components/ui/lab/TaskRow';
|
||||
import { LabCaseCommentsPanel } from '@/components/ui/lab/LabCaseCommentsPanel';
|
||||
import { groupTasksForDisplay } from '@/components/lab/taskListGrouping';
|
||||
import { canEditLabTaskStatus } from '@/components/lab/labTaskStatusDisplay';
|
||||
import { getUserFacingError } from '@/components/shared/formatApiError';
|
||||
import { asApiError } from '@/types/api';
|
||||
import { useAuth } from '@/lib/hooks/useAuth';
|
||||
import { useToast } from '@/lib/hooks/useToast';
|
||||
import { labCaseAccessApi, type LabCaseAccessSession } from '@/lib/api/lab-case-access';
|
||||
import { prosthesisCatalogApi } from '@/lib/api/prosthesis-catalog';
|
||||
import { tasksApi } from '@/lib/api/tasks';
|
||||
import { notifyTabBadgesChanged } from '@/lib/tabBadgeUtils';
|
||||
import type { LabTaskListItem, LabTaskStatus } from '@/types/cases';
|
||||
import type { ProsthesisCatalogEntry } from '@/types/treatment-catalog';
|
||||
|
||||
interface CaseTasksFocusViewProps {
|
||||
token: string;
|
||||
}
|
||||
|
||||
export function CaseTasksFocusView({ token }: CaseTasksFocusViewProps) {
|
||||
const t = useTranslations('labCaseAccess');
|
||||
const tTasks = useTranslations('tasks');
|
||||
const tErrors = useTranslations('errors');
|
||||
const { user, isAuthReady } = useAuth();
|
||||
const { showError, showSuccess } = useToast();
|
||||
|
||||
const [session, setSession] = useState<LabCaseAccessSession | null>(null);
|
||||
const [tasks, setTasks] = useState<LabTaskListItem[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [accessDenied, setAccessDenied] = useState(false);
|
||||
const [updatingTaskId, setUpdatingTaskId] = useState<string | null>(null);
|
||||
const [prosthesisCatalog, setProsthesisCatalog] = useState<ProsthesisCatalogEntry[]>([]);
|
||||
|
||||
const locale = user?.language ?? 'en';
|
||||
const canEditTasks = session?.canEditTaskStatus ?? false;
|
||||
const canPostComments = session?.canPostComments ?? false;
|
||||
const canToggleCommentVisibility = session?.canToggleCommentVisibility ?? false;
|
||||
|
||||
const statusOptions: { value: LabTaskStatus; label: string }[] = useMemo(
|
||||
() => [
|
||||
{ value: 'IN_PROGRESS', label: tTasks('statusInProgress') },
|
||||
{ value: 'COMPLETED', label: tTasks('statusCompleted') },
|
||||
],
|
||||
[tTasks],
|
||||
);
|
||||
|
||||
const loadData = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setAccessDenied(false);
|
||||
try {
|
||||
const [sessionRes, tasksRes] = await Promise.all([
|
||||
labCaseAccessApi.resolve(token),
|
||||
labCaseAccessApi.listTasks(token),
|
||||
]);
|
||||
setSession(sessionRes.data);
|
||||
setTasks(tasksRes.data.items);
|
||||
} catch (error: unknown) {
|
||||
if (asApiError(error)?.code === 'LAB_CASE_ACCESS_DENIED') {
|
||||
setAccessDenied(true);
|
||||
} else {
|
||||
showError(getUserFacingError(error, tErrors, t('accessDenied')));
|
||||
}
|
||||
setSession(null);
|
||||
setTasks([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [showError, t, tErrors, token]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAuthReady || !user) return;
|
||||
void loadData();
|
||||
}, [isAuthReady, user, loadData]);
|
||||
|
||||
useEffect(() => {
|
||||
void prosthesisCatalogApi
|
||||
.list()
|
||||
.then((response) => setProsthesisCatalog(response.data))
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
const displayModel = useMemo(() => groupTasksForDisplay(tasks, 'date'), [tasks]);
|
||||
|
||||
const handleStatusUpdate = useCallback(
|
||||
async (taskId: string, status: LabTaskStatus) => {
|
||||
if (!canEditTasks || !user?.id) return;
|
||||
const task = tasks.find((item) => item.id === taskId);
|
||||
if (!task || !canEditLabTaskStatus(task, user.id, canEditTasks)) return;
|
||||
setUpdatingTaskId(taskId);
|
||||
try {
|
||||
await tasksApi.updateStatus(taskId, status);
|
||||
await loadData();
|
||||
notifyTabBadgesChanged();
|
||||
if (status === 'COMPLETED') {
|
||||
showSuccess(tTasks('taskCompletedToast'));
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
showError(getUserFacingError(error, tErrors, tTasks('errorUpdateTask')));
|
||||
} finally {
|
||||
setUpdatingTaskId(null);
|
||||
}
|
||||
},
|
||||
[canEditTasks, loadData, showError, showSuccess, tErrors, tTasks, tasks, user?.id],
|
||||
);
|
||||
|
||||
if (!isAuthReady || loading) {
|
||||
return <p className="text-sm text-text-muted">{t('loading')}</p>;
|
||||
}
|
||||
|
||||
if (accessDenied) {
|
||||
return (
|
||||
<div className="rounded-md border border-border bg-background-secondary/40 p-4 sm:p-6 text-center space-y-2 min-w-0">
|
||||
<p className="text-sm font-medium text-text-primary">{t('accessDeniedTitle')}</p>
|
||||
<p className="text-sm text-text-muted break-words">{t('accessDenied')}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!session || displayModel.mode !== 'grouped' || displayModel.cases.length === 0) {
|
||||
return <p className="text-sm text-text-muted">{t('emptyTasks')}</p>;
|
||||
}
|
||||
|
||||
const caseGroup = displayModel.cases[0];
|
||||
|
||||
return (
|
||||
<div className="space-y-4 min-w-0">
|
||||
<header className="space-y-1 border-b border-border pb-3 min-w-0">
|
||||
<h1 className="text-xl sm:text-2xl font-semibold text-text-primary">{t('pageTitle')}</h1>
|
||||
<p className="text-sm text-text-muted break-words">
|
||||
{t('caseSummary', {
|
||||
clinic: session.clinic.name,
|
||||
patient: `${session.patient.firstName} ${session.patient.lastName}`.trim(),
|
||||
})}
|
||||
</p>
|
||||
{session.lab ? (
|
||||
<p className="text-sm text-text-muted break-words">
|
||||
{t('labName', { name: session.lab.name })}
|
||||
</p>
|
||||
) : null}
|
||||
</header>
|
||||
|
||||
<section className="surface-card min-h-[280px] min-w-0 overflow-x-hidden">
|
||||
<section className="border-b border-border last:border-b-0">
|
||||
<TaskCaseGroupHeader caseGroup={caseGroup} locale={locale} />
|
||||
{caseGroup.prosthesisGroups.map((prosthesisGroup) => (
|
||||
<div key={prosthesisGroup.key} className="border-t border-border/50 first:border-t-0">
|
||||
<TaskProsthesisGroupHeader
|
||||
group={prosthesisGroup}
|
||||
prosthesisCatalog={prosthesisCatalog}
|
||||
/>
|
||||
<ul>
|
||||
{prosthesisGroup.tasks.map((task) => (
|
||||
<TaskRow
|
||||
key={task.id}
|
||||
task={task}
|
||||
locale={locale}
|
||||
flatMode={false}
|
||||
canEdit={canEditTasks}
|
||||
currentUserId={user?.id}
|
||||
statusOptions={statusOptions}
|
||||
updatingTaskId={updatingTaskId}
|
||||
commentsOpen={false}
|
||||
showCommentsButton={false}
|
||||
prosthesisCatalog={prosthesisCatalog}
|
||||
onStatusUpdate={(id, status) => void handleStatusUpdate(id, status)}
|
||||
onToggleComments={() => {}}
|
||||
onCommentError={showError}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section className="surface-card p-3 sm:p-4 min-w-0 overflow-x-hidden">
|
||||
<LabCaseCommentsPanel
|
||||
caseId={session.labCaseId}
|
||||
canPost={canPostComments}
|
||||
canToggleVisibility={canToggleCommentVisibility}
|
||||
loadComments={async () => {
|
||||
const r = await labCaseAccessApi.listComments(token);
|
||||
return r.data;
|
||||
}}
|
||||
onPost={async (body, visibleToClinic) => {
|
||||
const r = await labCaseAccessApi.addComment(token, {
|
||||
body,
|
||||
visibleToClinic,
|
||||
});
|
||||
notifyTabBadgesChanged();
|
||||
return r.data;
|
||||
}}
|
||||
onToggleVisibility={
|
||||
canToggleCommentVisibility
|
||||
? async (commentId, visible) => {
|
||||
const r = await labCaseAccessApi.setCommentVisibility(
|
||||
token,
|
||||
commentId,
|
||||
visible,
|
||||
);
|
||||
return r.data;
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
onError={showError}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user