OrderHistoryView.tsx 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. import { useNavigate } from "react-router-dom";
  2. import { productApi } from "../../apis/productApi";
  3. import { startLoading, stopLoading } from "../../features/loading/loadingSlice";
  4. import { useAppDispatch } from "../../hooks/useRedux";
  5. import { OrderHistory } from "../../services/product/type";
  6. import { useMutation } from "@tanstack/react-query";
  7. import React, { useState, useEffect } from "react";
  8. import {
  9. convertOrderStatusToColor,
  10. convertOrderStatusToText,
  11. formatCurrency,
  12. formatNumber,
  13. } from "../../logic/loigicUtils";
  14. import { useTranslation } from "react-i18next";
  15. const OrderHistoryView = () => {
  16. const [searchOrder, setSearchOrder] = useState("");
  17. const lastDay = new Date(new Date().setDate(new Date().getDate() - 30))
  18. .toISOString()
  19. .split("T")[0];
  20. const today = new Date().toISOString().split("T")[0];
  21. // status is one of: -1, 1, 2
  22. const [status, setStatus] = useState("-1");
  23. const [fromDate, setFromDate] = useState(lastDay);
  24. const [toDate, setToDate] = useState(today);
  25. const [orders, setOrders] = useState<OrderHistory[]>([]);
  26. const { t } = useTranslation();
  27. const dispatch = useAppDispatch();
  28. const navigate = useNavigate();
  29. useEffect(() => {
  30. getOrderMutation.mutate();
  31. }, []);
  32. const getOrderMutation = useMutation({
  33. mutationFn: async () => {
  34. dispatch(startLoading({}));
  35. const res = await productApi.getOrderHistory({
  36. searchOrder: searchOrder,
  37. fromDate,
  38. toDate,
  39. status,
  40. });
  41. return res;
  42. },
  43. onSuccess: (data) => {
  44. dispatch(stopLoading());
  45. console.log("Get order history response data:", data);
  46. if (data && data.errorCode === "0") {
  47. console.log("Get order history successful");
  48. setOrders(data.data || []);
  49. } else {
  50. console.error("Get order history failed, no token received");
  51. }
  52. },
  53. onError: (error: any) => {
  54. dispatch(stopLoading());
  55. console.error("Get order history error:", error.response.data);
  56. },
  57. });
  58. // Input style matching LoginView
  59. const inputClass =
  60. "w-full bg-slate-50 border-2 border-transparent focus:border-[#EE0434]/20 rounded-2xl py-3 px-5 focus:outline-none focus:bg-white transition-all text-slate-700 font-bold placeholder:text-slate-300 text-sm md:text-base h-[50px]";
  61. // const filteredOrders = orders.filter((order) => {
  62. // const matchesSearch = order.id.includes(searchOrder);
  63. // const matchesStatus = status === "-1" || order.status === status;
  64. // return matchesSearch && matchesStatus;
  65. // });
  66. return (
  67. <div className="bg-[#fcfdfe] min-h-screen pb-20">
  68. {/* Breadcrumb */}
  69. <div className="max-w-7xl mx-auto px-4 py-4 md:py-6 border-b border-slate-50">
  70. <nav className="flex items-center space-x-2 text-xs md:text-sm text-slate-500 font-medium">
  71. <button
  72. // onClick={() => onViewChange(ViewMode.HOME)}
  73. className="hover:text-[#EE0434] text-[18px]"
  74. >
  75. {t("home")}
  76. </button>
  77. <svg
  78. className="w-3 h-3"
  79. fill="none"
  80. stroke="currentColor"
  81. viewBox="0 0 24 24"
  82. >
  83. <path
  84. d="M9 5l7 7-7 7"
  85. strokeWidth={2.5}
  86. strokeLinecap="round"
  87. strokeLinejoin="round"
  88. />
  89. </svg>
  90. <span className="text-slate-900 font-bold text-[18px]">
  91. {t("transactionHistory")}
  92. </span>
  93. </nav>
  94. </div>
  95. <div className="max-w-5xl mx-auto px-4 py-8 md:py-12">
  96. {/* Filter Bar */}
  97. <div className="grid grid-cols-1 md:grid-cols-12 gap-4 mb-8">
  98. <div className="md:col-span-4 relative">
  99. <input
  100. type="text"
  101. placeholder="Enter order id"
  102. className={inputClass}
  103. value={searchOrder}
  104. onChange={(e) => {
  105. setSearchOrder(e.target.value);
  106. getOrderMutation.mutate();
  107. }}
  108. />
  109. <button className="absolute right-2 top-1/2 -translate-y-1/2 w-8 h-8 bg-[#EE0434] rounded-xl flex items-center justify-center text-white shadow-md hover:scale-105 transition-transform">
  110. <svg
  111. className="w-4 h-4"
  112. fill="none"
  113. stroke="currentColor"
  114. viewBox="0 0 24 24"
  115. >
  116. <path
  117. strokeLinecap="round"
  118. strokeLinejoin="round"
  119. strokeWidth={2.5}
  120. d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
  121. />
  122. </svg>
  123. </button>
  124. </div>
  125. <div className="md:col-span-3">
  126. <input
  127. type="date"
  128. placeholder="From date"
  129. onFocus={(e) => (e.target.type = "date")}
  130. onBlur={(e) => (e.target.type = "date")}
  131. className={inputClass}
  132. value={fromDate}
  133. onChange={(e) => {
  134. setFromDate(e.target.value);
  135. getOrderMutation.mutate();
  136. }}
  137. />
  138. </div>
  139. <div className="md:col-span-3">
  140. <input
  141. type="date"
  142. placeholder="To date"
  143. onFocus={(e) => (e.target.type = "date")}
  144. onBlur={(e) => (e.target.type = "date")}
  145. className={inputClass}
  146. value={toDate}
  147. onChange={(e) => {
  148. setToDate(e.target.value);
  149. getOrderMutation.mutate();
  150. }}
  151. />
  152. </div>
  153. <div className="md:col-span-2 relative">
  154. <select
  155. className={`${inputClass} appearance-none cursor-pointer`}
  156. value={status}
  157. onChange={(e) => {
  158. setStatus(e.target.value);
  159. getOrderMutation.mutate();
  160. }}
  161. >
  162. {/* 1: chờ thanh toán 2: đã thanh toán, chờ xuất esim 3: thanh toán
  163. thất bại 4: đã trả esim */}
  164. <option value="-1">Trạng thái</option>
  165. <option value="1">Chờ thanh toán</option>
  166. <option value="2">Đã thanh toán, chờ xuất eSIM</option>
  167. <option value="3">Thanh toán thất bại</option>
  168. <option value="4">Đã trả eSIM</option>
  169. </select>
  170. <svg
  171. className="absolute right-4 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400 pointer-events-none"
  172. fill="none"
  173. stroke="currentColor"
  174. viewBox="0 0 24 24"
  175. >
  176. <path
  177. strokeLinecap="round"
  178. strokeLinejoin="round"
  179. strokeWidth={2}
  180. d="M19 9l-7 7-7-7"
  181. />
  182. </svg>
  183. </div>
  184. </div>
  185. {/* Order List */}
  186. <div className="space-y-4">
  187. {orders.length === 0 ? (
  188. <div className="text-center py-20 opacity-50">
  189. <p className="text-xl font-bold text-slate-400">
  190. No orders found
  191. </p>
  192. </div>
  193. ) : (
  194. orders.map((order) => (
  195. <div
  196. key={order.id}
  197. onClick={() =>
  198. navigate(`/order-history-detail/${order.id}`, {
  199. state: { id: order.id, orderHistory: order },
  200. })
  201. }
  202. className="bg-white rounded-[20px] p-6 shadow-sm border border-slate-100 hover:shadow-md transition-shadow flex flex-col md:flex-row justify-between items-start md:items-center gap-6"
  203. >
  204. <div className="space-y-3">
  205. <span
  206. className={`inline-block px-3 py-1 rounded-lg text-xs font-black uppercase tracking-wider ${convertOrderStatusToColor(
  207. order.status,
  208. )}`}
  209. >
  210. {convertOrderStatusToText(order.status)}
  211. </span>
  212. <div>
  213. <p className="text-xl font-black text-slate-900 tracking-tight">
  214. {order.orderCode}
  215. </p>
  216. <p className="text-sm font-medium text-slate-400 mt-1">
  217. {order.createdDate}
  218. </p>
  219. </div>
  220. </div>
  221. <div className="flex items-center justify-between w-full md:w-auto gap-8">
  222. <div className="flex -space-x-2">
  223. {/* {order.flags.map((flag, idx) => (
  224. <div
  225. key={idx}
  226. className="w-8 h-8 rounded-full border-2 border-white shadow-sm overflow-hidden z-0"
  227. >
  228. <img
  229. src={`https://flagcdn.com/w80/${flag}.png`}
  230. alt={flag}
  231. className="w-full h-full object-cover"
  232. />
  233. </div>
  234. ))} */}
  235. </div>
  236. <div className="text-right">
  237. <p className="text-xl font-black text-slate-900">
  238. {formatCurrency(order.paymentMoney, order.curency)}
  239. </p>
  240. <p className="text-sm font-bold text-slate-400">
  241. {formatCurrency(order.totalMoney, order.curency)}
  242. </p>
  243. </div>
  244. </div>
  245. </div>
  246. ))
  247. )}
  248. </div>
  249. </div>
  250. </div>
  251. );
  252. };
  253. export default OrderHistoryView;