Skip to content

Commit fffd746

Browse files
authored
Merge pull request #64 from UTSC-CSCC01-Software-Engineering-I/feat/set-shipment-status
shipment status
2 parents fda1192 + 190b5ad commit fffd746

7 files changed

Lines changed: 480 additions & 235 deletions

File tree

epiready-backend/app.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import eventlet
2-
eventlet.monkey_patch()
1+
if __name__ == '__main__':
2+
import eventlet
3+
eventlet.monkey_patch()
34
from flask import Flask, jsonify
45
from flask_mail import Mail
56
from flask_cors import CORS

epiready-backend/controllers/alerts.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
from flask_mail import Message, Mail
1111
import eventlet
1212

13+
eventlet.monkey_patch()
14+
1315
def parse_temp_range(temp_range):
1416
"""Parses a string like '2 to 8' into (2.0, 8.0)."""
1517
try:

epiready-backend/controllers/shipment.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,4 +237,37 @@ def get_latest_weather_data(user_id, shipment_id):
237237
# return jsonify({'error': 'Shipment not found'}), 404
238238
# return jsonify(shipment.to_dict()), 200
239239
# except Exception as e:
240-
# return jsonify({'error': str(e)}), 500
240+
# return jsonify({'error': str(e)}), 500
241+
242+
@token_required
243+
def update_shipment_status(user_id):
244+
user = User.query.get(user_id)
245+
if not user:
246+
return jsonify({'error': 'User not found'}), 401
247+
248+
data = request.get_json()
249+
shipment_id = data.get("shipment_id")
250+
251+
if not data or 'status' not in data:
252+
return jsonify({'error': 'Missing required field: status'}), 400
253+
254+
status = data['status'].lower()
255+
if status not in ['active', 'completed', 'cancelled']:
256+
return jsonify({'error': 'Invalid status'}), 400
257+
258+
print(status, shipment_id)
259+
260+
if user.role == 'transporter_manager':
261+
shipment = Shipment.query.filter_by(id=shipment_id, organization_id=user.organization_id).first()
262+
else:
263+
shipment = Shipment.query.filter_by(id=shipment_id, user_id=user_id).first()
264+
265+
if not shipment:
266+
return jsonify({'error': 'Shipment not found'}), 404
267+
268+
shipment.status = status
269+
shipment.updated_at = datetime.now(timezone.utc)
270+
271+
db.session.commit()
272+
273+
return jsonify(shipment.to_dict()), 200

epiready-backend/models/shipment.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def to_dict(self):
5454
}
5555

5656
@event.listens_for(Shipment, 'before_update')
57-
def validate_completed_shipment(target):
57+
def validate_completed_shipment(mapper, connection, target):
5858
if isinstance(target, Shipment):
5959
if target.status == 'completed' and not target.actual_arrival:
6060
target.actual_arrival = datetime.now(timezone.utc)
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from flask import Blueprint
2-
from controllers.shipment import create_shipment, get_shipments_by_user, get_shipment_by_name, get_all_shipments, get_weather_data
2+
from controllers.shipment import create_shipment, get_shipments_by_user, get_shipment_by_name, get_all_shipments, get_weather_data, update_shipment_status
33

44
shipment_blueprint = Blueprint("shipment", __name__)
55
shipment_blueprint.route("", methods=["POST"])(create_shipment)
66
shipment_blueprint.route("", methods=["GET"])(get_shipments_by_user)
77
shipment_blueprint.route("/all", methods=["GET"])(get_all_shipments)
88
shipment_blueprint.route("/<name>", methods=["GET"])(get_shipment_by_name)
9-
shipment_blueprint.route("/<string:shipment_id>/weather", methods=["GET"])(get_weather_data)
9+
shipment_blueprint.route("/<string:shipment_id>/weather", methods=["GET"])(get_weather_data)
10+
shipment_blueprint.route("/status", methods=["PUT"])(update_shipment_status)
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import React, { useState } from "react";
2+
import Popup from "reactjs-popup";
3+
import "reactjs-popup/dist/index.css";
4+
5+
export default function ShipmentStatus({
6+
open,
7+
onClose,
8+
onSubmit,
9+
currentStatus,
10+
loading = false,
11+
error = "",
12+
}) {
13+
const [status, setStatus] = useState(currentStatus);
14+
const [localError, setLocalError] = useState("");
15+
16+
const handleSubmit = () => {
17+
onSubmit(status);
18+
};
19+
20+
return (
21+
<Popup
22+
open={open}
23+
onClose={onClose}
24+
modal
25+
closeOnDocumentClick
26+
contentStyle={{
27+
background: "transparent",
28+
boxShadow: "none",
29+
padding: 0,
30+
border: "none",
31+
width: "95vw",
32+
maxWidth: "400px",
33+
minWidth: "0",
34+
}}
35+
overlayStyle={{ background: "rgba(0,0,0,0.5)" }}
36+
>
37+
<div className="relative bg-neutral-800 rounded-xl shadow-2xl p-8 w-full max-w-md flex flex-col">
38+
<button
39+
className="absolute top-0 right-1 text-gray-600 text-4xl font-bold hover:text-blue-900 transition"
40+
onClick={() => {
41+
setLocalError("");
42+
onClose();
43+
}}
44+
aria-label="Close"
45+
disabled={loading}
46+
>
47+
&times;
48+
</button>
49+
<h2 className="text-2xl font-bold text-[#bfc9d1] mb-4 text-center">
50+
Shipment Status
51+
</h2>
52+
<select
53+
value={status}
54+
onChange={(e) => {
55+
setStatus(e.target.value);
56+
}}
57+
className="p-4 mb-2"
58+
>
59+
<option value="active" className="text-gray-900">
60+
Active
61+
</option>
62+
<option value="completed" className="text-gray-900">
63+
Completed
64+
</option>
65+
<option value="cancelled" className="text-gray-900">
66+
Cancelled
67+
</option>
68+
</select>
69+
{(localError || error) && (
70+
<div className="text-red-400 text-center mb-2">
71+
{localError || error}
72+
</div>
73+
)}
74+
<div className="flex gap-4 justify-center">
75+
<button
76+
className="px-4 py-2 rounded bg-[#6B805E] text-white font-semibold hover:bg-[#4e6147] focus:bg-[#4e6147] transition"
77+
onClick={handleSubmit}
78+
disabled={loading}
79+
>
80+
{loading ? "Submitting..." : "Submit"}
81+
</button>
82+
<button
83+
className="px-4 py-2 rounded bg-neutral-700 text-white font-semibold hover:bg-neutral-600 focus:bg-neutral-600 transition"
84+
onClick={() => {
85+
setLocalError("");
86+
onClose();
87+
}}
88+
disabled={loading}
89+
>
90+
Cancel
91+
</button>
92+
</div>
93+
</div>
94+
</Popup>
95+
);
96+
}

0 commit comments

Comments
 (0)