Skip to content

Commit

Permalink
Merge pull request #47 from atlp-rwanda/187874853-order-dashboard
Browse files Browse the repository at this point in the history
[Finishes #187874853]Seller order Dashboard
  • Loading branch information
niyontwali authored Jul 23, 2024
2 parents a6ea4c6 + 303f262 commit 77cac66
Show file tree
Hide file tree
Showing 8 changed files with 350 additions and 3 deletions.
1 change: 0 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import Customers from './pages/seller/Customers';
import AdminSettings from './pages/admin/Settings';
import AddNewProduct from './pages/seller/AddNewProduct';
import RestrictedSellerRoute from './components/dashboard/RestrictedSellerLayout';

import CategoriesPage from './pages/CategoriesPage';
import ResetPassword from './pages/ResetPassword';
import NewPassword from './pages/NewPassword';
Expand Down
162 changes: 162 additions & 0 deletions src/components/OrderTables.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import React, { useState } from 'react';
import { Button } from './ui/button';
import { useGetOrdersQuery } from "../services/ordersApi";
import { Order } from "../types/Types";
import { CiSearch } from 'react-icons/ci';

const statusColors: { [key: string]: string } = {
'delivered': 'bg-[#d1fae5] text-[#065f46]',
'cancelled': 'bg-[#fee2e2] text-[#991b1b]',
'pending': 'bg-[#e9d5ff] text-[#6b21a8]',
'paid': 'bg-[#fef3c7] text-[#854d0e]',
};

const itemsPerPage = 15;

const TransactionTable: React.FC = () => {
const [selectedStatus, setSelectedStatus] = useState<string>('all');
const [currentPage, setCurrentPage] = useState<number>(1);
const [searchQuery, setSearchQuery] = useState<string>('');
const { data: ordersData, isLoading, isError } = useGetOrdersQuery();
const orders = ordersData?.data || [];

const handleStatusChange = (status: string) => {
setSelectedStatus(status);
setCurrentPage(1);
};

const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchQuery(event.target.value);
setCurrentPage(1);
};

const filteredTransactions = selectedStatus === 'all'
? orders
: orders.filter((transaction: Order) => transaction.status.toLowerCase() === selectedStatus);

const searchedTransactions = searchQuery
? filteredTransactions.filter((transaction: Order) =>
transaction.orderItems.some((item) =>
item.products.name.toLowerCase().includes(searchQuery.toLowerCase())
)
)
: filteredTransactions;

const totalPages = Math.ceil(searchedTransactions.length / itemsPerPage);

const currentTransactions = searchedTransactions.slice(
(currentPage - 1) * itemsPerPage,
currentPage * itemsPerPage
);

const handlePageChange = (page: number) => {
if (page >= 1 && page <= totalPages) {
setCurrentPage(page);
}
};

if (isLoading) {
return <div>Loading...</div>;
}

if (isError) {
return <div>Error loading orders.</div>;
}

return (
<div className='md:ml-64 p-4'>
<div className='bg-[#ffffff] shadow-md rounded-lg p-6 border border-grayColor'>
<div className='mb-4 flex justify-between items-center'>
<div>
<h2 className='text-lg font-semibold'>Orders Details</h2>
<p className='text-sm text-[#8F88f]'>Manage orders</p>
</div>
<div className='flex items-center rounded bg-whiteColor p-2 border border-grayColor'>
<CiSearch className='mr-1.5 text-[#A08D8D]' size={20} />
<input
type='text'
placeholder='Search orders'
className='border-none outline-none'
value={searchQuery}
onChange={handleSearchChange}
/>
</div>
</div>
<nav className="flex gap-4 sm:gap-6 text-sm font-medium">
{["all", "paid", "pending", "delivered", "canceled"].map(status => (
<Button
key={status}
variant={selectedStatus === status ? 'primary' : 'outline'}
onClick={() => handleStatusChange(status)}
className="px-4 py-2 rounded-md"
>
{status.charAt(0).toUpperCase() + status.slice(1)}
</Button>
))}
</nav>
<div className='overflow-x-auto'>
<table className='min-w-full bg-[#ffffff]'>
<thead>
<tr>
<th className='px-4 py-2 border-b border-[#e5e7eb] text-start'>Order ID</th>
<th className='px-4 py-2 border-b border-[#e5e7eb] text-start'>CUSTOMER</th>
<th className='px-4 py-2 border-b border-[#e5e7eb] text-start'>PRODUCT</th>
<th className='px-4 py-2 border-b border-[#e5e7eb] text-start'>DATE</th>
<th className='px-4 py-2 border-b border-[#e5e7eb] text-start'>STATUS</th>
<th className='px-4 py-2 border-b border-[#e5e7eb] text-start'>PRICE</th>
</tr>
</thead>
<tbody>
{currentTransactions.map((transaction: Order) => (
<React.Fragment key={transaction.id}>
{transaction.orderItems.map((orderItem, itemIndex) => (
<tr key={`${transaction.id}-${itemIndex}`}>
<td className='px-4 py-2 border-b border-[#e5e7eb] font-light'>#{(transaction.id).slice(0, 3)}</td>
<td className='px-4 py-2 border-b border-[#e5e7eb] font-light'>{transaction.user?.firstName} {transaction.user?.lastName}</td>
<td className='px-4 py-2 border-b border-[#e5e7eb] font-light'>{orderItem.products.name}</td>
<td className='px-4 py-2 border-b border-[#e5e7eb] font-light'>
{new Date(transaction.createdAt).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: '2-digit',
})}
</td>
<td className='px-4 py-2 border-b border-[#e5e7eb] font-light'>
<span className={`px-2 py-1 rounded-full text-xs font-medium ${statusColors[transaction.status]}`}>
{transaction.status}
</span>
</td>
<td className='px-4 py-2 border-b border-[#e5e7eb] font-light'>{orderItem.price.toFixed(1)}</td>
</tr>
))}
</React.Fragment>
))}
</tbody>
</table>
</div>
<div className="flex justify-between items-center mt-4">
<div>{`${searchedTransactions.length} out of ${orders.length} orders`}</div>
<div className="flex space-x-2">
<Button
variant="outline"
disabled={currentPage === 1}
onClick={() => handlePageChange(currentPage - 1)}
>
Previous
</Button>
<span>Page {currentPage} of {totalPages}</span>
<Button
variant="outline"
disabled={currentPage === totalPages}
onClick={() => handlePageChange(currentPage + 1)}
>
Next
</Button>
</div>
</div>
</div>
</div>
);
};

