• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

atlp-rwanda / knights-ecomm-fe / 10060387859

23 Jul 2024 10:55AM UTC coverage: 90.757% (+0.09%) from 90.671%
10060387859

push

github

web-flow
Merge pull request #73 from atlp-rwanda/ft-order-management

#68 Implement vendor and admin order management feature

1079 of 1360 branches covered (79.34%)

Branch coverage included in aggregate %.

1519 of 1727 new or added lines in 21 files covered. (87.96%)

3 existing lines in 2 files now uncovered.

11332 of 12315 relevant lines covered (92.02%)

12.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.45
/src/pages/Orders/AdminOrders.tsx
1
import React, { useEffect, useState } from 'react';
1✔
2
import searchIcon from '/search.svg';
1✔
3
import HeaderOrderManagement from '../../components/Order/DashboardOrderManagement/HeaderOrderManagement';
1✔
4
import SideBarOrderStatus from '../../components/Order/DashboardOrderManagement/SideBarOrderStatus';
1✔
5
import OrderPagination from '../../components/Order/DashboardOrderManagement/OrderPagination';
1✔
6
import { useNavigate } from 'react-router-dom';
1✔
7
import axios, { AxiosError } from 'axios';
1✔
8
import { AppDispatch, RootState } from '../../redux/store';
1✔
9
import { useDispatch, useSelector } from 'react-redux';
1✔
10
import { Order, setAdminOrders } from '../../redux/reducers/adminOrdersReducer';
1✔
11
import { setOrderStats } from '../../redux/reducers/orderStatsReducer';
1✔
12
import { PropagateLoader, PulseLoader } from 'react-spinners';
1✔
13
import toast from 'react-hot-toast';
1✔
14
import { ErrorResponse } from './SingleAdminOrder';
1✔
15

1✔
16
interface Pagination {
1✔
17
  start: number;
1✔
18
  end: number;
1✔
19
  total: number;
1✔
20
}
1✔
21
const AdminOrders = () => {
1✔
22
  const [filteredOrders, setFilteredOrders] = useState<Order[]>([]);
16✔
23
  const [pagination, setPagination] = useState<Pagination>({ start: 0, end: 0, total: 0 });
16✔
24
  const [loading, setLoading] = useState(true);
16✔
25
  const [showMenu, setShowMenu] = useState<number>();
16✔
26
  const [submitLoading, setSubmitLoading] = useState<boolean>(false);
16✔
27

16✔
28
  const { userToken } = useSelector((state: RootState) => state.auth);
16✔
29
  const { orders } = useSelector((state: RootState) => state.adminOrders);
16✔
30

16✔
31
  const orderStatsStates = useSelector((state: RootState) => state.orderStats);
16✔
32

16✔
33
  const navigate = useNavigate();
16✔
34
  const dispatch = useDispatch<AppDispatch>();
16✔
35

16✔
36
  useEffect(() => {
16✔
37
    const fetchOrders = async () => {
5✔
38
      try {
5✔
39
        const response = await axios.get(`${import.meta.env.VITE_APP_API_URL}/product/admin/orders`, {
5✔
40
          headers: {
5✔
41
            Authorization: `Bearer ${userToken}`
5✔
42
          }
5✔
43
        });
5✔
44

5✔
45
        setPagination((prevData) => {
5✔
46
          prevData.start = 1;
5✔
47
          prevData.end = response.data.data.orders.length > 6 ? 6 : response.data.data.orders.length;
5✔
48
          prevData.total = response.data.data.orders.length;
5✔
49
          return prevData;
5✔
50
        });
5✔
51
        dispatch(setAdminOrders(response.data.data.orders));
5✔
52

5✔
53
        const stats = {
5✔
54
          totalOrder: response.data.data.orders.length,
5✔
55
          completed: 0,
5✔
56
          cancelled: 0,
5✔
57
          pendingOrder: 0
5✔
58
        };
5✔
59
        (response.data.data.orders as Order[]).map((order) => {
5✔
60
          switch (order.orderStatus.toLowerCase()) {
5✔
61
            case 'completed':
5✔
62
              stats.completed += 1;
5✔
63
              break;
5✔
64
            case 'cancelled':
16✔
65
              stats.cancelled += 1;
16✔
66
              break;
7✔
67
            case 'returned':
7✔
68
              break;
16✔
69
            default:
2✔
70
              stats.pendingOrder += 1;
2✔
71
          }
16✔
72
        });
1✔
73

16✔
74
        dispatch(setOrderStats({ ...stats }));
6✔
75
        setLoading(false);
16✔
76
      } catch (error) {
5✔
77
        dispatch(setOrderStats({ totalOrder: 0, completed: 0, cancelled: 0, pendingOrder: 0 }));
5✔
78
        setLoading(false);
5✔
79
      }
5✔
80
    };
5✔
81

1✔
82
    fetchOrders();
1✔
83
  }, [dispatch, userToken]);
1✔
84

5✔
85
  const setPage = (event: React.ChangeEvent<unknown>, value: number) => {
5✔
86
    const newStart = value * 6 - 5;
5✔
87
    const newEnd = value * 6 > orders.length ? orders.length : value * 6;
16✔
88

16✔
89
    setPagination({
16✔
90
      start: newStart,
1✔
91
      end: newEnd,
1!
92
      total: pagination.total
1✔
93
    });
1✔
94
  };
1✔
95

1✔
96
  const searchHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
1✔
97
    const searchTerm = event.target.value.toLowerCase();
1✔
98
    const result: Order[] = orders.filter((order) => {
1✔
99
      const fullName = `${order.buyer.firstName.toLowerCase()} ${order.buyer.lastName.toLowerCase()}`;
16✔
100
      const address = order.address.toLowerCase();
16✔
101
      return fullName.includes(searchTerm) || address.includes(searchTerm);
1✔
102
    });
1✔
103
    setPagination((prevData) => {
2✔
104
      prevData.start = result.length ? 1 : 0;
2✔
105
      prevData.end = result.length > 6 ? 6 : result.length;
2✔
106
      prevData.total = result.length;
1✔
107
      return prevData;
1✔
108
    });
1!
109

1!
110
    setFilteredOrders(result);
1✔
111
  };
