Build out and push automatic report generation, remove emailing of every report indiidially
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import mongoose from "mongoose";
|
||||
import { Report } from "./models/report.js";
|
||||
|
||||
let isConnected = false;
|
||||
|
||||
@@ -23,6 +24,8 @@ export async function initDb(): Promise<void> {
|
||||
await mongoose.connect(MONGO_URI);
|
||||
isConnected = true;
|
||||
console.log('✅ MongoDB connected');
|
||||
await Report.syncIndexes();
|
||||
console.log('✅ Indexes synced');
|
||||
} catch (err) {
|
||||
console.error('❌ MongoDB connection error:', err)
|
||||
process.exit(1);
|
||||
|
||||
@@ -41,7 +41,7 @@ export async function handleFormData(data: any) {
|
||||
}
|
||||
}
|
||||
|
||||
sendMail(report);
|
||||
// sendMail(report); Disable sendMail in favour of daily,weekly,monthly reports
|
||||
sendToDatabase(report);
|
||||
}
|
||||
|
||||
|
||||
37
src/index.ts
37
src/index.ts
@@ -1,9 +1,10 @@
|
||||
import express, { Request, Response } from "express";
|
||||
import { handleFormData, fetchReports } from "./formHandler.js";
|
||||
import { initDb } from "./database.js";
|
||||
import { GenerateReportByDate } from "./reports/reports.js";
|
||||
|
||||
const app = express();
|
||||
const port = process.env.port || 3000;
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
initDb();
|
||||
|
||||
@@ -20,7 +21,7 @@ app.post('/submit', (req, res) => {
|
||||
res.status(200).json({ status: 'ok', message: 'Form received' });
|
||||
});
|
||||
|
||||
app.get('/fetch', async (req, res) => {
|
||||
app.get('/fetchall', async (req, res) => {
|
||||
try {
|
||||
const data = await fetchReports();
|
||||
res.status(200).json(data);
|
||||
@@ -29,6 +30,38 @@ app.get('/fetch', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
interface ReportQuery {
|
||||
start?: string;
|
||||
end?: string;
|
||||
password?: string;
|
||||
};
|
||||
|
||||
const reportHandler = async (req: Request, res: Response) => {
|
||||
const { start, end, password } = req.query;
|
||||
|
||||
if (password !== process.env.PASSWORD) {
|
||||
return res.status(401).send('Unauthorized');
|
||||
}
|
||||
|
||||
if (!start || !end) {
|
||||
return res.status(400).send('Missing Parameters');
|
||||
}
|
||||
|
||||
try {
|
||||
const startDate = new Date(start as string);
|
||||
const endDate = new Date(end as string);
|
||||
endDate.setHours(23, 59, 59, 999); // Set to end of day
|
||||
const html = await GenerateReportByDate(startDate, endDate);
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.send(html);
|
||||
} catch (err) {
|
||||
console.error('Report generation failed:', err);
|
||||
res.status(500).send('Error generating report');
|
||||
}
|
||||
};
|
||||
|
||||
app.get('/report', reportHandler as express.RequestHandler);
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log("Server running on port:", port);
|
||||
});
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Schema, model, Document } from "mongoose";
|
||||
import type { Report as ReportType, Fault as FaultType } from "../formHandler";
|
||||
import type { BaseReport } from "../reports/reports";
|
||||
|
||||
|
||||
interface ReportDocument extends ReportType, Document {}
|
||||
|
||||
@@ -16,4 +18,16 @@ const ReportSchema = new Schema<ReportDocument>({
|
||||
faults: { type: [FaultSchema], required: true },
|
||||
});
|
||||
|
||||
export const Report = model<ReportDocument>('Report', ReportSchema);
|
||||
ReportSchema.index({ utcTimestamp: 1 });
|
||||
ReportSchema.index({ unitNumber: 1 });
|
||||
|
||||
export const Report = model<ReportDocument>('Report', ReportSchema);
|
||||
|
||||
export async function fetchReportsInRange(start: Date, end: Date): Promise<BaseReport[]> {
|
||||
return Report.find({
|
||||
utcTimestamp: {
|
||||
$gte: start,
|
||||
$lte: end,
|
||||
}
|
||||
}).exec();
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>TrACreport - Report Output</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.preface {
|
||||
text-align: center;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.train-container {
|
||||
width: 75%;
|
||||
margin: 2em auto;
|
||||
padding: 1em;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.unit-number {
|
||||
text-align: center;
|
||||
margin-bottom: 1em;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.vehicle-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.coach {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.vehicle-number {
|
||||
margin-bottom: 0.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.vehicle-box {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
background-color: #e0e0e0;
|
||||
border: 2px solid black;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 6px;
|
||||
font-size: 1.2em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.cab-marker {
|
||||
width: 1.5em;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
background-color: #d0d0f0;
|
||||
border-radius: 4px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.report-count {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.comments-box {
|
||||
margin-top: 1.5em;
|
||||
padding: 1em;
|
||||
background-color: #f9f9f9;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.comments-box h2 {
|
||||
margin-top: 0;
|
||||
font-size: 1.2em;
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.comments-box p {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="preface">
|
||||
<h1>TrACreport</h1>
|
||||
<h2>Report period: 09/06/2025-09/07/2025</h2>
|
||||
<p>Total reports received for each vehicle in the West fleet</p>
|
||||
<p>158 cabs are not recorded as cab cooling is disabled for Guards</p>
|
||||
</div>
|
||||
<div class="train-container">
|
||||
<h1 class="unit-number">158747</h1>
|
||||
<div class="vehicle-container">
|
||||
<div class="coach">
|
||||
<div class="vehicle-number">57747</div>
|
||||
<div class="vehicle-box">
|
||||
<div class="cab-marker">0</div>
|
||||
<div class="report-count">2</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="coach">
|
||||
<div class="vehicle-number">51747</div>
|
||||
<div class="vehicle-box">
|
||||
<div class="report-count">4</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="coach">
|
||||
<div class="vehicle-number">52747</div>
|
||||
<div class="vehicle-box">
|
||||
<div class="report-count">5</div>
|
||||
<div class="cab-marker">0</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="comments-box">
|
||||
<h2>Comments</h2>
|
||||
<p>Why bother reporting?</p>
|
||||
<p>Blue button in cab not working</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,4 +1,4 @@
|
||||
import { GroupedUnitReport, VehicleReport } from "./reports";
|
||||
import { GroupedUnitReport, VehicleReport, Comments } from "./reports";
|
||||
|
||||
export function generateHTMLReport(units: GroupedUnitReport[], startDate: Date, endDate: Date, totalReports: number): string {
|
||||
const unitsWithReports = units.filter(unit =>
|
||||
@@ -44,17 +44,28 @@ function renderCoach(vehicle: VehicleReport, index: number, total: number): stri
|
||||
`;
|
||||
}
|
||||
|
||||
function renderComments(comments: string[]): string {
|
||||
const renderedComments = comments.length
|
||||
? comments.map(c => `<p>${c}</p>`).join('\n')
|
||||
: '<p>No comments submitted.</p>';
|
||||
function renderComments(comments: Comments[]): string {
|
||||
const formatDate = (date: Date): string =>
|
||||
new Intl.DateTimeFormat('en-GB', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
}).format(date);
|
||||
|
||||
const renderedComments = comments.length
|
||||
? comments.map(c =>
|
||||
`<p>Reported: <strong>${c.reportedBy}</strong> on: <strong>${formatDate(c.timestamp)}</strong><br>${c.comment}</p>`
|
||||
).join('\n')
|
||||
: '<p>No comment</p>';
|
||||
|
||||
return `
|
||||
<div class="comments-box">
|
||||
<h2>Comments</h2>
|
||||
${renderedComments}
|
||||
<h2>Comments</h2>
|
||||
${renderedComments}
|
||||
</div>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
|
||||
function wrapHtml(content: string, startDate: Date, endDate: Date, totalReports: number): string {
|
||||
@@ -167,6 +178,7 @@ function wrapHtml(content: string, startDate: Date, endDate: Date, totalReports:
|
||||
<div class="preface">
|
||||
<h1>TrACreport</h1>
|
||||
<h2>Report period: ${formatDate(startDate)} - ${formatDate(endDate)}</h2>
|
||||
<p>All times are in Universal Time Coordinated</p>
|
||||
<p class="bold">Total reports received: ${totalReports}</p>
|
||||
<p>Reports are shown per saloon or cab</p>
|
||||
<p>158 cabs are not recorded as cab cooling is disabled for Guards</p>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { generateHTMLReport } from "./reportHtml.js";
|
||||
import { writeFile } from "fs/promises";
|
||||
import { fetchReportsInRange } from "../models/report.js";
|
||||
|
||||
interface BaseReport {
|
||||
export interface BaseReport {
|
||||
unitNumber: string;
|
||||
reported: string;
|
||||
comments?: string;
|
||||
@@ -17,7 +18,13 @@ interface Fault {
|
||||
export interface GroupedUnitReport {
|
||||
unitNumber: string;
|
||||
vehicles: VehicleReport[];
|
||||
comments: string[];
|
||||
comments: Comments[];
|
||||
}
|
||||
|
||||
export interface Comments {
|
||||
timestamp: Date;
|
||||
reportedBy: string;
|
||||
comment: string;
|
||||
}
|
||||
|
||||
export interface VehicleReport {
|
||||
@@ -82,7 +89,11 @@ function populateFaultReports(
|
||||
const unit = unitMap.get(report.unitNumber);
|
||||
if (!unit) continue;
|
||||
|
||||
if (report.comments) unit.comments.push(report.comments);
|
||||
unit.comments.push({
|
||||
comment: report.comments ?? "",
|
||||
timestamp: report.utcTimestamp,
|
||||
reportedBy: report.reported,
|
||||
});
|
||||
|
||||
for (const fault of report.faults) {
|
||||
const vehicle = unit.vehicles.find(v => v.vehicleNumber === fault.coach);
|
||||
@@ -97,7 +108,17 @@ function populateFaultReports(
|
||||
return grouped;
|
||||
}
|
||||
|
||||
export async function GenerateReportByDate(start: Date, end: Date): Promise<string> {
|
||||
const unitLayout = await fetchUnitLayout();
|
||||
const reports = await fetchReportsInRange(start, end);
|
||||
const totalReports = reports.length;
|
||||
|
||||
const grouped = buildInitialReportStructure(unitLayout);
|
||||
const finalReport = populateFaultReports(grouped, reports);
|
||||
return generateHTMLReport(finalReport, start, end, totalReports);
|
||||
}
|
||||
|
||||
/*
|
||||
(async () => {
|
||||
console.log('Fetching unit layout...');
|
||||
const unitLayout = await fetchUnitLayout();
|
||||
|
||||
Reference in New Issue
Block a user