diff --git a/src/report.html b/src/reports/report.html similarity index 100% rename from src/report.html rename to src/reports/report.html diff --git a/src/reportGen.ts b/src/reports/reportGen.ts similarity index 85% rename from src/reportGen.ts rename to src/reports/reportGen.ts index 663b5ae..e28f36b 100644 --- a/src/reportGen.ts +++ b/src/reports/reportGen.ts @@ -5,6 +5,19 @@ interface ReportSummary { reportsPerUnit: Record; } +interface vehicleDetails { + vehicleNumber: string; + reportCount: number; + cabFront: boolean; + cabRear: boolean; +} + +interface ReportsByUnit { + unitNumber: string; + vehicles: vehicleDetails[]; + comments: string[]; +} + interface Report { unitNumber: string; reported: string; @@ -54,6 +67,10 @@ export async function organiseReports(): Promise { } } +// Fetch all unit numbers. +// Group reports by unit number. +// Build HTML for each report. + (async () => { try { const summary = await organiseReports(); diff --git a/src/reports/reportHtml.ts b/src/reports/reportHtml.ts new file mode 100644 index 0000000..42d5bed --- /dev/null +++ b/src/reports/reportHtml.ts @@ -0,0 +1,167 @@ +import { GroupedUnitReport, VehicleReport } from "./reports"; + +export function generateHTMLReport(units: GroupedUnitReport[], startDate: Date, endDate: Date): string { + const unitsWithReports = units.filter(unit => + unit.vehicles.some( + v => v.saloonReports > 0 || v.cabReports > 0 + ) + ); + + const body = unitsWithReports.map(renderUnitSection).join('\n'); + return wrapHtml(body, startDate, endDate); +} + +function renderUnitSection(unit: GroupedUnitReport): string { + const total = unit.vehicles.length; + + return ` +
+

${unit.unitNumber}

+
+ ${unit.vehicles.map((v, i) => renderCoach(v, i, total)).join('\n')} +
+ ${renderComments(unit.comments)} +
`; +} + +function renderCoach(vehicle: VehicleReport, index: number, total: number): string { + const cabMarkerHTML = vehicle.hasCabZone + ? `
${vehicle.cabReports}
` + : ''; + + const cabAbove = index === 0; + const cabBelow = index === total -1; + + return ` +
+
${vehicle.vehicleNumber}
+
+ ${cabAbove ? cabMarkerHTML : ''} +
${vehicle.saloonReports}
+ ${cabBelow && !cabAbove ? cabMarkerHTML : ''} +
+
+ `; +} + +function renderComments(comments: string[]): string { + const renderedComments = comments.length + ? comments.map(c => `

${c}

`).join('\n') + : '

No comments submitted.

'; + + return ` +
+

Comments

+ ${renderedComments} +
+ `; +} + +function wrapHtml(content: string, startDate: Date, endDate: Date): string { + const formatDate = (d: Date) => d.toISOString().split('T')[0]; + + return ` + + + + TrACreport - Report Output + + + +
+

TrACreport

+

Report period: ${formatDate(startDate)} - ${formatDate(endDate)}

+

Total reports received for each vehicle in the West fleet

+

158 cabs are not recorded as cab cooling is disabled for Guards

+
+ ${content} + +` +} \ No newline at end of file diff --git a/src/reports/reports.ts b/src/reports/reports.ts new file mode 100644 index 0000000..053b24b --- /dev/null +++ b/src/reports/reports.ts @@ -0,0 +1,103 @@ +interface BaseReport { + unitNumber: string; + reported: string; + comments?: string; + utcTimestamp: Date; + faults: Fault[]; +} + +interface Fault { + coach: string; + zone: string; +} + +export interface GroupedUnitReport { + unitNumber: string; + vehicles: VehicleReport[]; + comments: string[]; +} + +export interface VehicleReport { + vehicleNumber: string; + cabReports: number; + saloonReports: number; + hasCabZone: boolean; +} + +async function fetchReports(): Promise { + try { + const res = await fetch('https://rep.fjla.uk/fetch'); + if (!res.ok) throw new Error(`Fetch failed: ${res.status} ${res.statusText}`); + + const rawData = await res.json(); + + const reports: BaseReport[] = rawData.map((item: any) => ({ + unitNumber: item.unitNumber, + reported: item.reported, + comments: item.comments && item.comments.trim().toLowerCase() !== 'none' + ? item.comments + : undefined, + utcTimestamp: new Date(item.utcTimestamp), + faults: item.faults, + })); + + return reports; + } catch (err) { + console.error('Error fetching reports:', err); + throw err; + } +} + +async function fetchUnitLayout(): Promise> { + const res = await fetch('https://rep.fjla.uk/units.converted.json'); + if (!res.ok) throw new Error(`Failed to fetch unit layout: ${res.statusText}`); + return await res.json(); +} + +function buildInitialReportStructure( + unitLayout: Record +): GroupedUnitReport[] { + return Object.entries(unitLayout).map(([unitNumber, vehicles]) => ({ + unitNumber, + comments: [], + vehicles: vehicles.map(v => ({ + vehicleNumber: v.id, + saloonReports: 0, + cabReports: 0, + hasCabZone: v.zones.includes('cab'), + })) + })); +}; + +function populateFaultReports( + grouped: GroupedUnitReport[], + reports: BaseReport[] +): GroupedUnitReport[] { + const unitMap = new Map(grouped.map(u => [u.unitNumber, u])); + + for (const report of reports) { + const unit = unitMap.get(report.unitNumber); + if (!unit) continue; + + if (report.comments) unit.comments.push(report.comments); + + for (const fault of report.faults) { + const vehicle = unit.vehicles.find(v => v.vehicleNumber === fault.coach); + if (!vehicle) continue; + + const zone = fault.zone.toLowerCase(); + if (zone === 'cab') vehicle.cabReports++; + else vehicle.saloonReports++; + } + } + + return grouped; +} + +/* USAGE: +const unitLayout = await fetchUnitLayout(); +const reports = await fetchReports(); + +const grouped = buildInitialReportStructure(unitLayout); +const finalReport = populateFaultReports(grouped, reports); +*/ \ No newline at end of file