Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions clientapp/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import "./App.css";
import DashboardLayout from "./components/layout/DashboardLayout";
import Dashboard from "./features/dashboard/Dashboard";
import SubmitGoods from "./features/shipments/SubmitGoods";
import PastShipments from "./features/shipments/PastShipments";
import AwaitingShipments from "./features/shipments/AwaitingShipments";
import ShippingHistory from "./features/shipments/ShippingHistory";
import ReportIssue from "./features/report/ReportIssue";
import Settings from "./features/settings/Settings";

Expand All @@ -29,8 +30,8 @@ function AppRoutes() {
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/submit" element={<SubmitGoods />} />
<Route path="/awaiting" element={<PastShipments />} />
<Route path="/history" element={<PastShipments />} />
<Route path="/awaiting" element={<AwaitingShipments />} />
<Route path="/history" element={<ShippingHistory />} />
<Route path="/settings" element={<Settings />} />
<Route path="/report" element={<ReportIssue />} />
<Route path="/" element={<Navigate to="/dashboard" replace />} />
Expand Down
20 changes: 20 additions & 0 deletions clientapp/src/components/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from "react";

interface SearchBarProps {
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
placeholder?: string;
className?: string;
}

const SearchBar: React.FC<SearchBarProps> = ({ value, onChange, placeholder, className }) => (
<input
type="text"
className={className}
placeholder={placeholder}
value={value}
onChange={onChange}
/>
);

export default SearchBar;
52 changes: 52 additions & 0 deletions clientapp/src/components/ShipmentList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from "react";
import RecentShipmentCard from "./RecentShipmentCard";

// Accepts a list of shipments and displays them using RecentShipmentCard
interface ShipmentListProps {
shipments: Array<{
id: string;
trackingNumber?: string;
origin?: string;
destination?: string;
status?: string;
estimatedDelivery?: string;
updatedAt?: string;
description?: string;
// Accepts extra fields for flexibility
[key: string]: any;
}>;
emptyMessage?: string;
}

const ShipmentList: React.FC<ShipmentListProps> = ({ shipments, emptyMessage }) => {
if (!shipments.length) {
return (
<div className="text-center text-gray-500 dark:text-gray-400 py-12">
{emptyMessage || "No shipments found."}
</div>
);
}

return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{shipments.map((shipment) => (
<RecentShipmentCard
key={shipment.id}
shipment={{
...shipment,
trackingNumber: shipment.trackingNumber ?? "",
origin: shipment.origin ?? "",
destination: shipment.destination ?? "",
status: (["in-transit", "delivered", "delayed", "processing"].includes(shipment.status ?? "")
? shipment.status
: "processing") as "in-transit" | "delivered" | "delayed" | "processing",
estimatedDelivery: shipment.estimatedDelivery ?? "",
updatedAt: shipment.updatedAt ?? "",
}}
/>
))}
</div>
);
};

export default ShipmentList;
18 changes: 18 additions & 0 deletions clientapp/src/components/StatusFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from "react";

interface StatusFilterProps {
value: string;
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
options: { value: string; label: string }[];
className?: string;
}

