MED-90: improve doctor analysis detail view (#57)
* add doctor jobs view * change translation * another translation change * clean up * add analaysis detail view to paths config * translation * merge fix * fix path * MED-90: improve doctor analysis detail view * add key
This commit is contained in:
@@ -19,7 +19,7 @@ import {
|
|||||||
} from '@kit/doctor/schema/doctor-analysis-detail-view.schema';
|
} from '@kit/doctor/schema/doctor-analysis-detail-view.schema';
|
||||||
import {
|
import {
|
||||||
DoctorAnalysisFeedbackForm,
|
DoctorAnalysisFeedbackForm,
|
||||||
doctorAnalysisFeedbackSchema,
|
doctorAnalysisFeedbackFormSchema,
|
||||||
} from '@kit/doctor/schema/doctor-analysis.schema';
|
} from '@kit/doctor/schema/doctor-analysis.schema';
|
||||||
import ConfirmationModal from '@kit/shared/components/confirmation-modal';
|
import ConfirmationModal from '@kit/shared/components/confirmation-modal';
|
||||||
import { getFullName } from '@kit/shared/utils';
|
import { getFullName } from '@kit/shared/utils';
|
||||||
@@ -36,9 +36,11 @@ import { toast } from '@kit/ui/sonner';
|
|||||||
import { Textarea } from '@kit/ui/textarea';
|
import { Textarea } from '@kit/ui/textarea';
|
||||||
import { Trans } from '@kit/ui/trans';
|
import { Trans } from '@kit/ui/trans';
|
||||||
|
|
||||||
import Analysis from '~/home/(user)/(dashboard)/analysis-results/_components/analysis';
|
|
||||||
import { bmiFromMetric } from '~/lib/utils';
|
import { bmiFromMetric } from '~/lib/utils';
|
||||||
|
|
||||||
|
import DoctorAnalysisWrapper from './doctor-analysis-wrapper';
|
||||||
|
import DoctorJobSelect from './doctor-job-select';
|
||||||
|
|
||||||
export default function AnalysisView({
|
export default function AnalysisView({
|
||||||
patient,
|
patient,
|
||||||
order,
|
order,
|
||||||
@@ -54,16 +56,20 @@ export default function AnalysisView({
|
|||||||
|
|
||||||
const { data: user } = useUser();
|
const { data: user } = useUser();
|
||||||
|
|
||||||
const isInProgress =
|
const isInProgress = !!(
|
||||||
!!feedback?.status &&
|
!!feedback?.status &&
|
||||||
feedback?.doctor_user_id &&
|
feedback?.doctor_user_id &&
|
||||||
feedback?.status !== 'COMPLETED';
|
feedback?.status !== 'COMPLETED'
|
||||||
|
);
|
||||||
|
const isCurrentDoctorJob =
|
||||||
|
!!feedback?.doctor_user_id && feedback?.doctor_user_id === user?.id;
|
||||||
const isReadOnly =
|
const isReadOnly =
|
||||||
!isInProgress ||
|
!isInProgress ||
|
||||||
(!!feedback?.doctor_user_id && feedback?.doctor_user_id !== user?.id);
|
(!!feedback?.doctor_user_id && feedback?.doctor_user_id !== user?.id);
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
resolver: zodResolver(doctorAnalysisFeedbackSchema),
|
resolver: zodResolver(doctorAnalysisFeedbackFormSchema),
|
||||||
|
reValidateMode: 'onChange',
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
feedbackValue: feedback?.value ?? '',
|
feedbackValue: feedback?.value ?? '',
|
||||||
userId: patient.userId,
|
userId: patient.userId,
|
||||||
@@ -103,12 +109,22 @@ export default function AnalysisView({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDraftSubmit = () => {
|
const handleDraftSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
form.formState.errors.feedbackValue = undefined;
|
||||||
const formData = form.getValues();
|
const formData = form.getValues();
|
||||||
onSubmit(formData, 'DRAFT');
|
onSubmit(formData, 'DRAFT');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCompleteSubmit = () => {
|
const handleCompleteSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const isValid = await form.trigger();
|
||||||
|
if (!isValid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setIsConfirmOpen(true);
|
setIsConfirmOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -119,16 +135,31 @@ export default function AnalysisView({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h3>
|
<div className="xs:flex xs:justify-between">
|
||||||
<Trans
|
<h3>
|
||||||
i18nKey={getResultSetName(
|
<Trans
|
||||||
order.title,
|
i18nKey={getResultSetName(
|
||||||
order.isPackage,
|
order.title,
|
||||||
Object.keys(analyses)?.length,
|
order.isPackage,
|
||||||
)}
|
Object.keys(analyses)?.length,
|
||||||
/>
|
)}
|
||||||
</h3>
|
/>
|
||||||
<div className="grid grid-cols-2">
|
</h3>
|
||||||
|
<div className="xs:flex hidden">
|
||||||
|
<DoctorJobSelect
|
||||||
|
analysisOrderId={order.analysisOrderId}
|
||||||
|
userId={patient.userId}
|
||||||
|
doctorUserId={feedback?.doctor_user_id}
|
||||||
|
isRemovable={isCurrentDoctorJob && isInProgress}
|
||||||
|
onJobUpdate={() =>
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
predicate: (query) => query.queryKey.includes('doctor-jobs'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="xs:grid-cols-2 grid">
|
||||||
<div className="font-bold">
|
<div className="font-bold">
|
||||||
<Trans i18nKey="doctor:name" />
|
<Trans i18nKey="doctor:name" />
|
||||||
</div>
|
</div>
|
||||||
@@ -156,7 +187,7 @@ export default function AnalysisView({
|
|||||||
<div className="font-bold">
|
<div className="font-bold">
|
||||||
<Trans i18nKey="doctor:smoking" />
|
<Trans i18nKey="doctor:smoking" />
|
||||||
</div>
|
</div>
|
||||||
<div></div>
|
<div>-</div>
|
||||||
<div className="font-bold">
|
<div className="font-bold">
|
||||||
<Trans i18nKey="doctor:phone" />
|
<Trans i18nKey="doctor:phone" />
|
||||||
</div>
|
</div>
|
||||||
@@ -166,30 +197,37 @@ export default function AnalysisView({
|
|||||||
</div>
|
</div>
|
||||||
<div>{patient.email}</div>
|
<div>{patient.email}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="xs:hidden block">
|
||||||
|
<DoctorJobSelect
|
||||||
|
className="w-full"
|
||||||
|
analysisOrderId={order.analysisOrderId}
|
||||||
|
userId={patient.userId}
|
||||||
|
doctorUserId={feedback?.doctor_user_id}
|
||||||
|
isRemovable={isCurrentDoctorJob && isInProgress}
|
||||||
|
onJobUpdate={() =>
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
predicate: (query) => query.queryKey.includes('doctor-jobs'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<h3>
|
<h3>
|
||||||
<Trans i18nKey="doctor:results" />
|
<Trans i18nKey="doctor:results" />
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{analyses.map((analysisData) => {
|
{analyses.map((analysisData) => {
|
||||||
return (
|
return (
|
||||||
<Analysis
|
<DoctorAnalysisWrapper
|
||||||
key={analysisData.id}
|
key={analysisData.id}
|
||||||
analysisElement={{
|
analysisData={analysisData}
|
||||||
analysis_name_lab: analysisData.analysis_name,
|
|
||||||
}}
|
|
||||||
results={analysisData}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>
|
<h3>
|
||||||
<Trans i18nKey="doctor:feedback" />
|
<Trans i18nKey="doctor:feedback" />
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<p>{feedback?.value ?? '-'}</p>
|
<p>{feedback?.value ?? '-'}</p>
|
||||||
|
|
||||||
{!isReadOnly && (
|
{!isReadOnly && (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form className="space-y-4 lg:w-1/2">
|
<form className="space-y-4 lg:w-1/2">
|
||||||
@@ -206,23 +244,21 @@ export default function AnalysisView({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex gap-2">
|
<div className="xs:flex block justify-end gap-2 space-y-2">
|
||||||
<Button
|
<Button
|
||||||
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={(e) => {
|
onClick={handleDraftSubmit}
|
||||||
e.preventDefault();
|
|
||||||
handleDraftSubmit();
|
|
||||||
}}
|
|
||||||
disabled={isReadOnly}
|
disabled={isReadOnly}
|
||||||
|
className="xs:w-1/4 w-full"
|
||||||
>
|
>
|
||||||
<Trans i18nKey="common:saveAsDraft" />
|
<Trans i18nKey="common:saveAsDraft" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={(e) => {
|
type="button"
|
||||||
e.preventDefault();
|
onClick={handleCompleteSubmit}
|
||||||
handleCompleteSubmit();
|
|
||||||
}}
|
|
||||||
disabled={isReadOnly}
|
disabled={isReadOnly}
|
||||||
|
className="xs:w-1/4 w-full"
|
||||||
>
|
>
|
||||||
<Trans i18nKey="common:save" />
|
<Trans i18nKey="common:save" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
103
app/doctor/_components/doctor-analysis-wrapper.tsx
Normal file
103
app/doctor/_components/doctor-analysis-wrapper.tsx
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { CaretDownIcon, QuestionMarkCircledIcon } from '@radix-ui/react-icons';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { AnalysisResponse } from '@kit/doctor/schema/doctor-analysis-detail-view.schema';
|
||||||
|
import { InfoTooltip } from '@kit/shared/components/ui/info-tooltip';
|
||||||
|
import { formatDate } from '@kit/shared/utils';
|
||||||
|
import {
|
||||||
|
Collapsible,
|
||||||
|
CollapsibleContent,
|
||||||
|
CollapsibleTrigger,
|
||||||
|
} from '@kit/ui/collapsible';
|
||||||
|
import { Trans } from '@kit/ui/trans';
|
||||||
|
|
||||||
|
import Analysis from '~/home/(user)/(dashboard)/analysis-results/_components/analysis';
|
||||||
|
|
||||||
|
export default function DoctorAnalysisWrapper({
|
||||||
|
analysisData,
|
||||||
|
}: {
|
||||||
|
analysisData: AnalysisResponse;
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Collapsible className="w-full" key={analysisData.id}>
|
||||||
|
<CollapsibleTrigger
|
||||||
|
disabled={!analysisData.latestPreviousAnalysis}
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<div className="[&[data-state=open]_.caret-icon]:rotate-180">
|
||||||
|
<Analysis
|
||||||
|
startIcon={
|
||||||
|
analysisData.latestPreviousAnalysis && (
|
||||||
|
<CaretDownIcon className="caret-icon transition-transform duration-200" />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
endIcon={
|
||||||
|
analysisData.comment && (
|
||||||
|
<>
|
||||||
|
<div className="xs:flex hidden">
|
||||||
|
<InfoTooltip
|
||||||
|
content={analysisData.comment}
|
||||||
|
icon={
|
||||||
|
<QuestionMarkCircledIcon className="mx-2 text-blue-800" />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className="xs:hidden">
|
||||||
|
<strong>
|
||||||
|
<Trans i18nKey="doctor:labComment" />:
|
||||||
|
</strong>{' '}
|
||||||
|
{analysisData.comment}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
analysisElement={{
|
||||||
|
analysis_name_lab: analysisData.analysis_name,
|
||||||
|
}}
|
||||||
|
results={analysisData}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
{analysisData.latestPreviousAnalysis && (
|
||||||
|
<CollapsibleContent>
|
||||||
|
<div className="my-1 flex flex-col">
|
||||||
|
<Analysis
|
||||||
|
endIcon={
|
||||||
|
analysisData.latestPreviousAnalysis.comment && (
|
||||||
|
<>
|
||||||
|
<div className="xs:flex hidden">
|
||||||
|
<InfoTooltip
|
||||||
|
content={analysisData.latestPreviousAnalysis.comment}
|
||||||
|
icon={
|
||||||
|
<QuestionMarkCircledIcon className="mx-2 text-blue-800" />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className="xs:hidden">
|
||||||
|
<strong>
|
||||||
|
<Trans i18nKey="doctor:labComment" />:{' '}
|
||||||
|
</strong>
|
||||||
|
{analysisData.latestPreviousAnalysis.comment}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
analysisElement={{
|
||||||
|
analysis_name_lab: t('doctor:previousResults', {
|
||||||
|
date: formatDate(
|
||||||
|
analysisData.latestPreviousAnalysis.response_time,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
results={analysisData.latestPreviousAnalysis}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CollapsibleContent>
|
||||||
|
)}
|
||||||
|
</Collapsible>
|
||||||
|
);
|
||||||
|
}
|
||||||
108
app/doctor/_components/doctor-job-select.tsx
Normal file
108
app/doctor/_components/doctor-job-select.tsx
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useTransition } from 'react';
|
||||||
|
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
|
import { LoaderCircle } from 'lucide-react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
selectJobAction,
|
||||||
|
unselectJobAction,
|
||||||
|
} from '@kit/doctor/actions/doctor-server-actions';
|
||||||
|
import { Button, ButtonProps } from '@kit/ui/button';
|
||||||
|
import { toast } from '@kit/ui/sonner';
|
||||||
|
import { Trans } from '@kit/ui/trans';
|
||||||
|
import { cn } from '@kit/ui/utils';
|
||||||
|
|
||||||
|
export default function DoctorJobSelect({
|
||||||
|
className,
|
||||||
|
size = 'sm',
|
||||||
|
doctorUserId,
|
||||||
|
doctorName,
|
||||||
|
analysisOrderId,
|
||||||
|
userId,
|
||||||
|
isRemovable,
|
||||||
|
onJobUpdate,
|
||||||
|
linkTo,
|
||||||
|
}: {
|
||||||
|
className?: string;
|
||||||
|
size?: ButtonProps['size'];
|
||||||
|
doctorUserId?: string | null;
|
||||||
|
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();
|
||||||
|
linkTo && 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={cn('w-16', className)}
|
||||||
|
size={size}
|
||||||
|
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={cn('w-16', className)}
|
||||||
|
size={size}
|
||||||
|
onClick={handleSelectJob}
|
||||||
|
disabled={isPending}
|
||||||
|
>
|
||||||
|
{isPending ? (
|
||||||
|
<LoaderCircle className="h-4 w-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Trans i18nKey="doctor:selectJob" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>{doctorName}</>;
|
||||||
|
}
|
||||||
@@ -6,6 +6,9 @@ import { usePathname } from 'next/navigation';
|
|||||||
import { UserWorkspace } from '@/app/home/(user)/_lib/server/load-user-workspace';
|
import { UserWorkspace } from '@/app/home/(user)/_lib/server/load-user-workspace';
|
||||||
import { LayoutDashboard } from 'lucide-react';
|
import { LayoutDashboard } from 'lucide-react';
|
||||||
|
|
||||||
|
import { AppLogo } from '@kit/shared/components/app-logo';
|
||||||
|
import { ProfileAccountDropdownContainer } from '@kit/shared/components/personal-account-dropdown-container';
|
||||||
|
import { pathsConfig } from '@kit/shared/config';
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
SidebarContent,
|
SidebarContent,
|
||||||
@@ -20,11 +23,6 @@ import {
|
|||||||
} from '@kit/ui/shadcn-sidebar';
|
} from '@kit/ui/shadcn-sidebar';
|
||||||
import { Trans } from '@kit/ui/trans';
|
import { Trans } from '@kit/ui/trans';
|
||||||
|
|
||||||
import { AppLogo } from '@kit/shared/components/app-logo';
|
|
||||||
import { ProfileAccountDropdownContainer } from '@kit/shared/components/personal-account-dropdown-container';
|
|
||||||
|
|
||||||
import { pathsConfig } from '@kit/shared/config';
|
|
||||||
|
|
||||||
export function DoctorSidebar({
|
export function DoctorSidebar({
|
||||||
accounts,
|
accounts,
|
||||||
}: {
|
}: {
|
||||||
@@ -75,10 +73,7 @@ export function DoctorSidebar({
|
|||||||
isActive={path === pathsConfig.app.myJobs}
|
isActive={path === pathsConfig.app.myJobs}
|
||||||
asChild
|
asChild
|
||||||
>
|
>
|
||||||
<Link
|
<Link className={'flex gap-2.5'} href={pathsConfig.app.myJobs}>
|
||||||
className={'flex gap-2.5'}
|
|
||||||
href={pathsConfig.app.myJobs}
|
|
||||||
>
|
|
||||||
<LayoutDashboard className={'h-4'} />
|
<LayoutDashboard className={'h-4'} />
|
||||||
<Trans i18nKey={'doctor:sidebar.myReviews'} />
|
<Trans i18nKey={'doctor:sidebar.myReviews'} />
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -3,15 +3,10 @@
|
|||||||
import { useTransition } from 'react';
|
import { useTransition } from 'react';
|
||||||
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useRouter } from 'next/navigation';
|
|
||||||
|
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { Eye, LoaderCircle } from 'lucide-react';
|
import { Eye } from 'lucide-react';
|
||||||
|
|
||||||
import {
|
|
||||||
selectJobAction,
|
|
||||||
unselectJobAction,
|
|
||||||
} from '@kit/doctor/actions/doctor-server-actions';
|
|
||||||
import { getResultSetName } from '@kit/doctor/lib/helpers';
|
import { getResultSetName } from '@kit/doctor/lib/helpers';
|
||||||
import { ResponseTable } from '@kit/doctor/schema/doctor-analysis.schema';
|
import { ResponseTable } from '@kit/doctor/schema/doctor-analysis.schema';
|
||||||
import { pathsConfig } from '@kit/shared/config';
|
import { pathsConfig } from '@kit/shared/config';
|
||||||
@@ -28,94 +23,7 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from '@kit/ui/table';
|
} from '@kit/ui/table';
|
||||||
import { Trans } from '@kit/ui/trans';
|
import { Trans } from '@kit/ui/trans';
|
||||||
|
import DoctorJobSelect from './doctor-job-select';
|
||||||
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({
|
export default function ResultsTable({
|
||||||
results = [],
|
results = [],
|
||||||
@@ -272,7 +180,7 @@ export default function ResultsTable({
|
|||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<DoctorCell
|
<DoctorJobSelect
|
||||||
doctorUserId={result.doctor?.primary_owner_user_id}
|
doctorUserId={result.doctor?.primary_owner_user_id}
|
||||||
doctorName={getFullName(
|
doctorName={getFullName(
|
||||||
result.doctor?.name,
|
result.doctor?.name,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { cache } from 'react';
|
|||||||
|
|
||||||
import { getAnalysisResultsForDoctor } from '@kit/doctor/services/doctor-analysis.service';
|
import { getAnalysisResultsForDoctor } from '@kit/doctor/services/doctor-analysis.service';
|
||||||
import { PageBody, PageHeader } from '@kit/ui/page';
|
import { PageBody, PageHeader } from '@kit/ui/page';
|
||||||
|
|
||||||
import AnalysisView from '../../_components/analysis-view';
|
import AnalysisView from '../../_components/analysis-view';
|
||||||
import { DoctorGuard } from '../../_components/doctor-guard';
|
import { DoctorGuard } from '../../_components/doctor-guard';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useMemo, useState } from 'react';
|
import React, { ReactElement, ReactNode, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { UserAnalysisElement } from '@/packages/features/accounts/src/types/accounts';
|
import { UserAnalysisElement } from '@/packages/features/accounts/src/types/accounts';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
@@ -37,9 +37,13 @@ export enum AnalysisStatus {
|
|||||||
const Analysis = ({
|
const Analysis = ({
|
||||||
analysisElement,
|
analysisElement,
|
||||||
results,
|
results,
|
||||||
|
startIcon,
|
||||||
|
endIcon,
|
||||||
}: {
|
}: {
|
||||||
analysisElement: Pick<AnalysisElement, 'analysis_name_lab'>;
|
analysisElement: Pick<AnalysisElement, 'analysis_name_lab'>;
|
||||||
results?: AnalysisResultForDisplay;
|
results?: AnalysisResultForDisplay;
|
||||||
|
startIcon?: ReactElement | null;
|
||||||
|
endIcon?: ReactNode | null;
|
||||||
}) => {
|
}) => {
|
||||||
const name = analysisElement.analysis_name_lab || '';
|
const name = analysisElement.analysis_name_lab || '';
|
||||||
const status = results?.norm_status || AnalysisStatus.NORMAL;
|
const status = results?.norm_status || AnalysisStatus.NORMAL;
|
||||||
@@ -76,59 +80,66 @@ const Analysis = ({
|
|||||||
}, [results, value, normLower]);
|
}, [results, value, normLower]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border-border flex flex-col items-center justify-between gap-2 rounded-lg border px-5 px-12 py-3 sm:h-[65px] sm:flex-row sm:gap-0">
|
<div className="border-border rounded-lg border px-5">
|
||||||
<div className="flex items-center gap-2 font-semibold">
|
<div className="flex flex-col items-center justify-between gap-2 py-3 sm:h-[65px] sm:flex-row sm:gap-0">
|
||||||
{name}
|
<div className="flex items-center gap-2 font-semibold">
|
||||||
{results?.response_time && (
|
{startIcon || <div className="w-4" />}
|
||||||
<div
|
{name}
|
||||||
className="group/tooltip relative"
|
{results?.response_time && (
|
||||||
onClick={() => setShowTooltip(!showTooltip)}
|
|
||||||
onMouseLeave={() => setShowTooltip(false)}
|
|
||||||
>
|
|
||||||
<Info className="hover" />{' '}
|
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className="group/tooltip relative"
|
||||||
'absolute bottom-full left-1/2 z-10 mb-2 hidden -translate-x-1/2 rounded border bg-white p-4 text-sm whitespace-nowrap group-hover/tooltip:block',
|
onClick={(e) => {
|
||||||
{ block: showTooltip },
|
e.stopPropagation();
|
||||||
)}
|
setShowTooltip(!showTooltip);
|
||||||
|
}}
|
||||||
|
onMouseLeave={() => setShowTooltip(false)}
|
||||||
>
|
>
|
||||||
<Trans i18nKey="analysis-results:analysisDate" />
|
<Info className="hover" />{' '}
|
||||||
{': '}
|
<div
|
||||||
{format(new Date(results.response_time), 'dd.MM.yyyy HH:mm')}
|
className={cn(
|
||||||
|
'absolute bottom-full left-1/2 z-10 mb-2 hidden -translate-x-1/2 rounded border bg-white p-4 text-sm whitespace-nowrap group-hover/tooltip:block',
|
||||||
|
{ block: showTooltip },
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Trans i18nKey="analysis-results:analysisDate" />
|
||||||
|
{': '}
|
||||||
|
{format(new Date(results.response_time), 'dd.MM.yyyy HH:mm')}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
</div>
|
||||||
|
{results ? (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center gap-3 sm:ml-auto">
|
||||||
|
<div className="font-semibold">{value}</div>
|
||||||
|
<div className="text-muted-foreground text-sm">{unit}</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-muted-foreground mx-8 flex flex-col-reverse gap-2 text-center text-sm sm:block sm:gap-0">
|
||||||
|
{normLower} - {normUpper}
|
||||||
|
<div>
|
||||||
|
<Trans i18nKey="analysis-results:results.range.normal" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<AnalysisLevelBar
|
||||||
|
results={results}
|
||||||
|
normLowerIncluded={normLowerIncluded}
|
||||||
|
normUpperIncluded={normUpperIncluded}
|
||||||
|
level={analysisResultLevel!}
|
||||||
|
/>
|
||||||
|
{endIcon || <div className="mx-2 w-4" />}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center gap-3 sm:ml-auto">
|
||||||
|
<div className="font-semibold">
|
||||||
|
<Trans i18nKey="analysis-results:waitingForResults" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mx-8 w-[60px]"></div>
|
||||||
|
<AnalysisLevelBarSkeleton />
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{results ? (
|
|
||||||
<>
|
|
||||||
<div className="flex items-center gap-3 sm:ml-auto">
|
|
||||||
<div className="font-semibold">{value}</div>
|
|
||||||
<div className="text-muted-foreground text-sm">{unit}</div>
|
|
||||||
</div>
|
|
||||||
<div className="text-muted-foreground mx-8 flex flex-col-reverse gap-2 text-center text-sm sm:block sm:gap-0">
|
|
||||||
{normLower} - {normUpper}
|
|
||||||
<div>
|
|
||||||
<Trans i18nKey="analysis-results:results.range.normal" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<AnalysisLevelBar
|
|
||||||
results={results}
|
|
||||||
normLowerIncluded={normLowerIncluded}
|
|
||||||
normUpperIncluded={normUpperIncluded}
|
|
||||||
level={analysisResultLevel!}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<div className="flex items-center gap-3 sm:ml-auto">
|
|
||||||
<div className="font-semibold">
|
|
||||||
<Trans i18nKey="analysis-results:waitingForResults" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mx-8 w-[60px]"></div>
|
|
||||||
<AnalysisLevelBarSkeleton />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -637,6 +637,7 @@ async function syncPrivateMessage({
|
|||||||
unit: element.Mootyhik ?? null,
|
unit: element.Mootyhik ?? null,
|
||||||
original_response_element: element,
|
original_response_element: element,
|
||||||
analysis_name: element.UuringNimi || element.KNimetus,
|
analysis_name: element.UuringNimi || element.KNimetus,
|
||||||
|
comment: element.UuringuKommentaar
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export const selectJobAction = doctorAction(
|
|||||||
|
|
||||||
logger.info({ analysisOrderId }, `Successfully selected`);
|
logger.info({ analysisOrderId }, `Successfully selected`);
|
||||||
|
|
||||||
|
revalidateDoctorAnalysis();
|
||||||
return { success: true };
|
return { success: true };
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -60,7 +61,7 @@ export const unselectJobAction = doctorAction(
|
|||||||
{ analysisOrderId },
|
{ analysisOrderId },
|
||||||
`Successfully removed current doctor from job`,
|
`Successfully removed current doctor from job`,
|
||||||
);
|
);
|
||||||
|
revalidateDoctorAnalysis();
|
||||||
return { success: true };
|
return { success: true };
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -101,6 +102,7 @@ export const giveFeedbackAction = doctorAction(
|
|||||||
);
|
);
|
||||||
logger.info({ analysisOrderId }, `Successfully submitted feedback`);
|
logger.info({ analysisOrderId }, `Successfully submitted feedback`);
|
||||||
|
|
||||||
|
revalidateDoctorAnalysis();
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -108,3 +110,7 @@ export const giveFeedbackAction = doctorAction(
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function revalidateDoctorAnalysis() {
|
||||||
|
revalidatePath('/doctor/analysis');
|
||||||
|
}
|
||||||
|
|||||||
@@ -66,6 +66,27 @@ export const AnalysisResponseSchema = z.object({
|
|||||||
updated_at: z.string().nullable(),
|
updated_at: z.string().nullable(),
|
||||||
analysis_name: z.string().nullable(),
|
analysis_name: z.string().nullable(),
|
||||||
analysis_responses: AnalysisResponsesSchema,
|
analysis_responses: AnalysisResponsesSchema,
|
||||||
|
comment: z.string().nullable(),
|
||||||
|
latestPreviousAnalysis: z
|
||||||
|
.object({
|
||||||
|
id: z.number(),
|
||||||
|
analysis_response_id: z.number(),
|
||||||
|
analysis_element_original_id: z.string(),
|
||||||
|
unit: z.string().nullable(),
|
||||||
|
response_value: z.number(),
|
||||||
|
response_time: z.string(),
|
||||||
|
norm_upper: z.number().nullable(),
|
||||||
|
norm_upper_included: z.boolean().nullable(),
|
||||||
|
norm_lower: z.number().nullable(),
|
||||||
|
norm_lower_included: z.boolean().nullable(),
|
||||||
|
norm_status: z.number().nullable(),
|
||||||
|
created_at: z.string(),
|
||||||
|
updated_at: z.string().nullable(),
|
||||||
|
analysis_name: z.string().nullable(),
|
||||||
|
comment: z.string().nullable(),
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
});
|
});
|
||||||
export type AnalysisResponse = z.infer<typeof AnalysisResponseSchema>;
|
export type AnalysisResponse = z.infer<typeof AnalysisResponseSchema>;
|
||||||
|
|
||||||
|
|||||||
@@ -15,18 +15,19 @@ export type DoctorJobUnselect = z.infer<typeof doctorJobUnselectSchema>;
|
|||||||
|
|
||||||
export const FeedbackStatus = z.enum(['STARTED', 'DRAFT', 'COMPLETED']);
|
export const FeedbackStatus = z.enum(['STARTED', 'DRAFT', 'COMPLETED']);
|
||||||
export const doctorAnalysisFeedbackFormSchema = z.object({
|
export const doctorAnalysisFeedbackFormSchema = z.object({
|
||||||
feedbackValue: z.string().min(15),
|
feedbackValue: z.string().min(10, { message: 'doctor:feedbackLengthError' }),
|
||||||
userId: z.string().uuid(),
|
userId: z.string().uuid(),
|
||||||
});
|
});
|
||||||
export type DoctorAnalysisFeedbackForm = z.infer<
|
export type DoctorAnalysisFeedbackForm = z.infer<
|
||||||
typeof doctorAnalysisFeedbackFormSchema
|
typeof doctorAnalysisFeedbackFormSchema
|
||||||
>;
|
>;
|
||||||
export const doctorAnalysisFeedbackSchema = z.object({
|
export const doctorAnalysisFeedbackSchema = z.object({
|
||||||
feedbackValue: z.string().min(15),
|
feedbackValue: z.string(),
|
||||||
userId: z.string().uuid(),
|
userId: z.string().uuid(),
|
||||||
analysisOrderId: z.number(),
|
analysisOrderId: z.number(),
|
||||||
status: FeedbackStatus,
|
status: FeedbackStatus,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type DoctorAnalysisFeedback = z.infer<
|
export type DoctorAnalysisFeedback = z.infer<
|
||||||
typeof doctorAnalysisFeedbackSchema
|
typeof doctorAnalysisFeedbackSchema
|
||||||
>;
|
>;
|
||||||
|
|||||||
@@ -360,7 +360,7 @@ export async function getAnalysisResultsForDoctor(
|
|||||||
): Promise<AnalysisResultDetails> {
|
): Promise<AnalysisResultDetails> {
|
||||||
const supabase = getSupabaseServerClient();
|
const supabase = getSupabaseServerClient();
|
||||||
|
|
||||||
const { data: analysisResponse, error } = await supabase
|
const { data: analysisResponseElements, error } = await supabase
|
||||||
.schema('medreport')
|
.schema('medreport')
|
||||||
.from(`analysis_response_elements`)
|
.from(`analysis_response_elements`)
|
||||||
.select(
|
.select(
|
||||||
@@ -373,20 +373,26 @@ export async function getAnalysisResultsForDoctor(
|
|||||||
throw new Error('Something went wrong.');
|
throw new Error('Something went wrong.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstAnalysisResponse = analysisResponse?.[0];
|
const firstAnalysisResponse = analysisResponseElements?.[0];
|
||||||
const userId = firstAnalysisResponse?.analysis_responses.user_id;
|
const userId = firstAnalysisResponse?.analysis_responses.user_id;
|
||||||
const medusaOrderId =
|
const medusaOrderId =
|
||||||
firstAnalysisResponse?.analysis_responses?.analysis_order_id
|
firstAnalysisResponse?.analysis_responses?.analysis_order_id
|
||||||
?.medusa_order_id;
|
?.medusa_order_id;
|
||||||
|
|
||||||
if (!analysisResponse?.length || !userId || !medusaOrderId) {
|
if (!analysisResponseElements?.length || !userId || !medusaOrderId) {
|
||||||
throw new Error('Failed to retrieve full analysis data.');
|
throw new Error('Failed to retrieve full analysis data.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const responseElementAnalysisElementOriginalIds =
|
||||||
|
analysisResponseElements.map(
|
||||||
|
({ analysis_element_original_id }) => analysis_element_original_id,
|
||||||
|
);
|
||||||
|
|
||||||
const [
|
const [
|
||||||
{ data: medusaOrderItems, error: medusaOrderError },
|
{ data: medusaOrderItems, error: medusaOrderError },
|
||||||
{ data: accountWithParams, error: accountError },
|
{ data: accountWithParams, error: accountError },
|
||||||
{ data: doctorFeedback, error: feedbackError },
|
{ data: doctorFeedback, error: feedbackError },
|
||||||
|
{ data: previousAnalyses, error: previousAnalysesError },
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
supabase
|
supabase
|
||||||
.schema('public')
|
.schema('public')
|
||||||
@@ -403,7 +409,7 @@ export async function getAnalysisResultsForDoctor(
|
|||||||
.eq('is_personal_account', true)
|
.eq('is_personal_account', true)
|
||||||
.eq('primary_owner_user_id', userId)
|
.eq('primary_owner_user_id', userId)
|
||||||
.limit(1),
|
.limit(1),
|
||||||
await supabase
|
supabase
|
||||||
.schema('medreport')
|
.schema('medreport')
|
||||||
.from('doctor_analysis_feedback')
|
.from('doctor_analysis_feedback')
|
||||||
.select(`*`)
|
.select(`*`)
|
||||||
@@ -412,9 +418,39 @@ export async function getAnalysisResultsForDoctor(
|
|||||||
firstAnalysisResponse.analysis_responses.analysis_order_id.id,
|
firstAnalysisResponse.analysis_responses.analysis_order_id.id,
|
||||||
)
|
)
|
||||||
.limit(1),
|
.limit(1),
|
||||||
|
supabase
|
||||||
|
.schema('medreport')
|
||||||
|
.from('analysis_response_elements')
|
||||||
|
.select(
|
||||||
|
`
|
||||||
|
*,
|
||||||
|
analysis_responses!inner(
|
||||||
|
user_id
|
||||||
|
)
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.in(
|
||||||
|
'analysis_element_original_id',
|
||||||
|
responseElementAnalysisElementOriginalIds,
|
||||||
|
)
|
||||||
|
.not(
|
||||||
|
'analysis_response_id',
|
||||||
|
'eq',
|
||||||
|
firstAnalysisResponse.analysis_response_id,
|
||||||
|
)
|
||||||
|
.eq(
|
||||||
|
'analysis_responses.user_id',
|
||||||
|
firstAnalysisResponse.analysis_responses.user_id,
|
||||||
|
)
|
||||||
|
.order('response_time'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (medusaOrderError || accountError || feedbackError) {
|
if (
|
||||||
|
medusaOrderError ||
|
||||||
|
accountError ||
|
||||||
|
feedbackError ||
|
||||||
|
previousAnalysesError
|
||||||
|
) {
|
||||||
throw new Error('Something went wrong.');
|
throw new Error('Something went wrong.');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,8 +469,21 @@ export async function getAnalysisResultsForDoctor(
|
|||||||
account_params,
|
account_params,
|
||||||
} = accountWithParams[0];
|
} = accountWithParams[0];
|
||||||
|
|
||||||
|
const analysisResponseElementsWithPreviousData = [];
|
||||||
|
for (const analysisResponseElement of analysisResponseElements) {
|
||||||
|
const latestPreviousAnalysis = previousAnalyses.find(
|
||||||
|
({ analysis_element_original_id }) =>
|
||||||
|
analysis_element_original_id ===
|
||||||
|
analysisResponseElement.analysis_element_original_id,
|
||||||
|
);
|
||||||
|
analysisResponseElementsWithPreviousData.push({
|
||||||
|
...analysisResponseElement,
|
||||||
|
latestPreviousAnalysis,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
analysisResponse,
|
analysisResponse: analysisResponseElementsWithPreviousData,
|
||||||
order: {
|
order: {
|
||||||
title: medusaOrderItems?.[0]?.item_id.product_title ?? '-',
|
title: medusaOrderItems?.[0]?.item_id.product_title ?? '-',
|
||||||
isPackage:
|
isPackage:
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export function InfoTooltip({
|
|||||||
content,
|
content,
|
||||||
icon,
|
icon,
|
||||||
}: {
|
}: {
|
||||||
content?: string;
|
content?: string | null;
|
||||||
icon?: JSX.Element;
|
icon?: JSX.Element;
|
||||||
}) {
|
}) {
|
||||||
if (!content) return null;
|
if (!content) return null;
|
||||||
|
|||||||
@@ -549,6 +549,7 @@ export type Database = {
|
|||||||
analysis_element_original_id: string
|
analysis_element_original_id: string
|
||||||
analysis_name: string | null
|
analysis_name: string | null
|
||||||
analysis_response_id: number
|
analysis_response_id: number
|
||||||
|
comment: string | null
|
||||||
created_at: string
|
created_at: string
|
||||||
id: number
|
id: number
|
||||||
norm_lower: number | null
|
norm_lower: number | null
|
||||||
@@ -566,6 +567,7 @@ export type Database = {
|
|||||||
analysis_element_original_id: string
|
analysis_element_original_id: string
|
||||||
analysis_name?: string | null
|
analysis_name?: string | null
|
||||||
analysis_response_id: number
|
analysis_response_id: number
|
||||||
|
comment?: string | null
|
||||||
created_at?: string
|
created_at?: string
|
||||||
id?: number
|
id?: number
|
||||||
norm_lower?: number | null
|
norm_lower?: number | null
|
||||||
@@ -583,6 +585,7 @@ export type Database = {
|
|||||||
analysis_element_original_id?: string
|
analysis_element_original_id?: string
|
||||||
analysis_name?: string | null
|
analysis_name?: string | null
|
||||||
analysis_response_id?: number
|
analysis_response_id?: number
|
||||||
|
comment?: string | null
|
||||||
created_at?: string
|
created_at?: string
|
||||||
id?: number
|
id?: number
|
||||||
norm_lower?: number | null
|
norm_lower?: number | null
|
||||||
@@ -1723,7 +1726,9 @@ export type Database = {
|
|||||||
Returns: Json
|
Returns: Json
|
||||||
}
|
}
|
||||||
create_team_account: {
|
create_team_account: {
|
||||||
Args: { account_name: string; new_personal_code: string }
|
Args:
|
||||||
|
| { account_name: string }
|
||||||
|
| { account_name: string; new_personal_code: string }
|
||||||
Returns: {
|
Returns: {
|
||||||
application_role: Database["medreport"]["Enums"]["application_role"]
|
application_role: Database["medreport"]["Enums"]["application_role"]
|
||||||
city: string | null
|
city: string | null
|
||||||
@@ -1910,6 +1915,22 @@ export type Database = {
|
|||||||
}
|
}
|
||||||
Returns: undefined
|
Returns: undefined
|
||||||
}
|
}
|
||||||
|
update_analysis_order_status: {
|
||||||
|
Args: {
|
||||||
|
order_id: number
|
||||||
|
medusa_order_id_param: string
|
||||||
|
status_param: Database["medreport"]["Enums"]["analysis_order_status"]
|
||||||
|
}
|
||||||
|
Returns: {
|
||||||
|
analysis_element_ids: number[] | null
|
||||||
|
analysis_ids: number[] | null
|
||||||
|
created_at: string
|
||||||
|
id: number
|
||||||
|
medusa_order_id: string
|
||||||
|
status: Database["medreport"]["Enums"]["analysis_order_status"]
|
||||||
|
user_id: string
|
||||||
|
}
|
||||||
|
}
|
||||||
upsert_order: {
|
upsert_order: {
|
||||||
Args: {
|
Args: {
|
||||||
target_account_id: string
|
target_account_id: string
|
||||||
|
|||||||
@@ -115,5 +115,6 @@
|
|||||||
"saveAsDraft": "Save as draft",
|
"saveAsDraft": "Save as draft",
|
||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
"previous": "Previous",
|
"previous": "Previous",
|
||||||
"next": "Next"
|
"next": "Next",
|
||||||
|
"invalidDataError": "Invalid data submitted"
|
||||||
}
|
}
|
||||||
@@ -30,14 +30,17 @@
|
|||||||
"phone": "Phone",
|
"phone": "Phone",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"results": "Analysis results",
|
"results": "Analysis results",
|
||||||
"feedback": "Feedback",
|
"feedback": "Summary",
|
||||||
"selectJob": "Select",
|
"selectJob": "Select",
|
||||||
"unselectJob": "Deselect",
|
"unselectJob": "Unselect",
|
||||||
|
"previousResults": "Previous results ({{date}})",
|
||||||
|
"labComment": "Lab comment",
|
||||||
"confirmFeedbackModal": {
|
"confirmFeedbackModal": {
|
||||||
"title": "Confirm publishing feedback",
|
"title": "Confirm publishing summary",
|
||||||
"description": "When confirmed, the feedback will be published to the patient."
|
"description": "When confirmed, the summary will be published to the patient."
|
||||||
},
|
},
|
||||||
"updateFeedbackSuccess": "Feedback updated",
|
"updateFeedbackSuccess": "Summary updated",
|
||||||
"updateFeedbackLoading": "Updating feedback...",
|
"updateFeedbackLoading": "Updating summary...",
|
||||||
"updateFeedbackError": "Failed to update feedback"
|
"updateFeedbackError": "Failed to update summary",
|
||||||
}
|
"feedbackLengthError": "Summary must be at least 10 characters"
|
||||||
|
}
|
||||||
@@ -133,5 +133,6 @@
|
|||||||
"saveAsDraft": "Salvesta mustandina",
|
"saveAsDraft": "Salvesta mustandina",
|
||||||
"confirm": "Kinnita",
|
"confirm": "Kinnita",
|
||||||
"previous": "Eelmine",
|
"previous": "Eelmine",
|
||||||
"next": "Järgmine"
|
"next": "Järgmine",
|
||||||
|
"invalidDataError": "Vigased andmed"
|
||||||
}
|
}
|
||||||
@@ -30,14 +30,17 @@
|
|||||||
"phone": "Telefon",
|
"phone": "Telefon",
|
||||||
"email": "E-mail",
|
"email": "E-mail",
|
||||||
"results": "Analüüside tulemused",
|
"results": "Analüüside tulemused",
|
||||||
"feedback": "Tagasiside",
|
"feedback": "Kokkuvõte",
|
||||||
"selectJob": "Vali",
|
"selectJob": "Vali",
|
||||||
"unselectJob": "Loobu",
|
"unselectJob": "Loobu",
|
||||||
|
"previousResults": "Eelnevad tulemused ({{date}})",
|
||||||
|
"labComment": "Labori kommentaarid",
|
||||||
"confirmFeedbackModal": {
|
"confirmFeedbackModal": {
|
||||||
"title": "Kinnita tagasiside avaldamine",
|
"title": "Kinnita kokkuvõtte avaldamine",
|
||||||
"description": "Tagasiside kinnitamisel avaldatakse see patsiendile."
|
"description": "Kinnitamisel avaldatakse kokkuvõte patsiendile."
|
||||||
},
|
},
|
||||||
"updateFeedbackSuccess": "Tagasiside uuendatud",
|
"updateFeedbackSuccess": "Kokkuvõte uuendatud",
|
||||||
"updateFeedbackLoading": "Tagasiside uuendatakse...",
|
"updateFeedbackLoading": "Kokkuvõtet uuendatakse...",
|
||||||
"updateFeedbackError": "Tagasiside uuendamine ebaõnnestus"
|
"updateFeedbackError": "Kokkuvõtte uuendamine ebaõnnestus",
|
||||||
|
"feedbackLengthError": "Kokkuvõte peab olema vähemalt 10 tähemärki pikk"
|
||||||
}
|
}
|
||||||
@@ -113,5 +113,6 @@
|
|||||||
"saveAsDraft": "Save as draft",
|
"saveAsDraft": "Save as draft",
|
||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
"previous": "Previous",
|
"previous": "Previous",
|
||||||
"next": "Next"
|
"next": "Next",
|
||||||
|
"invalidDataError": "Invalid data submitted"
|
||||||
}
|
}
|
||||||
@@ -30,14 +30,17 @@
|
|||||||
"phone": "Phone",
|
"phone": "Phone",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"results": "Analysis results",
|
"results": "Analysis results",
|
||||||
"feedback": "Feedback",
|
"feedback": "Summary",
|
||||||
"selectJob": "Select",
|
"selectJob": "Select",
|
||||||
"unselectJob": "Deselect",
|
"unselectJob": "Unselect",
|
||||||
|
"previousResults": "Previous results ({{date}})",
|
||||||
|
"labComment": "Lab comment",
|
||||||
"confirmFeedbackModal": {
|
"confirmFeedbackModal": {
|
||||||
"title": "Confirm publishing feedback",
|
"title": "Confirm publishing summary",
|
||||||
"description": "When confirmed, the feedback will be published to the patient."
|
"description": "When confirmed, the summary will be published to the patient."
|
||||||
},
|
},
|
||||||
"updateFeedbackSuccess": "Feedback updated",
|
"updateFeedbackSuccess": "Summary updated",
|
||||||
"updateFeedbackLoading": "Updating feedback...",
|
"updateFeedbackLoading": "Updating summary...",
|
||||||
"updateFeedbackError": "Failed to update feedback"
|
"updateFeedbackError": "Failed to update summary",
|
||||||
|
"feedbackLengthError": "Summary must be at least 10 characters"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE medreport.analysis_response_elements
|
||||||
|
ADD comment text;
|
||||||
Reference in New Issue
Block a user