1✔
112

1✔
113
  const updateOrderHandler = async (id: string) => {
1✔
114
    setSubmitLoading(true);
1✔
115
    try {
1✔
116
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
16✔
117
      const { data } = await axios.put(
16✔
NEW
118
        `${import.meta.env.VITE_APP_API_URL}/product/admin/orders/${id}`,
×
NEW
119
        {},
×
NEW
120
        {
×
NEW
121
          headers: {
×
NEW
122
            Authorization: `Bearer ${userToken}`,
×
NEW
123
            'Content-Type': 'application/json'
×
NEW
124
          }
×
NEW
125
        }
×
NEW
126
      );
×
NEW
127

×
NEW
128
      dispatch(
×
NEW
129
        setOrderStats({
×
NEW
130
          ...orderStatsStates,
×
NEW
131
          pendingOrder: orderStatsStates.pendingOrder + 1,
×
NEW
132
          completed: orderStatsStates.completed + 1
×
NEW
133
        })
×
NEW
134
      );
×
NEW
135

×
NEW
136
      toast.success('The order has been successfully updated.');
×
NEW
137
      setShowMenu(undefined);
×
NEW
138
      setSubmitLoading(false);
×
NEW
139
    } catch (error) {
×
NEW
140
      if ((error as AxiosError).response && [400, 404].includes((error as AxiosError).response!.status)) {
×
NEW
141
        toast.error('Unable to update your order,\n Please try again!');
×
NEW
142
      } else {
×
NEW
143
        const axiosError = error as AxiosError<ErrorResponse>;
×
NEW
144
        if (axiosError.response?.data.message) {
×
NEW
145
          toast.error(axiosError.response?.data.message);
×
NEW
146
        } else {
×
NEW
147
          toast.error('Something went wrong,\n Please try again!');
×
NEW
148
        }
×
NEW
149
      }
×
NEW
150
      setSubmitLoading(false);
×
NEW
151
    }
×
NEW
152
  };
×
NEW
153