export default TransactionTable;
76 changes: 76 additions & 0 deletions src/components/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import * as React from "react"
import { cn } from "../../utils"

const buttonVariants = {
default: {
base: "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
variant: "bg-primary text-primary-foreground hover:bg-primary/90",
size: "h-10 px-4 py-2",
},
destructive: {
base: "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
variant: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
size: "h-10 px-4 py-2",
},
outline: {
base: "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
variant: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
size: "h-10 px-4 py-2",
},
secondary: {
base: "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
variant: "bg-secondary text-blue-500 hover:bg-secondary/80",
size: "h-10 px-4 py-2",
},
ghost: {
base: "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
variant: "hover:bg-accent hover:text-accent-foreground",
size: "h-10 px-4 py-2",
},
link: {
base: "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
variant: "text-primary underline-offset-4 hover:underline",
size: "h-10 px-4 py-2",
},
sm: {
base: "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
variant: "",
size: "h-9 rounded-md px-3",
},
lg: {
base: "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
variant: "",
size: "h-11 rounded-md px-8",
},
icon: {
base: "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
variant: "",
size: "h-10 w-10",
},
primary: {
base: "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
variant: "bg-primary text-primary-foreground hover:bg-primary/90",
size: "h-10 px-4 py-2",
},
}

export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: keyof typeof buttonVariants
size?: keyof typeof buttonVariants
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant = "default", ...props }, ref) => {
const { base, variant: variantClass, size: sizeClass } = buttonVariants[variant]
return (
<button
className={cn(base, variantClass, sizeClass, className)}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"

export { Button }
16 changes: 15 additions & 1 deletion src/pages/seller/Orders.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
import OrderTable from '../../components/OrderTables';
import Navbar from '../../components/dashboard/Navbar';



export default function Orders() {
return <div>Orders</div>;

return (
<>
<div>
<Navbar location='Orders' page='seller/orders' />
<OrderTable/>
</div>
</>
);
}

39 changes: 39 additions & 0 deletions src/redux/slices/orderSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { Order } from '../../types/Types';

interface OrdersStateProps {
error: any;
isLoading: boolean;
ordersFetched: boolean;
ordersDataList: Order[];
}

const initialState: OrdersStateProps = {
isLoading: true,
error: null,
ordersFetched: false,
ordersDataList: [],
};

const orderSlice = createSlice({
name: 'orders',
initialState,
reducers: {
setOrdersFetched: (state, action: PayloadAction<boolean>) => {
state.ordersFetched = action.payload;
},
setOrdersDataList: (state, action: PayloadAction<Order[]>) => {
state.ordersDataList = action.payload;
},
setIsLoading: (state, action: PayloadAction<boolean>) => {
state.isLoading = action.payload;
},
setError: (state, action: PayloadAction<any>) => {
state.error = action.payload;
},
},
});

export const { setOrdersFetched, setOrdersDataList, setIsLoading, setError } = orderSlice.actions;

export default orderSlice.reducer;
2 changes: 2 additions & 0 deletions src/redux/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import userReducer from './slices/userSlice';
import productReducer from './slices/productsSlice';
import categoriesReducer from './slices/categorySlice';
import sidebarSlice from './slices/sidebarSlice';
import orderReducer from './slices/orderSlice';

export const store = configureStore({
reducer: {
Expand All @@ -15,6 +16,7 @@ export const store = configureStore({
register: registerReducer,
sidebar: sidebarSlice,
category: categoriesReducer,
orders: orderReducer,
[mavericksApi.reducerPath]: mavericksApi.reducer,
},
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(mavericksApi.middleware),
Expand Down
22 changes: 22 additions & 0 deletions src/services/ordersApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { mavericksApi } from '.';
import { Order, orderResponse } from '../types/Types';

export const ordersApi = mavericksApi.injectEndpoints({
endpoints: builder => ({
getOrders: builder.query<orderResponse, void>({
query: () => ({
url: 'orders/get-orders',
headers: {
Accept: 'application/json',
Authorization: `Bearer ${localStorage.getItem('token') || ''}`,
},
}),
}),
getOrderById: builder.query<Order, string>({
query: id => `orders/${id}`,
}),
}),
overrideExisting: false,
});

export const { useGetOrdersQuery, useGetOrderByIdQuery } = ordersApi;
35 changes: 34 additions & 1 deletion src/types/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,37 @@ export type ChatMessage = {
content: string;
senderId: string;
User: ChatUser;
};
}
export interface OrderItem {
id: string;
orderId: string;
productId: string;
sizeId: string;
quantity: number;
price: number;
createdAt: string;
updatedAt: string;
products: Product;
}

export interface Order {
user: User;
id: string;
status: string;
shippingAddress1: string;
shippingAddress2: string | null;
phone: string;
zipCode: string | null;
city: string;
country: string;
userId: string;
totalPrice: number;
createdAt: string;
updatedAt: string;
orderItems: OrderItem[];
}

export interface orderResponse{
data:Order[];

}

0 comments on commit 77cac66

Please sign in to comment.