Skip to content

Commit

Permalink
#12 & #21 -Backend and Frontend view tenders - Aisha Athman Lali (#36)
Browse files Browse the repository at this point in the history
* Update the code

* fix code according to feedback

* modify backend

* edit Readme file to include how to upload the latest version

* edit tenderlist.js file

* edit tenderlist.js file

* edit the code to fix the pagination requirement

* fix some errors

* revert changes to some files

* edit Readme file

* testing to revert changes

* fix some errors

* fix unwanted changes

* fix some errors in README file
  • Loading branch information
aishaathmanlali authored Jul 27, 2024
1 parent bbf2562 commit bb1150a
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 74 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ Ensure the following are installed on your system before proceeding:

This is our deployed website link https://love-me-tender-51qa.onrender.com

The render is on auto-deploy so it should render new changes to the website.
To deploy the project's latest version follow the steps below;

1. Go to render dashboard
2. Click on Manual Deploy button
3. Select deploy latest commit

## ⏬ Installation and setup

Expand Down
7 changes: 7 additions & 0 deletions client/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import PublishTenderForm from "./PublishTenderForm";
import BuyerTenderList from "./BuyerTenderList";
import BidderBiddingList from "./BidderBiddingList";
import SignUp from "./pages/SignUp";
import AdminDashboard from "./pages/AdminDashboard";
import BuyerDashboard from "./pages/BuyerDashboard";
import TendersList from "./TenderList";

