MED-137: add doctor other jobs view (#55)
* 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
This commit is contained in:
322
app/doctor/_components/results-table.tsx
Normal file
322
app/doctor/_components/results-table.tsx
Normal file
@@ -0,0 +1,322 @@
|
||||
'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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user