458 lines
15 KiB
JavaScript
458 lines
15 KiB
JavaScript
// import React, { useState } from "react";
|
|
// import { useGetOrdersQuery } from "../../features/orders/ordersApi";
|
|
|
|
// const statusColor = {
|
|
// PENDING: "bg-yellow-100 text-yellow-800",
|
|
// PAID: "bg-blue-100 text-blue-800",
|
|
// SHIPPED: "bg-purple-100 text-purple-800",
|
|
// DELIVERED: "bg-green-100 text-green-800",
|
|
// CANCELLED: "bg-red-100 text-red-800",
|
|
// RETURNED: "bg-gray-100 text-gray-800",
|
|
// };
|
|
|
|
// // LEFT FILTERS
|
|
// const OrderFilters = ({ filters, setFilters }) => {
|
|
// const toggleStatus = (status) => {
|
|
// if (filters.status.includes(status)) {
|
|
// setFilters({
|
|
// ...filters,
|
|
// status: filters.status.filter((s) => s !== status),
|
|
// });
|
|
// } else {
|
|
// setFilters({ ...filters, status: [...filters.status, status] });
|
|
// }
|
|
// };
|
|
|
|
// return (
|
|
// <div className="sticky top-24 p-6 border rounded-3xl bg-white shadow-lg space-y-6">
|
|
// <h2 className="text-xl font-semibold text-gray-800">Filter Orders</h2>
|
|
|
|
// <div>
|
|
// <p className="font-medium text-gray-700 mb-2">Status</p>
|
|
// <div className="flex flex-col gap-2">
|
|
// {["PENDING", "ON_THE_WAY", "DELIVERED", "CANCELLED", "RETURNED"].map(
|
|
// (status) => (
|
|
// <label
|
|
// key={status}
|
|
// className="flex items-center gap-3 cursor-pointer hover:text-blue-600"
|
|
// >
|
|
// <input
|
|
// type="checkbox"
|
|
// checked={filters.status.includes(status)}
|
|
// onChange={() => toggleStatus(status)}
|
|
// className="h-4 w-4 accent-blue-500"
|
|
// />
|
|
// <span className="capitalize">{status.replace("_", " ")}</span>
|
|
// </label>
|
|
// )
|
|
// )}
|
|
// </div>
|
|
// </div>
|
|
|
|
// <div>
|
|
// <p className="font-medium text-gray-700 mb-2">Order Time</p>
|
|
// <select
|
|
// value={filters.time}
|
|
// onChange={(e) => setFilters({ ...filters, time: e.target.value })}
|
|
// className="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-400 focus:outline-none"
|
|
// >
|
|
// <option value="30_DAYS">Last 30 Days</option>
|
|
// <option value="2024">2024</option>
|
|
// <option value="2023">2023</option>
|
|
// </select>
|
|
// </div>
|
|
// </div>
|
|
// );
|
|
// };
|
|
|
|
// // ORDER CARD
|
|
// const OrderCard = ({ order }) => {
|
|
// return (
|
|
// <div className="bg-white border border-gray-200 rounded-3xl shadow hover:shadow-xl transition p-6 space-y-4">
|
|
// {/* Header */}
|
|
// <div className="flex flex-col md:flex-row md:justify-between md:items-center gap-3">
|
|
// <div>
|
|
// <p className="text-sm text-gray-400">Order ID</p>
|
|
// <p className="font-semibold text-gray-800">{order.orderNumber}</p>
|
|
// <p className="text-xs text-gray-400">
|
|
// {new Date(order.createdAt).toLocaleString()}
|
|
// </p>
|
|
// </div>
|
|
|
|
// <div className="flex items-center gap-4 flex-wrap">
|
|
// <span
|
|
// className={`px-4 py-1 rounded-full text-sm font-medium ${
|
|
// statusColor[order.status] || "bg-gray-100 text-gray-800"
|
|
// }`}
|
|
// >
|
|
// {order.status.replace("_", " ")}
|
|
// </span>
|
|
// <span className="font-bold text-lg text-gray-900">
|
|
// ₹{Number(order.totalAmount).toFixed(2)}
|
|
// </span>
|
|
// </div>
|
|
// </div>
|
|
|
|
// {/* Items */}
|
|
// <div className="divide-y divide-gray-200">
|
|
// {order.items.map((item) => (
|
|
// <div
|
|
// key={item.id}
|
|
// className="flex items-center justify-between py-3"
|
|
// >
|
|
// <div>
|
|
// <p className="font-medium text-gray-800">{item.productName}</p>
|
|
// <p className="text-sm text-gray-500">Qty: {item.quantity}</p>
|
|
// </div>
|
|
// <p className="font-semibold text-gray-900">
|
|
// ₹{Number(item.price).toFixed(2)}
|
|
// </p>
|
|
// </div>
|
|
// ))}
|
|
// </div>
|
|
|
|
// {/* Footer */}
|
|
// <div className="grid md:grid-cols-2 gap-4 mt-4 text-sm text-gray-700">
|
|
// <div>
|
|
// <p className="font-medium text-gray-800">Shipping Address</p>
|
|
// <p className="mt-1">
|
|
// {order.address.firstName} {order.address.lastName},{" "}
|
|
// {order.address.addressLine1}, {order.address.city},{" "}
|
|
// {order.address.state} - {order.address.postalCode}
|
|
// </p>
|
|
// </div>
|
|
|
|
// <div className="md:text-right">
|
|
// <p>
|
|
// <span className="font-medium">Payment:</span>{" "}
|
|
// {order.paymentMethod}
|
|
// </p>
|
|
// <p>
|
|
// <span className="font-medium">Payment Status:</span>{" "}
|
|
// {order.paymentStatus}
|
|
// </p>
|
|
// </div>
|
|
// </div>
|
|
// </div>
|
|
// );
|
|
// };
|
|
|
|
// // MAIN COMPONENT
|
|
// const MyOrders = () => {
|
|
// const { data, isLoading, isError } = useGetOrdersQuery({
|
|
// page: 1,
|
|
// limit: 50,
|
|
// });
|
|
|
|
// const [filters, setFilters] = useState({
|
|
// status: [],
|
|
// time: "30_DAYS",
|
|
// });
|
|
|
|
// const orders = data?.orders || [];
|
|
|
|
// const filteredOrders = orders.filter((order) => {
|
|
// if (filters.status.length && !filters.status.includes(order.status)) return false;
|
|
|
|
// const orderDate = new Date(order.createdAt);
|
|
// const now = new Date();
|
|
|
|
// if (filters.time === "30_DAYS" && (now - orderDate) / (1000 * 60 * 60 * 24) > 30)
|
|
// return false;
|
|
// if (filters.time === "2024" && orderDate.getFullYear() !== 2024) return false;
|
|
// if (filters.time === "2023" && orderDate.getFullYear() !== 2023) return false;
|
|
|
|
// return true;
|
|
// });
|
|
|
|
// if (isLoading)
|
|
// return (
|
|
// <div className="min-h-[60vh] flex items-center justify-center text-lg">
|
|
// Loading orders...
|
|
// </div>
|
|
// );
|
|
|
|
// if (isError || !orders.length)
|
|
// return (
|
|
// <div className="min-h-[60vh] flex items-center justify-center text-gray-500">
|
|
// No orders found
|
|
// </div>
|
|
// );
|
|
|
|
// return (
|
|
// <div className="max-w-7xl mx-auto px-4 py-12 mt-24">
|
|
// <h1 className="text-4xl font-bold mb-10 text-gray-800">My Orders</h1>
|
|
|
|
// <div className="grid grid-cols-1 md:grid-cols-4 gap-8">
|
|
// {/* Filters */}
|
|
// <OrderFilters filters={filters} setFilters={setFilters} />
|
|
|
|
// {/* Orders */}
|
|
// <div className="md:col-span-3 flex flex-col gap-6 max-h-[75vh] overflow-y-auto">
|
|
// {filteredOrders.length === 0 ? (
|
|
// <div className="text-gray-500 text-center py-20">
|
|
// No orders match your filters
|
|
// </div>
|
|
// ) : (
|
|
// filteredOrders.map((order) => <OrderCard key={order.id} order={order} />)
|
|
// )}
|
|
// </div>
|
|
// </div>
|
|
// </div>
|
|
// );
|
|
// };
|
|
|
|
// export default MyOrders;
|
|
|
|
import React, { useState } from "react";
|
|
import { useGetOrdersQuery } from "../../features/orders/ordersApi";
|
|
import { useNavigate } from "react-router-dom";
|
|
import { statusColor } from "../../constants/statusColor";
|
|
|
|
// const statusColor = {
|
|
// PENDING: "bg-yellow-100 text-yellow-800",
|
|
// PAID: "bg-blue-100 text-blue-800",
|
|
// SHIPPED: "bg-purple-100 text-purple-800",
|
|
// DELIVERED: "bg-green-100 text-green-800",
|
|
// CANCELLED: "bg-red-100 text-red-800",
|
|
// RETURNED: "bg-gray-100 text-gray-800",
|
|
// };
|
|
|
|
// LEFT FILTERS
|
|
const OrderFilters = ({ filters, setFilters }) => {
|
|
const toggleStatus = (status) => {
|
|
if (filters.status.includes(status)) {
|
|
setFilters({
|
|
...filters,
|
|
status: filters.status.filter((s) => s !== status),
|
|
});
|
|
} else {
|
|
setFilters({ ...filters, status: [...filters.status, status] });
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="sticky top-24 p-6 border rounded-3xl bg-white shadow-lg space-y-6">
|
|
<h2 className="text-xl font-semibold text-gray-800">Filter Orders</h2>
|
|
|
|
<div>
|
|
<p className="font-medium text-gray-700 mb-2">Status</p>
|
|
<div className="flex flex-col gap-2">
|
|
{["PENDING", "ON_THE_WAY", "DELIVERED", "CANCELLED", "RETURNED"].map(
|
|
(status) => (
|
|
<label
|
|
key={status}
|
|
className="flex items-center gap-3 cursor-pointer hover:text-blue-600"
|
|
>
|
|
<input
|
|
type="checkbox"
|
|
checked={filters.status.includes(status)}
|
|
onChange={() => toggleStatus(status)}
|
|
className="h-4 w-4 accent-blue-500"
|
|
/>
|
|
<span className="capitalize">{status.replace("_", " ")}</span>
|
|
</label>
|
|
)
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<p className="font-medium text-gray-700 mb-2">Order Time</p>
|
|
<select
|
|
value={filters.time}
|
|
onChange={(e) => setFilters({ ...filters, time: e.target.value })}
|
|
className="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-400 focus:outline-none"
|
|
>
|
|
<option value="30_DAYS">Last 30 Days</option>
|
|
<option value="2024">2024</option>
|
|
<option value="2023">2023</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
// ORDER CARD
|
|
const OrderCard = ({ order }) => {
|
|
const navigate = useNavigate();
|
|
return (
|
|
<div
|
|
className="bg-white border border-gray-200 rounded-3xl shadow hover:shadow-xl transition p-6 space-y-4 cursor-pointer"
|
|
onClick={() => navigate(`/my-orders/${order.id}`)}
|
|
>
|
|
{/* Header */}
|
|
<div className="flex flex-col md:flex-row md:justify-between md:items-center gap-3">
|
|
<div>
|
|
<p className="text-sm text-gray-400">Order ID</p>
|
|
<p className="font-semibold text-gray-800">{order.orderNumber}</p>
|
|
<p className="text-xs text-gray-400">
|
|
{new Date(order.createdAt).toLocaleString()}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-4 flex-wrap">
|
|
<span
|
|
className={`px-4 py-1 rounded-full text-sm font-medium ${
|
|
statusColor[order.status] || "bg-gray-100 text-gray-800"
|
|
}`}
|
|
>
|
|
{order.status.replace("_", " ")}
|
|
</span>
|
|
<span className="font-bold text-lg text-gray-900">
|
|
₹{Number(order.totalAmount).toFixed(2)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Items */}
|
|
<div className="divide-y divide-gray-200">
|
|
{order.items.map((item) => (
|
|
<div key={item.id} className="flex items-center justify-between py-3">
|
|
<div>
|
|
<p className="font-medium text-gray-800">{item.productName}</p>
|
|
<p className="text-sm text-gray-500">Qty: {item.quantity}</p>
|
|
</div>
|
|
<p className="font-semibold text-gray-900">
|
|
₹{Number(item.price).toFixed(2)}
|
|
</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Footer */}
|
|
<div className="grid md:grid-cols-2 gap-4 mt-4 text-sm text-gray-700">
|
|
<div>
|
|
<p className="font-medium text-gray-800">Shipping Address</p>
|
|
<p className="mt-1">
|
|
{order.address.firstName} {order.address.lastName},{" "}
|
|
{order.address.addressLine1}, {order.address.city},{" "}
|
|
{order.address.state} - {order.address.postalCode}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="md:text-right">
|
|
<p>
|
|
<span className="font-medium">Payment:</span> {order.paymentMethod}
|
|
</p>
|
|
<p>
|
|
<span className="font-medium">Payment Status:</span>{" "}
|
|
{order.paymentStatus}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
// MAIN COMPONENT
|
|
const MyOrders = () => {
|
|
const { data, isLoading, isError } = useGetOrdersQuery({
|
|
page: 1,
|
|
limit: 50,
|
|
});
|
|
|
|
const [filters, setFilters] = useState({
|
|
status: [],
|
|
time: "30_DAYS",
|
|
});
|
|
|
|
const [searchQuery, setSearchQuery] = useState("");
|
|
|
|
const orders = data?.orders || [];
|
|
|
|
const filteredOrders = orders
|
|
.filter((order) => {
|
|
if (filters.status.length && !filters.status.includes(order.status))
|
|
return false;
|
|
|
|
const orderDate = new Date(order.createdAt);
|
|
const now = new Date();
|
|
|
|
if (
|
|
filters.time === "30_DAYS" &&
|
|
(now - orderDate) / (1000 * 60 * 60 * 24) > 30
|
|
)
|
|
return false;
|
|
if (filters.time === "2024" && orderDate.getFullYear() !== 2024)
|
|
return false;
|
|
if (filters.time === "2023" && orderDate.getFullYear() !== 2023)
|
|
return false;
|
|
|
|
return true;
|
|
})
|
|
.filter((order) => {
|
|
// SEARCH FILTER
|
|
if (!searchQuery) return true;
|
|
const q = searchQuery.toLowerCase();
|
|
const inOrderId = order.orderNumber.toLowerCase().includes(q);
|
|
const inCustomer = `${order.address.firstName} ${order.address.lastName}`
|
|
.toLowerCase()
|
|
.includes(q);
|
|
const inProduct = order.items.some((item) =>
|
|
item.productName.toLowerCase().includes(q)
|
|
);
|
|
return inOrderId || inCustomer || inProduct;
|
|
});
|
|
|
|
if (isLoading)
|
|
return (
|
|
<div className="min-h-[60vh] flex items-center justify-center text-lg">
|
|
Loading orders...
|
|
</div>
|
|
);
|
|
|
|
if (isError || !orders.length)
|
|
return (
|
|
<div className="min-h-[60vh] flex items-center justify-center text-gray-500">
|
|
No orders found
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<div className="max-w-7xl mx-auto px-4 py-12 mt-24">
|
|
<h1 className="text-2xl font-bold mb-6 text-primary-dark">My Orders</h1>
|
|
|
|
{/* SEARCH BAR */}
|
|
{/* SEARCH BAR */}
|
|
<div className="mb-8 flex justify-center">
|
|
<div className="w-full md:w-1/2 flex">
|
|
<input
|
|
type="text"
|
|
value={searchQuery}
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
placeholder="Search by Order ID, Product, or Customer"
|
|
className="flex-1 border border-gray-300 rounded-l-xl px-4 py-3 shadow-sm focus:outline-none focus:ring-1 focus:ring-orange-700"
|
|
/>
|
|
<button
|
|
onClick={() => {}}
|
|
className="bg-primary-default text-white px-5 py-3 rounded-r-xl font-medium hover:bg-primary-dark transition"
|
|
>
|
|
Search
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
|
|
{/* Filters */}
|
|
<OrderFilters filters={filters} setFilters={setFilters} />
|
|
|
|
{/* Orders */}
|
|
<div className="md:col-span-3 flex flex-col gap-6 max-h-[75vh] overflow-y-auto">
|
|
{filteredOrders.length === 0 ? (
|
|
<div className="text-gray-500 text-center py-20">
|
|
No orders match your filters
|
|
</div>
|
|
) : (
|
|
filteredOrders.map((order) => (
|
|
<OrderCard key={order.id} order={order} />
|
|
))
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default MyOrders;
|