diff --git a/app/doctor/_components/analysis-feedback.tsx b/app/doctor/_components/analysis-feedback.tsx
new file mode 100644
index 0000000..c1d4c54
--- /dev/null
+++ b/app/doctor/_components/analysis-feedback.tsx
@@ -0,0 +1,156 @@
+import React, { useState } from 'react';
+
+import { giveFeedbackAction } from '@/packages/features/doctor/src/lib/server/actions/doctor-server-actions';
+import {
+ DoctorFeedback,
+ Order,
+ Patient,
+} from '@/packages/features/doctor/src/lib/server/schema/doctor-analysis-detail-view.schema';
+import {
+ DoctorAnalysisFeedbackForm,
+ doctorAnalysisFeedbackFormSchema,
+} from '@/packages/features/doctor/src/lib/server/schema/doctor-analysis.schema';
+import ConfirmationModal from '@/packages/shared/src/components/confirmation-modal';
+import { useUser } from '@/packages/supabase/src/hooks/use-user';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { useQueryClient } from '@tanstack/react-query';
+import { useForm } from 'react-hook-form';
+
+import { Trans } from '@kit/ui/makerkit/trans';
+import { Button } from '@kit/ui/shadcn/button';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormMessage,
+} from '@kit/ui/shadcn/form';
+import { toast } from '@kit/ui/shadcn/sonner';
+import { Textarea } from '@kit/ui/shadcn/textarea';
+
+const AnalysisFeedback = ({
+ feedback,
+ patient,
+ order,
+}: {
+ feedback?: DoctorFeedback;
+ patient: Patient;
+ order: Order;
+}) => {
+ const [isDraftSubmitting, setIsDraftSubmitting] = useState(false);
+ const [isConfirmOpen, setIsConfirmOpen] = useState(false);
+ const { data: user } = useUser();
+ const queryClient = useQueryClient();
+
+ const form = useForm({
+ resolver: zodResolver(doctorAnalysisFeedbackFormSchema),
+ reValidateMode: 'onChange',
+ defaultValues: {
+ feedbackValue: feedback?.value ?? '',
+ userId: patient.userId,
+ },
+ });
+
+ const isReadOnly =
+ !!feedback?.doctor_user_id && feedback?.doctor_user_id !== user?.id;
+
+ const handleDraftSubmit = async (e: React.FormEvent) => {
+ setIsDraftSubmitting(true);
+ e.preventDefault();
+
+ form.formState.errors.feedbackValue = undefined;
+ const formData = form.getValues();
+ await onSubmit(formData, 'DRAFT');
+ setIsDraftSubmitting(false);
+ };
+ const handleCompleteSubmit = form.handleSubmit(async () => {
+ setIsConfirmOpen(true);
+ });
+
+ const onSubmit = async (
+ data: DoctorAnalysisFeedbackForm,
+ status: 'DRAFT' | 'COMPLETED',
+ ) => {
+ const result = await giveFeedbackAction({
+ ...data,
+ analysisOrderId: order.analysisOrderId,
+ status,
+ });
+
+ if (!result.success) {
+ return toast.error();
+ }
+
+ queryClient.invalidateQueries({
+ predicate: (query) => query.queryKey.includes('doctor-jobs'),
+ });
+
+ toast.success();
+
+ return setIsConfirmOpen(false);
+ };
+
+ const confirmComplete = form.handleSubmit(async (data) => {
+ await onSubmit(data, 'COMPLETED');
+ });
+
+ return (
+ <>
+
+
+
+ {feedback?.value ?? '-'}
+ {!isReadOnly && (
+
+
+ )}
+ setIsConfirmOpen(false)}
+ onConfirm={confirmComplete}
+ titleKey="doctor:confirmFeedbackModal.title"
+ descriptionKey="doctor:confirmFeedbackModal.description"
+ />
+ >
+ );
+};
+
+export default AnalysisFeedback;
diff --git a/app/doctor/_components/analysis-view.tsx b/app/doctor/_components/analysis-view.tsx
index 6996d11..e1fc8e4 100644
--- a/app/doctor/_components/analysis-view.tsx
+++ b/app/doctor/_components/analysis-view.tsx
@@ -1,13 +1,8 @@
'use client';
-import { useState } from 'react';
-
-import { zodResolver } from '@hookform/resolvers/zod';
import { useQueryClient } from '@tanstack/react-query';
import { capitalize } from 'lodash';
-import { useForm } from 'react-hook-form';
-import { giveFeedbackAction } from '@kit/doctor/actions/doctor-server-actions';
import {
getDOBWithAgeStringFromPersonalCode,
getResultSetName,
@@ -18,28 +13,14 @@ import {
Order,
Patient,
} from '@kit/doctor/schema/doctor-analysis-detail-view.schema';
-import {
- DoctorAnalysisFeedbackForm,
- doctorAnalysisFeedbackFormSchema,
-} from '@kit/doctor/schema/doctor-analysis.schema';
-import ConfirmationModal from '@kit/shared/components/confirmation-modal';
import { useCurrentLocaleLanguageNames } from '@kit/shared/hooks';
import { getFullName } from '@kit/shared/utils';
import { useUser } from '@kit/supabase/hooks/use-user';
-import { Button } from '@kit/ui/button';
-import {
- Form,
- FormControl,
- FormField,
- FormItem,
- FormMessage,
-} from '@kit/ui/form';
-import { toast } from '@kit/ui/sonner';
-import { Textarea } from '@kit/ui/textarea';
import { Trans } from '@kit/ui/trans';
import { bmiFromMetric } from '~/lib/utils';
+import AnalysisFeedback from './analysis-feedback';
import DoctorAnalysisWrapper from './doctor-analysis-wrapper';
import DoctorJobSelect from './doctor-job-select';
@@ -54,10 +35,8 @@ export default function AnalysisView({
analyses: AnalysisResponse[];
feedback?: DoctorFeedback;
}) {
- const [isConfirmOpen, setIsConfirmOpen] = useState(false);
- const [isDraftSubmitting, setIsDraftSubmitting] = useState(false);
-
const { data: user } = useUser();
+ const queryClient = useQueryClient();
const languageNames = useCurrentLocaleLanguageNames();
@@ -68,65 +47,11 @@ export default function AnalysisView({
);
const isCurrentDoctorJob =
!!feedback?.doctor_user_id && feedback?.doctor_user_id === user?.id;
- const isReadOnly =
- !!feedback?.doctor_user_id && feedback?.doctor_user_id !== user?.id;
-
- const form = useForm({
- resolver: zodResolver(doctorAnalysisFeedbackFormSchema),
- reValidateMode: 'onChange',
- defaultValues: {
- feedbackValue: feedback?.value ?? '',
- userId: patient.userId,
- },
- });
-
- const queryClient = useQueryClient();
if (!patient || !order || !analyses) {
return null;
}
- const onSubmit = async (
- data: DoctorAnalysisFeedbackForm,
- status: 'DRAFT' | 'COMPLETED',
- ) => {
- const result = await giveFeedbackAction({
- ...data,
- analysisOrderId: order.analysisOrderId,
- status,
- });
-
- if (!result.success) {
- return toast.error();
- }
-
- queryClient.invalidateQueries({
- predicate: (query) => query.queryKey.includes('doctor-jobs'),
- });
-
- toast.success();
-
- return setIsConfirmOpen(false);
- };
-
- const handleDraftSubmit = async (e: React.FormEvent) => {
- setIsDraftSubmitting(true);
- e.preventDefault();
-
- form.formState.errors.feedbackValue = undefined;
- const formData = form.getValues();
- await onSubmit(formData, 'DRAFT');
- setIsDraftSubmitting(false);
- };
-
- const handleCompleteSubmit = form.handleSubmit(async () => {
- setIsConfirmOpen(true);
- });
-
- const confirmComplete = form.handleSubmit(async (data) => {
- await onSubmit(data, 'COMPLETED');
- });
-
return (
<>
@@ -228,59 +153,9 @@ export default function AnalysisView({
);
})}
-
-
-
- {feedback?.value ?? '-'}
- {!isReadOnly && (
-
-
+ {order.isPackage && (
+
)}
- setIsConfirmOpen(false)}
- onConfirm={confirmComplete}
- titleKey="doctor:confirmFeedbackModal.title"
- descriptionKey="doctor:confirmFeedbackModal.description"
- />
>
);
}
diff --git a/packages/features/doctor/src/lib/server/schema/doctor-analysis-detail-view.schema.ts b/packages/features/doctor/src/lib/server/schema/doctor-analysis-detail-view.schema.ts
index b4c1f16..3195009 100644
--- a/packages/features/doctor/src/lib/server/schema/doctor-analysis-detail-view.schema.ts
+++ b/packages/features/doctor/src/lib/server/schema/doctor-analysis-detail-view.schema.ts
@@ -92,7 +92,7 @@ export const AnalysisResponseSchema = z.object({
export type AnalysisResponse = z.infer;
export const AnalysisResultDetailsSchema = z.object({
- analysisResponse: z.array(AnalysisResponseSchema).nullable(),
+ analysisResponse: z.array(AnalysisResponseSchema),
order: OrderSchema,
doctorFeedback: DoctorFeedbackSchema,
patient: PatientSchema,
diff --git a/supabase/migrations/20251007155300_allow_doctor_to_update_analysis.sql b/supabase/migrations/20251007155300_allow_doctor_to_update_analysis.sql
new file mode 100644
index 0000000..4d48a49
--- /dev/null
+++ b/supabase/migrations/20251007155300_allow_doctor_to_update_analysis.sql
@@ -0,0 +1,12 @@
+grant execute on function medreport.update_analysis_order_status (
+ bigint,
+ text,
+ medreport.analysis_order_status
+ ) to authenticated, service_role;
+
+create policy "doctor_update_analysis_order"
+on "medreport"."analysis_orders"
+as PERMISSIVE
+for UPDATE
+to authenticated
+using (medreport.is_doctor());
\ No newline at end of file