Skip to content

Commit 833b8eb

Browse files
Send email upon absence fill
1 parent 49c8402 commit 833b8eb

File tree

3 files changed

+134
-7
lines changed

3 files changed

+134
-7
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { createAbsenceFillConfirmationEmailBody } from '@utils/emailTemplates';
2+
import { getAdminEmails } from '@utils/getAdminEmails';
3+
import { prisma } from '@utils/prisma';
4+
import { sendEmail } from '@utils/sendEmail';
5+
import { NextResponse } from 'next/server';
6+
7+
export async function POST(req: Request) {
8+
const { absenceId } = await req.json();
9+
10+
const absence = await prisma.absence.findUnique({
11+
where: { id: absenceId },
12+
include: {
13+
absentTeacher: {
14+
select: { firstName: true, lastName: true, email: true },
15+
},
16+
substituteTeacher: {
17+
select: { firstName: true, lastName: true, email: true },
18+
},
19+
location: true,
20+
subject: true,
21+
lessonPlan: { select: { name: true, url: true } },
22+
},
23+
});
24+
25+
if (!absence) {
26+
return NextResponse.json({ error: 'Absence not found' }, { status: 404 });
27+
}
28+
29+
if (!absence.substituteTeacher) {
30+
return NextResponse.json(
31+
{ error: 'No filling teacher on this absence' },
32+
{ status: 400 }
33+
);
34+
}
35+
36+
const html = createAbsenceFillConfirmationEmailBody(
37+
{
38+
firstName: absence.substituteTeacher.firstName,
39+
lastName: absence.substituteTeacher.lastName,
40+
},
41+
{
42+
firstName: absence.absentTeacher.firstName,
43+
lastName: absence.absentTeacher.lastName,
44+
},
45+
{
46+
lessonDate: absence.lessonDate,
47+
subject: absence.subject,
48+
location: absence.location,
49+
lessonPlan: absence.lessonPlan,
50+
}
51+
);
52+
53+
const to = [absence.substituteTeacher.email];
54+
const admins = await getAdminEmails();
55+
const cc = [...admins, absence.absentTeacher.email];
56+
57+
const { success, error } = await sendEmail({
58+
to,
59+
cc,
60+
subject: 'Sistema Toronto Tacet - an absence has been filled',
61+
html,
62+
});
63+
64+
if (!success) {
65+
console.error('Fill email error:', error);
66+
return NextResponse.json({ error }, { status: 500 });
67+
}
68+
69+
return NextResponse.json({ success: true });
70+
}

src/components/absences/details/AbsenceDetails.tsx

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,20 +81,28 @@ const AbsenceDetails: React.FC<AbsenceDetailsProps> = ({
8181
roomNumber: event.roomNumber,
8282
}),
8383
});
84-
8584
if (!response.ok) {
8685
throw new Error('Failed to fill absence');
8786
}
8887

89-
const formattedDate = formatFullDate(event.start);
88+
const emailRes = await fetch('/api/emails/fillAbsence', {
89+
method: 'POST',
90+
headers: { 'Content-Type': 'application/json' },
91+
body: JSON.stringify({ absenceId: event.absenceId }),
92+
});
93+
if (!emailRes.ok) {
94+
console.error('Claim email failed:', await emailRes.text());
95+
}
9096

97+
const formattedDate = formatFullDate(event.start);
9198
showToast({
9299
status: 'success',
93100
description: (
94101
<Text>
95102
You have successfully filled{' '}
96103
<Text as="span" fontWeight="bold">
97-
{event.absentTeacher.firstName}&apos;s
104+
{event.absentTeacher.firstName}
105+
&apos;s
98106
</Text>{' '}
99107
absence on{' '}
100108
<Text as="span" fontWeight="bold">
@@ -108,16 +116,18 @@ const AbsenceDetails: React.FC<AbsenceDetailsProps> = ({
108116
setIsFillModalOpen(false);
109117
setIsFillThanksOpen(true);
110118
onTabChange('declared');
111-
} catch {
119+
} catch (err: any) {
112120
const formattedDate = formatFullDate(event.start);
113-
114121
showToast({
115122
status: 'error',
116123
description: (
117124
<Text>
118-
There was an error in filling{' '}
125+
{err.message.includes('email')
126+
? 'Failed to send confirmation email.'
127+
: 'There was an error in filling '}
119128
<Text as="span" fontWeight="bold">
120-
{event.absentTeacher.firstName}&apos;s
129+
{event.absentTeacher.firstName}
130+
&apos;s
121131
</Text>{' '}
122132
absence on{' '}
123133
<Text as="span" fontWeight="bold">

utils/emailTemplates.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,50 @@ export function createAbsenceModificationEmailBody(
111111
</html>
112112
`;
113113
}
114+
115+
export function createAbsenceFillConfirmationEmailBody(
116+
fillingTeacher: { firstName: string; lastName: string },
117+
reportingTeacher: { firstName: string; lastName: string },
118+
absence: {
119+
lessonDate: Date;
120+
subject: { name: string };
121+
location: { name: string };
122+
lessonPlan?: { name: string; url: string } | null;
123+
}
124+
): string {
125+
const formattedDate = new Intl.DateTimeFormat('en-US', {
126+
year: 'numeric',
127+
month: 'long',
128+
day: 'numeric',
129+
}).format(absence.lessonDate);
130+
131+
return `
132+
<html>
133+
<body>
134+
<p>Hello ${fillingTeacher.firstName} ${fillingTeacher.lastName},</p>
135+
<p>
136+
This email confirms you have successfully filled
137+
<strong>${reportingTeacher.firstName} ${reportingTeacher.lastName}’s</strong>
138+
<strong>${absence.subject.name}</strong> absence from
139+
<strong>${absence.location.name}</strong> on
140+
<strong>${formattedDate}</strong>.
141+
</p>
142+
<p>
143+
<strong>Click the link below to view:</strong><br/>
144+
<a href="${UPLOAD_LINK}" target="_blank">Tacet Calendar</a>
145+
</p>
146+
${
147+
absence.lessonPlan
148+
? `<p>
149+
<strong>Lesson Plan:</strong><br/>
150+
<a href="${absence.lessonPlan.url}" target="_blank">
151+
${absence.lessonPlan.name}
152+
</a>
153+
</p>`
154+
: ``
155+
}
156+
<p>Sistema Toronto</p>
157+
</body>
158+
</html>
159+
`;
160+
}

0 commit comments

Comments
 (0)