sme-pos-package 1.0.7 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (318) hide show
  1. package/dist/index.d.mts +37 -2
  2. package/dist/index.mjs +17 -104
  3. package/dist/index.mjs.map +1 -1
  4. package/dist/tailwind.preset.d.mts +27 -0
  5. package/dist/tailwind.preset.mjs +2 -0
  6. package/dist/tailwind.preset.mjs.map +1 -0
  7. package/package.json +18 -30
  8. package/public/assets/images/bank-logos.png +0 -0
  9. package/public/assets/images/banner1.png +0 -0
  10. package/public/assets/images/banner2.png +0 -0
  11. package/public/assets/images/banner3.png +0 -0
  12. package/public/assets/images/banner4.png +0 -0
  13. package/public/assets/images/cashier.svg +9 -0
  14. package/public/assets/images/empty-cart.png +0 -0
  15. package/public/assets/images/empty-table-state.png +0 -0
  16. package/public/assets/images/layout-display/left-layout.png +0 -0
  17. package/public/assets/images/layout-display/right-layout.png +0 -0
  18. package/public/assets/images/modal-illustration.png +0 -0
  19. package/public/assets/images/order-empty-state.png +0 -0
  20. package/public/assets/images/order.png +0 -0
  21. package/public/assets/images/payment-success-label.png +0 -0
  22. package/public/assets/images/table-empty.png +0 -0
  23. package/public/assets/images/take-away.png +0 -0
  24. package/public/assets/lottie/loading.json +1 -0
  25. package/public/assets/lottie/soft_pos.json +1 -0
  26. package/public/assets/lottie/success.json +1 -0
  27. package/public/config.json +27 -0
  28. package/public/env.json +17 -0
  29. package/src/animations/success.json +9730 -0
  30. package/src/assets/icons/breadcrumbs-icon.tsx +85 -0
  31. package/src/assets/icons/cart-icon.tsx +14 -0
  32. package/src/assets/icons/common.tsx +270 -0
  33. package/src/assets/icons/customer-view-icon.tsx +20 -0
  34. package/src/assets/icons/dining-table.tsx +34 -0
  35. package/src/assets/icons/edit-line-icon.tsx +18 -0
  36. package/src/assets/icons/emenu-icon.tsx +39 -0
  37. package/src/assets/icons/file-close-line-icon.tsx +18 -0
  38. package/src/assets/icons/helper-icon.tsx +84 -0
  39. package/src/assets/icons/home-gear-line-icon.tsx +18 -0
  40. package/src/assets/icons/kanban-icon.tsx +16 -0
  41. package/src/assets/icons/loading-icon.tsx +45 -0
  42. package/src/assets/icons/logout-box-line-icon.tsx +18 -0
  43. package/src/assets/icons/order-icon.tsx +92 -0
  44. package/src/assets/icons/payment-icon.tsx +429 -0
  45. package/src/assets/icons/pincode.tsx +196 -0
  46. package/src/assets/icons/pos-connect-icon.tsx +67 -0
  47. package/src/assets/icons/pos-device-icon.tsx +52 -0
  48. package/src/assets/icons/pos-icon.tsx +75 -0
  49. package/src/assets/icons/printer-line-icon.tsx +18 -0
  50. package/src/assets/icons/star-icon.tsx +174 -0
  51. package/src/assets/icons/table-icon.tsx +77 -0
  52. package/src/assets/icons/user-add-line-icon.tsx +33 -0
  53. package/src/assets/icons/user-icon.tsx +65 -0
  54. package/src/components/common/button-action.tsx +46 -0
  55. package/src/components/common/button-icon.tsx +23 -0
  56. package/src/components/common/dropdown/index.tsx +79 -0
  57. package/src/components/common/empty-state.tsx +60 -0
  58. package/src/components/common/header/index.tsx +89 -0
  59. package/src/components/common/header/tabs.tsx +119 -0
  60. package/src/components/common/image.tsx +20 -0
  61. package/src/components/common/input/index.tsx +55 -0
  62. package/src/components/common/input/radio-group.tsx +75 -0
  63. package/src/components/common/input/text-area.tsx +28 -0
  64. package/src/components/common/modal/delete-confirm-modal-container.tsx +65 -0
  65. package/src/components/common/modal/index.ts +2 -0
  66. package/src/components/common/modal/modal-direction.tsx +125 -0
  67. package/src/components/common/modal/modal-header.tsx +37 -0
  68. package/src/components/common/modal/modal-wrapper.tsx +56 -0
  69. package/src/components/common/modal/modal-zoom.tsx +61 -0
  70. package/src/components/common/pagination.tsx +95 -0
  71. package/src/components/common/refresh-button.tsx +28 -0
  72. package/src/components/common/search-bar.tsx +27 -0
  73. package/src/components/common/search.tsx +59 -0
  74. package/src/components/common/skeleton.tsx +7 -0
  75. package/src/components/common/tabs-layout.tsx +98 -0
  76. package/src/components/common/use-check-session.tsx +19 -0
  77. package/src/components/customer-management/add-customer/add-customer-drawer.tsx +286 -0
  78. package/src/components/customer-management/add-customer/address-select.tsx +51 -0
  79. package/src/components/customer-management/choose-customer-drawer.tsx +177 -0
  80. package/src/components/customer-management/customer-info-drawer.tsx +166 -0
  81. package/src/components/customer-management/customer-no-data.tsx +18 -0
  82. package/src/components/emenu/emenu-qr-code.tsx +59 -0
  83. package/src/components/emenu/menu-link.tsx +67 -0
  84. package/src/components/index.ts +8 -0
  85. package/src/components/kanban-template/kitchen-template.tsx +60 -0
  86. package/src/components/kanban-template/pos-order-template.tsx +77 -0
  87. package/src/components/kanban-template/pos-template.tsx +170 -0
  88. package/src/components/kitchen-management/change-status-confirm-modal.tsx +61 -0
  89. package/src/components/kitchen-management/index.ts +1 -0
  90. package/src/components/kitchen-management/kitchen-order/index.tsx +153 -0
  91. package/src/components/kitchen-management/kitchen-order/line.tsx +78 -0
  92. package/src/components/kitchen-management/kitchen-order/state.tsx +52 -0
  93. package/src/components/kitchen-management/kitchen-status.tsx +69 -0
  94. package/src/components/kitchen-management/orders-kitchen-management.tsx +113 -0
  95. package/src/components/order-management/index.tsx +128 -0
  96. package/src/components/order-management/order-actions.tsx +46 -0
  97. package/src/components/order-management/order-detail/header.tsx +37 -0
  98. package/src/components/order-management/order-detail/index.ts +3 -0
  99. package/src/components/order-management/order-detail/information.tsx +96 -0
  100. package/src/components/order-management/order-detail/order-detail.tsx +84 -0
  101. package/src/components/order-management/order-detail/order-line.tsx +56 -0
  102. package/src/components/order-management/order-detail/summary.tsx +221 -0
  103. package/src/components/order-management/order-header.tsx +113 -0
  104. package/src/components/order-management/order-pagination.tsx +49 -0
  105. package/src/components/order-management/order-status.tsx +58 -0
  106. package/src/components/order-management/order-table.tsx +179 -0
  107. package/src/components/order-management/order-tabs.tsx +48 -0
  108. package/src/components/pos-management/add-product-from-barcode/index.tsx +97 -0
  109. package/src/components/pos-management/cart-content/action-line.tsx +81 -0
  110. package/src/components/pos-management/cart-content/cancel-order-line-modal.tsx +41 -0
  111. package/src/components/pos-management/cart-content/cart-item.tsx +173 -0
  112. package/src/components/pos-management/cart-content/cart-items.tsx +130 -0
  113. package/src/components/pos-management/cart-content/draft-orders/draft-order-line.tsx +67 -0
  114. package/src/components/pos-management/cart-content/draft-orders/index.tsx +40 -0
  115. package/src/components/pos-management/cart-content/edit-cart-modal.tsx +11 -0
  116. package/src/components/pos-management/cart-content/index.tsx +260 -0
  117. package/src/components/pos-management/cart-content/no-select-order.tsx +26 -0
  118. package/src/components/pos-management/cart-content/order-info/customer-info.tsx +53 -0
  119. package/src/components/pos-management/cart-content/order-info/index.tsx +109 -0
  120. package/src/components/pos-management/cart-content/order-total.tsx +36 -0
  121. package/src/components/pos-management/cart-content/orders-line.tsx +72 -0
  122. package/src/components/pos-management/menu-content/category-tab.tsx +38 -0
  123. package/src/components/pos-management/menu-content/create-order-modal.tsx +65 -0
  124. package/src/components/pos-management/menu-content/product-content.tsx +93 -0
  125. package/src/components/pos-management/menu-content/product-detail/info.tsx +28 -0
  126. package/src/components/pos-management/menu-content/product-detail/modal.tsx +148 -0
  127. package/src/components/pos-management/menu-content/product-detail/option.tsx +87 -0
  128. package/src/components/pos-management/menu-content/product-detail/quantity-control.tsx +155 -0
  129. package/src/components/pos-management/menu-content/product-detail/sale-keypad.tsx +100 -0
  130. package/src/components/pos-management/menu-content/product-item.tsx +65 -0
  131. package/src/components/pos-management/order-helper/cancel-order-confirm.tsx +43 -0
  132. package/src/components/pos-management/order-helper/history-call.tsx +105 -0
  133. package/src/components/pos-management/order-helper/order-helper-modal.tsx +116 -0
  134. package/src/components/pos-management/payment-content/bill/actions.tsx +46 -0
  135. package/src/components/pos-management/payment-content/bill/content.tsx +207 -0
  136. package/src/components/pos-management/payment-content/bill/index.tsx +18 -0
  137. package/src/components/pos-management/payment-content/card-payment/index.tsx +124 -0
  138. package/src/components/pos-management/payment-content/card-payment/pos-payment/pos-connect.tsx +183 -0
  139. package/src/components/pos-management/payment-content/card-payment/pos-payment/qr-connect.tsx +185 -0
  140. package/src/components/pos-management/payment-content/card-payment/waiting-for-payment-pos-modal.tsx +37 -0
  141. package/src/components/pos-management/payment-content/cash-payment/index.tsx +216 -0
  142. package/src/components/pos-management/payment-content/index.tsx +70 -0
  143. package/src/components/pos-management/payment-content/payment-info.tsx +67 -0
  144. package/src/components/pos-management/payment-content/payment-layout.tsx +16 -0
  145. package/src/components/pos-management/payment-content/payment-method-cards.tsx +99 -0
  146. package/src/components/pos-management/payment-content/qr-payment/index.tsx +118 -0
  147. package/src/components/pos-management/payment-content/qr-payment/qr_code.js +29 -0
  148. package/src/components/pos-management/payment-content/qr-payment/vietqr.js +436 -0
  149. package/src/components/pos-management/payment-content/session-close-modal.tsx +35 -0
  150. package/src/components/pos-management/payment-content/soft-pos-payment/index.tsx +48 -0
  151. package/src/components/pos-management/pos-helpers/back-confirm.tsx +45 -0
  152. package/src/components/pos-management/pos-helpers/index.tsx +135 -0
  153. package/src/components/pos-management/pos-helpers/layout-display-modal.tsx +99 -0
  154. package/src/components/pos-management/search.tsx +48 -0
  155. package/src/components/pos-management/table-content/filtered-tables.tsx +59 -0
  156. package/src/components/pos-management/table-content/floor-tabs.tsx +65 -0
  157. package/src/components/pos-management/table-content/index.tsx +76 -0
  158. package/src/components/pos-management/table-content/table-item.tsx +127 -0
  159. package/src/components/pos-management/table-content/tables.tsx +96 -0
  160. package/src/components/restaurant-management/add-floor-modal.tsx +133 -0
  161. package/src/components/restaurant-management/add-table-modal.tsx +198 -0
  162. package/src/components/restaurant-management/delete-floor-confirm-modal.tsx +42 -0
  163. package/src/components/restaurant-management/delete-table-confirm-modal.tsx +51 -0
  164. package/src/components/restaurant-management/header.tsx +22 -0
  165. package/src/components/restaurant-management/index.tsx +54 -0
  166. package/src/components/restaurant-management/left-side.tsx +51 -0
  167. package/src/components/restaurant-management/right-side.tsx +179 -0
  168. package/src/components/restaurant-management/table-empty.tsx +43 -0
  169. package/src/components/session-management/close-session-modal/actions.tsx +45 -0
  170. package/src/components/session-management/close-session-modal/close-session-confirm.tsx +157 -0
  171. package/src/components/session-management/close-session-modal/close-session-ticket.tsx +218 -0
  172. package/src/components/session-management/close-session-modal/container.tsx +25 -0
  173. package/src/components/session-management/close-session-modal/draft-orders-confirm.tsx +53 -0
  174. package/src/components/session-management/close-session-modal/draft-orders-summary.tsx +82 -0
  175. package/src/components/session-management/close-session-modal/draft-orders.tsx +34 -0
  176. package/src/components/session-management/close-session-modal/index.tsx +35 -0
  177. package/src/components/session-management/close-session-modal/order/index.ts +1 -0
  178. package/src/components/session-management/close-session-modal/order/order-line.tsx +64 -0
  179. package/src/components/session-management/close-session-modal/order/orders-in-draft.tsx +31 -0
  180. package/src/components/session-management/close-session-modal/session-summary.tsx +165 -0
  181. package/src/components/session-management/index.ts +1 -0
  182. package/src/components/session-management/open-shift/index.tsx +172 -0
  183. package/src/components/table-management/index.tsx +0 -0
  184. package/src/constants/api/index.ts +1 -0
  185. package/src/constants/headers.constant.tsx +24 -0
  186. package/src/constants/index.ts +1 -0
  187. package/src/constants/language.store.ts +21 -0
  188. package/src/constants/methods.constant.ts +10 -0
  189. package/src/constants/order-state.ts +8 -0
  190. package/src/constants/payment-color.constant.ts +26 -0
  191. package/src/constants/query.constant.ts +14 -0
  192. package/src/constants/routes.constants.ts +23 -0
  193. package/src/constants/template-name.constant.ts +9 -0
  194. package/src/hooks/bill/use-get-bill-information.ts +49 -0
  195. package/src/hooks/bill/use-print-bill.tsx +0 -0
  196. package/src/hooks/common/load-env-config.ts +98 -0
  197. package/src/hooks/common/use-back.ts +13 -0
  198. package/src/hooks/common/use-broadcast.ts +22 -0
  199. package/src/hooks/common/use-click-outside.ts +58 -0
  200. package/src/hooks/common/use-get-base64-photo.ts +71 -0
  201. package/src/hooks/common/use-get-query.ts +25 -0
  202. package/src/hooks/common/use-pos.ts +21 -0
  203. package/src/hooks/common/use-query-params.ts +89 -0
  204. package/src/hooks/common/use-speech.ts +36 -0
  205. package/src/hooks/customer/use-customer-provider.ts +247 -0
  206. package/src/hooks/floor/use-floor-provider.ts +172 -0
  207. package/src/hooks/kitchen/use-kitchen-provider.ts +80 -0
  208. package/src/hooks/order/use-get-point-reward.ts +50 -0
  209. package/src/hooks/order/use-loop-fetch-order.ts +30 -0
  210. package/src/hooks/order/use-order-items.ts +61 -0
  211. package/src/hooks/order/use-orders.ts +76 -0
  212. package/src/hooks/payment/use-payment-provider.ts +221 -0
  213. package/src/hooks/product/use-product-provider.ts +87 -0
  214. package/src/hooks/session/use-check-session.tsx +19 -0
  215. package/src/hooks/session/use-session-provider.ts +79 -0
  216. package/src/hooks/table/use-pos-table-provider.ts +193 -0
  217. package/src/index.css +5 -0
  218. package/src/index.ts +10 -0
  219. package/src/layouts/common.tsx +15 -0
  220. package/src/layouts/index.ts +4 -0
  221. package/src/layouts/pos-layout.tsx +33 -0
  222. package/src/layouts/pos-order-layout.tsx +43 -0
  223. package/src/layouts/sale-layout.tsx +25 -0
  224. package/src/locales/en.json +394 -0
  225. package/src/locales/vi.json +400 -0
  226. package/src/pages/index.ts +4 -0
  227. package/src/pages/login/index.tsx +128 -0
  228. package/src/pages/pincode/index.tsx +198 -0
  229. package/src/pages/pos/kitchen-management/layout.tsx +18 -0
  230. package/src/pages/pos/management-floor/index.tsx +7 -0
  231. package/src/pages/pos/management-order/index.tsx +7 -0
  232. package/src/pages/pos/management-order/order-detail/index.tsx +7 -0
  233. package/src/pages/pos/management-table/index.tsx +0 -0
  234. package/src/pages/pos/payment-success/index.tsx +75 -0
  235. package/src/provider/app-provider.tsx +56 -0
  236. package/src/provider/fallback/custom-error.tsx +26 -0
  237. package/src/provider/main-provider.tsx +41 -0
  238. package/src/provider/meta-provider.tsx +72 -0
  239. package/src/provider/modal-provider.tsx +48 -0
  240. package/src/provider/notification-provider.tsx +64 -0
  241. package/src/provider/pos-provider/close-session-provider/index.tsx +207 -0
  242. package/src/provider/pos-provider/customer-provider/index.tsx +30 -0
  243. package/src/provider/pos-provider/floor-provider/index.tsx +39 -0
  244. package/src/provider/pos-provider/index.ts +6 -0
  245. package/src/provider/pos-provider/kitchen-provider/index.tsx +30 -0
  246. package/src/provider/pos-provider/order-detail-provider/index.tsx +233 -0
  247. package/src/provider/pos-provider/order-provider/index.tsx +803 -0
  248. package/src/provider/pos-provider/payment-provider/index.tsx +30 -0
  249. package/src/provider/pos-provider/product-provider/index.tsx +30 -0
  250. package/src/provider/pos-provider/sale-provider/index.tsx +168 -0
  251. package/src/provider/pos-provider/session-provider/index.tsx +30 -0
  252. package/src/provider/pos-provider/socket-provider/index.tsx +118 -0
  253. package/src/provider/pos-provider/table-provider/index.tsx +30 -0
  254. package/src/provider/public-provider.tsx +104 -0
  255. package/src/provider/use-session-provider.ts +79 -0
  256. package/src/router/app-router.tsx +38 -0
  257. package/src/router/index.tsx +26 -0
  258. package/src/router/pos-route.tsx +75 -0
  259. package/src/router/protected-route.tsx +16 -0
  260. package/src/router/public-route.tsx +11 -0
  261. package/src/store/header/index.ts +21 -0
  262. package/src/store/session/index.ts +25 -0
  263. package/src/store/states/cart.state.ts +135 -0
  264. package/src/store/states/close-session.state.ts +20 -0
  265. package/src/store/states/customer.state.ts +19 -0
  266. package/src/store/states/payment.state.ts +14 -0
  267. package/src/styles/base.css +212 -0
  268. package/src/styles/common.css +65 -0
  269. package/src/styles/lib/tailwind.css +0 -0
  270. package/src/styles/lib/tailwind.theme.css +34 -0
  271. package/src/styles/loading.css +72 -0
  272. package/src/styles/main.css +5 -0
  273. package/src/styles/refreshing-content.css +55 -0
  274. package/src/styles/text.css +44 -0
  275. package/src/types/account.ts +61 -0
  276. package/src/types/branch.ts +6 -0
  277. package/src/types/category.ts +13 -0
  278. package/src/types/common.ts +4 -0
  279. package/src/types/customer.ts +52 -0
  280. package/src/types/dashboard.ts +18 -0
  281. package/src/types/edc.ts +37 -0
  282. package/src/types/export-e-invoice.ts +17 -0
  283. package/src/types/filter.d.ts +12 -0
  284. package/src/types/floor/floor.ts +16 -0
  285. package/src/types/floor.d.ts +36 -0
  286. package/src/types/index.ts +127 -0
  287. package/src/types/invoice.ts +32 -0
  288. package/src/types/modal.ts +15 -0
  289. package/src/types/notification.ts +12 -0
  290. package/src/types/order.ts +182 -0
  291. package/src/types/payment.ts +44 -0
  292. package/src/types/product/product.ts +47 -0
  293. package/src/types/product.ts +137 -0
  294. package/src/types/provider-info.d.ts +14 -0
  295. package/src/types/qr.d.ts +25 -0
  296. package/src/types/report.d.ts +43 -0
  297. package/src/types/sale.d.ts +68 -0
  298. package/src/types/search.d.ts +7 -0
  299. package/src/types/session.d.ts +30 -0
  300. package/src/types/shop-qr.d.ts +89 -0
  301. package/src/types/staff.d.ts +35 -0
  302. package/src/types/store.d.ts +25 -0
  303. package/src/types/table.d.ts +57 -0
  304. package/src/utils/close-session-ticket.tsx +129 -0
  305. package/src/utils/functions.ts +299 -0
  306. package/src/utils/i18n.ts +25 -0
  307. package/src/utils/pos/bill.ts +156 -0
  308. package/src/utils/pos/convert-order.ts +58 -0
  309. package/src/utils/pos/helpers.ts +48 -0
  310. package/src/utils/pos/session.ts +44 -0
  311. package/src/utils/storages/index.ts +1 -0
  312. package/src/utils/storages/local-storage.ts +72 -0
  313. package/tailwind.preset.ts +25 -0
  314. package/tsconfig.json +30 -0
  315. package/tsup.config.ts +30 -0
  316. package/dist/index.d.ts +0 -58
  317. package/dist/index.js +0 -145
  318. package/dist/index.js.map +0 -1