const StatusFilter: React.FC<StatusFilterProps> = ({ value, onChange, options, className }) => (
<select className={className} value={value} onChange={onChange}>
{options.map(opt => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
</select>
);

export default StatusFilter;
15 changes: 15 additions & 0 deletions clientapp/src/features/shipments/AwaitingShipments.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import ShipmentsPage from "./ShipmentsPage";

const AwaitingShipments = () => {
// Awaiting = not delivered
const awaitingStatuses = ["pending", "in-transit", "processing", "delayed"];
return (
<ShipmentsPage
title="Awaiting Shipments"
filterStatus={awaitingStatuses}
emptyMessage="No awaiting shipments found."
/>
);
};

export default AwaitingShipments;
170 changes: 150 additions & 20 deletions clientapp/src/features/shipments/PastShipments.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { useEffect, useState } from "react";
import SearchBar from "../../components/SearchBar";
import StatusFilter from "../../components/StatusFilter";
import ShipmentList from "../../components/ShipmentList";

// This file is now replaced by ShipmentsPage, AwaitingShipments, and ShippingHistory wrappers.
// You can remove this file or keep it for reference, but it is no longer used in the router.

// // TODO: Fetch past shipments from API here// useEffect(() => {// API: Placeholder for fetching past shipments// import axios from "axios"; // Uncomment when ready to use APIs

Expand All @@ -8,38 +14,162 @@ import { useEffect, useState } from "react";
interface Shipment {
id: string;
description: string;
trackingNumber?: string;
origin?: string;
destination?: string;
status?: string;
estimatedDelivery?: string; // Add estimatedDelivery field
// Add more properties as needed
}

const PastShipments = () => {
const [loading, setLoading] = useState(true);
const [shipments, setShipments] = useState<Shipment[]>([]);
const [search, setSearch] = useState("");
const [status, setStatus] = useState("");

// Example statuses for filter dropdown
const STATUS_OPTIONS = [
{ value: "", label: "All Statuses" },
{ value: "pending", label: "Pending" },
{ value: "in-transit", label: "In Transit" },
{ value: "processing", label: "Processing" },
{ value: "delayed", label: "Delayed" },
{ value: "delivered", label: "Delivered" },
];

// Filtered shipments
const filteredShipments = shipments.filter((shipment) => {
const matchesSearch =
search === "" ||
shipment.description?.toLowerCase().includes(search.toLowerCase()) ||
shipment.id?.toLowerCase().includes(search.toLowerCase()) ||
shipment.trackingNumber?.toLowerCase().includes(search.toLowerCase()) ||
shipment.origin?.toLowerCase().includes(search.toLowerCase()) ||
shipment.destination?.toLowerCase().includes(search.toLowerCase());
const matchesStatus =
!status || (shipment.status && shipment.status.toLowerCase() === status);
return matchesSearch && matchesStatus;
});

// Dummy data for demonstration
useEffect(() => {
// Fetch past shipments from API here
const fetchShipments = async () => {
try {
const response = await fetch('/api/shipments/past'); // Replace with actual API endpoint
const data: Shipment[] = await response.json();
setShipments(data);
} catch (error) {
console.error("Error fetching shipments:", error);
}
};

fetchShipments();
// const fetchShipments = async () => {
// try {
// const response = await fetch('/api/shipments/past'); // Replace with actual API endpoint
// const data: Shipment[] = await response.json();
// setShipments(data);
// } catch (error) {
// console.error("Error fetching shipments:", error);
// }
// };

// fetchShipments();

const dummyShipments: Shipment[] = [
{
id: "1",
description: "Electronics - Laptop",
trackingNumber: "TRK-10001",
origin: "Accra, Ghana",
destination: "London, UK",
status: "in-transit",
estimatedDelivery: "May 20, 2025",
},
{
id: "2",
description: "Clothing - Summer Collection",
trackingNumber: "TRK-10002",
origin: "Takoradi, Ghana",
destination: "Tamale, Ghana",
status: "pending",
estimatedDelivery: "May 22, 2025",
},
{
id: "3",
description: "Books - Educational",
trackingNumber: "TRK-10003",
origin: "Cape Coast, Ghana",
destination: "New York, USA",
status: "processing",
estimatedDelivery: "May 23, 2025",
},
{
id: "4",
description: "Furniture - Office Chair",
trackingNumber: "TRK-10004",
origin: "Tema, Ghana",
destination: "Ho, Ghana",
status: "delayed",
estimatedDelivery: "May 25, 2025",
},
{
id: "5",
description: "Food - Non-perishable",
trackingNumber: "TRK-10005",
origin: "Koforidua, Ghana",
destination: "Berlin, Germany",
status: "in-transit",
estimatedDelivery: "May 21, 2025",
},
{
id: "6",
description: "Medical Supplies",
trackingNumber: "TRK-10006",
origin: "Wa, Ghana",
destination: "Dambai, Ghana",
status: "pending",
estimatedDelivery: "May 24, 2025",
},
{
id: "7",
description: "Machinery Parts",
trackingNumber: "TRK-10007",
origin: "Sefwi Wiawso, Ghana",
destination: "Dubai, UAE",
status: "processing",
estimatedDelivery: "May 26, 2025",
},
];
setShipments(dummyShipments);
setLoading(false);
}, []);

if (loading) {
return (
<div className="flex items-center justify-center h-96">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
</div>
);
}

return (
<div>
<h1>Past Shipments</h1>
{shipments.length > 0 ? (
<ul>
{shipments.map((shipment) => (
<li key={shipment.id}>{shipment.description}</li>
))}
</ul>
<div className="mx-auto space-y-6">
<h1 className="text-2xl font-bold mb-4 dark:text-gray-200">
Awaiting Shipments
</h1>
<div className="flex flex-col md:flex-row gap-4 mb-6">
<SearchBar
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search by tracking number, description, origin, destination..."
className="w-full md:w-1/2 rounded-lg border border-gray-300 dark:border-gray-700 px-4 py-2 text-gray-900 dark:text-gray-100 bg-white dark:bg-gray-800 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-700 transition-all"
/>
<StatusFilter
value={status}
onChange={(e) => setStatus(e.target.value)}
options={STATUS_OPTIONS}
className="w-full md:w-1/4 rounded-lg border border-gray-300 dark:border-gray-700 px-4 py-2 text-gray-900 dark:text-gray-100 bg-white dark:bg-gray-800 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-700 transition-all"
/>
</div>
{filteredShipments.length > 0 ? (
<ShipmentList
shipments={filteredShipments}
emptyMessage="No awaiting shipments found."
/>
) : (
<p>No past shipments found.</p>
<p>No awaiting shipments found.</p>
)}
</div>
);
Expand Down
Loading