* add doctor jobs view * change translation * another translation change * clean up * add analaysis detail view to paths config * translation * merge fix * fix path * move components to shared * refactor * imports * clean up
323 lines
8.7 KiB
TypeScript
323 lines
8.7 KiB
TypeScript
'use client';
|
|
|
|
import { useTransition } from 'react';
|
|
|
|
import Link from 'next/link';
|
|
import { useRouter } from 'next/navigation';
|
|
|
|
import { format } from 'date-fns';
|
|
import { Eye, LoaderCircle } from 'lucide-react';
|
|
|
|
import {
|
|
selectJobAction,
|
|
unselectJobAction,
|
|
} from '@kit/doctor/actions/doctor-server-actions';
|
|
import { getResultSetName } from '@kit/doctor/lib/helpers';
|
|
import { ResponseTable } from '@kit/doctor/schema/doctor-analysis.schema';
|
|
import { pathsConfig } from '@kit/shared/config';
|
|
import { getFullName } from '@kit/shared/utils';
|
|
import { useUser } from '@kit/supabase/hooks/use-user';
|
|
import { Button } from '@kit/ui/button';
|
|
import { toast } from '@kit/ui/sonner';
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from '@kit/ui/table';
|
|
import { Trans } from '@kit/ui/trans';
|
|
|
|
function DoctorCell({
|
|
doctorUserId,
|
|
doctorName,
|
|
analysisOrderId,
|
|
userId,
|
|
isRemovable,
|
|
onJobUpdate,
|
|
linkTo,
|
|
}: {
|
|
doctorUserId?: string;
|
|
doctorName?: string;
|
|
analysisOrderId: number;
|
|
userId: string;
|
|
isRemovable?: boolean;
|
|
linkTo: string;
|
|
onJobUpdate: () => void;
|
|
}) {
|
|
const [isPending, startTransition] = useTransition();
|
|
const router = useRouter();
|
|
|
|
const handleSelectJob = () => {
|
|
startTransition(async () => {
|
|
const result = await selectJobAction({
|
|
analysisOrderId,
|
|
userId,
|
|
});
|
|
|
|
if (result?.success) {
|
|
onJobUpdate();
|
|
router.push(linkTo);
|
|
} else {
|
|
toast.error('common.genericServerError');
|
|
}
|
|
});
|
|
};
|
|
|
|
const handleUnselectJob = () => {
|
|
startTransition(async () => {
|
|
const result = await unselectJobAction({
|
|
analysisOrderId,
|
|
});
|
|
|
|
if (result?.success) {
|
|
onJobUpdate();
|
|
} else {
|
|
toast.error('common.genericServerError');
|
|
}
|
|
});
|
|
};
|
|
|
|
if (isRemovable) {
|
|
return (
|
|
<Button
|
|
className="w-16"
|
|
size="sm"
|
|
variant="destructive"
|
|
onClick={handleUnselectJob}
|
|
disabled={isPending}
|
|
>
|
|
{isPending ? (
|
|
<LoaderCircle className="h-4 w-4 animate-spin" />
|
|
) : (
|
|
<Trans i18nKey="doctor:unselectJob" />
|
|
)}
|
|
</Button>
|
|
);
|
|
}
|
|
|
|
if (!doctorUserId) {
|
|
return (
|
|
<Button
|
|
className="w-16"
|
|
size="sm"
|
|
onClick={handleSelectJob}
|
|
disabled={isPending}
|
|
>
|
|
{isPending ? (
|
|
<LoaderCircle className="h-4 w-4 animate-spin" />
|
|
) : (
|
|
<Trans i18nKey="doctor:selectJob" />
|
|
)}
|
|
</Button>
|
|
);
|
|
}
|
|
|
|
return <>{doctorName}</>;
|
|
}
|
|
|
|
export default function ResultsTable({
|
|
results = [],
|
|
pagination = {
|
|
currentPage: 1,
|
|
totalPages: 1,
|
|
totalCount: 0,
|
|
pageSize: 10,
|
|
},
|
|
fetchAction,
|
|
onJobUpdate,
|
|
}: {
|
|
results: ResponseTable[] | null;
|
|
pagination?: {
|
|
currentPage: number;
|
|
totalPages: number;
|
|
totalCount: number;
|
|
pageSize: number;
|
|
};
|
|
fetchAction: ({
|
|
page,
|
|
pageSize,
|
|
}: {
|
|
page: number;
|
|
pageSize: number;
|
|
}) => Promise<{
|
|
success: boolean;
|
|
data: null;
|
|
}>;
|
|
onJobUpdate: () => void;
|
|
}) {
|
|
const [isPending, startTransition] = useTransition();
|
|
const { data: currentUser } = useUser();
|
|
|
|
const fetchPage = async (page: number) => {
|
|
startTransition(async () => {
|
|
const result = await fetchAction({
|
|
page,
|
|
pageSize: pagination.pageSize,
|
|
});
|
|
if (!result.success) {
|
|
toast.error('common.genericServerError');
|
|
}
|
|
});
|
|
};
|
|
|
|
const handleNextPage = () => {
|
|
if (pagination.currentPage < pagination.totalPages) {
|
|
fetchPage(pagination.currentPage + 1);
|
|
}
|
|
};
|
|
|
|
const handlePrevPage = () => {
|
|
if (pagination.currentPage > 1) {
|
|
fetchPage(pagination.currentPage - 1);
|
|
}
|
|
};
|
|
|
|
const handleJobUpdate = () => {
|
|
onJobUpdate();
|
|
fetchPage(pagination.currentPage);
|
|
};
|
|
|
|
if (!results?.length) {
|
|
return (
|
|
<p>
|
|
<Trans i18nKey="common:noData" />.
|
|
</p>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Table className="border-separate rounded-lg border">
|
|
<TableHeader className="text-ui-fg-subtle txt-medium-plus">
|
|
<TableRow>
|
|
<TableHead className="w-6"></TableHead>
|
|
<TableHead className="w-20">
|
|
<Trans i18nKey="doctor:resultsTable.patientName" />
|
|
</TableHead>
|
|
<TableHead className="w-20">
|
|
<Trans i18nKey="doctor:resultsTable.serviceName" />
|
|
</TableHead>
|
|
<TableHead className="w-20">
|
|
<Trans i18nKey="doctor:resultsTable.orderNr" />
|
|
</TableHead>
|
|
<TableHead className="w-20">
|
|
<Trans i18nKey="doctor:resultsTable.time" />
|
|
</TableHead>
|
|
<TableHead className="w-20">
|
|
<Trans i18nKey="doctor:resultsTable.resultsStatus" />
|
|
</TableHead>
|
|
<TableHead className="w-20">
|
|
<Trans i18nKey="doctor:resultsTable.assignedTo" />
|
|
</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{results
|
|
?.sort((a, b) =>
|
|
(a.created_at ?? '') > (b.created_at ?? '') ? -1 : 1,
|
|
)
|
|
.map((result) => {
|
|
const isCompleted = result.feedback?.status === 'COMPLETED';
|
|
const isCurrentDoctorJob =
|
|
!!result.doctor?.primary_owner_user_id &&
|
|
result.doctor?.primary_owner_user_id === currentUser?.id;
|
|
|
|
const resultsReceived = result.elements.length || 0;
|
|
const elementsInOrder = Array.from(
|
|
new Set(result.analysis_order_id.analysis_element_ids),
|
|
)?.length;
|
|
|
|
return (
|
|
<TableRow key={result.order_number}>
|
|
<TableCell className="text-center">
|
|
<Link
|
|
href={`/${pathsConfig.app.analysisDetails}/${result.id}`}
|
|
>
|
|
<Eye />
|
|
</Link>
|
|
</TableCell>
|
|
<TableCell>
|
|
{getFullName(
|
|
result.patient?.name,
|
|
result.patient?.last_name,
|
|
)}
|
|
</TableCell>
|
|
<TableCell>
|
|
<Trans
|
|
i18nKey={getResultSetName(
|
|
result.order?.title ?? '-',
|
|
result.order?.isPackage,
|
|
result.elements?.length || 0,
|
|
)}
|
|
/>
|
|
</TableCell>
|
|
<TableCell>{result.order_number}</TableCell>
|
|
<TableCell>
|
|
{result.firstSampleGivenAt
|
|
? format(result.firstSampleGivenAt, 'dd.MM.yyyy HH:mm')
|
|
: '-'}
|
|
</TableCell>
|
|
<TableCell>
|
|
<Trans
|
|
i18nKey={
|
|
resultsReceived === elementsInOrder
|
|
? 'doctor:resultsTable.responsesReceived'
|
|
: 'doctor:resultsTable.waitingForNr'
|
|
}
|
|
values={{
|
|
nr: elementsInOrder - resultsReceived,
|
|
}}
|
|
/>
|
|
</TableCell>
|
|
<TableCell>
|
|
<DoctorCell
|
|
doctorUserId={result.doctor?.primary_owner_user_id}
|
|
doctorName={getFullName(
|
|
result.doctor?.name,
|
|
result.doctor?.last_name,
|
|
)}
|
|
analysisOrderId={result.analysis_order_id?.id}
|
|
userId={result.patient?.id}
|
|
isRemovable={!isCompleted && isCurrentDoctorJob}
|
|
onJobUpdate={handleJobUpdate}
|
|
linkTo={`/${pathsConfig.app.analysisDetails}/${result.id}`}
|
|
/>
|
|
</TableCell>
|
|
</TableRow>
|
|
);
|
|
})}
|
|
</TableBody>
|
|
</Table>
|
|
<div className="mt-4 flex items-center justify-between">
|
|
<Button
|
|
onClick={handlePrevPage}
|
|
disabled={pagination.currentPage === 1 || isPending}
|
|
variant="outline"
|
|
>
|
|
<Trans i18nKey="common:previous" />
|
|
</Button>
|
|
<span className="text-sm text-gray-600">
|
|
<Trans
|
|
i18nKey={'common:pageOfPages'}
|
|
values={{
|
|
page: pagination.currentPage,
|
|
total: pagination.totalPages,
|
|
}}
|
|
/>
|
|
</span>
|
|
<Button
|
|
onClick={handleNextPage}
|
|
disabled={
|
|
pagination.currentPage === pagination.totalPages || isPending
|
|
}
|
|
variant="outline"
|
|
>
|
|
<Trans i18nKey="common:next" />
|
|
</Button>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|