const App = () => (
<Routes>
Expand All @@ -12,6 +15,10 @@ const App = () => (
<Route path="/BuyerTenderList" element={<BuyerTenderList />} />
<Route path="/BidderBiddingList" element={<BidderBiddingList />} />
<Route path="/signup" element={<SignUp />} />
<Route path="/admin-dashboard" element={<AdminDashboard />} />
<Route path="/buyer-dashboard" element={<BuyerDashboard />} />
<Route path="/list-tenders" element={<TendersList />} />
<Route path="/list-tenders/page/:pageNumber" element={<TendersList />} />
</Routes>
);

Expand Down
101 changes: 101 additions & 0 deletions client/src/TenderList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React, { useState, useEffect } from "react";
import { useParams, useNavigate } from "react-router-dom";

const TendersList = () => {
const { pageNumber } = useParams();
const currentPage = pageNumber ? parseInt(pageNumber, 10) : 1;
const [tenders, setTenders] = useState([]);
const [loading, setLoading] = useState(true);
const [pagination, setPagination] = useState({
itemsPerPage: 25,
currentPage: currentPage,
totalPages: 1,
});
const [error, setError] = useState(null);
const navigate = useNavigate();

const fetchTenders = async (page) => {
setLoading(true);
try {
const response = await fetch(`/api/tenders?page=${page}`);
if (!response.ok) {
throw new Error("Failed to fetch tenders. Please try again later.");
}
const data = await response.json();

if (data.results && data.pagination) {
setTenders(data.results);
setPagination(data.pagination);
setError(null);
} else {
throw new Error("Server error");
}
} catch (error) {
setError("Error fetching tenders: " + error.message);
} finally {
setLoading(false);
}
};

useEffect(() => {
fetchTenders(currentPage);
}, [currentPage]);

const loadNextPage = () => {
if (pagination.currentPage < pagination.totalPages && !loading) {
navigate(`/list-tenders/page/${pagination.currentPage + 1}`);
}
};

const loadPreviousPage = () => {
if (pagination.currentPage > 1 && !loading) {
navigate(`/list-tenders/page/${pagination.currentPage - 1}`);
}
};

return (
<div>
<h2>Tenders List</h2>
{error && <p style={{ color: "red" }}>{error}</p>}
<table>
<thead>
<tr>
<th>Tender ID</th>
<th>Tender Title</th>
<th>Tender Created Date</th>
<th>Tender Announcement Date</th>
<th>Tender Project Deadline Date</th>
<th>Tender Status</th>
</tr>
</thead>
<tbody>
{tenders.map((tender) => (
<tr key={tender.id}>
<td>{tender.id}</td>
<td>{tender.title}</td>
<td>{new Date(tender.creation_date).toLocaleDateString()}</td>
<td>{new Date(tender.announcement_date).toLocaleDateString()}</td>
<td>{new Date(tender.deadline).toLocaleDateString()}</td>
<td>{tender.status}</td>
</tr>
))}
</tbody>
</table>
{loading && <p>Loading...</p>}
<div>
{pagination.currentPage > 1 && (
<button onClick={loadPreviousPage} disabled={loading}>
Previous Page
</button>
)}
{pagination.currentPage < pagination.totalPages && (
<button onClick={loadNextPage} disabled={loading}>
Next Page
</button>
)}
</div>
</div>
);
};

export default TendersList;
15 changes: 15 additions & 0 deletions client/src/pages/AdminDashboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from "react";
import { Link } from "react-router-dom";

const AdminDashboard = () => {
return (
<div>
<h1>Admin Dashboard</h1>
<Link to="/list-tenders">
<button>View Tenders</button>
</Link>
</div>
);
};

export default AdminDashboard;
17 changes: 17 additions & 0 deletions client/src/pages/BuyerDashboard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from "react";
import { Link } from "react-router-dom";

export function BuyerDashboard() {
return (
<main role="main">
<div>
<h1>Buyer Dashboard</h1>
<Link to="/publish-tender">
<button>Publish Tender</button>
</Link>
</div>
</main>
);
}

export default BuyerDashboard;
7 changes: 3 additions & 4 deletions client/src/pages/Home.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,10 @@ export function Home() {
<h1 className="message" data-qa="message">
{message}
</h1>
<Link to="/about/this/site">About</Link>
<Link to="/admin-dashboard">Admin Dashboard</Link>
<br />
<Link to="/buyer-dashboard">Buyer Dashboard</Link>
<br />
<Link to="/publish-tender">
<button>Publish Tender</button>
</Link>
<Link to="/BuyerTenderList">
<button>My Tenders</button>
</Link>
Expand Down
97 changes: 33 additions & 64 deletions server/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,70 +71,6 @@ router.post("/publish-tenders", (req, res) => {
res.status(200).json({ message: "Form submitted successfully!" });
});

router.get("/skills", (req, res) => {
const skills = [
"Website",
"Android",
"iOS",
"Backend",
"Frontend",
"Full-stack",
];

skills.sort();

res.status(200).json({ skills });
});

router.post("/publish-tenders", (req, res) => {
const formData = req.body;

const newErrors = [];

if (
!formData.title ||
formData.title.length < 10 ||
formData.title.length > 50
) {
newErrors.push("Tender Title must be between 10 and 50 characters.");
}

if (
!formData.description ||
formData.description.length < 100 ||
formData.description.length > 7500
) {
newErrors.push(
"Tender Description must be between 100 and 7500 characters."
);
}

const today = new Date().toISOString().split("T")[0];
if (formData.closingDate < today) {
newErrors.push("Tender Closing Date cannot be in the past.");
}

if (formData.announcementDate > formData.closingDate) {
newErrors.push("Tender Announcement Date must be before the Closing Date.");
}

if (formData.deadlineDate < formData.announcementDate) {
newErrors.push(
"Tender Project Deadline Date must be after the Announcement Date."
);
}

if (formData.selectedSkills.length === 0) {
newErrors.push("Please select at least one skill.");
}

if (newErrors.length > 0) {
return res.status(400).json({ errors: newErrors });
}

res.status(200).json({ message: "Form submitted successfully!" });
});

router.get("/buyer-tender", async (req, res) => {
const buyerId = 1;
let page = parseInt(req.query.page) || 1;
Expand Down Expand Up @@ -165,4 +101,37 @@ router.get("/bidder-bid", async (req, res) => {
: res.status(500).send({ code: "SERVER_ERROR" });
});

router.get("/tenders", async (req, res) => {
const page = parseInt(req.query.page, 10) || 1;
const limit = 25;
const offset = (page - 1) * limit;

const countSql = "SELECT COUNT(*) FROM tender";
const dataSql = `
SELECT id, title, creation_date, announcement_date, deadline, status
FROM tender
ORDER BY creation_date DESC
LIMIT $1 OFFSET $2
`;
try {
const countResult = await db.query(countSql);
const totalItems = parseInt(countResult.rows[0].count, 10);
const totalPages = Math.ceil(totalItems / limit);

const dataResult = await db.query(dataSql, [limit, offset]);
const tenders = dataResult.rows;

res.status(200).json({
results: tenders,
pagination: {
itemsPerPage: limit,
currentPage: page,
totalPages,
},
});
} catch (err) {
res.status(500).json({ code: "SERVER_ERROR" });
}
});

export default router;
7 changes: 2 additions & 5 deletions server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import http from "node:http";
import app from "./app";
import config from "./utils/config";
import logger from "./utils/logger";
import { connectDb, disconnectDb } from "./db";

const server = http.createServer(app);

Expand All @@ -11,10 +12,6 @@ server.on("listening", () => {
logger.info("listening on: %s", bind);
});

const disconnectDb = () => {
logger.info("Disconnecting from the database...");
};

process.on("SIGTERM", () => server.close(() => disconnectDb()));

server.listen(config.port);
connectDb().then(() => server.listen(config.port));

0 comments on commit bb1150a

Please sign in to comment.