feat: add day logs to report

This commit is contained in:
Riccardo Senica
2025-01-19 20:50:08 +00:00
parent 8c60f400ea
commit 89a20c214b
2 changed files with 98 additions and 17 deletions

View File

@@ -1,6 +1,7 @@
import { Resend } from 'resend'; import { Resend } from 'resend';
import { Prisma } from '@prisma/client';
import prisma from '@prisma/prisma'; import prisma from '@prisma/prisma';
import { ReportData } from '@utils/types'; import { ReportExpenseData, ReportDayLogsData } from '@utils/types';
export class ExpenseReporter { export class ExpenseReporter {
private resend: Resend; private resend: Resend;
@@ -22,7 +23,10 @@ export class ExpenseReporter {
this.recipientEmail = process.env.RECIPIENT_EMAIL; this.recipientEmail = process.env.RECIPIENT_EMAIL;
} }
private async generateReport(from: Date, to: Date): Promise<ReportData> { private async generateExpenses(
from: Date,
to: Date
): Promise<ReportExpenseData> {
const startDate = new Date(from); const startDate = new Date(from);
startDate.setHours(0, 0, 0, 0); startDate.setHours(0, 0, 0, 0);
@@ -77,7 +81,38 @@ export class ExpenseReporter {
}; };
} }
private generateHtmlReport(data: ReportData): string { private async generateDayLogs(
from: Date,
to: Date
): Promise<ReportDayLogsData> {
const startDate = new Date(from);
startDate.setHours(0, 0, 0, 0);
const endDate = new Date(to);
endDate.setHours(23, 59, 59, 999);
const dayLogs = await prisma.dayLog.findMany({
where: {
createdAt: {
gte: startDate,
lte: endDate
}
},
orderBy: {
createdAt: 'desc'
}
});
return {
dayLogs,
dateRange: { from, to }
};
}
private generateHtmlReport(
expenses: ReportExpenseData,
dayLogs: ReportDayLogsData
): string {
const formatDate = (date: Date) => const formatDate = (date: Date) =>
date.toLocaleDateString('en-US', { date.toLocaleDateString('en-US', {
year: 'numeric', year: 'numeric',
@@ -105,12 +140,12 @@ export class ExpenseReporter {
</style> </style>
</head> </head>
<body> <body>
<h1>Expense Report</h1> <h1>Diary Report</h1>
<p>From ${formatDate(data.dateRange.from)} to ${formatDate(data.dateRange.to)}</p> <p>From ${formatDate(expenses.dateRange.from)} to ${formatDate(expenses.dateRange.to)}</p>
<div class="summary"> <div class="summary">
<h2>Summary</h2> <h2>Summary</h2>
<p><strong>Total Expenses:</strong> ${formatCurrency(data.summary.totalExpenses)}</p> <p><strong>Total Expenses:</strong> ${formatCurrency(expenses.summary.totalExpenses)}</p>
<div class="category-summary"> <div class="category-summary">
<h3>Expenses by Category</h3> <h3>Expenses by Category</h3>
@@ -120,7 +155,7 @@ export class ExpenseReporter {
<th>Total</th> <th>Total</th>
<th>Count</th> <th>Count</th>
</tr> </tr>
${data.summary.byCategory ${expenses.summary.byCategory
.map( .map(
cat => ` cat => `
<tr> <tr>
@@ -144,7 +179,7 @@ export class ExpenseReporter {
<th>Category</th> <th>Category</th>
<th>Amount</th> <th>Amount</th>
</tr> </tr>
${data.expenses ${expenses.expenses
.map( .map(
exp => ` exp => `
<tr> <tr>
@@ -158,6 +193,33 @@ export class ExpenseReporter {
) )
.join('')} .join('')}
</table> </table>
<h2>Day Logs Report</h2>
<p>From ${formatDate(dayLogs.dateRange.from)} to ${formatDate(dayLogs.dateRange.to)}</p>
<table>
<tr>
<th>ID</th>
<th>Date</th>
<th>Log</th>
</tr>
${dayLogs.dayLogs
.filter(
(dl): dl is typeof dl & { comments: any[] } =>
dl.comments !== null && Array.isArray(dl.comments)
)
.flatMap(dl =>
dl.comments.map(
comment => `
<tr>
<td>${dl.id}</td>
<td>${formatDate(dl.createdAt)}</td>
<td>${comment.text}</td>
</tr>
`
)
)
.join('')}
</table>
</body> </body>
</html> </html>
`; `;
@@ -168,16 +230,27 @@ export class ExpenseReporter {
to: Date, to: Date,
includeJson: boolean = false includeJson: boolean = false
): Promise<void> { ): Promise<void> {
const reportData = await this.generateReport(from, to); const reportExpenseData = await this.generateExpenses(from, to);
const htmlContent = this.generateHtmlReport(reportData); const reportDayLogData = await this.generateDayLogs(from, to);
const htmlContent = this.generateHtmlReport(
reportExpenseData,
reportDayLogData
);
const attachments = []; const attachments = [];
if (includeJson) { if (includeJson) {
const jsonData = JSON.stringify(reportData, null, 2); const jsonExpenseData = JSON.stringify(reportExpenseData, null, 2);
attachments.push({ attachments.push({
filename: 'expense-report.json', filename: 'expenses.json',
content: Buffer.from(jsonData).toString('base64'), content: Buffer.from(jsonExpenseData).toString('base64'),
contentType: 'application/json' // Added MIME type contentType: 'application/json'
});
const jsonDayLogData = JSON.stringify(reportDayLogData, null, 2);
attachments.push({
filename: 'day-logs.json',
content: Buffer.from(jsonDayLogData).toString('base64'),
contentType: 'application/json'
}); });
} }
@@ -185,7 +258,7 @@ export class ExpenseReporter {
const response = await this.resend.emails.send({ const response = await this.resend.emails.send({
from: this.senderEmail, from: this.senderEmail,
to: this.recipientEmail, to: this.recipientEmail,
subject: `Expense Report: ${from.toLocaleDateString()} - ${to.toLocaleDateString()}`, subject: `Diary Report: ${from.toLocaleDateString()} - ${to.toLocaleDateString()}`,
html: htmlContent, html: htmlContent,
attachments attachments
}); });

View File

@@ -1,4 +1,4 @@
import { Category, Expense } from '@prisma/client'; import { Category, DayLog, Expense } from '@prisma/client';
import { z } from 'zod'; import { z } from 'zod';
interface Flag { interface Flag {
@@ -40,7 +40,7 @@ const ExpenseSchema = z.object({
export type ExpenseType = z.infer<typeof ExpenseSchema>; export type ExpenseType = z.infer<typeof ExpenseSchema>;
export interface ReportData { export interface ReportExpenseData {
expenses: (Expense & { category: Category })[]; expenses: (Expense & { category: Category })[];
summary: { summary: {
totalExpenses: number; totalExpenses: number;
@@ -55,3 +55,11 @@ export interface ReportData {
to: Date; to: Date;
}; };
} }
export interface ReportDayLogsData {
dayLogs: DayLog[];
dateRange: {
from: Date;
to: Date;
};
}