MED-88: add doctor email notifications (#65)
* MED-88: add doctor email notifications * add logging, send open jobs notification on partial analysis response * update permissions * fix import, permissions * casing, let email be null * unused import
This commit is contained in:
@@ -4,14 +4,15 @@ import { useState } from 'react';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { SubmitButton } from '@kit/shared/components/ui/submit-button';
|
||||
import { sendCompanyOfferEmail } from '@/lib/services/mailer.service';
|
||||
import { sendEmailFromTemplate } from '@/lib/services/mailer.service';
|
||||
import { CompanySubmitData } from '@/lib/types/company';
|
||||
import { companyOfferSchema } from '@/lib/validations/company-offer.schema';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { renderCompanyOfferEmail } from '@kit/email-templates';
|
||||
import { SubmitButton } from '@kit/shared/components/ui/submit-button';
|
||||
import { FormItem } from '@kit/ui/form';
|
||||
import { Input } from '@kit/ui/input';
|
||||
import { Label } from '@kit/ui/label';
|
||||
@@ -39,7 +40,14 @@ const CompanyOfferForm = () => {
|
||||
});
|
||||
|
||||
try {
|
||||
sendCompanyOfferEmail(data, language)
|
||||
sendEmailFromTemplate(
|
||||
renderCompanyOfferEmail,
|
||||
{
|
||||
companyData: data,
|
||||
language,
|
||||
},
|
||||
process.env.CONTACT_EMAIL!,
|
||||
)
|
||||
.then(() => router.push('/company-offer/success'))
|
||||
.catch((error) => {
|
||||
setIsLoading(false);
|
||||
|
||||
23
app/api/job/handler/send-open-jobs-emails.ts
Normal file
23
app/api/job/handler/send-open-jobs-emails.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { renderNewJobsAvailableEmail } from '@kit/email-templates';
|
||||
|
||||
import { getDoctorAccounts } from '~/lib/services/account.service';
|
||||
import { getOpenJobAnalysisResponseIds } from '~/lib/services/doctor-jobs.service';
|
||||
import { sendEmailFromTemplate } from '~/lib/services/mailer.service';
|
||||
|
||||
export default async function sendOpenJobsEmails() {
|
||||
const analysisResponseIds = await getOpenJobAnalysisResponseIds();
|
||||
|
||||
const doctorAccounts = await getDoctorAccounts();
|
||||
const doctorEmails: string[] = doctorAccounts
|
||||
.map(({ email }) => email)
|
||||
.filter((email): email is string => !!email);
|
||||
|
||||
await sendEmailFromTemplate(
|
||||
renderNewJobsAvailableEmail,
|
||||
{
|
||||
language: 'et',
|
||||
analysisResponseIds,
|
||||
},
|
||||
doctorEmails,
|
||||
);
|
||||
}
|
||||
53
app/api/job/send-open-jobs-emails/route.ts
Normal file
53
app/api/job/send-open-jobs-emails/route.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import {
|
||||
NotificationAction,
|
||||
createNotificationLog,
|
||||
} from '~/lib/services/audit/notificationEntries.service';
|
||||
import loadEnv from '../handler/load-env';
|
||||
import sendOpenJobsEmails from '../handler/send-open-jobs-emails';
|
||||
import validateApiKey from '../handler/validate-api-key';
|
||||
|
||||
export const POST = async (request: NextRequest) => {
|
||||
loadEnv();
|
||||
|
||||
try {
|
||||
validateApiKey(request);
|
||||
} catch (e) {
|
||||
return NextResponse.json({}, { status: 401, statusText: 'Unauthorized' });
|
||||
}
|
||||
|
||||
try {
|
||||
await sendOpenJobsEmails();
|
||||
console.info(
|
||||
'Successfully sent out open job notification emails to doctors.',
|
||||
);
|
||||
await createNotificationLog({
|
||||
action: NotificationAction.NEW_JOBS_ALERT,
|
||||
status: 'SUCCESS',
|
||||
});
|
||||
return NextResponse.json(
|
||||
{
|
||||
message:
|
||||
'Successfully sent out open job notification emails to doctors.',
|
||||
},
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (e: any) {
|
||||
console.error(
|
||||
'Error sending out open job notification emails to doctors.',
|
||||
e,
|
||||
);
|
||||
await createNotificationLog({
|
||||
action: NotificationAction.NEW_JOBS_ALERT,
|
||||
status: 'FAIL',
|
||||
comment: e?.message,
|
||||
});
|
||||
return NextResponse.json(
|
||||
{
|
||||
message: 'Failed to send out open job notification emails to doctors.',
|
||||
},
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -4,6 +4,7 @@ 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';
|
||||
@@ -22,6 +23,9 @@ import {
|
||||
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';
|
||||
@@ -57,6 +61,8 @@ export default function AnalysisView({
|
||||
|
||||
const { data: user } = useUser();
|
||||
|
||||
const languageNames = useCurrentLocaleLanguageNames();
|
||||
|
||||
const isInProgress = !!(
|
||||
!!feedback?.status &&
|
||||
feedback?.doctor_user_id &&
|
||||
@@ -191,6 +197,12 @@ export default function AnalysisView({
|
||||
<Trans i18nKey="doctor:email" />
|
||||
</div>
|
||||
<div>{patient.email}</div>
|
||||
<div className="font-bold">
|
||||
<Trans i18nKey="common:language" />
|
||||
</div>
|
||||
<div>
|
||||
{capitalize(languageNames.of(patient.preferred_locale ?? 'et'))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="xs:hidden block">
|
||||
<DoctorJobSelect
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Eye } from 'lucide-react';
|
||||
import { getResultSetName } from '@kit/doctor/lib/helpers';
|
||||
import { ResponseTable } from '@kit/doctor/schema/doctor-analysis.schema';
|
||||
import { pathsConfig } from '@kit/shared/config';
|
||||
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';
|
||||
@@ -23,7 +24,9 @@ import {
|
||||
TableRow,
|
||||
} from '@kit/ui/table';
|
||||
import { Trans } from '@kit/ui/trans';
|
||||
|
||||
import DoctorJobSelect from './doctor-job-select';
|
||||
import { capitalize } from 'lodash';
|
||||
|
||||
export default function ResultsTable({
|
||||
results = [],
|
||||
@@ -58,6 +61,8 @@ export default function ResultsTable({
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const { data: currentUser } = useUser();
|
||||
|
||||
const languageNames = useCurrentLocaleLanguageNames();
|
||||
|
||||
const fetchPage = async (page: number) => {
|
||||
startTransition(async () => {
|
||||
const result = await fetchAction({
|
||||
@@ -116,6 +121,9 @@ export default function ResultsTable({
|
||||
<TableHead className="w-20">
|
||||
<Trans i18nKey="doctor:resultsTable.resultsStatus" />
|
||||
</TableHead>
|
||||
<TableHead className="w-20">
|
||||
<Trans i18nKey="doctor:resultsTable.language" />
|
||||
</TableHead>
|
||||
<TableHead className="w-20">
|
||||
<Trans i18nKey="doctor:resultsTable.assignedTo" />
|
||||
</TableHead>
|
||||
@@ -179,6 +187,11 @@ export default function ResultsTable({
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{capitalize(
|
||||
languageNames.of(result?.patient?.preferred_locale ?? 'et'),
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<DoctorJobSelect
|
||||
doctorUserId={result.doctor?.primary_owner_user_id}
|
||||
|
||||
@@ -18,8 +18,8 @@ async function AnalysisPage({
|
||||
id: string;
|
||||
}>;
|
||||
}) {
|
||||
const { id: analysisResponseId } = await params;
|
||||
const analysisResultDetails = await loadResult(Number(analysisResponseId));
|
||||
const { id: analysisOrderId } = await params;
|
||||
const analysisResultDetails = await loadResult(Number(analysisOrderId));
|
||||
|
||||
if (!analysisResultDetails) {
|
||||
return null;
|
||||
@@ -28,7 +28,7 @@ async function AnalysisPage({
|
||||
if (analysisResultDetails) {
|
||||
await createDoctorPageViewLog({
|
||||
action: DoctorPageViewAction.VIEW_ANALYSIS_RESULTS,
|
||||
recordKey: analysisResponseId,
|
||||
recordKey: analysisOrderId,
|
||||
dataOwnerUserId: analysisResultDetails.patient.userId,
|
||||
});
|
||||
}
|
||||
@@ -50,3 +50,5 @@ async function AnalysisPage({
|
||||
|
||||
export default DoctorGuard(AnalysisPage);
|
||||
const loadResult = cache(getAnalysisResultsForDoctor);
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user