×
NEW
154
  return (
×
NEW
155
    <div className="flex flex-col justify-start gap-8 py-8 w-full min-h-[100vh] h-full px-4 lg:px-10 2xl:px-20 bg-[#EEF5FF]">
×
NEW
156
      <HeaderOrderManagement />
×
NEW
157
      <div className="flex flex-col lg:flex-row min-h-0 gap-7 w-full">
×
NEW
158
        <div className="flex flex-col justify-between gap-y-7 bg-white rounded-lg border border-neutral-300 basis-3/4 p-3 transition-transform">
×
NEW
159
          <div className="flex flex-col gap-y-7">
×
160
            <div className="flex gap-x-2 xmd:gap-x-6 justify-end text-[.7rem] lg:text-sm text-nowrap">
16✔
161
              <div className="flex items-center gap-x-1 border focus-within:border-neutral-900 border-neutral-400 rounded-lg py-[.35rem] pl-3 pr-3 xmd:pr-12 overflow-hidden">
16✔
162
                <img src={searchIcon} alt="search-icon" className="w-4 lg:w-5" />
16✔
163
                <span className="">
16✔
164
                  <input
16✔
165
                    type="text"
16✔
166
                    placeholder="Search by Buyer, Address"
16✔
167
                    onChange={searchHandler}
16✔
168
                    className="outline-none"
16✔
169
                  />
16✔
170
                </span>
16✔
171
              </div>
16✔
172
              <div className="flex items-center justify-center py-[.3rem] gap-x-2 px-3 bg-primary text-white rounded-md">
16✔
173
                <span>Filter</span>
16✔
174
                <span className="material-symbols-outlined text-xl leading-3">filter_list</span>
16✔
175
              </div>
16✔
176
            </div>
16✔
177
            <table className="w-full text-left">
16✔
178
              <thead className="text-[.75rem] xmd:text-[.82rem] lg:text-[.88rem]">
16✔
179
                <tr className="bg-primary text-white h-12 ">
16✔
180
                  <th className="font-normal px-3">
16✔
181
                    N<sup>o</sup>
16✔
182
                  </th>
16✔
183
                  <th className="font-normal">Buyer</th>
16✔
184
                  <th className="font-normal">Address</th>
16✔
185
                  <th className="font-normal align-middle">Placed Date</th>
16✔
186
                  <th className="font-normal">Completed</th>
16✔
187
                  <th className="font-normal hidden xmd:table-cell">Action</th>
16✔
188
                </tr>
16✔
189
              </thead>
16✔
190
              {!loading && (
16✔
191
                <tbody className="text-[.7rem] xmd:text-[.76rem] lg:text-[.82rem]">
16✔
192
                  {!filteredOrders.length
16✔
193
                    ? orders.slice(pagination.start - 1, pagination.end).map((order, index) => (
16✔
194
                        <tr
16✔
195
                          key={index}
16✔
196
                          onClick={() => navigate(`${order.id}`)}
16✔
197
                          className="border-b-[1px] h-14 border-neutral-300 cursor-pointer hover:bg-neutral-200"
16✔
198
                        >
8✔
199
                          <td className="px-3">{pagination.start + index}</td>
8✔
200
                          <td>
6✔
201
                            <p className="leading-3 capitalize">
18✔
202
                              {order.buyer.firstName + ' ' + order.buyer.firstName}
18✔
203
                            </p>
18✔
204
                            <p className="text-[.67rem] xmd:text-[.73rem] lg:text-[.8rem] text-neutral-600">
18✔
205
                              {order.buyer.phoneNumber}
18✔
206
                            </p>
18✔
207
                          </td>
18✔
208
                          <td>
18✔
209
                            <p className="leading-3">{order.address.split(', ').slice(0, 2).join('-')}</p>
18✔
210
                            <p>{order.address.split(', ')[2]}</p>
18✔
211
                          </td>
18✔
212
                          <td className="align-middle">{order.createdAt.toLocaleString().split('T')[0]}</td>
18✔
213
                          <td className="align-middle pl-6 xmd:pl-8 text-base">
18✔
214
                            {order.orderStatus.toLowerCase() === 'completed' ? (
18✔
215
                              <i className="fa-solid fa-check text-green-700"></i>
18✔
216
                            ) : (
18✔
217
                              <i className="fa-solid fa-x text-red-700"></i>
18✔
218
                            )}
18✔
219
                          </td>
18✔
220
                          <td
18✔
221
                            onClick={(e: React.MouseEvent<HTMLDivElement>) => {
18✔
222
                              e.stopPropagation();
18✔
223
                              setShowMenu(undefined);
18✔
224
                              setShowMenu(index);
18✔
225
                            }}
18✔
226
                            data-testid="menuAction"
8✔
227
                            className="group relative hidden xmd:table-cell pl-5"
10✔
228
                          >
10✔
229
                            <i className="group-hover:text-base fa-solid fa-ellipsis-vertical"></i>
18✔
230

18✔
231
                            {showMenu === index && (
18✔
232
                              <div
18✔
233
                                onMouseLeave={() =>
1✔
234
                                  setTimeout(() => {
1✔
235
                                    setShowMenu(undefined);
1✔
236
                                  }, 300)
1✔
237
                                }
18✔
238
                                className="z-10 animate-slideInFromTop absolute top-2 right-3 bg-baseWhite xmd:w-40 px-1 py-2 shadow-xl border-[1px] border-neutral-200 rounded-lg text-[.7rem] xmd:text-[.76rem] lg:text-[.8rem]"
18✔
239
                              >
18✔
240
                                <ul>
18✔
241
                                  <li
18✔
242
                                    onClick={() => navigate(`${order.id}`)}
18✔
243
                                    className="cursor-pointer hover:bg-neutral-200 active:bg-neutral-200 bg-neutral-100 py-2 px-2 border-b-[1px] border-neutral-300"
1✔
244
                                  >
1✔
NEW
245
                                    View & Update
×
NEW
246
                                  </li>
×
NEW
247
                                  <li
×
248
                                    data-testid='data-testid="menuActionUpdate'
1✔
249
                                    onClick={() => {
1✔
250
                                      if (order.orderStatus.toLowerCase() !== 'completed') {
1✔
251
                                        updateOrderHandler(order.id);
1✔
252
                                      }
1✔
253
                                    }}
1✔
254
                                    className={
1✔
255
                                      (order.orderStatus.toLowerCase() === 'completed'
1✔
256
                                        ? 'cursor-not-allowed text-neutral-500'
1✔
257
                                        : 'cursor-pointer hover:bg-neutral-200 active:bg-neutral-200') +
1✔
258
                                      '  bg-neutral-100 py-2 px-2 border-b-[1px] border-neutral-300'
1✔
259
                                    }
1✔
260
                                  >
1✔
NEW
261
                                    {!submitLoading && <span>Mark as Completed</span>}
×
NEW
262
                                    {submitLoading && <PulseLoader color="#000" size={6} className="text-center" />}
×
NEW
263
                                  </li>
×
NEW
264
                                </ul>
×
265
                              </div>
1✔
266
                            )}
1✔
267
                          </td>
1!
NEW
268
                        </tr>
×
269
                      ))
1✔
270
                    : filteredOrders.slice(pagination.start - 1, pagination.end).map((order, index) => (
1✔
271
                        <tr
1✔
272
                          key={index}
1✔
273
                          onClick={() => navigate(`${order.id}`)}
1!
NEW
274
                          className="border-b-[1px] h-14 border-neutral-300 cursor-pointer hover:bg-neutral-200"
×
275
                        >
1✔
276
                          <td className="px-3">{pagination.start + index}</td>
1✔
277
                          <td>
1✔
278
                            <p className="leading-3 capitalize">
1✔
279
                              {order.buyer.firstName + ' ' + order.buyer.firstName}
18✔
280
                            </p>
18✔
281
                            <p className="text-[.67rem] xmd:text-[.73rem] lg:text-[.8rem] text-neutral-600">
18✔
282
                              {order.buyer.phoneNumber}
6✔
283
                            </p>
2✔
284
                          </td>
2✔
285
                          <td>
2✔
286
                            <p className="leading-3">{order.address.split(', ').slice(0, 2).join('-')}</p>
2✔
287
                            <p>{order.address.split(', ')[2]}</p>
2✔
288
                          </td>
2✔
289
                          <td className="align-middle">{order.createdAt.toLocaleString().split('T')[0]}</td>
2✔
290
                          <td className="align-middle pl-6 xmd:pl-8 text-base">
2✔
291
                            {order.orderStatus.toLowerCase() === 'completed' ? (
2✔
292
                              <i className="fa-solid fa-check text-green-700"></i>
2✔
293
                            ) : (
2✔
294
                              <i className="fa-solid fa-x text-red-700"></i>
2✔
295
                            )}
2✔
296
                          </td>
2✔
297
                          <td
2✔
298
                            onClick={(e: React.MouseEvent<HTMLDivElement>) => {
2✔
299
                              e.stopPropagation();
2✔
300
                              setShowMenu(undefined);
2✔
301
                              setShowMenu(index);
2✔
302
                            }}
2✔
303
                            data-testid="menuAction"
2✔
304
                            className="group relative hidden xmd:table-cell pl-5"
2✔
305
                          >
2✔
306
                            <i className="group-hover:text-base fa-solid fa-ellipsis-vertical"></i>
2✔
307

2✔
308
                            {showMenu === index && (
2✔
309
                              <div
2✔
310
                                onMouseLeave={() =>
2!
NEW
311
                                  setTimeout(() => {
×
312
                                    setShowMenu(undefined);
2✔
313
                                  }, 300)
2✔
314
                                }
2✔
315
                                className="z-10 animate-slideInFromTop absolute top-2 right-3 bg-baseWhite xmd:w-40 px-1 py-2 shadow-xl border-[1px] border-neutral-200 rounded-lg text-[.7rem] xmd:text-[.76rem] lg:text-[.8rem]"
2✔
316
                              >
2✔
317
                                <ul>
2✔
318
                                  <li
1✔
319
                                    onClick={() => navigate(`${order.id}`)}
1✔
320
                                    className="cursor-pointer hover:bg-neutral-200 active:bg-neutral-200 bg-neutral-100 py-2 px-2 border-b-[1px] border-neutral-300"
1✔
321
                                  >
1✔
322
                                    View & Update
2✔
323
                                  </li>
2✔
324
                                  <li
2✔
325
                                    data-testid='data-testid="menuActionUpdate'
2✔
326
                                    onClick={() => {
2✔
327
                                      if (order.orderStatus.toLowerCase() !== 'completed') {
2✔
328
                                        updateOrderHandler(order.id);
1✔
329
                                      }
1✔
NEW
330
                                    }}
×
NEW
331
                                    className={
×
NEW
332
                                      (order.orderStatus.toLowerCase() === 'completed'
×
333
                                        ? 'cursor-not-allowed text-neutral-500'
1✔
334
                                        : 'cursor-pointer hover:bg-neutral-200 active:bg-neutral-200') +
1✔
335
                                      '  bg-neutral-100 py-2 px-2 border-b-[1px] border-neutral-300'
1✔
336
                                    }
1✔
337
                                  >
1✔
338
                                    {!submitLoading && <span>Mark as Completed</span>}
1✔
339
                                    {submitLoading && <PulseLoader color="#000" size={6} className="text-center" />}
1✔
340
                                  </li>
1✔
341
                                </ul>
1✔
342
                              </div>
1✔
343
                            )}
1✔
344
                          </td>
1✔
345
                        </tr>
1✔
NEW
346
                      ))}
×
NEW
347
                </tbody>
×
NEW
348
              )}
×
NEW
349
            </table>
×
350
          </div>
1✔
351
          {loading && (
1!
NEW
352
            <div className="flex justify-center items-center">
×
353
              <PropagateLoader className="ml-[-.5rem]" />
1✔
354
            </div>
1✔
355
          )}
1✔
356
          {!orders.length && (
1✔
357
            <div className="flex justify-center items-center">
1✔
358
              <p>Currently, no orders are in the system!</p>
1!
NEW
359
            </div>
×
NEW
360
          )}
×
NEW
361
          <OrderPagination start={pagination.start} end={pagination.end} total={pagination.total} setPage={setPage} />
×
NEW
362
        </div>
×
NEW
363
        <div>
×
364
          <SideBarOrderStatus />
1✔
365
        </div>
1✔
366
      </div>
1✔
367
    </div>
1✔
368
  );
2✔
369
};
2✔
370

2✔
371
export default AdminOrders;
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc