I am currently working on a React + Node application.
The application stores data entries in a data base. Users should be able to download all stored data from the database to an excel workbook. To achieve this, I'm using exceljs npm module.
The Problem: I implement a POST route called downloadFile which should be triggered by a client when the download button is clicked. The route is admittedly triggered but no excel file is downloaded.
Ironically, while troubleshooting, I changed the POST route to a GET route in the index.js of my node application, then manually typed the route (http://localhost:3001/downloadFile) and hit the enter key on the keyboard and the workbook was downloaded. This made me believe that the core functionality of the downloadFile route works.
What can I do to enable clients trigger the downloadFile POST route by clicking a button?
Below are the codes for the project thus far. I didn't include codes for other routes in the index.js file that are working perfectly to ensure brevity and more clarity.
SERVER-SIDE CODE (NODEJS & EXPRESS)
index.js
const express = require("express");
const bodyParser = require("body-parser");
const mongoose = require("mongoose");
const excel = require("exceljs");
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
mongoose.connect("mongodb://localhost:27017/NOC", {
useNewUrlParser: true,
useUnifiedTopology: true,
});
const db = mongoose.connection;
db.on("error", console.error.bind(console, "connection error:"));
db.once("open", function () {
console.log("We're connected to db!");
});
//Ticket schema
const ticketSchema = new mongoose.Schema({
nodeA: {
type: String,
required: true,
},
nodeB: {
type: String,
required: true,
},
vendor: {
type: String,
required: true,
},
impact: {
type: String,
required: false,
},
route: {
type: String,
required: false,
},
timeDown: {
type: String,
required: true,
},
timeUp: {
type: String,
required: false,
},
TTR: {
type: Number,
required: false,
},
RDT: {
type: Number,
required: false,
},
siteIDWithPowerFailure: {
type: String,
required: false,
},
COF: {
type: String,
required: false,
},
action: {
type: String,
required: false,
},
byWhom: {
type: String,
required: true,
},
subSystem: {
type: String,
required: false,
},
});
const ticket = mongoose.model("ticket", ticketSchema);
...
//downloadFile post route
app.post("/downloadFile", function (req, res) {
//retrieve all tickets in database and store it as result array
ticket.find({}, function (err, results) {
let modifiedResult = results.reverse();
if (err) {
console.log("Could not retrieve data from database " + err);
res.status(400);
} else {
//create a new excel workbook, a worksheet and set its properties.
const workbook = new excel.Workbook();
const sheet = workbook.addWorksheet("FIBRE_SWITCH", {
properties: {
tabColor: { argb: "FFc0000" },
},
});
sheet.columns = [
{ header: "S/N", key: "S/N", width: 4 },
{ header: "TERMINAL A", key: "nodeA", width: 12 },
{ header: "TERMINAL B", key: "nodeB", width: 12 },
{ header: "VENDOR", key: "vendor", width: 9 },
{ header: "IMPACT", key: "impact", width: 12 },
{ header: "ROUTE", key: "route", width: 10 },
{
header: "FROM (DATE & TIME)",
key: "timeDown",
width: 12,
},
{ header: "TO (DATE & TIME)", key: "timeUp", width: 12 },
{ header: "TTR (HRS)", key: "TTR (HRS)", width: 8 },
{ header: "RDT (HRS)", key: "RDT (HRS)", width: 8 },
{
header: "SITE ID OF OFC ISSUE WITH POWER",
key: "siteIDWithPowerFailure",
width: 9,
},
{
header: "PROBLEM/SPECIFIC CAUSE",
key: "COF",
width: 14,
},
{ header: "ACTION TAKEN", key: "action", width: 14 },
{ header: "BY WHOM", key: "byWhom", width: 12 },
{ header: "SUBSYSTEM", key: "subSystem", width: 12 },
];
//write the data from the database to the excel worksheet cells.
sheet.addRows(modifiedResult);
res.setHeader(
"Content-Type",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
);
res.setHeader(
"Content-Disposition",
"attachment; filename=" + "OFC REPORT.xlsx"
);
//Download workbook.
return workbook.xlsx.write(res).then(function () {
console.log("Download success");
res.status(200);
});
}
});
});
app.listen(3001, function () {
console.log("Server started at port 3001");
});
CLIENT-SIDE CODE (REACT.JS)
App.js
import React from "react";
import "bootstrap/dist/css/bootstrap.min.css";
import Failure from "./Failure";
function App() {
return (
<div className="App">
<Failure />
</div>
);
}
export default App;
Failure.js
import React, { useEffect, useState } from "react";
export default function Failure() {
const [ticketList, setTicketList] = useState([]);
const [formData, setFormData] = useState({
nodeA: "",
nodeB: "",
vendor: "",
timeDown: "",
OFCsiteWithPowerIssue: "",
byWhom: "",
});
useEffect(() => {
if (ticketList.length === 0) {
getAllTicket();
}
});
const onChangeHandler = (e) => {
const name = e.target.name;
const value = e.target.value;
setFormData((prevState) => {
return {
...prevState,
[name]: typeof value === "string" ? value.toUpperCase() : value,
};
});
};
const updateUi = (res) => {
res &&
res.json().then((data) => {
const reversedData = data.reverse();
setTicketList(reversedData);
});
};
const registerFailure = (event, data) => {
event.preventDefault();
fetch("/registerFailure", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
})
.then((response) => {
if (response.status === 200) {
updateUi(response);
}
})
.catch((err) => {
alert("Failed to register ticket, " + err);
});
};
const deleteTicket = (_id) => {
const id = { _id: _id };
fetch("/deleteTicket", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(id),
})
.then((response) => {
if (response.status === 200) {
updateUi(response);
}
})
.catch((err) => {
alert("Failed to delete ticket, " + err);
});
};
const getAllTicket = () => {
fetch("/getTable", {
method: "POST",
headers: { "Content-Type": "application/json" },
})
.then((response) => {
if (response.status === 200) {
updateUi(response);
}
})
.catch((err) => {
alert("Failed to fetch all ticket, " + err);
});
};
//Download excel workbook handler function
const downloadExcelFile = (e) => {
e.preventDefault();
fetch("/downloadFile", {
method: "POST",
})
.then((response) => {
if (response.status === 200) {
console.log("Download started");
}
})
.catch((err) => {
alert("Failed to fetch all ticket, " + err);
});
};
return (
<>
<h1>Register Failure</h1>
<div className="row contact-form">
<div className="col-sm-12">
<form
className="form-horizontal"
onSubmit={(e) => registerFailure(e, formData)}
method="POST"
>
<div className="form-group">
<div className="col-sm-2">
<input
type="text"
className="form-control"
id="fullname"
placeholder="Terminal A"
name="nodeA"
onChange={(e) => onChangeHandler(e)}
value={formData.nodeA}
required
/>
</div>
<div className="col-sm-2">
<input
type="text"
className="form-control"
id="fullname"
placeholder="Terminal B"
name="nodeB"
onChange={(e) => onChangeHandler(e)}
value={formData.nodeB}
required
/>
</div>
<div className="col-sm-2">
<input
type="text"
className="form-control"
id="fullname"
placeholder="Vendor"
name="vendor"
onChange={(e) => onChangeHandler(e)}
value={formData.vendor}
required
/>
</div>
<div className="col-sm-2">
<input
type="text"
className="form-control"
id="fullname"
placeholder="From"
name="timeDown"
onChange={(e) => onChangeHandler(e)}
value={formData.timeDown}
required
/>
</div>
<div className="col-sm-2">
<input
type="text"
className="form-control"
id="fullname"
placeholder="Site with power issue"
name="OFCsiteWithPowerIssue"
onChange={(e) => onChangeHandler(e)}
value={formData.OFCsiteWithPowerIssue}
/>
</div>
<div className="col-sm-2">
<input
type="text"
className="form-control"
id="fullname"
placeholder="By Whom"
name="byWhom"
onChange={(e) => onChangeHandler(e)}
value={formData.byWhom}
required
/>
</div>
</div>
<div className="form-group">
<div className="col-sm-offset-2 col-sm-12">
<button
type="submit"
className="btn btn-outline-success form-button"
>
Submit
</button>
</div>
</div>
</form>
<form
className="form-horizontal"
onSubmit={(e) => downloadExcelFile(e)}
method="POST"
>
<div className="form-group">
<div className="col-sm-offset-2 col-sm-12">
<button
type="submit"
className="btn btn-outline-success form-button"
>
Download Excel File
</button>
</div>
</div>
</form>
{ticketList.length === 0 ? (
<h1 style={{ textAlign: "center" }}>
There are no entries to display
</h1>
) : (
<table className="table">
<thead>
<tr>
<th>Terminal A</th>
<th>Terminal B</th>
<th>Vendor</th>
<th>Impact</th>
<th>Route</th>
<th>From</th>
<th>To</th>
<th>TTR</th>
<th>RDT</th>
<th>SITE ID WITH POWER FAILURE</th>
<th>PROBABLE/SPECIFIC CAUSE</th>
<th>ACTION TAKEN</th>
<th>BY WHOM</th>
<th>SUB SYSTEM</th>
<th></th>
</tr>
</thead>
<tbody>
{ticketList.map((ticket) => {
return (
<tr key={ticket._id}>
<td>{ticket.nodeA}</td>
<td>{ticket.nodeB}</td>
<td>{ticket.vendor}</td>
<td>{ticket.impact}</td>
<td>{ticket.route}</td>
<td>{ticket.timeDown}</td>
<td>{ticket.timeUp}</td>
<td>{ticket.TTR}</td>
<td>{ticket.RDT}</td>
<td>{ticket.siteIDWithPowerFailure}</td>
<td>{ticket.COF}</td>
<td>{ticket.action}</td>
<td>{ticket.byWhom}</td>
<td>{ticket.subSystem}</td>
<td>
<button
className="btn btn-outline-danger"
onClick={() => deleteTicket(ticket._id)}
>
Delete Ticket
</button>
</td>
</tr>
);
})}
</tbody>
</table>
)}
</div>
</div>
</>
);
}