improve reimbursement status
This commit is contained in:
parent
eea220639c
commit
8f6b9806a9
1 changed files with 150 additions and 29 deletions
|
@ -177,6 +177,125 @@ async function sendStatusChangeEmail(pb: any, resend: any, fromEmail: string, re
|
||||||
status: data.newStatus
|
status: data.newStatus
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Helper function to generate status progress bar HTML
|
||||||
|
function generateStatusProgressBar(currentStatus: string): string {
|
||||||
|
const statusOrder = ['submitted', 'under_review', 'approved', 'in_progress', 'paid'];
|
||||||
|
const rejectedStatus = ['submitted', 'under_review', 'rejected'];
|
||||||
|
|
||||||
|
const isRejected = currentStatus === 'rejected';
|
||||||
|
const statuses = isRejected ? rejectedStatus : statusOrder;
|
||||||
|
|
||||||
|
const statusIcons: Record<string, string> = {
|
||||||
|
submitted: '→',
|
||||||
|
under_review: '○',
|
||||||
|
approved: '✓',
|
||||||
|
rejected: '✗',
|
||||||
|
in_progress: '◐',
|
||||||
|
paid: '✓'
|
||||||
|
};
|
||||||
|
|
||||||
|
const statusLabels: Record<string, string> = {
|
||||||
|
submitted: 'Submitted',
|
||||||
|
under_review: 'Under Review',
|
||||||
|
approved: 'Approved',
|
||||||
|
rejected: 'Rejected',
|
||||||
|
in_progress: 'In Progress',
|
||||||
|
paid: 'Paid'
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentIndex = statuses.indexOf(currentStatus);
|
||||||
|
|
||||||
|
let progressBarHtml = `
|
||||||
|
<div style="background: #f8fafc; padding: 30px 20px; border-radius: 8px; margin: 20px 0; border: 1px solid #e2e8f0;">
|
||||||
|
<h3 style="margin: 0 0 30px 0; color: #1e293b; font-size: 16px; font-weight: 600; text-align: center;">Request Progress</h3>
|
||||||
|
<div style="display: flex; align-items: flex-start; justify-content: space-between; position: relative; max-width: 400px; margin: 0 auto;">
|
||||||
|
<div style="position: absolute; left: 12px; right: 12px; top: 12px; height: 2px; background: #e2e8f0; z-index: 1;"></div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
statuses.forEach((status, index) => {
|
||||||
|
const isActive = index <= currentIndex;
|
||||||
|
const isCurrent = status === currentStatus;
|
||||||
|
|
||||||
|
let backgroundColor, textColor, lineColor;
|
||||||
|
if (isCurrent) {
|
||||||
|
if (status === 'rejected') {
|
||||||
|
backgroundColor = '#ef4444';
|
||||||
|
textColor = 'white';
|
||||||
|
lineColor = '#ef4444';
|
||||||
|
} else if (status === 'paid') {
|
||||||
|
backgroundColor = '#10b981';
|
||||||
|
textColor = 'white';
|
||||||
|
lineColor = '#10b981';
|
||||||
|
} else if (status === 'in_progress') {
|
||||||
|
backgroundColor = '#f59e0b';
|
||||||
|
textColor = 'white';
|
||||||
|
lineColor = '#f59e0b';
|
||||||
|
} else {
|
||||||
|
backgroundColor = '#3b82f6';
|
||||||
|
textColor = 'white';
|
||||||
|
lineColor = '#3b82f6';
|
||||||
|
}
|
||||||
|
} else if (isActive) {
|
||||||
|
backgroundColor = '#e2e8f0';
|
||||||
|
textColor = '#475569';
|
||||||
|
lineColor = '#cbd5e1';
|
||||||
|
} else {
|
||||||
|
backgroundColor = '#f8fafc';
|
||||||
|
textColor = '#94a3b8';
|
||||||
|
lineColor = '#e2e8f0';
|
||||||
|
}
|
||||||
|
|
||||||
|
progressBarHtml += `
|
||||||
|
<div style="display: flex; flex-direction: column; align-items: center; position: relative; z-index: 10;">
|
||||||
|
<div style="
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: ${backgroundColor};
|
||||||
|
color: ${textColor};
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
border: 2px solid white;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
line-height: 1;
|
||||||
|
text-align: center;
|
||||||
|
">
|
||||||
|
${statusIcons[status]}
|
||||||
|
</div>
|
||||||
|
<span style="
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: ${isCurrent ? (status === 'rejected' ? '#ef4444' : status === 'paid' ? '#10b981' : status === 'in_progress' ? '#f59e0b' : '#3b82f6') : isActive ? '#475569' : '#94a3b8'};
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-top: 8px;
|
||||||
|
max-width: 60px;
|
||||||
|
line-height: 1.2;
|
||||||
|
">
|
||||||
|
${statusLabels[status]}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Add colored line segment for active states
|
||||||
|
if (index < statuses.length - 1 && isActive) {
|
||||||
|
progressBarHtml += `
|
||||||
|
<div style="position: absolute; left: ${12 + (index * (376 / (statuses.length - 1)))}px; width: ${376 / (statuses.length - 1)}px; top: 12px; height: 2px; background: ${lineColor}; z-index: 2;"></div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
progressBarHtml += `
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return progressBarHtml;
|
||||||
|
}
|
||||||
|
|
||||||
const html = `
|
const html = `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
@ -195,6 +314,8 @@ async function sendStatusChangeEmail(pb: any, resend: any, fromEmail: string, re
|
||||||
<p>Hello ${user.name},</p>
|
<p>Hello ${user.name},</p>
|
||||||
<p>Your reimbursement request "<strong>${reimbursement.title}</strong>" has been updated.</p>
|
<p>Your reimbursement request "<strong>${reimbursement.title}</strong>" has been updated.</p>
|
||||||
|
|
||||||
|
${generateStatusProgressBar(data.newStatus)}
|
||||||
|
|
||||||
<div style="background: white; padding: 20px; border-radius: 8px; border-left: 4px solid ${statusColor}; margin: 20px 0;">
|
<div style="background: white; padding: 20px; border-radius: 8px; border-left: 4px solid ${statusColor}; margin: 20px 0;">
|
||||||
<div style="margin-bottom: 15px;">
|
<div style="margin-bottom: 15px;">
|
||||||
<span style="font-weight: bold; color: #666;">Status:</span>
|
<span style="font-weight: bold; color: #666;">Status:</span>
|
||||||
|
@ -259,7 +380,7 @@ async function sendStatusChangeEmail(pb: any, resend: any, fromEmail: string, re
|
||||||
</html>
|
</html>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
console.log('📤 Attempting to send email via Resend...');
|
console.log('Attempting to send email via Resend...');
|
||||||
const result = await resend.emails.send({
|
const result = await resend.emails.send({
|
||||||
from: fromEmail,
|
from: fromEmail,
|
||||||
to: [user.email],
|
to: [user.email],
|
||||||
|
@ -268,11 +389,11 @@ async function sendStatusChangeEmail(pb: any, resend: any, fromEmail: string, re
|
||||||
html,
|
html,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('✅ Resend response:', result);
|
console.log('Resend response:', result);
|
||||||
console.log('🎉 Status change email sent successfully!');
|
console.log('Status change email sent successfully!');
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Failed to send status change email:', error);
|
console.error('Failed to send status change email:', error);
|
||||||
console.error('Error details:', {
|
console.error('Error details:', {
|
||||||
name: error instanceof Error ? error.name : 'Unknown',
|
name: error instanceof Error ? error.name : 'Unknown',
|
||||||
message: error instanceof Error ? error.message : String(error),
|
message: error instanceof Error ? error.message : String(error),
|
||||||
|
@ -284,7 +405,7 @@ async function sendStatusChangeEmail(pb: any, resend: any, fromEmail: string, re
|
||||||
|
|
||||||
async function sendCommentEmail(pb: any, resend: any, fromEmail: string, replyToEmail: string, data: any): Promise<boolean> {
|
async function sendCommentEmail(pb: any, resend: any, fromEmail: string, replyToEmail: string, data: any): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
console.log('💬 Starting comment email process...');
|
console.log('Starting comment email process...');
|
||||||
console.log('Comment data received:', {
|
console.log('Comment data received:', {
|
||||||
reimbursementId: data.reimbursementId,
|
reimbursementId: data.reimbursementId,
|
||||||
commentByUserId: data.commentByUserId,
|
commentByUserId: data.commentByUserId,
|
||||||
|
@ -294,49 +415,49 @@ async function sendCommentEmail(pb: any, resend: any, fromEmail: string, replyTo
|
||||||
|
|
||||||
// Don't send emails for private comments
|
// Don't send emails for private comments
|
||||||
if (data.isPrivate) {
|
if (data.isPrivate) {
|
||||||
console.log('🔒 Comment is private, skipping email notification');
|
console.log('Comment is private, skipping email notification');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get reimbursement details
|
// Get reimbursement details
|
||||||
console.log('🔍 Fetching reimbursement details for:', data.reimbursementId);
|
console.log('Fetching reimbursement details for:', data.reimbursementId);
|
||||||
const reimbursement = await pb.collection('reimbursement').getOne(data.reimbursementId);
|
const reimbursement = await pb.collection('reimbursement').getOne(data.reimbursementId);
|
||||||
console.log('✅ Reimbursement fetched:', {
|
console.log('Reimbursement fetched:', {
|
||||||
id: reimbursement.id,
|
id: reimbursement.id,
|
||||||
title: reimbursement.title,
|
title: reimbursement.title,
|
||||||
submitted_by: reimbursement.submitted_by
|
submitted_by: reimbursement.submitted_by
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get submitter user details
|
// Get submitter user details
|
||||||
console.log('👤 Fetching submitter user details for:', reimbursement.submitted_by);
|
console.log('Fetching submitter user details for:', reimbursement.submitted_by);
|
||||||
const user = await pb.collection('users').getOne(reimbursement.submitted_by);
|
const user = await pb.collection('users').getOne(reimbursement.submitted_by);
|
||||||
if (!user || !user.email) {
|
if (!user || !user.email) {
|
||||||
console.error('❌ User not found or no email:', reimbursement.submitted_by);
|
console.error('User not found or no email:', reimbursement.submitted_by);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
console.log('✅ Submitter user fetched:', {
|
console.log('Submitter user fetched:', {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
email: user.email
|
email: user.email
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get commenter user name
|
// Get commenter user name
|
||||||
console.log('👤 Fetching commenter user details for:', data.commentByUserId);
|
console.log('Fetching commenter user details for:', data.commentByUserId);
|
||||||
let commentByName = 'Unknown User';
|
let commentByName = 'Unknown User';
|
||||||
try {
|
try {
|
||||||
const commentByUser = await pb.collection('users').getOne(data.commentByUserId);
|
const commentByUser = await pb.collection('users').getOne(data.commentByUserId);
|
||||||
commentByName = commentByUser?.name || 'Unknown User';
|
commentByName = commentByUser?.name || 'Unknown User';
|
||||||
console.log('✅ Commenter user fetched:', {
|
console.log('Commenter user fetched:', {
|
||||||
id: commentByUser?.id,
|
id: commentByUser?.id,
|
||||||
name: commentByName
|
name: commentByName
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('⚠️ Could not get commenter user name:', error);
|
console.warn('Could not get commenter user name:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const subject = `New Comment on Reimbursement: ${reimbursement.title}`;
|
const subject = `New Comment on Reimbursement: ${reimbursement.title}`;
|
||||||
|
|
||||||
console.log('📝 Comment email details:', {
|
console.log('Comment email details:', {
|
||||||
to: user.email,
|
to: user.email,
|
||||||
subject,
|
subject,
|
||||||
commentBy: commentByName,
|
commentBy: commentByName,
|
||||||
|
@ -401,7 +522,7 @@ async function sendCommentEmail(pb: any, resend: any, fromEmail: string, replyTo
|
||||||
</html>
|
</html>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
console.log('📤 Attempting to send comment email via Resend...');
|
console.log('Attempting to send comment email via Resend...');
|
||||||
const result = await resend.emails.send({
|
const result = await resend.emails.send({
|
||||||
from: fromEmail,
|
from: fromEmail,
|
||||||
to: [user.email],
|
to: [user.email],
|
||||||
|
@ -410,11 +531,11 @@ async function sendCommentEmail(pb: any, resend: any, fromEmail: string, replyTo
|
||||||
html,
|
html,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('✅ Resend comment email response:', result);
|
console.log('Resend comment email response:', result);
|
||||||
console.log('🎉 Comment email sent successfully!');
|
console.log('Comment email sent successfully!');
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Failed to send comment email:', error);
|
console.error('Failed to send comment email:', error);
|
||||||
console.error('Comment email error details:', {
|
console.error('Comment email error details:', {
|
||||||
name: error instanceof Error ? error.name : 'Unknown',
|
name: error instanceof Error ? error.name : 'Unknown',
|
||||||
message: error instanceof Error ? error.message : String(error),
|
message: error instanceof Error ? error.message : String(error),
|
||||||
|
@ -449,7 +570,7 @@ async function sendSubmissionEmail(pb: any, resend: any, fromEmail: string, repl
|
||||||
</head>
|
</head>
|
||||||
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
|
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
|
||||||
<div style="background: linear-gradient(135deg, #28a745 0%, #20c997 100%); padding: 30px; border-radius: 10px; margin-bottom: 30px;">
|
<div style="background: linear-gradient(135deg, #28a745 0%, #20c997 100%); padding: 30px; border-radius: 10px; margin-bottom: 30px;">
|
||||||
<h1 style="color: white; margin: 0; font-size: 24px;">✅ Reimbursement Submitted Successfully</h1>
|
<h1 style="color: white; margin: 0; font-size: 24px;">Reimbursement Submitted Successfully</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="background: #f8f9fa; padding: 25px; border-radius: 10px; margin-bottom: 25px;">
|
<div style="background: #f8f9fa; padding: 25px; border-radius: 10px; margin-bottom: 25px;">
|
||||||
|
@ -523,7 +644,7 @@ async function sendSubmissionEmail(pb: any, resend: any, fromEmail: string, repl
|
||||||
</head>
|
</head>
|
||||||
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
|
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
|
||||||
<div style="background: linear-gradient(135deg, #007bff 0%, #0056b3 100%); padding: 30px; border-radius: 10px; margin-bottom: 30px;">
|
<div style="background: linear-gradient(135deg, #007bff 0%, #0056b3 100%); padding: 30px; border-radius: 10px; margin-bottom: 30px;">
|
||||||
<h1 style="color: white; margin: 0; font-size: 24px;">📋 New Reimbursement Request</h1>
|
<h1 style="color: white; margin: 0; font-size: 24px;">New Reimbursement Request</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="background: #f8f9fa; padding: 25px; border-radius: 10px; margin-bottom: 25px;">
|
<div style="background: #f8f9fa; padding: 25px; border-radius: 10px; margin-bottom: 25px;">
|
||||||
|
@ -583,7 +704,7 @@ async function sendSubmissionEmail(pb: any, resend: any, fromEmail: string, repl
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="background: #e7f3ff; padding: 15px; border-radius: 8px; border-left: 4px solid #007bff; margin: 20px 0;">
|
<div style="background: #e7f3ff; padding: 15px; border-radius: 8px; border-left: 4px solid #007bff; margin: 20px 0;">
|
||||||
<h4 style="margin: 0 0 10px 0; color: #004085;">📋 Next Steps:</h4>
|
<h4 style="margin: 0 0 10px 0; color: #004085;">Next Steps:</h4>
|
||||||
<ul style="margin: 0; padding-left: 20px; color: #004085;">
|
<ul style="margin: 0; padding-left: 20px; color: #004085;">
|
||||||
<li>Review the submitted receipts and documentation</li>
|
<li>Review the submitted receipts and documentation</li>
|
||||||
<li>Log into the reimbursement portal to approve or request changes</li>
|
<li>Log into the reimbursement portal to approve or request changes</li>
|
||||||
|
@ -630,7 +751,7 @@ async function sendSubmissionEmail(pb: any, resend: any, fromEmail: string, repl
|
||||||
|
|
||||||
async function sendTestEmail(resend: any, fromEmail: string, replyToEmail: string, email: string): Promise<boolean> {
|
async function sendTestEmail(resend: any, fromEmail: string, replyToEmail: string, email: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
console.log('🧪 Starting test email process...');
|
console.log('Starting test email process...');
|
||||||
console.log('Test email configuration:', {
|
console.log('Test email configuration:', {
|
||||||
fromEmail,
|
fromEmail,
|
||||||
replyToEmail,
|
replyToEmail,
|
||||||
|
@ -650,7 +771,7 @@ async function sendTestEmail(resend: any, fromEmail: string, replyToEmail: strin
|
||||||
</head>
|
</head>
|
||||||
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
|
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
|
||||||
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; border-radius: 10px; margin-bottom: 30px;">
|
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; border-radius: 10px; margin-bottom: 30px;">
|
||||||
<h1 style="color: white; margin: 0; font-size: 24px;">🧪 Test Email</h1>
|
<h1 style="color: white; margin: 0; font-size: 24px;">Test Email</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="background: #f8f9fa; padding: 25px; border-radius: 10px; margin-bottom: 25px;">
|
<div style="background: #f8f9fa; padding: 25px; border-radius: 10px; margin-bottom: 25px;">
|
||||||
|
@ -659,7 +780,7 @@ async function sendTestEmail(resend: any, fromEmail: string, replyToEmail: strin
|
||||||
<p>If you receive this email, the notification system is working correctly!</p>
|
<p>If you receive this email, the notification system is working correctly!</p>
|
||||||
|
|
||||||
<div style="background: #d4edda; padding: 15px; border-radius: 8px; border-left: 4px solid #28a745; margin: 20px 0;">
|
<div style="background: #d4edda; padding: 15px; border-radius: 8px; border-left: 4px solid #28a745; margin: 20px 0;">
|
||||||
<p style="margin: 0; color: #155724;">✅ Email delivery successful</p>
|
<p style="margin: 0; color: #155724;">Email delivery successful</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -671,7 +792,7 @@ async function sendTestEmail(resend: any, fromEmail: string, replyToEmail: strin
|
||||||
</html>
|
</html>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
console.log('📤 Sending test email via Resend...');
|
console.log('Sending test email via Resend...');
|
||||||
const result = await resend.emails.send({
|
const result = await resend.emails.send({
|
||||||
from: fromEmail,
|
from: fromEmail,
|
||||||
to: [email],
|
to: [email],
|
||||||
|
@ -680,11 +801,11 @@ async function sendTestEmail(resend: any, fromEmail: string, replyToEmail: strin
|
||||||
html,
|
html,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('✅ Resend test email response:', result);
|
console.log('Resend test email response:', result);
|
||||||
console.log('🎉 Test email sent successfully!');
|
console.log('Test email sent successfully!');
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Failed to send test email:', error);
|
console.error('Failed to send test email:', error);
|
||||||
console.error('Test email error details:', {
|
console.error('Test email error details:', {
|
||||||
name: error instanceof Error ? error.name : 'Unknown',
|
name: error instanceof Error ? error.name : 'Unknown',
|
||||||
message: error instanceof Error ? error.message : String(error),
|
message: error instanceof Error ? error.message : String(error),
|
||||||
|
|
Loading…
Reference in a new issue