@@ -0,0 +1,98 @@
1
+ import { IcRefresh } from "@assets/icons/breadcrumbs-icon";
2
+ import { useQueryParams } from "@hooks/common/use-query-params";
3
+ import { cn } from "@utils/functions";
4
+ import { useEffect, useState } from "react";
5
+ import { ButtonIcon } from "./button-icon";
6
+
7
+ export type TTab = {
8
+ id: number | string;
9
+ name: string;
10
+ };
11
+
12
+ export const TabsLayout = ({
13
+ tabs,
14
+ onClick,
15
+ queryKey,
16
+ className,
17
+ activeValue,
18
+ rightButton,
19
+ alwaysShowButton,
20
+ }: {
21
+ tabs: TTab[];
22
+ queryKey: string;
23
+ activeValue?: number | string;
24
+ onClick?: (e: any) => void;
25
+ className?: string;
26
+ rightButton?: React.ReactNode;
27
+ alwaysShowButton?: React.ReactNode;
28
+ }) => {
29
+ const { getQuery, updateQuery } = useQueryParams();
30
+ const activeId = activeValue ?? getQuery(queryKey);
31
+
32
+ useEffect(() => {
33
+ const activeEl = document.getElementById(`${queryKey}${activeId}`);
34
+ if (activeEl) {
35
+ activeEl.scrollIntoView({
36
+ behavior: "smooth",
37
+ block: "nearest",
38
+ inline: "center",
39
+ });
40
+ }
41
+ }, [tabs]);
42
+
43
+ return (
44
+ <div
45
+ className={cn(
46
+ "flex justify-between items-center bg-white h-[60px] px-3 gap-2 w-full",
47
+ className
48
+ )}
49
+ >
50
+ <div className="flex overflow-x-auto flex-1 relative hide-scrollbar">
51
+ {tabs?.map((tab) => (
52
+ <div
53
+ key={tab.id}
54
+ onClick={() => {
55
+ !activeValue && updateQuery(queryKey, tab.id.toString());
56
+ onClick && onClick(tab);
57
+ }}
58
+ className={`cursor-pointer px-4 h-12 max-h-12 flex items-center transition-colors min-w-fit rounded-lg ${
59
+ activeId === tab.id.toString()
60
+ ? "bg-surface-primary text-text-active font-semibold"
61
+ : "font-medium text-gray-500 hover:text-gray-700 hover:border-gray-300"
62
+ }`}
63
+ id={`${queryKey}${tab.id}`}
64
+ >
65
+ {tab.name}
66
+ </div>
67
+ ))}
68
+ </div>
69
+ <div className="flex gap-2 items-center">
70
+ {tabs?.length > 0 && rightButton} {alwaysShowButton}
71
+ </div>
72
+ </div>
73
+ );
74
+ };
75
+
76
+ export const RefreshButton = ({ onRefresh }: { onRefresh: () => void }) => {
77
+ const [isRefresh, setIsRefresh] = useState(false);
78
+
79
+ setTimeout(() => {
80
+ if (isRefresh) {
81
+ setIsRefresh(false);
82
+ }
83
+ }, 3000);
84
+
85
+ return (
86
+ <ButtonIcon
87
+ onClick={() => {
88
+ setIsRefresh(true);
89
+ onRefresh();
90
+ }}
91
+ iconSrc={
92
+ <div className={`${isRefresh ? "spin" : ""}`}>
93
+ <IcRefresh />
94
+ </div>
95
+ }
96
+ />
97
+ );
98
+ };
@@ -0,0 +1,19 @@
1
+ import { LoadDataModel, useSale } from "@provider/pos-provider/sale-provider";
2
+
3
+ const useCheckSession = () => {
4
+ const { getData } = useSale();
5
+
6
+ const onCheckSession = async () => {
7
+ const currentPos = getData(LoadDataModel.POS_CONFIG)?.[0];
8
+
9
+ const isSessionOpened = currentPos.pos_session_state === "opened";
10
+
11
+ return isSessionOpened;
12
+ };
13
+
14
+ return {
15
+ onCheckSession,
16
+ };
17
+ };
18
+
19
+ export default useCheckSession;
@@ -0,0 +1,286 @@
1
+ import ButtonAction from "@components/common/button-action";
2
+ import Input from "@components/common/input";
3
+ import RadioGroup from "@components/common/input/radio-group";
4
+ import ModalDirection from "@components/common/modal/modal-direction";
5
+ import { zodResolver } from "@hookform/resolvers/zod";
6
+ import useGetQuery from "@hooks/common/use-get-query";
7
+ import { useCustomer } from "@provider/pos-provider/customer-provider";
8
+ import { useOrder } from "@provider/pos-provider/order-provider";
9
+ import { useSale } from "@provider/pos-provider/sale-provider";
10
+ import { useCustomerStore } from "@store/states/customer.state";
11
+ import { TCustomerPayload, TUpdateCustomerPayload } from "@type/customer";
12
+ import { filterObject, normalizeObject } from "@utils/functions";
13
+ import { useEffect, useMemo, useState } from "react";
14
+ import { useForm } from "react-hook-form";
15
+ import { useTranslation } from "react-i18next";
16
+ import { toast } from "react-toastify";
17
+ import z from "zod";
18
+ import AddressSelect, { TOption } from "./address-select";
19
+
20
+ type TProps = {
21
+ isOpen: boolean;
22
+ onClose: () => void;
23
+ };
24
+
25
+ const schema = z.object({
26
+ phone: z.string().min(1, "Số điện thoại là bắt buộc"),
27
+ name: z.string().min(1, "Tên khách hàng là bắt buộc"),
28
+ email: z.string(),
29
+ title: z.string().optional(),
30
+ street2: z.string().optional(),
31
+ birth_date: z.string().optional(),
32
+ });
33
+
34
+ export const AddCustomerDrawer = (props: TProps) => {
35
+ const { isOpen, onClose } = props;
36
+ const [isCreating, setIsCreating] = useState(false);
37
+ const [citySelected, setCitySelected] = useState<TOption>();
38
+ const [wardSelected, setWardSelected] = useState<TOption>();
39
+
40
+ const { t } = useTranslation();
41
+ const { createCustomer, updateCustomer } = useCustomer();
42
+ const { updateOrder } = useOrder();
43
+ const { queryResult } = useSale();
44
+ const { orderId } = useGetQuery();
45
+ const {
46
+ isEditMode,
47
+ customer: customerData,
48
+ setCustomer,
49
+ setIsEditMode,
50
+ } = useCustomerStore();
51
+ const { cities, setStateId, stateId, wards, partnerTitles } = useCustomer();
52
+ const customer = normalizeObject(customerData);
53
+
54
+ const cityMatched = useMemo(() => {
55
+ if (!customer?.state_id) return undefined;
56
+
57
+ return cities.find((city: any) => city.id === Number(customer?.state_id));
58
+ }, [cities, customer]);
59
+
60
+ const wardMatched = useMemo(() => {
61
+ if (!customer?.ward_id) return undefined;
62
+
63
+ return wards.find((ward: any) => ward.id === Number(customer?.ward_id));
64
+ }, [wards, customer]);
65
+
66
+ const {
67
+ handleSubmit,
68
+ register,
69
+ formState: { errors },
70
+ setError,
71
+ reset,
72
+ } = useForm({
73
+ mode: "onChange",
74
+ resolver: zodResolver(schema),
75
+ });
76
+
77
+ const clearData = () => {
78
+ setCitySelected(undefined);
79
+ setWardSelected(undefined);
80
+ setError("name", { message: "" });
81
+ setError("phone", { message: "" });
82
+ reset({
83
+ name: "",
84
+ email: "",
85
+ phone: "",
86
+ title: "",
87
+ street2: "",
88
+ birth_date: "",
89
+ });
90
+ setIsEditMode(false);
91
+ setCustomer(null);
92
+ setStateId("");
93
+ };
94
+
95
+ const onSubmit = handleSubmit(async (data) => {
96
+ try {
97
+ setIsCreating(true);
98
+
99
+ const payload = filterObject({
100
+ name: data.name,
101
+ phone: data.phone,
102
+ email: data.email,
103
+ country_id: 241,
104
+ birth_date: data.birth_date,
105
+ state_id: Number(stateId),
106
+ ward_id: Number(wardSelected?.id),
107
+ street2: data.street2,
108
+ title: Number(data.title),
109
+ });
110
+
111
+ if (isEditMode) {
112
+ await updateCustomer.mutateAsync({
113
+ ...payload,
114
+ id: Number(customer?.id),
115
+ } as TUpdateCustomerPayload);
116
+ } else {
117
+ const customerId = await createCustomer({
118
+ payload: payload as TCustomerPayload,
119
+ });
120
+
121
+ await updateOrder.mutateAsync({
122
+ orderId: Number(orderId),
123
+ partnerId: customerId,
124
+ });
125
+ }
126
+
127
+ await Promise.all([queryResult.refetch()]);
128
+
129
+ clearData();
130
+ onClose();
131
+
132
+ if (isEditMode) {
133
+ toast.success(t("updateCustomerSuccess"));
134
+ } else {
135
+ toast.success(t("addCustomerSuccess"));
136
+ }
137
+ } catch (error: any) {
138
+ toast.error(error?.message);
139
+ } finally {
140
+ setIsCreating(false);
141
+ }
142
+ });
143
+
144
+ useEffect(() => {
145
+ if (!isOpen) {
146
+ clearData();
147
+ return;
148
+ }
149
+
150
+ if (isEditMode && customer) {
151
+ reset({
152
+ name: customer.name,
153
+ phone: customer.phone,
154
+ email: typeof customer.email === "string" ? customer.email : "",
155
+ title: String(customer.title),
156
+ birth_date: customer.birth_date,
157
+ street2: customer.street2,
158
+ });
159
+ setStateId(String(customer.state_id));
160
+ setCitySelected({
161
+ id: Number(customer.state_id),
162
+ name: cityMatched?.name,
163
+ });
164
+ setWardSelected({
165
+ id: Number(customer.ward_id),
166
+ name: wardMatched?.name,
167
+ });
168
+ } else {
169
+ clearData();
170
+ }
171
+ }, [isOpen, wardMatched]);
172
+
173
+ return (
174
+ <ModalDirection
175
+ isOpen={isOpen}
176
+ onClose={onClose}
177
+ direction="right"
178
+ className="max-w-md bg-white font-medium"
179
+ title="Thông tin khách hàng"
180
+ >
181
+ <form onSubmit={onSubmit} className="flex flex-col h-full">
182
+ <div className="flex-1 overflow-y-auto pr-2 flex flex-col gap-4">
183
+ <Input
184
+ title="Họ và tên"
185
+ register={register("name")}
186
+ placeholder="Nhập họ và tên"
187
+ isRequired
188
+ name="name"
189
+ errorMessage={errors.name?.message}
190
+ inputClassName="p-3"
191
+ />
192
+
193
+ <Input
194
+ title="Số điện thoại"
195
+ register={register("phone")}
196
+ name="phone"
197
+ isRequired
198
+ placeholder="Nhập số điện thoại"
199
+ errorMessage={errors.phone?.message}
200
+ inputClassName="p-3"
201
+ />
202
+
203
+ <Input
204
+ title="Email"
205
+ register={register("email")}
206
+ placeholder="Nhập email"
207
+ name="email"
208
+ errorMessage={errors.email?.message}
209
+ inputClassName="p-3"
210
+ />
211
+
212
+ <Input
213
+ title="Ngày sinh"
214
+ register={register("birth_date")}
215
+ placeholder="ngày/tháng/năm"
216
+ name="birth_date"
217
+ errorMessage={errors.birth_date?.message}
218
+ inputClassName="p-3"
219
+ type="date"
220
+ />
221
+
222
+ <RadioGroup
223
+ title="Giới tính"
224
+ name="title"
225
+ register={register("title")}
226
+ options={partnerTitles}
227
+ errorMessage={errors.title?.message as string}
228
+ direction="row"
229
+ />
230
+
231
+ <p className="font-semibold text-text-primary leading-6 tracking-[-0.24px]">
232
+ Địa chỉ
233
+ </p>
234
+
235
+ <AddressSelect
236
+ label="Tỉnh/Thành phố"
237
+ options={cities}
238
+ onSelect={(opt) => {
239
+ setCitySelected(opt);
240
+ setStateId(opt.id.toString());
241
+ }}
242
+ selectedValue={citySelected}
243
+ placeholder="Nhập tình/thành phố"
244
+ />
245
+
246
+ {stateId && (
247
+ <AddressSelect
248
+ label="Phường/xã"
249
+ options={wards}
250
+ onSelect={(opt) => setWardSelected(opt)}
251
+ selectedValue={wardSelected}
252
+ placeholder="Nhập phường/xã"
253
+ />
254
+ )}
255
+
256
+ <Input
257
+ title="Địa chỉ cụ thể"
258
+ register={register("street2")}
259
+ placeholder="Nhập địa chỉ cụ thể"
260
+ name="address"
261
+ errorMessage={errors.street2?.message}
262
+ inputClassName="p-3"
263
+ />
264
+ </div>
265
+
266
+ <div className="flex items-center gap-2 pt-3">
267
+ <ButtonAction
268
+ onClick={onClose}
269
+ text={t("cancel")}
270
+ className="border border-stroke-disabled flex-1"
271
+ />
272
+
273
+ <ButtonAction
274
+ type="submit"
275
+ disabled={isCreating}
276
+ className="flex-1 min-w-fit button-primary button disabled:bg-primary/50"
277
+ >
278
+ <p className="text-sm font-medium leading-5 tracking-[-0.12px] text-text-btn">
279
+ {isEditMode ? "Cập nhật" : "Lưu"}
280
+ </p>
281
+ </ButtonAction>
282
+ </div>
283
+ </form>
284
+ </ModalDirection>
285
+ );
286
+ };
@@ -0,0 +1,51 @@
1
+ import DropdownSelector from "@components/common/dropdown";
2
+
3
+ export type TOption = {
4
+ id: number;
5
+ name: string;
6
+ };
7
+
8
+ type TProps = {
9
+ label: string;
10
+ options: TOption[];
11
+ onSelect: (value: TOption) => void;
12
+ selectedValue: TOption | undefined;
13
+ placeholder?: string;
14
+ };
15
+
16
+ const AddressSelect = ({
17
+ label,
18
+ options,
19
+ onSelect,
20
+ selectedValue,
21
+ placeholder,
22
+ }: TProps) => {
23
+ return (
24
+ <div className="flex flex-col gap-[6px]">
25
+ <p className="text-sm font-medium text-text-primary leading-5 tracking-[-0.12px]">
26
+ {label}
27
+ </p>
28
+
29
+ <DropdownSelector<TOption>
30
+ options={options}
31
+ selectedValue={{
32
+ label: selectedValue?.name,
33
+ }}
34
+ onSelect={onSelect}
35
+ renderItem={(item) => {
36
+ return (
37
+ <div className="px-3 py-2 text-sm hover:bg-gray-50">
38
+ {item?.name}
39
+ </div>
40
+ );
41
+ }}
42
+ className="w-full"
43
+ buttonClassName="text-sm font-medium text-text-primary leading-5 tracking-[-0.12px] p-3"
44
+ dropdownClassName="w-full top-[calc(100%_+_4px)] flex flex-col gap-1 !max-h-[200px] overflow-y-auto !min-h-[200px]"
45
+ placeholder={placeholder}
46
+ />
47
+ </div>
48
+ );
49
+ };
50
+
51
+ export default AddressSelect;
@@ -0,0 +1,177 @@
1
+ import { IcSearch } from "@assets/icons/breadcrumbs-icon";
2
+ import { IcMore } from "@assets/icons/common";
3
+ import { IcCustomer, IcUserPlus } from "@assets/icons/user-icon";
4
+ import ModalDirection from "@components/common/modal/modal-direction";
5
+ import { useOrder } from "@provider/pos-provider/order-provider";
6
+ import { LoadDataModel, useSale } from "@provider/pos-provider/sale-provider";
7
+ import { useCustomerStore } from "@store/states/customer.state";
8
+ import { TCustomer } from "@type/customer";
9
+ import React, { useEffect, useState } from "react";
10
+ import { useTranslation } from "react-i18next";
11
+ import { useDebounce } from "use-debounce";
12
+ import CustomerNoData from "./customer-no-data";
13
+
14
+ type TProps = {
15
+ isOpen: boolean;
16
+ onClose: () => void;
17
+ onOpenAddCustomerModal: () => void;
18
+ };
19
+
20
+ export const ChooseCustomerDrawer = (props: TProps) => {
21
+ const { isOpen, onClose, onOpenAddCustomerModal } = props;
22
+ const { t } = useTranslation();
23
+ const { getData } = useSale();
24
+ const { onUpdateCustomer } = useOrder();
25
+ const [customerPhoneNumber, setCustomerPhoneNumber] = useState<string>("");
26
+ const [customerSearchValue] = useDebounce(customerPhoneNumber, 700);
27
+
28
+ const customers = getData(LoadDataModel.RES_PARTNER) as TCustomer[];
29
+ const [filteredCustomers, setFilteredCustomers] = useState<TCustomer[]>(
30
+ customers || []
31
+ );
32
+
33
+ const handleCustomerSelect = (customer: TCustomer) => {
34
+ onUpdateCustomer({
35
+ customer,
36
+ onSuccess: () => {
37
+ setCustomerPhoneNumber("");
38
+ onClose();
39
+ },
40
+ });
41
+ };
42
+
43
+ const onSearch = async (e: React.ChangeEvent<HTMLInputElement>) => {
44
+ setCustomerPhoneNumber(e.target.value);
45
+ };
46
+
47
+ const handleClose = () => {
48
+ setCustomerPhoneNumber("");
49
+ onClose();
50
+ };
51
+
52
+ useEffect(() => {
53
+ if (!customers || !customers.length) return;
54
+
55
+ const lower = customerSearchValue.toLowerCase();
56
+
57
+ const filteredCustomers = customers?.filter((customer) => {
58
+ return (
59
+ customer.phone?.toString()?.includes(lower) ||
60
+ customer.name?.toString()?.toLowerCase()?.includes(lower)
61
+ );
62
+ });
63
+
64
+ setFilteredCustomers(filteredCustomers || []);
65
+ }, [customerSearchValue, customers]);
66
+
67
+ return (
68
+ <>
69
+ <ModalDirection
70
+ isOpen={isOpen}
71
+ onClose={handleClose}
72
+ direction="right"
73
+ title={t("choose_customer")}
74
+ >
75
+ <div className="flex flex-col h-full gap-6">
76
+ <div className="flex items-center justify-center gap-3">
77
+ <div
78
+ style={{
79
+ boxShadow: "0 16px 32px -12px rgba(88, 92, 95, 0.10);",
80
+ }}
81
+ className="flex w-full border h-11 border-stroke-default rounded-lg items-center gap-2 p-2"
82
+ >
83
+ <IcSearch />
84
+
85
+ <input
86
+ value={customerPhoneNumber}
87
+ onChange={(e) => onSearch(e)}
88
+ placeholder={t("search...")}
89
+ className="outline-none flex-1 text-sm leading-5 tracking-[-0.12px] text-text-primary placeholder:text-tertiary"
90
+ />
91
+ </div>
92
+
93
+ <button
94
+ onClick={onOpenAddCustomerModal}
95
+ className="h-11 px-3 py-2 bg-primary text-text-btn rounded-lg flex items-center justify-center gap-2"
96
+ >
97
+ <IcUserPlus />
98
+ <span className="text-sm font-medium leading-5 tracking-[-0.12px]">
99
+ {t("add")}
100
+ </span>
101
+ </button>
102
+ </div>
103
+
104
+ <div className="flex-1 overflow-y-auto space-y-3 pr-2">
105
+ {filteredCustomers && filteredCustomers.length > 0 ? (
106
+ filteredCustomers.map((customer: TCustomer) => (
107
+ <CustomerItem
108
+ key={customer.id}
109
+ customer={customer}
110
+ onCustomerSelect={handleCustomerSelect}
111
+ onOpenAddCustomerModal={onOpenAddCustomerModal}
112
+ />
113
+ ))
114
+ ) : customerPhoneNumber !== "" ? (
115
+ <CustomerNoData />
116
+ ) : (
117
+ <></>
118
+ )}
119
+ </div>
120
+ </div>
121
+ </ModalDirection>
122
+ </>
123
+ );
124
+ };
125
+
126
+ const CustomerItem = ({
127
+ customer,
128
+ onCustomerSelect,
129
+ onOpenAddCustomerModal,
130
+ }: {
131
+ customer: TCustomer;
132
+ onCustomerSelect: (customer: TCustomer) => void;
133
+ onOpenAddCustomerModal: () => void;
134
+ }) => {
135
+ const { setCustomer, setIsEditMode } = useCustomerStore();
136
+
137
+ const onEditCustomer = () => {
138
+ onOpenAddCustomerModal();
139
+ setCustomer(customer);
140
+ setIsEditMode(true);
141
+ };
142
+
143
+ return (
144
+ <div
145
+ key={customer.id}
146
+ onClick={() => onCustomerSelect(customer)}
147
+ className="flex items-center justify-between gap-3 py-4 px-3 hover:bg-gray-50 border-stroke-default rounded-lg cursor-pointer border"
148
+ >
149
+ <div className="flex items-center space-x-3">
150
+ <div className="p-[10px] bg-brand-50 rounded-full flex items-center justify-center">
151
+ <IcCustomer />
152
+ </div>
153
+ <div className="flex flex-col gap-[2px]">
154
+ <p className="text-sm font-semibold leading-5 text-text-primary tracking-[-0.12px]">
155
+ {customer.name}
156
+ </p>
157
+ <p className="text-xs leading-4 text-text-secondary">
158
+ {customer.phone}
159
+ </p>
160
+ <p className="text-xs leading-4 text-text-secondary">
161
+ {customer.email}
162
+ </p>
163
+ </div>
164
+ </div>
165
+
166
+ <button
167
+ onClick={(e) => {
168
+ e.stopPropagation();
169
+ onEditCustomer();
170
+ }}
171
+ className="p-2 px-4 hover:bg-gray-200 rounded"
172
+ >
173
+ <IcMore />
174
+ </button>
175
+ </div>
176
+ );
177
+ };