From b6ccde2c747b0a0c558e2a2bd2a77ebb0df9cada Mon Sep 17 00:00:00 2001 From: TuanVT Date: Sat, 23 May 2026 09:48:07 +0700 Subject: [PATCH] first commit --- .yarnrc.yml | 1 + app/globals.css | 26 - app/page.tsx | 65 -- eslint.config.mjs | 6 + next.config.ts | 4 +- package.json | 16 +- public/static/anim/loading_screen.json | 620 ++++++++++++++++++ public/static/images/avatar-default.png | Bin 0 -> 49762 bytes public/static/images/background_auth.jpg | Bin 0 -> 73315 bytes public/static/images/box-remove.svg | 8 + public/static/images/emptyFile.svg | 39 ++ public/static/images/empty_table.png | Bin 0 -> 26881 bytes public/static/images/full-screen.svg | 3 + public/static/images/icon-hamburger copy.svg | 5 + public/static/images/icon-hamburger.svg | 5 + public/static/images/pause.svg | 4 + public/static/images/play-circle.svg | 3 + public/static/images/tick-circle.svg | 5 + public/static/images/warning-2.svg | 5 + src/app/auth/forgot-password/page.tsx | 15 + src/app/auth/login/page.tsx | 15 + src/app/consultation/page.tsx | 9 + src/app/dashboard/page.tsx | 15 + {app => src/app}/favicon.ico | Bin src/app/globals.css | 198 ++++++ {app => src/app}/layout.tsx | 17 +- src/app/page.tsx | 11 + src/app/patient/page.tsx | 14 + src/app/prescription/page.tsx | 9 + src/common/funcs/convertCoin.ts | 14 + src/common/funcs/delay.ts | 3 + src/common/funcs/localStorage.ts | 18 + src/common/funcs/optionConvert.ts | 168 +++++ src/common/funcs/selectData.ts | 99 +++ src/common/funcs/toast.ts | 52 ++ src/common/funcs/validate.ts | 14 + src/common/hooks/useStyleClass.ts | 14 + src/components/customs/DataWrapper.tsx | 64 ++ src/components/customs/StateActive.tsx | 64 ++ src/components/customs/custom-button.tsx | 123 ++++ src/components/customs/custom-dialog.tsx | 70 ++ src/components/customs/custom-input.tsx | 106 +++ src/components/customs/custom-loading.tsx | 54 ++ src/components/customs/custom-pagination.tsx | 205 ++++++ src/components/customs/custom-popup.tsx | 89 +++ src/components/customs/custom-portal.tsx | 52 ++ src/components/customs/custom-search.tsx | 56 ++ src/components/customs/custom-table.tsx | 241 +++++++ src/components/customs/custom-toast.tsx | 42 ++ src/components/customs/slider-props.tsx | 122 ++++ .../layouts/BaseLayout/BaseLayout.tsx | 71 ++ .../BaseLayout/componets/Header/Header.tsx | 101 +++ .../BaseLayout/componets/Header/index.ts | 1 + .../componets/Header/interface/index.ts | 4 + .../componets/MenuProfile/MenuProfile.tsx | 120 ++++ .../BaseLayout/componets/MenuProfile/index.ts | 1 + .../componets/MenuProfile/interface/index.ts | 3 + .../BaseLayout/componets/Navbar/Navbar.tsx | 71 ++ .../BaseLayout/componets/Navbar/index.ts | 1 + src/components/layouts/BaseLayout/index.ts | 1 + .../layouts/BaseLayout/interface/index.ts | 12 + .../layouts/ChartWrapper/ChartWrapper.tsx | 38 ++ src/components/layouts/ChartWrapper/index.ts | 0 .../layouts/ChartWrapper/interface/index.ts | 5 + .../layouts/GridColumn/GridColumn.tsx | 69 ++ src/components/layouts/GridColumn/index.ts | 1 + .../layouts/GridColumn/interface/index.ts | 14 + .../layouts/LayoutAuth‎/LayoutAuth‎.tsx | 40 ++ src/components/layouts/LayoutAuth‎/index.ts | 1 + .../layouts/LayoutPages/LayoutPages.tsx | 57 ++ src/components/layouts/LayoutPages/index.ts | 1 + .../layouts/LayoutPages/interface/index.ts | 8 + .../page/Home/MainPageHome/MainPageHome.tsx | 8 + .../page/Home/MainPageHome/index.ts | 1 + .../MainForgotPassword/MainForgotPassword.tsx | 66 ++ .../components/FormEmail/FormEmail.tsx | 126 ++++ .../components/FormEmail/index.ts | 1 + .../components/FormOTP/FormOTP.tsx | 241 +++++++ .../components/FormOTP/index.ts | 1 + .../components/FormPassword/FormPassword.tsx | 169 +++++ .../components/FormPassword/index.ts | 0 .../auth/MainForgotPassword/context/index.ts | 16 + .../page/auth/MainForgotPassword/index.ts | 0 .../MainForgotPassword/interface/index.ts | 11 + .../page/auth/MainLogin/MainLogin.tsx | 269 ++++++++ src/components/page/auth/MainLogin/index.ts | 1 + .../protected/LoadingTopBar/LoadingTopBar.tsx | 35 + .../protected/LoadingTopBar/index.ts | 1 + .../protected/RequiredAuth/index.tsx | 30 + .../protected/RequiredLogout/index.tsx | 34 + .../protected/SplashScreen/SplashScreen.tsx | 111 ++++ .../protected/SplashScreen/index.ts | 1 + .../utils/FormCustom/FormCustom.tsx | 70 ++ .../components/InputForm/InputForm.tsx | 401 +++++++++++ .../FormCustom/components/InputForm/index.ts | 1 + .../components/InputForm/interface/index.ts | 35 + .../components/SelectForm/SelectForm.tsx | 175 +++++ .../FormCustom/components/SelectForm/index.ts | 1 + .../components/SelectForm/interface/index.ts | 15 + .../components/SelectMany/SelectMany.tsx | 261 ++++++++ .../FormCustom/components/SelectMany/index.ts | 1 + .../components/SelectMany/interface/index.ts | 27 + .../components/SelectSingle/SelectSingle.tsx | 206 ++++++ .../components/SelectSingle/index.ts | 1 + .../SelectSingle/interface/index.ts | 17 + .../components/TextArea/TextArea.tsx | 192 ++++++ .../FormCustom/components/TextArea/index.ts | 1 + .../components/TextArea/interface/index.ts | 17 + .../utils/FormCustom/contexts/index.ts | 23 + src/components/utils/FormCustom/index.ts | 1 + .../utils/FormCustom/interface/index.ts | 8 + src/components/utils/IconRound.tsx | 86 +++ .../PositionContainer/PositionContainer.tsx | 116 ++++ .../components/ClickOpen/ClickOpen.tsx | 36 + .../components/ClickOpen/index.ts | 1 + .../components/ClickOpen/interface/index.ts | 5 + .../utils/PositionContainer/index.ts | 0 .../PositionContainer/interface/index.ts | 11 + .../utils/UploadAvatar/UploadAvatar.tsx | 132 ++++ src/components/utils/UploadAvatar/index.ts | 1 + .../utils/UploadAvatar/interface/index.ts | 8 + src/constant/config/enum.ts | 12 + src/constant/config/index.ts | 57 ++ src/constant/images/icons.ts | 23 + src/contexts/AppProvider.tsx | 47 ++ src/redux/reducer/auth.ts | 45 ++ src/redux/reducer/site.ts | 28 + src/redux/reducer/user.ts | 31 + src/redux/store.ts | 21 + src/services/accountServices.ts | 50 ++ src/services/authServices.ts | 25 + src/services/index.ts | 197 ++++++ tsconfig.json | 2 +- yarn.lock | 371 ++++++++++- 134 files changed, 7178 insertions(+), 110 deletions(-) create mode 100644 .yarnrc.yml delete mode 100644 app/globals.css delete mode 100644 app/page.tsx create mode 100644 public/static/anim/loading_screen.json create mode 100644 public/static/images/avatar-default.png create mode 100644 public/static/images/background_auth.jpg create mode 100644 public/static/images/box-remove.svg create mode 100644 public/static/images/emptyFile.svg create mode 100644 public/static/images/empty_table.png create mode 100644 public/static/images/full-screen.svg create mode 100644 public/static/images/icon-hamburger copy.svg create mode 100644 public/static/images/icon-hamburger.svg create mode 100644 public/static/images/pause.svg create mode 100644 public/static/images/play-circle.svg create mode 100644 public/static/images/tick-circle.svg create mode 100644 public/static/images/warning-2.svg create mode 100644 src/app/auth/forgot-password/page.tsx create mode 100644 src/app/auth/login/page.tsx create mode 100644 src/app/consultation/page.tsx create mode 100644 src/app/dashboard/page.tsx rename {app => src/app}/favicon.ico (100%) create mode 100644 src/app/globals.css rename {app => src/app}/layout.tsx (55%) create mode 100644 src/app/page.tsx create mode 100644 src/app/patient/page.tsx create mode 100644 src/app/prescription/page.tsx create mode 100644 src/common/funcs/convertCoin.ts create mode 100644 src/common/funcs/delay.ts create mode 100644 src/common/funcs/localStorage.ts create mode 100644 src/common/funcs/optionConvert.ts create mode 100644 src/common/funcs/selectData.ts create mode 100644 src/common/funcs/toast.ts create mode 100644 src/common/funcs/validate.ts create mode 100644 src/common/hooks/useStyleClass.ts create mode 100644 src/components/customs/DataWrapper.tsx create mode 100644 src/components/customs/StateActive.tsx create mode 100644 src/components/customs/custom-button.tsx create mode 100644 src/components/customs/custom-dialog.tsx create mode 100644 src/components/customs/custom-input.tsx create mode 100644 src/components/customs/custom-loading.tsx create mode 100644 src/components/customs/custom-pagination.tsx create mode 100644 src/components/customs/custom-popup.tsx create mode 100644 src/components/customs/custom-portal.tsx create mode 100644 src/components/customs/custom-search.tsx create mode 100644 src/components/customs/custom-table.tsx create mode 100644 src/components/customs/custom-toast.tsx create mode 100644 src/components/customs/slider-props.tsx create mode 100644 src/components/layouts/BaseLayout/BaseLayout.tsx create mode 100644 src/components/layouts/BaseLayout/componets/Header/Header.tsx create mode 100644 src/components/layouts/BaseLayout/componets/Header/index.ts create mode 100644 src/components/layouts/BaseLayout/componets/Header/interface/index.ts create mode 100644 src/components/layouts/BaseLayout/componets/MenuProfile/MenuProfile.tsx create mode 100644 src/components/layouts/BaseLayout/componets/MenuProfile/index.ts create mode 100644 src/components/layouts/BaseLayout/componets/MenuProfile/interface/index.ts create mode 100644 src/components/layouts/BaseLayout/componets/Navbar/Navbar.tsx create mode 100644 src/components/layouts/BaseLayout/componets/Navbar/index.ts create mode 100644 src/components/layouts/BaseLayout/index.ts create mode 100644 src/components/layouts/BaseLayout/interface/index.ts create mode 100644 src/components/layouts/ChartWrapper/ChartWrapper.tsx create mode 100644 src/components/layouts/ChartWrapper/index.ts create mode 100644 src/components/layouts/ChartWrapper/interface/index.ts create mode 100644 src/components/layouts/GridColumn/GridColumn.tsx create mode 100644 src/components/layouts/GridColumn/index.ts create mode 100644 src/components/layouts/GridColumn/interface/index.ts create mode 100644 src/components/layouts/LayoutAuth‎/LayoutAuth‎.tsx create mode 100644 src/components/layouts/LayoutAuth‎/index.ts create mode 100644 src/components/layouts/LayoutPages/LayoutPages.tsx create mode 100644 src/components/layouts/LayoutPages/index.ts create mode 100644 src/components/layouts/LayoutPages/interface/index.ts create mode 100644 src/components/page/Home/MainPageHome/MainPageHome.tsx create mode 100644 src/components/page/Home/MainPageHome/index.ts create mode 100644 src/components/page/auth/MainForgotPassword/MainForgotPassword.tsx create mode 100644 src/components/page/auth/MainForgotPassword/components/FormEmail/FormEmail.tsx create mode 100644 src/components/page/auth/MainForgotPassword/components/FormEmail/index.ts create mode 100644 src/components/page/auth/MainForgotPassword/components/FormOTP/FormOTP.tsx create mode 100644 src/components/page/auth/MainForgotPassword/components/FormOTP/index.ts create mode 100644 src/components/page/auth/MainForgotPassword/components/FormPassword/FormPassword.tsx create mode 100644 src/components/page/auth/MainForgotPassword/components/FormPassword/index.ts create mode 100644 src/components/page/auth/MainForgotPassword/context/index.ts create mode 100644 src/components/page/auth/MainForgotPassword/index.ts create mode 100644 src/components/page/auth/MainForgotPassword/interface/index.ts create mode 100644 src/components/page/auth/MainLogin/MainLogin.tsx create mode 100644 src/components/page/auth/MainLogin/index.ts create mode 100644 src/components/protected/LoadingTopBar/LoadingTopBar.tsx create mode 100644 src/components/protected/LoadingTopBar/index.ts create mode 100644 src/components/protected/RequiredAuth/index.tsx create mode 100644 src/components/protected/RequiredLogout/index.tsx create mode 100644 src/components/protected/SplashScreen/SplashScreen.tsx create mode 100644 src/components/protected/SplashScreen/index.ts create mode 100644 src/components/utils/FormCustom/FormCustom.tsx create mode 100644 src/components/utils/FormCustom/components/InputForm/InputForm.tsx create mode 100644 src/components/utils/FormCustom/components/InputForm/index.ts create mode 100644 src/components/utils/FormCustom/components/InputForm/interface/index.ts create mode 100644 src/components/utils/FormCustom/components/SelectForm/SelectForm.tsx create mode 100644 src/components/utils/FormCustom/components/SelectForm/index.ts create mode 100644 src/components/utils/FormCustom/components/SelectForm/interface/index.ts create mode 100644 src/components/utils/FormCustom/components/SelectMany/SelectMany.tsx create mode 100644 src/components/utils/FormCustom/components/SelectMany/index.ts create mode 100644 src/components/utils/FormCustom/components/SelectMany/interface/index.ts create mode 100644 src/components/utils/FormCustom/components/SelectSingle/SelectSingle.tsx create mode 100644 src/components/utils/FormCustom/components/SelectSingle/index.ts create mode 100644 src/components/utils/FormCustom/components/SelectSingle/interface/index.ts create mode 100644 src/components/utils/FormCustom/components/TextArea/TextArea.tsx create mode 100644 src/components/utils/FormCustom/components/TextArea/index.ts create mode 100644 src/components/utils/FormCustom/components/TextArea/interface/index.ts create mode 100644 src/components/utils/FormCustom/contexts/index.ts create mode 100644 src/components/utils/FormCustom/index.ts create mode 100644 src/components/utils/FormCustom/interface/index.ts create mode 100644 src/components/utils/IconRound.tsx create mode 100644 src/components/utils/PositionContainer/PositionContainer.tsx create mode 100644 src/components/utils/PositionContainer/components/ClickOpen/ClickOpen.tsx create mode 100644 src/components/utils/PositionContainer/components/ClickOpen/index.ts create mode 100644 src/components/utils/PositionContainer/components/ClickOpen/interface/index.ts create mode 100644 src/components/utils/PositionContainer/index.ts create mode 100644 src/components/utils/PositionContainer/interface/index.ts create mode 100644 src/components/utils/UploadAvatar/UploadAvatar.tsx create mode 100644 src/components/utils/UploadAvatar/index.ts create mode 100644 src/components/utils/UploadAvatar/interface/index.ts create mode 100644 src/constant/config/enum.ts create mode 100644 src/constant/config/index.ts create mode 100644 src/constant/images/icons.ts create mode 100644 src/contexts/AppProvider.tsx create mode 100644 src/redux/reducer/auth.ts create mode 100644 src/redux/reducer/site.ts create mode 100644 src/redux/reducer/user.ts create mode 100644 src/redux/store.ts create mode 100644 src/services/accountServices.ts create mode 100644 src/services/authServices.ts create mode 100644 src/services/index.ts diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..8b757b2 --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules \ No newline at end of file diff --git a/app/globals.css b/app/globals.css deleted file mode 100644 index a2dc41e..0000000 --- a/app/globals.css +++ /dev/null @@ -1,26 +0,0 @@ -@import "tailwindcss"; - -:root { - --background: #ffffff; - --foreground: #171717; -} - -@theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } -} - -body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; -} diff --git a/app/page.tsx b/app/page.tsx deleted file mode 100644 index 3f36f7c..0000000 --- a/app/page.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import Image from "next/image"; - -export default function Home() { - return ( -
-
- Next.js logo -
-

- To get started, edit the page.tsx file. -

-

- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. -

-
-
- - Vercel logomark - Deploy Now - - - Documentation - -
-
-
- ); -} diff --git a/eslint.config.mjs b/eslint.config.mjs index 05e726d..a986ff8 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -13,6 +13,12 @@ const eslintConfig = defineConfig([ "build/**", "next-env.d.ts", ]), + { + rules: { + "no-var": "off", + "@typescript-eslint/no-explicit-any": "off", // tắt lỗi any + }, + }, ]); export default eslintConfig; diff --git a/next.config.ts b/next.config.ts index e9ffa30..078e9e1 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,9 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + turbopack: { + root: process.cwd(), + }, }; export default nextConfig; diff --git a/package.json b/package.json index 7ac131c..2ef519f 100644 --- a/package.json +++ b/package.json @@ -9,15 +9,29 @@ "lint": "eslint" }, "dependencies": { + "@reduxjs/toolkit": "^2.12.0", + "@tanstack/react-query": "^5.100.11", + "axios": "^1.16.1", + "clsx": "^2.1.1", + "lottie-react": "^2.4.1", + "lucide-react": "^1.16.0", + "md5": "^2.3.0", + "moment": "^2.30.1", "next": "16.2.6", + "nprogress": "^0.2.0", "react": "19.2.4", - "react-dom": "19.2.4" + "react-dom": "19.2.4", + "react-redux": "^9.3.0", + "react-toastify": "^11.1.0" }, "devDependencies": { "@tailwindcss/postcss": "^4", + "@types/md5": "^2", "@types/node": "^20", + "@types/nprogress": "^0", "@types/react": "^19", "@types/react-dom": "^19", + "babel-plugin-react-compiler": "1.0.0", "eslint": "^9", "eslint-config-next": "16.2.6", "tailwindcss": "^4", diff --git a/public/static/anim/loading_screen.json b/public/static/anim/loading_screen.json new file mode 100644 index 0000000..19615dd --- /dev/null +++ b/public/static/anim/loading_screen.json @@ -0,0 +1,620 @@ +{ + "v": "5.5.2", + "fr": 60, + "ip": 20, + "op": 90, + "w": 200, + "h": 200, + "nm": "Comp 1", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "Shape Layer 1", + "sr": 1, + "ks": { + "o": { "a": 0, "k": 100, "ix": 11 }, + "r": { "a": 0, "k": 0, "ix": 10 }, + "p": { "a": 0, "k": [99.944, 100.007, 0], "ix": 2 }, + "a": { "a": 0, "k": [-2.909, 2.363, 0], "ix": 1 }, + "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ty": "rc", + "d": 1, + "s": { "a": 0, "k": [33.3, 33.3], "ix": 2 }, + "p": { "a": 0, "k": [0, 0], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 4 }, + "nm": "Rectangle Path 1", + "mn": "ADBE Vector Shape - Rect", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [0.017408838459, 0.417201023476, 0.690012254902, 1], + "ix": 4 + }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [29.135, -29.73], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.333, 0.333], "y": [0, 0] }, + "t": 40, + "s": [100, 100] + }, + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.333, 0.333], "y": [0, 0] }, + "t": 55, + "s": [0, 0] + }, + { "t": 70, "s": [100, 100] } + ], + "ix": 3 + }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Rectangle 9", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "gr", + "it": [ + { + "ty": "rc", + "d": 1, + "s": { "a": 0, "k": [33.3, 33.3], "ix": 2 }, + "p": { "a": 0, "k": [0, 0], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 4 }, + "nm": "Rectangle Path 1", + "mn": "ADBE Vector Shape - Rect", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [0.017408838459, 0.417201023476, 0.690012254902, 1], + "ix": 4 + }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [29.135, 2.337], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.333, 0.333], "y": [0, 0] }, + "t": 35, + "s": [100, 100] + }, + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.333, 0.333], "y": [0, 0] }, + "t": 50, + "s": [0, 0] + }, + { "t": 65, "s": [100, 100] } + ], + "ix": 3 + }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Rectangle 8", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 2, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "gr", + "it": [ + { + "ty": "rc", + "d": 1, + "s": { "a": 0, "k": [33.3, 33.3], "ix": 2 }, + "p": { "a": 0, "k": [0, 0], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 4 }, + "nm": "Rectangle Path 1", + "mn": "ADBE Vector Shape - Rect", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [0.017408838459, 0.417201023476, 0.690012254902, 1], + "ix": 4 + }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [29.135, 34.455], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.333, 0.333], "y": [0, 0] }, + "t": 30, + "s": [100, 100] + }, + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.333, 0.333], "y": [0, 0] }, + "t": 45, + "s": [0, 0] + }, + { "t": 60, "s": [100, 100] } + ], + "ix": 3 + }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Rectangle 7", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 3, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "gr", + "it": [ + { + "ty": "rc", + "d": 1, + "s": { "a": 0, "k": [33.3, 33.3], "ix": 2 }, + "p": { "a": 0, "k": [0, 0], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 4 }, + "nm": "Rectangle Path 1", + "mn": "ADBE Vector Shape - Rect", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [0.017408838459, 0.417201023476, 0.690012254902, 1], + "ix": 4 + }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [-2.858, -29.73], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.333, 0.333], "y": [0, 0] }, + "t": 35, + "s": [100, 100] + }, + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.333, 0.333], "y": [0, 0] }, + "t": 50, + "s": [0, 0] + }, + { "t": 65, "s": [100, 100] } + ], + "ix": 3 + }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Rectangle 6", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 4, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "gr", + "it": [ + { + "ty": "rc", + "d": 1, + "s": { "a": 0, "k": [33.3, 33.3], "ix": 2 }, + "p": { "a": 0, "k": [0, 0], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 4 }, + "nm": "Rectangle Path 1", + "mn": "ADBE Vector Shape - Rect", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [0.017408838459, 0.417201023476, 0.690012254902, 1], + "ix": 4 + }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [-2.858, 2.337], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.333, 0.333], "y": [0, 0] }, + "t": 30, + "s": [100, 100] + }, + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.333, 0.333], "y": [0, 0] }, + "t": 45, + "s": [0, 0] + }, + { "t": 60, "s": [100, 100] } + ], + "ix": 3 + }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Rectangle 5", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 5, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "gr", + "it": [ + { + "ty": "rc", + "d": 1, + "s": { "a": 0, "k": [33.3, 33.3], "ix": 2 }, + "p": { "a": 0, "k": [0, 0], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 4 }, + "nm": "Rectangle Path 1", + "mn": "ADBE Vector Shape - Rect", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [0.017408838459, 0.417201023476, 0.690012254902, 1], + "ix": 4 + }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [-2.858, 34.455], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.333, 0.333], "y": [0, 0] }, + "t": 25, + "s": [100, 100] + }, + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.333, 0.333], "y": [0, 0] }, + "t": 40, + "s": [0, 0] + }, + { "t": 55, "s": [100, 100] } + ], + "ix": 3 + }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Rectangle 4", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 6, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "gr", + "it": [ + { + "ty": "rc", + "d": 1, + "s": { "a": 0, "k": [33.3, 33.3], "ix": 2 }, + "p": { "a": 0, "k": [0, 0], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 4 }, + "nm": "Rectangle Path 1", + "mn": "ADBE Vector Shape - Rect", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [0.017408838459, 0.417201023476, 0.690012254902, 1], + "ix": 4 + }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [-34.953, -29.73], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.333, 0.333], "y": [0, 0] }, + "t": 30, + "s": [100, 100] + }, + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.333, 0.333], "y": [0, 0] }, + "t": 45, + "s": [0, 0] + }, + { "t": 60, "s": [100, 100] } + ], + "ix": 3 + }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Rectangle 3", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 7, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "gr", + "it": [ + { + "ty": "rc", + "d": 1, + "s": { "a": 0, "k": [33.3, 33.3], "ix": 2 }, + "p": { "a": 0, "k": [0, 0], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 4 }, + "nm": "Rectangle Path 1", + "mn": "ADBE Vector Shape - Rect", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [0.017408838459, 0.417201023476, 0.690012254902, 1], + "ix": 4 + }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [-34.953, 2.337], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.333, 0.333], "y": [0, 0] }, + "t": 25, + "s": [100, 100] + }, + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.333, 0.333], "y": [0, 0] }, + "t": 40, + "s": [0, 0] + }, + { "t": 55, "s": [100, 100] } + ], + "ix": 3 + }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Rectangle 2", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 8, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "gr", + "it": [ + { + "ty": "rc", + "d": 1, + "s": { "a": 0, "k": [33.3, 33.3], "ix": 2 }, + "p": { "a": 0, "k": [0, 0], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 4 }, + "nm": "Rectangle Path 1", + "mn": "ADBE Vector Shape - Rect", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [0.017408838459, 0.417201023476, 0.690012254902, 1], + "ix": 4 + }, + "o": { "a": 0, "k": 100, "ix": 5 }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { "a": 0, "k": [-34.953, 34.455], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.333, 0.333], "y": [0, 0] }, + "t": 20, + "s": [100, 100] + }, + { + "i": { "x": [0.667, 0.667], "y": [1, 1] }, + "o": { "x": [0.333, 0.333], "y": [0, 0] }, + "t": 35, + "s": [0, 0] + }, + { "t": 50, "s": [100, 100] } + ], + "ix": 3 + }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "Rectangle 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 9, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 141, + "st": 0, + "bm": 0 + } + ], + "markers": [] +} diff --git a/public/static/images/avatar-default.png b/public/static/images/avatar-default.png new file mode 100644 index 0000000000000000000000000000000000000000..9334d43f37fa7bb5eeafc1de63a07fd9c4a49cc1 GIT binary patch literal 49762 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 z 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%0AC_1J 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 XPOD!GgGXWV00000NkvXXu0mjfk|CkE literal 0 HcmV?d00001 diff --git a/public/static/images/background_auth.jpg b/public/static/images/background_auth.jpg new file mode 100644 index 0000000000000000000000000000000000000000..33e3c78c950e94eda1c1f8ea50b7081aadaf012a GIT binary patch literal 73315 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 + + + + + + + diff --git a/public/static/images/emptyFile.svg b/public/static/images/emptyFile.svg new file mode 100644 index 0000000..345931d --- /dev/null +++ b/public/static/images/emptyFile.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/static/images/empty_table.png b/public/static/images/empty_table.png new file mode 100644 index 0000000000000000000000000000000000000000..cf8ddaf1bfc6da0f7139f4a49f2a45a3c829872c GIT binary patch literal 26881 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 R-(LZbAO#s!NF~@D_CIX<#2f$s literal 0 HcmV?d00001 diff --git a/public/static/images/full-screen.svg b/public/static/images/full-screen.svg new file mode 100644 index 0000000..95cf99c --- /dev/null +++ b/public/static/images/full-screen.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/static/images/icon-hamburger copy.svg b/public/static/images/icon-hamburger copy.svg new file mode 100644 index 0000000..5e7744e --- /dev/null +++ b/public/static/images/icon-hamburger copy.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/static/images/icon-hamburger.svg b/public/static/images/icon-hamburger.svg new file mode 100644 index 0000000..5e7744e --- /dev/null +++ b/public/static/images/icon-hamburger.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/static/images/pause.svg b/public/static/images/pause.svg new file mode 100644 index 0000000..c41d8ca --- /dev/null +++ b/public/static/images/pause.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/static/images/play-circle.svg b/public/static/images/play-circle.svg new file mode 100644 index 0000000..b3906d4 --- /dev/null +++ b/public/static/images/play-circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/static/images/tick-circle.svg b/public/static/images/tick-circle.svg new file mode 100644 index 0000000..869e8f1 --- /dev/null +++ b/public/static/images/tick-circle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/static/images/warning-2.svg b/public/static/images/warning-2.svg new file mode 100644 index 0000000..2ecb51f --- /dev/null +++ b/public/static/images/warning-2.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/app/auth/forgot-password/page.tsx b/src/app/auth/forgot-password/page.tsx new file mode 100644 index 0000000..a532b4b --- /dev/null +++ b/src/app/auth/forgot-password/page.tsx @@ -0,0 +1,15 @@ +"use client"; + +import LayoutAuth from "@/components/layouts/LayoutAuth‎"; +import MainForgotPassword from "@/components/page/auth/MainForgotPassword/MainForgotPassword"; +import React from "react"; + +const page = () => { + return ( + + + + ); +}; + +export default page; diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx new file mode 100644 index 0000000..018a548 --- /dev/null +++ b/src/app/auth/login/page.tsx @@ -0,0 +1,15 @@ +"use client"; + +import LayoutAuth from "@/components/layouts/LayoutAuth‎"; +import MainLogin from "@/components/page/auth/MainLogin"; +import React from "react"; + +const page = () => { + return ( + + + + ); +}; + +export default page; diff --git a/src/app/consultation/page.tsx b/src/app/consultation/page.tsx new file mode 100644 index 0000000..348f97d --- /dev/null +++ b/src/app/consultation/page.tsx @@ -0,0 +1,9 @@ +"use client"; +import BaseLayout from "@/components/layouts/BaseLayout"; +import React from "react"; + +const page = () => { + return Phiên khám; +}; + +export default page; diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx new file mode 100644 index 0000000..1a7fc19 --- /dev/null +++ b/src/app/dashboard/page.tsx @@ -0,0 +1,15 @@ +"use client"; + +import BaseLayout from "@/components/layouts/BaseLayout/BaseLayout"; +import MainPageHome from "@/components/page/Home/MainPageHome"; +import React from "react"; + +const page = () => { + return ( + + + + ); +}; + +export default page; diff --git a/app/favicon.ico b/src/app/favicon.ico similarity index 100% rename from app/favicon.ico rename to src/app/favicon.ico diff --git a/src/app/globals.css b/src/app/globals.css new file mode 100644 index 0000000..c6cb94a --- /dev/null +++ b/src/app/globals.css @@ -0,0 +1,198 @@ +@import "tailwindcss"; +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-sans); + --font-mono: var(--font-geist-mono); + --font-heading: var(--font-sans); + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --radius-sm: calc(var(--radius) * 0.6); + --radius-md: calc(var(--radius) * 0.8); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) * 1.4); + --radius-2xl: calc(var(--radius) * 1.8); + --radius-3xl: calc(var(--radius) * 2.2); + --radius-4xl: calc(var(--radius) * 2.6); +} + +:root { + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.87 0 0); + --chart-2: oklch(0.556 0 0); + --chart-3: oklch(0.439 0 0); + --chart-4: oklch(0.371 0 0); + --chart-5: oklch(0.269 0 0); + --radius: 0.625rem; + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.87 0 0); + --chart-2: oklch(0.556 0 0); + --chart-3: oklch(0.439 0 0); + --chart-4: oklch(0.371 0 0); + --chart-5: oklch(0.269 0 0); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } + html { + @apply font-sans; + } +} + +/* ===== JODIT CUSTOM UI ===== */ + +.editor-wrapper .jodit-container { + border: none !important; + background: transparent !important; + font-family: inherit !important; +} + +.editor-wrapper .jodit-workplace { + background: transparent !important; + min-height: 120px !important; +} + +.editor-wrapper .jodit-wysiwyg { + padding: 18px 20px !important; + color: #111827 !important; + font-size: 15px !important; + line-height: 1.7 !important; +} + +.editor-wrapper .jodit-toolbar__box { + border: none !important; + background: transparent !important; + padding: 14px 14px 10px 14px !important; +} + +.editor-wrapper .jodit-toolbar-button { + width: 34px !important; + height: 34px !important; + border-radius: 10px !important; + transition: all 0.2s ease !important; +} + +.editor-wrapper .jodit-toolbar-button:hover { + background: #e5e7eb !important; +} + +.editor-wrapper .jodit-toolbar-button__button { + min-width: 34px !important; + min-height: 34px !important; +} + +.editor-wrapper .jodit-status-bar { + display: none !important; +} + +.editor-wrapper .jodit-add-new-line { + display: none !important; +} + +.editor-wrapper .jodit-ui-group { + gap: 6px !important; +} + +.editor-wrapper .jodit-icon { + width: 18px !important; + height: 18px !important; +} + +.editor-wrapper .jodit-toolbar-button_active { + background: #dbeafe !important; +} + +.editor-wrapper .jodit-toolbar-button_active svg { + fill: #2563eb !important; +} + +.editor-wrapper .jodit-wysiwyg:focus { + outline: none !important; +} diff --git a/app/layout.tsx b/src/app/layout.tsx similarity index 55% rename from app/layout.tsx rename to src/app/layout.tsx index 976eb90..d6a36ff 100644 --- a/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,8 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; +import "react-toastify/dist/ReactToastify.css"; +import Providers from "@/contexts/AppProvider"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -23,11 +25,16 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - - {children} + // + // {children} + // + + + {children} + ); } diff --git a/src/app/page.tsx b/src/app/page.tsx new file mode 100644 index 0000000..1e187e4 --- /dev/null +++ b/src/app/page.tsx @@ -0,0 +1,11 @@ +import BaseLayout from "@/components/layouts/BaseLayout/BaseLayout"; +import MainPageHome from "@/components/page/Home/MainPageHome/MainPageHome"; +import Image from "next/image"; + +export default function Home() { + return ( + + + + ); +} diff --git a/src/app/patient/page.tsx b/src/app/patient/page.tsx new file mode 100644 index 0000000..4a75145 --- /dev/null +++ b/src/app/patient/page.tsx @@ -0,0 +1,14 @@ +"use client"; + +import BaseLayout from "@/components/layouts/BaseLayout"; +import React from "react"; + +const page = () => { + return ( +
+ Bệnh nhân +
+ ); +}; + +export default page; diff --git a/src/app/prescription/page.tsx b/src/app/prescription/page.tsx new file mode 100644 index 0000000..404852d --- /dev/null +++ b/src/app/prescription/page.tsx @@ -0,0 +1,9 @@ +"use client"; +import BaseLayout from "@/components/layouts/BaseLayout"; +import React from "react"; + +const page = () => { + return Đơn thuốc; +}; + +export default page; diff --git a/src/common/funcs/convertCoin.ts b/src/common/funcs/convertCoin.ts new file mode 100644 index 0000000..92008ad --- /dev/null +++ b/src/common/funcs/convertCoin.ts @@ -0,0 +1,14 @@ +// Hàm chuyển đổi số coin thành chuỗi định dạng tiền tệ +export const convertCoin = (coin: number | null) => { + return coin + ? coin.toLocaleString("de-DE", { maximumFractionDigits: 2 }) + : "0"; +}; + +// Hàm chuyển đổi chuỗi hoặc số thành giá trị số +export const price = (value: string | number): number => { + const numericValue = Number( + `${value}`.replace(/[^0-9,]/g, "").replace(",", "."), + ); + return isNaN(numericValue) ? 0 : numericValue; +}; diff --git a/src/common/funcs/delay.ts b/src/common/funcs/delay.ts new file mode 100644 index 0000000..91b78df --- /dev/null +++ b/src/common/funcs/delay.ts @@ -0,0 +1,3 @@ +export async function delay(duration: number) { + return await new Promise((resolve) => setTimeout(resolve, duration)); +} diff --git a/src/common/funcs/localStorage.ts b/src/common/funcs/localStorage.ts new file mode 100644 index 0000000..dfa9d07 --- /dev/null +++ b/src/common/funcs/localStorage.ts @@ -0,0 +1,18 @@ +export function getItemStorage(key: string) { + const value = localStorage.getItem(key)!; + if (value !== "undefined") { + return JSON.parse(value); + } else { + return null; + } +} + +export function setItemStorage(key: string, value: any) { + localStorage.setItem(key, JSON.stringify(value)); +} + +export function deleteItemStorage(...keys: Array) { + keys.forEach((key) => { + localStorage.removeItem(key); + }); +} diff --git a/src/common/funcs/optionConvert.ts b/src/common/funcs/optionConvert.ts new file mode 100644 index 0000000..0ad0a05 --- /dev/null +++ b/src/common/funcs/optionConvert.ts @@ -0,0 +1,168 @@ +import md5 from "md5"; +import moment from "moment"; + +export function removeVietnameseTones(str: string): string { + if (!str || typeof str !== "string") { + return ""; + } + return str + .trim() + .toLowerCase() + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, "") + .replace(/đ/g, "d") + .replace(/Đ/g, "D"); +} + +export function convertWeight(weight: number | null) { + if (!weight) { + return 0; + } + + return (weight / 1000).toLocaleString("vi-VN", { + minimumFractionDigits: 3, + maximumFractionDigits: 3, + }); +} + +export function obfuscateEmail(email: string) { + // Tách phần trước @ và phần tên miền + const [username, domain] = email.split("@"); + + // Giữ lại ký tự đầu tiên và cuối cùng của tên người dùng + const firstChar = username[0]; + const lastChar = username[username.length - 1]; + + // Tạo phần che giấu giữa + const middleHidden = "..."; + + // Tạo tên người dùng mới với phần che giấu + const newUsername = firstChar + middleHidden + lastChar; + + // Kết hợp với tên miền để tạo email đã che giấu + const obfuscatedEmail = newUsername + "@" + domain; + + return obfuscatedEmail; +} + +export default function fancyTimeFormat(duration: number) { + // Hours, minutes and seconds + var hrs = ~~(duration / 3600); + var mins = ~~((duration % 3600) / 60); + var secs = ~~duration % 60; + + // Output like "1:01" or "4:03:59" or "123:03:59" + var ret = ""; + + if (hrs > 0) { + ret += "" + hrs + ":" + (mins < 10 ? "0" : ""); + } + + ret += "" + mins + ":" + (secs < 10 ? "0" : ""); + ret += "" + secs; + return ret; +} + +export function getKeyCert(): { + time: string; + keyCert: string; +} { + const key: string = process.env.NEXT_PUBLIC_KEY_CERT!; + const time = moment(new Date()).format("MM/DD/YYYY HH:mm:ss"); + return { + time: time, + keyCert: md5(`${key}${time}`), + }; +} + +export function getTextAddress(detailAddress: any, address?: string): string { + if ( + !detailAddress?.province && + !detailAddress?.district && + !detailAddress?.town && + !address + ) { + return "---"; + } + + const parts = [ + address, + detailAddress?.town?.name, + detailAddress?.district?.name, + detailAddress?.province?.name, + ].filter(Boolean); + + return parts.length ? parts.join(", ") : "---"; +} + +export function numberToWords(number: number) { + if (typeof number !== "number" || isNaN(number)) { + return "Không hợp lệ"; + } + + if (number === 0) { + return "Không đồng"; + } + + const units = [ + "", + "một", + "hai", + "ba", + "bốn", + "năm", + "sáu", + "bảy", + "tám", + "chín", + ]; + const levels = ["", "nghìn", "triệu", "tỷ", "nghìn tỷ", "triệu tỷ", "tỷ tỷ"]; + + function readThreeDigits(num: number) { + let str = ""; + const hundred = Math.floor(num / 100); + const ten = Math.floor((num % 100) / 10); + const unit = num % 10; + + if (hundred > 0) { + str += units[hundred] + " trăm"; + if (ten === 0 && unit > 0) str += " linh"; + } + + if (ten > 1) { + str += " " + units[ten] + " mươi"; + if (unit === 1) str += " mốt"; + else if (unit === 5) str += " lăm"; + else if (unit > 0) str += " " + units[unit]; + } else if (ten === 1) { + str += " mười"; + if (unit > 0) str += " " + units[unit]; + } else if (unit > 0) { + str += " " + units[unit]; + } + + return str.trim(); + } + + function splitIntoChunks(num: number) { + const chunks = []; + while (num > 0) { + chunks.push(num % 1000); + num = Math.floor(num / 1000); + } + return chunks; + } + + const chunks = splitIntoChunks(number); + let result = ""; + + for (let i = chunks.length - 1; i >= 0; i--) { + if (chunks[i] > 0) { + result += readThreeDigits(chunks[i]) + " " + levels[i] + " "; + } + } + + result = result.trim().replace(/\s+/g, " "); + result = result.charAt(0).toUpperCase() + result.slice(1) + " đồng"; + return result; +} diff --git a/src/common/funcs/selectData.ts b/src/common/funcs/selectData.ts new file mode 100644 index 0000000..459cd88 --- /dev/null +++ b/src/common/funcs/selectData.ts @@ -0,0 +1,99 @@ +import { TYPE_DATE } from "@/constant/config/enum"; + +export function getDateRange(range: number): { + from: Date | null; + to: Date | null; +} { + const today = new Date(); + today.setHours(0, 0, 0, 0); + + switch (range) { + case TYPE_DATE.ALL: + return { from: null, to: null }; + + // Tuần này + case TYPE_DATE.TODAY: + return { from: new Date(), to: new Date() }; + + // Hôm qua + case TYPE_DATE.YESTERDAY: + const firstYesterday = new Date(); + firstYesterday.setDate(today.getDate() - 1); + + const lastYesterday = new Date(); + + lastYesterday.setDate(today.getDate() - 1); + + return { + from: firstYesterday, + to: lastYesterday, + }; + + // Tuần này + case TYPE_DATE.THIS_WEEK: + const firstDayOfWeek = new Date(today); + firstDayOfWeek.setDate(today.getDate() - today.getDay()); + + const lastDayOfWeek = new Date(today); + lastDayOfWeek.setDate(today.getDate() - today.getDay() + 6); + + return { from: firstDayOfWeek, to: lastDayOfWeek }; + + // Tuần trước + case TYPE_DATE.LAST_WEEK: + const firstDayLastWeek = new Date(today); + firstDayLastWeek.setDate(today.getDate() - today.getDay() - 7); + + const lastDayLastWeek = new Date(today); + lastDayLastWeek.setDate(today.getDate() - today.getDay() - 1); + + return { from: firstDayLastWeek, to: lastDayLastWeek }; + + // Tháng này + case TYPE_DATE.THIS_MONTH: + const firstDayOfMonth = new Date( + today.getFullYear(), + today.getMonth(), + 1, + ); + const lastDayOfMonth = new Date( + today.getFullYear(), + today.getMonth() + 1, + 0, + ); + + return { from: firstDayOfMonth, to: lastDayOfMonth }; + + // Tháng trước + case TYPE_DATE.LAST_MONTH: + const firstDayLastMonth = new Date( + today.getFullYear(), + today.getMonth() - 1, + 1, + ); + const lastDayLastMonth = new Date( + today.getFullYear(), + today.getMonth(), + 0, + ); + + return { from: firstDayLastMonth, to: lastDayLastMonth }; + + // Năm nay + case TYPE_DATE.THIS_YEAR: + const firstDayOfYear = new Date(today.getFullYear(), 0, 1); + const lastDayOfYear = new Date(today.getFullYear(), 11, 31); + + return { from: firstDayOfYear, to: lastDayOfYear }; + + // 7 ngày trước + case TYPE_DATE.LAST_7_DAYS: + const firstDayLast7Days = new Date(today); + firstDayLast7Days.setDate(today.getDate() - 7); + + return { from: firstDayLast7Days, to: today }; + + default: + return { from: null, to: null }; + } +} diff --git a/src/common/funcs/toast.ts b/src/common/funcs/toast.ts new file mode 100644 index 0000000..0e21877 --- /dev/null +++ b/src/common/funcs/toast.ts @@ -0,0 +1,52 @@ +import { toast } from "react-toastify"; +import IconToastifyCustom from "@/components/customs/custom-toast"; + +export const toastText = ({ msg }: { msg: string }) => + toast.info(msg, { + position: "top-center", + autoClose: 2000, + hideProgressBar: true, + closeButton: false, + className: "toastify-custom", + icon: IconToastifyCustom({ type: "info" }), + }); + +export const toastSuccess = ({ msg }: { msg: string }) => + toast.success(msg, { + autoClose: 2000, + hideProgressBar: true, + position: "top-center", + closeButton: true, + className: "toastify-custom-success", + icon: IconToastifyCustom({ type: "success" }), + }); + +export const toastInfo = ({ msg }: { msg: string }) => + toast.info(msg, { + autoClose: 2000, + hideProgressBar: true, + position: "top-center", + closeButton: true, + className: "toastify-custom-info", + icon: IconToastifyCustom({ type: "info" }), + }); + +export const toastWarn = ({ msg }: { msg: string }) => + toast.warning(msg, { + autoClose: 2000, + hideProgressBar: true, + position: "top-center", + closeButton: true, + className: "toastify-custom-warn", + icon: IconToastifyCustom({ type: "warn" }), + }); + +export const toastError = ({ msg }: { msg: string }) => + toast.error(msg, { + autoClose: 2000, + hideProgressBar: true, + position: "top-center", + closeButton: true, + className: "toastify-custom-error", + icon: IconToastifyCustom({ type: "error" }), + }); diff --git a/src/common/funcs/validate.ts b/src/common/funcs/validate.ts new file mode 100644 index 0000000..6fceb9d --- /dev/null +++ b/src/common/funcs/validate.ts @@ -0,0 +1,14 @@ +export function validateEmail(value: string) { + return !!value + ?.trim() + .toLowerCase() + .match( + /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, + ); +} + +export function validatePhoneNumber(phoneNumber: string) { + const vietnamPhoneRegex = /^(03|05|07|08|09)[0-9]{8}$/; + + return vietnamPhoneRegex.test(phoneNumber); +} diff --git a/src/common/hooks/useStyleClass.ts b/src/common/hooks/useStyleClass.ts new file mode 100644 index 0000000..dc36bcd --- /dev/null +++ b/src/common/hooks/useStyleClass.ts @@ -0,0 +1,14 @@ +export const useStyleClass = (props: any, style: any) => { + let styleProps: Array = []; + + for (let i in props) { + styleProps.push(i); + } + + const convert: any = styleProps.map((item) => { + if (style[item] !== undefined) { + return { [style[item]]: !!props[item] }; + } + }); + return convert; +}; diff --git a/src/components/customs/DataWrapper.tsx b/src/components/customs/DataWrapper.tsx new file mode 100644 index 0000000..ec7974d --- /dev/null +++ b/src/components/customs/DataWrapper.tsx @@ -0,0 +1,64 @@ +"use client"; + +import React, { ReactNode } from "react"; +import Image from "next/image"; +import clsx from "clsx"; +import icons from "@/constant/images/icons"; + +interface PropsDataWrapper { + title?: string; + note?: string; + loading: boolean; + data: unknown[]; + children: ReactNode; + button?: ReactNode; +} + +function DataWrapper({ + title = "Dữ liệu trống", + note = "Hiện tại dữ liệu đang trống!", + data, + loading, + button, + children, +}: PropsDataWrapper) { + // ================= LOADING ================= + if (loading) { + return ( +
+
+ +

Đang tải...

+

+ Vui lòng chờ, đang tải dữ liệu! +

+
+ ); + } + + // ================= EMPTY ================= + if (!loading && (!data || data.length === 0)) { + return ( +
+ empty-data + +

{title}

+ +

{note}

+ + {button &&
{button}
} +
+ ); + } + + // ================= HAS DATA ================= + return <>{children}; +} + +export default DataWrapper; diff --git a/src/components/customs/StateActive.tsx b/src/components/customs/StateActive.tsx new file mode 100644 index 0000000..dcf6dc5 --- /dev/null +++ b/src/components/customs/StateActive.tsx @@ -0,0 +1,64 @@ +"use client"; + +import React, { useMemo } from "react"; + +interface StateItem { + state: number | string; + text: string; + backgroundColor?: string; + textColor?: string; +} + +interface PropsStateActive { + isBox?: boolean; + stateActive: number | string; + listState: StateItem[]; +} + +function StateActive({ + isBox = true, + stateActive, + listState, +}: PropsStateActive) { + // memo trạng thái hiện tại + const current = useMemo(() => { + return listState.find((item) => item.state === stateActive); + }, [stateActive, listState]); + + if (isBox) { + return ( +
+ {current?.text ?? "---"} +
+ ); + } + + return ( +
+ {/* DOT */} +
+ + {/* TEXT */} +

+ {current?.text ?? "---"} +

+
+ ); +} + +export default StateActive; diff --git a/src/components/customs/custom-button.tsx b/src/components/customs/custom-button.tsx new file mode 100644 index 0000000..d0443d0 --- /dev/null +++ b/src/components/customs/custom-button.tsx @@ -0,0 +1,123 @@ +"use client"; + +import React from "react"; +import Link from "next/link"; +import clsx from "clsx"; + +export interface PropsButton { + onClick?: (e: React.MouseEvent) => void; + children?: React.ReactNode; + href?: string; + icon?: React.ReactNode; + className?: string; + target?: string; + disabled?: boolean; + + variant?: + | "default" + | "midnightBlue" + | "green" + | "red" + | "grey" + | "white" + | "black" + | "warning"; + + size?: "sm" | "md" | "lg"; + rounded?: "sm" | "md" | "lg" | "full"; + fullWidth?: boolean; +} + +export default function CustomButton({ + children, + onClick, + href, + icon, + className, + target, + disabled = false, + variant = "default", + size = "md", + rounded = "md", + fullWidth = true, +}: PropsButton) { + // 🎨 Variant + const variantClasses: Record = { + default: "bg-gray-100 text-gray-700 border border-gray-200 cursor-pointer", + midnightBlue: + "bg-blue-900 text-white border border-blue-900 cursor-pointer", + green: "bg-green-500 text-white border border-green-500 cursor-pointer", + red: "bg-red-500 text-white border border-red-500 cursor-pointer", + grey: "bg-white text-gray-700 border border-gray-200 cursor-pointer", + white: + "bg-white text-gray-700 border border-gray-200 shadow-sm cursor-pointer", + black: "bg-gray-800 text-white border border-gray-800 cursor-pointer", + warning: "bg-orange-400 text-white border border-orange-400 cursor-pointer", + }; + + // 📏 Size + const sizeClasses = { + sm: "px-3 py-1 text-sm", + md: "px-5 py-2 text-sm", + lg: "px-6 py-3 text-base", + }; + + // 🔵 Rounded + const roundedClasses = { + sm: "rounded-md", + md: "rounded-xl", + lg: "rounded-2xl", + full: "rounded-full", + }; + + const baseClass = clsx( + "inline-flex items-center justify-center gap-2", + "transition-all duration-200 select-none", + "active:scale-[0.98] hover:opacity-90", + variantClasses[variant], + sizeClasses[size], + roundedClasses[rounded], + fullWidth ? "w-full" : "w-fit", + disabled && "opacity-40 pointer-events-none cursor-not-allowed", + className, + ); + + const content = ( + <> + {icon && {icon}} + {children} + + ); + + const handleClick = (e: React.MouseEvent) => { + if (disabled) return; + onClick?.(e); + }; + + // 🔗 Link mode + if (href) { + return ( + + {content} + + ); + } + + // 🔘 Button mode + return ( + + ); +} diff --git a/src/components/customs/custom-dialog.tsx b/src/components/customs/custom-dialog.tsx new file mode 100644 index 0000000..30a4224 --- /dev/null +++ b/src/components/customs/custom-dialog.tsx @@ -0,0 +1,70 @@ +"use client"; + +import React from "react"; +import IconRound, { IconRoundType } from "../utils/IconRound"; +import CustomPopup from "./custom-popup"; +import CustomButton from "./custom-button"; + +/* ================== TYPES ================== */ +export interface PropsDialog { + open: boolean; + title: React.ReactNode; + note?: React.ReactNode; + onClose: () => void; + onSubmit: () => void; + titleCancel?: string; + titleSubmit?: string; + type?: IconRoundType; +} + +/* ================== COMPONENT ================== */ +export default function CustomDialog({ + open, + title, + note, + titleCancel = "Hủy bỏ", + titleSubmit = "Xác nhận", + onClose, + onSubmit, + type = "success", +}: PropsDialog) { + return ( + +
+ {/* ICON */} + + + {/* TITLE */} +

{title}

+ + {/* NOTE */} + {note &&

{note}

} + + {/* ACTIONS */} +
+ + {titleCancel} + + + + {titleSubmit} + +
+
+
+ ); +} diff --git a/src/components/customs/custom-input.tsx b/src/components/customs/custom-input.tsx new file mode 100644 index 0000000..d5f7ec2 --- /dev/null +++ b/src/components/customs/custom-input.tsx @@ -0,0 +1,106 @@ +"use client"; + +import { ReactNode, useMemo, useRef } from "react"; +import clsx from "clsx"; + +interface Props { + lenght?: number; + onSetValue?: (value: any) => void; + name?: string; + className?: string; +} + +export default function InputSingle({ + className, + lenght = 6, + onSetValue, + name = "otp_code", +}: Props) { + const inputsRef = useRef([]); + + // update value + const updateOTPValue = () => { + const code = inputsRef.current.map((input) => input?.value || ""); + + onSetValue?.((prev: any) => ({ + ...prev, + [name]: code.join(""), + })); + }; + + // input change + const handleChange = (e: React.FormEvent) => { + const target = e.currentTarget; + + const index = Number(target.dataset.index); + + let value = target.value; + + if (value.length > 1) { + value = value.slice(0, 1); + + target.value = value; + } + + updateOTPValue(); + + if (value) { + inputsRef.current[index + 1]?.focus(); + } + }; + + // backspace + const handleKeyDown = (e: React.KeyboardEvent) => { + const index = Number(e.currentTarget.dataset.index); + + if (e.key === "Backspace") { + if (!e.currentTarget.value) { + inputsRef.current[index - 1]?.focus(); + } + + requestAnimationFrame(() => { + updateOTPValue(); + }); + } + }; + + // set refs + const setRef = (el: HTMLInputElement | null, index: number) => { + if (el) { + inputsRef.current[index] = el; + } + }; + + const renderInputs = useMemo(() => { + const list: ReactNode[] = []; + + for (let i = 0; i < lenght; i++) { + list.push( + setRef(el, i)} + data-index={i} + inputMode="numeric" + maxLength={1} + onInput={handleChange} + onKeyDown={handleKeyDown} + className={clsx( + "h-[72px] w-[56px]", + "rounded-xl border-2 border-[#dae1ea]", + "text-center text-[42px] font-medium", + "outline-none transition", + "focus:border-[#066bb1]", + )} + />, + ); + } + + return list; + }, [lenght]); + + return ( +
+ {renderInputs} +
+ ); +} diff --git a/src/components/customs/custom-loading.tsx b/src/components/customs/custom-loading.tsx new file mode 100644 index 0000000..664f8c0 --- /dev/null +++ b/src/components/customs/custom-loading.tsx @@ -0,0 +1,54 @@ +"use client"; + +import CustomPortal from "./custom-portal"; + +interface PropsLoading { + loading?: boolean; +} + +const SCALE = 2.5; + +export default function CustomLoading({ loading }: PropsLoading) { + if (!loading) return null; + + return ( + +
+
+ {Array.from({ length: 12 }).map((_, i) => { + const rotate = i * 30; + + return ( +
+
+
+ ); + })} +
+
+ + ); +} diff --git a/src/components/customs/custom-pagination.tsx b/src/components/customs/custom-pagination.tsx new file mode 100644 index 0000000..4517b4b --- /dev/null +++ b/src/components/customs/custom-pagination.tsx @@ -0,0 +1,205 @@ +"use client"; + +import React, { + useEffect, + useMemo, + useRef, + useState, + forwardRef, +} from "react"; +import clsx from "clsx"; +import { ChevronDown, ArrowLeft, ArrowRight } from "lucide-react"; + +export interface PropsPagination { + total: number; + page: number; + onSetPage: (page: number) => void; + pageSize: number; + onSetPageSize: (pageSize: number) => void; + dependencies?: any[]; +} + +// Wrapper component to forward ref for the dropdown trigger +const TriggerButton = forwardRef< + HTMLDivElement, + { + onClick: () => void; + className: string; + pageSize: number; + openLimit: boolean; + } +>(({ onClick, className, pageSize, openLimit }, ref) => ( +
+ {pageSize} + +
+)); + +TriggerButton.displayName = "TriggerButton"; + +export default function Pagination({ + total, + page, + pageSize, + onSetPage, + onSetPageSize, + dependencies = [], +}: PropsPagination) { + const pageSizes = [50, 100, 200]; + + const [openLimit, setOpenLimit] = useState(false); + const dropdownRef = useRef(null); + + const maxPage = Math.ceil(total / Number(pageSize)); + + const items = useMemo(() => { + const nodes: React.ReactNode[] = []; + + for (let i = 1; i <= maxPage; i++) { + const isCurrent = page === i; + const isNear = + i === page || + i === page - 1 || + i === page + 1 || + i === 1 || + i === maxPage; + + if (isNear) { + nodes.push( +
  • onSetPage(i)} + className={clsx( + "h-[30px] w-[30px] flex items-center justify-center text-[13px] font-semibold border rounded cursor-pointer transition", + isCurrent + ? "bg-[#0011ab] text-white border-[#0011ab] pointer-events-none" + : "bg-white text-black border-[#efefef] hover:opacity-70", + )} + > + {i} +
  • , + ); + } + + if ((i === page - 2 && page >= 4) || (i === page + 2 && i < maxPage)) { + nodes.push( +
  • + ... +
  • , + ); + } + } + + return nodes; + }, [maxPage, page, onSetPage]); + + const handlePrev = () => { + if (page > 1) onSetPage(page - 1); + }; + + const handleNext = () => { + if (page < maxPage) onSetPage(page + 1); + }; + + useEffect(() => { + if (dependencies.length > 0) { + onSetPage(1); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, dependencies); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + openLimit && + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) + ) { + setOpenLimit(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, [openLimit]); + + if (total <= 0) return null; + + return ( +
    + {/* LEFT */} +
    +

    Hiển thị

    + +
    + setOpenLimit(!openLimit)} + className={clsx( + "w-[80px] flex items-center justify-between px-3 py-1 border rounded-full cursor-pointer bg-white", + "border-gray-300", + )} + pageSize={pageSize} + openLimit={openLimit} + /> + + {openLimit && ( +
    + {pageSizes.map((v) => ( +
    { + onSetPageSize(v); + setOpenLimit(false); + }} + className={clsx( + "px-2 py-1 text-[13px] cursor-pointer rounded", + "hover:bg-[#0011ab] hover:text-white", + pageSize === v && "bg-[#0011ab] text-white", + )} + > + {v} +
    + ))} +
    + )} +
    + +

    + trong tổng {total} kết quả +

    +
    + + {/* PAGINATION */} +
    + {page > 1 && ( + + )} + +
      {items}
    + + {page < maxPage && ( + + )} +
    +
    + ); +} diff --git a/src/components/customs/custom-popup.tsx b/src/components/customs/custom-popup.tsx new file mode 100644 index 0000000..b6c8b09 --- /dev/null +++ b/src/components/customs/custom-popup.tsx @@ -0,0 +1,89 @@ +"use client"; + +import React, { ReactNode, useEffect } from "react"; +import { createPortal } from "react-dom"; +import clsx from "clsx"; + +/* ================== TYPES ================== */ +interface Props { + open: boolean; + isFull?: boolean; + onClose: () => void; + children?: ReactNode; + showOverlay?: boolean; +} + +/* ================== COMPONENT ================== */ +export default function CustomPopup({ + open, + onClose, + showOverlay = true, + isFull, + children, +}: Props) { + // lock body scroll + useEffect(() => { + if (!open) return; + + const original = document.body.style.overflow; + + document.body.style.overflow = "hidden"; + + return () => { + document.body.style.overflow = original || "auto"; + }; + }, [open]); + + // SSR safe + if (typeof window === "undefined") return null; + + // popup closed + if (!open) return null; + + return createPortal( + <> + {/* OVERLAY */} + {showOverlay && ( +
    + )} + + {/* CONTENT */} +
    + {children} +
    + , + document.body, + ); +} diff --git a/src/components/customs/custom-portal.tsx b/src/components/customs/custom-portal.tsx new file mode 100644 index 0000000..53b0e3e --- /dev/null +++ b/src/components/customs/custom-portal.tsx @@ -0,0 +1,52 @@ +"use client"; + +import { ReactNode, useLayoutEffect, useMemo } from "react"; + +import { createPortal } from "react-dom"; + +interface Props { + children: ReactNode; + className?: string; + containerId?: string; +} + +export default function CustomPortal({ + children, + className, + containerId, +}: Props) { + // ✅ tạo element 1 lần duy nhất + const element = useMemo(() => { + if (typeof window === "undefined") return null; + + const el = document.createElement("div"); + + if (className) { + el.className = className; + } + + return el; + }, [className]); + + useLayoutEffect(() => { + if (!element) return; + + const parent = containerId + ? document.getElementById(containerId) + : document.body; + + if (!parent) return; + + parent.appendChild(element); + + return () => { + parent.removeChild(element); + }; + }, [element, containerId]); + + // ✅ không dùng ref.current + // ✅ không dùng setState + if (!element) return null; + + return createPortal(children, element); +} diff --git a/src/components/customs/custom-search.tsx b/src/components/customs/custom-search.tsx new file mode 100644 index 0000000..0b87c57 --- /dev/null +++ b/src/components/customs/custom-search.tsx @@ -0,0 +1,56 @@ +"use client"; + +import React, { useEffect, useRef, useState } from "react"; +import clsx from "clsx"; +import { Search as SearchIcon } from "lucide-react"; + +interface PropsSearch { + placeholder?: string; + keyword?: string; + setKeyword: (value: string) => void; +} + +export default function Search({ + keyword = "", + setKeyword, + placeholder = "Nhập từ khóa tìm kiếm", +}: PropsSearch) { + const inputRef = useRef(null); + const [searchTerm, setSearchTerm] = useState(keyword); + + // useEffect(() => { + // setSearchTerm(keyword); + // }, [keyword]); + + useEffect(() => { + const timer = setTimeout(() => { + setKeyword(searchTerm.trim()); + }, 600); + + return () => clearTimeout(timer); + }, [searchTerm]); + + return ( +
    inputRef.current?.focus()} + className={clsx( + "flex h-12 cursor-text items-center gap-2 rounded-full border bg-white px-4 transition-colors", + "border-[#e1e5ed] hover:border-[#3772ff] focus-within:border-[#3772ff]", + )} + > + + + setSearchTerm(e.target.value)} + placeholder={placeholder} + className=" + flex-1 border-none bg-transparent p-2 + text-[16px] font-medium outline-none + placeholder:text-[#777e90] + " + /> +
    + ); +} diff --git a/src/components/customs/custom-table.tsx b/src/components/customs/custom-table.tsx new file mode 100644 index 0000000..96ff1bc --- /dev/null +++ b/src/components/customs/custom-table.tsx @@ -0,0 +1,241 @@ +"use client"; + +import { Fragment, useEffect, useMemo, useRef, useState } from "react"; +import clsx from "clsx"; +import { + ChevronUp, + ArrowDownUp, + ArrowDownWideNarrow, + ArrowDownNarrowWide, +} from "lucide-react"; + +/* ================= TYPES ================= */ +interface ColumnType { + title: string | React.ReactNode; + render: (row: T, index: number, path?: number[]) => React.ReactNode; + className?: string; + checkBox?: boolean; + fixedLeft?: boolean; + fixedRight?: boolean; + maxWidth?: number | string; + sortTable?: string; +} + +interface PropsTable { + data: T[]; + column: ColumnType[]; + fixedHeader?: boolean; + activeHeader?: boolean; + handleCheckedAll?: (e: React.ChangeEvent) => void; + isCheckedAll?: boolean; + handleCheckedRow?: (e: React.ChangeEvent, row: T) => void; + handleIsCheckedRow?: (row: T) => boolean; + rowKey: (row: T) => React.Key; + getChildren?: (row: T) => T[] | undefined; + useIndexPathAsKey?: boolean; +} + +/* ================= COMPONENT ================= */ +export default function Table({ + data, + column, + fixedHeader, + activeHeader, + handleCheckedAll, + isCheckedAll, + handleCheckedRow, + handleIsCheckedRow, + rowKey, + getChildren, + useIndexPathAsKey, +}: PropsTable) { + const tableRef = useRef(null); + const thRefs = useRef<(HTMLTableCellElement | null)[]>([]); + + const [expandedRows, setExpandedRows] = useState([]); + const [sortConfig, setSortConfig] = useState<{ + key: string; + direction: "asc" | "desc" | null; + }>({ key: "", direction: null }); + + /* ================= SORT ================= */ + const handleSort = (key: string) => { + setSortConfig((prev) => { + if (prev.key === key) { + const next = + prev.direction === "asc" + ? "desc" + : prev.direction === "desc" + ? null + : "asc"; + return { key, direction: next }; + } + return { key, direction: "asc" }; + }); + }; + + const sortedData = useMemo(() => { + if (!sortConfig.key || !sortConfig.direction) return data; + + const copy = [...data]; + + copy.sort((a: any, b: any) => { + const aVal = a?.[sortConfig.key]; + const bVal = b?.[sortConfig.key]; + + if (typeof aVal === "number" && typeof bVal === "number") { + return sortConfig.direction === "asc" ? aVal - bVal : bVal - aVal; + } + + if (typeof aVal === "string" && typeof bVal === "string") { + return sortConfig.direction === "asc" + ? aVal.localeCompare(bVal) + : bVal.localeCompare(aVal); + } + + return 0; + }); + + return copy; + }, [data, sortConfig]); + + /* ================= EXPAND ================= */ + const toggleExpand = (key: React.Key) => { + setExpandedRows((prev) => + prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key], + ); + }; + + /* ================= ROWS ================= */ + const renderRows = (rows: T[], path: number[] = []): React.ReactNode => + rows.map((row, i) => { + const currentPath = [...path, i]; + const keyPath = useIndexPathAsKey ? currentPath.join(".") : rowKey(row); + + const children = getChildren?.(row); + const expanded = expandedRows.includes(keyPath); + + return ( + + + {getChildren && ( + + {!!children?.length && ( + + )} + + )} + + {column.map((col, idx) => ( + +
    + {col.checkBox && ( + handleCheckedRow?.(e, row)} + checked={handleIsCheckedRow?.(row) ?? false} + /> + )} + {col.render(row, i, currentPath)} +
    + + ))} + + + {expanded && children && renderRows(children, currentPath)} +
    + ); + }); + + /* ================= UI ================= */ + return ( +
    table>thead]:sticky [&>table>thead]:top-0", + activeHeader && "[&>table>thead]:bg-[#F4F7FA]", + )} + > + + + + {getChildren && + ); + })} + + + + {renderRows(sortedData)} +
    } + + {column.map((col, i) => { + const isActive = col.sortTable === sortConfig.key; + + return ( + { + thRefs.current[i] = el; + }} + onClick={() => col.sortTable && handleSort(col.sortTable)} + className={clsx( + "px-4 py-3 text-left text-[15px] font-semibold whitespace-nowrap", + col.sortTable && "cursor-pointer", + isActive && "text-blue-600", + col.fixedLeft && "sticky left-0 bg-white z-10", + col.fixedRight && "sticky right-0 bg-white z-10", + )} + > +
    + {col.checkBox && ( + + )} + + {col.title} + + {col.sortTable && + (isActive ? ( + sortConfig.direction === "asc" ? ( + + ) : sortConfig.direction === "desc" ? ( + + ) : ( + + ) + ) : ( + + ))} +
    +
    +
    + ); +} diff --git a/src/components/customs/custom-toast.tsx b/src/components/customs/custom-toast.tsx new file mode 100644 index 0000000..80a244c --- /dev/null +++ b/src/components/customs/custom-toast.tsx @@ -0,0 +1,42 @@ +"use client"; + +import { CircleCheck, CircleAlert, BadgeCheck } from "lucide-react"; +import clsx from "clsx"; + +export interface PropsIconToastifyCustom { + type: "success" | "info" | "warn" | "error"; +} + +const ICON_CONFIG = { + success: { + bg: "bg-[#06d7a0]", + icon: CircleCheck, + }, + info: { + bg: "bg-[#4bc9f0]", + icon: CircleCheck, + }, + warn: { + bg: "bg-[#ffd167]", + icon: CircleAlert, + }, + error: { + bg: "bg-[#ee464c]", + icon: BadgeCheck, + }, +} as const; + +export default function IconToastifyCustom({ type }: PropsIconToastifyCustom) { + const { bg, icon: Icon } = ICON_CONFIG[type]; + + return ( +
    + +
    + ); +} diff --git a/src/components/customs/slider-props.tsx b/src/components/customs/slider-props.tsx new file mode 100644 index 0000000..4acc218 --- /dev/null +++ b/src/components/customs/slider-props.tsx @@ -0,0 +1,122 @@ +"use client"; + +import React from "react"; +import clsx from "clsx"; + +interface CustomSliderProps { + label?: string; + value: number; + min?: number; + max?: number; + step?: number; + + onChange?: (value: number) => void; + + showValue?: boolean; + disabled?: boolean; + + className?: string; + labelClassName?: string; + sliderClassName?: string; + thumbClassName?: string; + trackClassName?: string; + + color?: string; + trackColor?: string; + + renderValue?: (value: number) => React.ReactNode; +} + +const CustomSlider = ({ + label, + value, + min = 0, + max = 1, + step = 0.01, + + onChange, + + showValue = true, + disabled = false, + + className, + labelClassName, + sliderClassName, + + color = "#2563eb", + trackColor = "#e5e7eb", + + renderValue, +}: CustomSliderProps) => { + const percentage = ((value - min) / (max - min)) * 100; + + return ( +
    + {/* Header */} + {(label || showValue) && ( +
    + {label && ( + {label} + )} + + {showValue && ( + + {renderValue ? renderValue(value) : value} + + )} +
    + )} + + {/* Slider */} + onChange?.(Number(e.target.value))} + className={clsx( + ` + w-full + appearance-none + bg-transparent + cursor-pointer + + disabled:opacity-50 + disabled:cursor-not-allowed + + [&::-webkit-slider-runnable-track]:h-2 + [&::-webkit-slider-runnable-track]:rounded-full + + [&::-webkit-slider-thumb]:appearance-none + [&::-webkit-slider-thumb]:h-6 + [&::-webkit-slider-thumb]:w-6 + [&::-webkit-slider-thumb]:rounded-full + [&::-webkit-slider-thumb]:bg-white + [&::-webkit-slider-thumb]:border-4 + [&::-webkit-slider-thumb]:shadow-md + [&::-webkit-slider-thumb]:-mt-2 + + [&::-moz-range-track]:h-2 + [&::-moz-range-track]:rounded-full + + [&::-moz-range-thumb]:h-6 + [&::-moz-range-thumb]:w-6 + [&::-moz-range-thumb]:rounded-full + [&::-moz-range-thumb]:bg-white + [&::-moz-range-thumb]:border-4 + `, + sliderClassName, + )} + style={{ + background: `linear-gradient(to right, ${color} ${percentage}%, ${trackColor} ${percentage}%)`, + }} + /> +
    + ); +}; + +export default CustomSlider; diff --git a/src/components/layouts/BaseLayout/BaseLayout.tsx b/src/components/layouts/BaseLayout/BaseLayout.tsx new file mode 100644 index 0000000..7c4b9fe --- /dev/null +++ b/src/components/layouts/BaseLayout/BaseLayout.tsx @@ -0,0 +1,71 @@ +"use client"; +import React from "react"; +import { PropsBaseLayout, TContextBaseLayout } from "./interface"; +import clsx from "clsx"; +import Header from "./componets/Header"; +import Navbar from "./componets/Navbar"; +import RequireAuth from "@/components/protected/RequiredAuth"; + +export const ContextBaseLayout = React.createContext({}); + +const BaseLayout = ({ children, title, breadcrumb }: PropsBaseLayout) => { + const [showFull, setShowFull] = React.useState(false); + const [openMenuMobile, setOpenMenuMobile] = React.useState(false); + return ( + // + +
    + {/* OVERLAY cho Mobile */} +
    setOpenMenuMobile(false)} + /> + + {/* SIDEBAR (Dùng chung cho cả Desktop & Mobile) */} + + + {/* HEADER */} +
    +
    +
    + + {/* MAIN CONTENT */} +
    + {children} +
    +
    + + // + ); +}; + +export default BaseLayout; diff --git a/src/components/layouts/BaseLayout/componets/Header/Header.tsx b/src/components/layouts/BaseLayout/componets/Header/Header.tsx new file mode 100644 index 0000000..3d3f3d4 --- /dev/null +++ b/src/components/layouts/BaseLayout/componets/Header/Header.tsx @@ -0,0 +1,101 @@ +"use client"; +import React, { useContext, useEffect, useState } from "react"; +import { PropsHeader } from "./interface"; +import { usePathname } from "next/dist/client/components/navigation"; +import { useSelector } from "react-redux"; +import { RootState } from "@/redux/store"; +import { ContextBaseLayout } from "../../BaseLayout"; +import Image from "next/image"; +import icons from "@/constant/images/icons"; +import { ChevronDown } from "lucide-react"; +import clsx from "clsx"; +import MenuProfile from "../MenuProfile"; + +const Header = ({ title, breadcrumb }: PropsHeader) => { + const pathname = usePathname(); + const { infoUser } = useSelector((state: RootState) => state.user); + const context = useContext(ContextBaseLayout); + + const [openProfile, setOpenProfile] = useState(false); + + //Đóng menu mobile khi đổi route + useEffect(() => { + context.setOpenMenuMobile?.(false); + }, [pathname, context]); + + const toggleFullScreen = () => { + if (!document.fullscreenElement) { + document.documentElement.requestFullscreen(); + } else { + document.exitFullscreen(); + } + }; + + return ( +
    +
    + {/* Nút bấm Mobile: Mở menu mobile */} +
    context?.setOpenMenuMobile?.(true)} + > + menu +
    + {/* Nút bấm Desktop: Thu gọn/Mở rộng sidebar */} +
    context?.setShowFull?.(!context?.showFull)} + > + menu +
    + {breadcrumb ? ( + breadcrumb + ) : ( +

    {title}

    + )} +
    + +
    +
    + +
    + +
    setOpenProfile(!openProfile)} + > +
    + avatar +
    +

    + {infoUser?.fullname || "User admin"} +

    + + + {openProfile && ( +
    + setOpenProfile(false)} /> +
    + )} +
    +
    +
    + ); +}; + +export default Header; diff --git a/src/components/layouts/BaseLayout/componets/Header/index.ts b/src/components/layouts/BaseLayout/componets/Header/index.ts new file mode 100644 index 0000000..2764567 --- /dev/null +++ b/src/components/layouts/BaseLayout/componets/Header/index.ts @@ -0,0 +1 @@ +export { default } from "./Header"; diff --git a/src/components/layouts/BaseLayout/componets/Header/interface/index.ts b/src/components/layouts/BaseLayout/componets/Header/interface/index.ts new file mode 100644 index 0000000..bd52b48 --- /dev/null +++ b/src/components/layouts/BaseLayout/componets/Header/interface/index.ts @@ -0,0 +1,4 @@ +export interface PropsHeader { + title: string; + breadcrumb?: React.ReactNode; +} diff --git a/src/components/layouts/BaseLayout/componets/MenuProfile/MenuProfile.tsx b/src/components/layouts/BaseLayout/componets/MenuProfile/MenuProfile.tsx new file mode 100644 index 0000000..157fb72 --- /dev/null +++ b/src/components/layouts/BaseLayout/componets/MenuProfile/MenuProfile.tsx @@ -0,0 +1,120 @@ +import React, { use, useCallback, useState } from "react"; +import { PropsMenuProfile } from "./interface"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { useMutation } from "@tanstack/react-query"; +import { httpRequest } from "@/services"; +import authServices from "@/services/authServices"; +import { store } from "@/redux/store"; +import { logout } from "@/redux/reducer/auth"; +import { setInfoUser } from "@/redux/reducer/user"; +import { PATH } from "@/constant/config"; +import Link from "next/link"; +import clsx from "clsx"; +import { LogOut, ShieldCog, ShieldUser } from "lucide-react"; +import CustomDialog from "@/components/customs/custom-dialog"; + +const MenuProfile = ({ onClose }: PropsMenuProfile) => { + const router = useRouter(); + const pathname = usePathname(); + const searchParams = useSearchParams(); + + const [openLogout, setOpenLogout] = useState(false); + + /* ================== CHECK ACTIVE ================== */ + const checkActive = useCallback( + (path: string) => { + return pathname === path; + }, + [pathname], + ); + + /* ================== LOGOUT ================== */ + const logoutMutation = useMutation({ + mutationFn: () => + httpRequest({ + showMessageFailed: true, + showMessageSuccess: false, + http: authServices.logout(), + }), + onSuccess(data) { + if (data) { + store.dispatch(logout()); + store.dispatch(setInfoUser(null)); + router.push(PATH.LOGIN); + } + }, + }); + + const handleLogout = () => { + logoutMutation.mutate(); + }; + + return ( +
    + {/* PROFILE */} + + +
    +

    Thông tin cá nhân

    +

    Chi tiết tài khoản

    +
    + +
    + + {/* CHANGE PASSWORD */} + + +
    +

    Đổi mật khẩu

    +

    Thay đổi mật khẩu

    +
    + +
    + + {/* LOGOUT */} +
    { + setOpenLogout(true); + onClose(); + }} + className="flex items-center gap-3 p-3 rounded-lg hover:bg-red-50 cursor-pointer transition" + > + +
    +

    Đăng xuất

    +

    Đăng xuất khỏi hệ thống

    +
    +
    + + {/* DIALOG */} + setOpenLogout(false)} + onSubmit={handleLogout} + title="Đăng xuất" + note="Bạn có muốn đăng xuất khỏi hệ thống không?" + titleCancel="Không" + titleSubmit="Đăng xuất" + type="error" + /> +
    + ); +}; + +export default MenuProfile; diff --git a/src/components/layouts/BaseLayout/componets/MenuProfile/index.ts b/src/components/layouts/BaseLayout/componets/MenuProfile/index.ts new file mode 100644 index 0000000..12ae6c8 --- /dev/null +++ b/src/components/layouts/BaseLayout/componets/MenuProfile/index.ts @@ -0,0 +1 @@ +export { default } from "./MenuProfile"; diff --git a/src/components/layouts/BaseLayout/componets/MenuProfile/interface/index.ts b/src/components/layouts/BaseLayout/componets/MenuProfile/interface/index.ts new file mode 100644 index 0000000..88f466b --- /dev/null +++ b/src/components/layouts/BaseLayout/componets/MenuProfile/interface/index.ts @@ -0,0 +1,3 @@ +export interface PropsMenuProfile { + onClose: () => void; +} diff --git a/src/components/layouts/BaseLayout/componets/Navbar/Navbar.tsx b/src/components/layouts/BaseLayout/componets/Navbar/Navbar.tsx new file mode 100644 index 0000000..6733296 --- /dev/null +++ b/src/components/layouts/BaseLayout/componets/Navbar/Navbar.tsx @@ -0,0 +1,71 @@ +"use client"; + +import React, { useCallback } from "react"; +import { usePathname } from "next/navigation"; +import Link from "next/link"; +import { Menus, PATH } from "@/constant/config"; +import Image from "next/image"; +import icons from "@/constant/images/icons"; +import clsx from "clsx"; + +const Navbar = () => { + const pathname = usePathname(); + + const checkActive = useCallback( + (pathActive: string) => { + const currentRoute = pathname.split("/")[1]; + return pathActive === `/${currentRoute}`; + }, + [pathname], + ); + + return ( +
    + + Logo +

    + Quản lý +

    + +
    + {Menus.map((menu, i) => ( +
    + {/* TITLE */} +
    + {menu.title} +
    + +
    + {menu.group.map((tab, y) => { + const isActive = checkActive(tab.pathActive); + + return ( + + +

    {tab.title}

    + + ); + })} +
    +
    + ))} +
    +
    + ); +}; + +export default Navbar; diff --git a/src/components/layouts/BaseLayout/componets/Navbar/index.ts b/src/components/layouts/BaseLayout/componets/Navbar/index.ts new file mode 100644 index 0000000..8d95d66 --- /dev/null +++ b/src/components/layouts/BaseLayout/componets/Navbar/index.ts @@ -0,0 +1 @@ +export { default } from "./Navbar"; diff --git a/src/components/layouts/BaseLayout/index.ts b/src/components/layouts/BaseLayout/index.ts new file mode 100644 index 0000000..cd391ad --- /dev/null +++ b/src/components/layouts/BaseLayout/index.ts @@ -0,0 +1 @@ +export { default } from "./BaseLayout"; diff --git a/src/components/layouts/BaseLayout/interface/index.ts b/src/components/layouts/BaseLayout/interface/index.ts new file mode 100644 index 0000000..637b24c --- /dev/null +++ b/src/components/layouts/BaseLayout/interface/index.ts @@ -0,0 +1,12 @@ +export interface PropsBaseLayout { + children: React.ReactNode; + title: string; + breadcrumb?: React.ReactNode; +} + +export interface TContextBaseLayout { + showFull?: boolean; + setShowFull?: (show: boolean) => void; + openMenuMobile?: boolean; + setOpenMenuMobile?: (show: boolean) => void; +} diff --git a/src/components/layouts/ChartWrapper/ChartWrapper.tsx b/src/components/layouts/ChartWrapper/ChartWrapper.tsx new file mode 100644 index 0000000..2545a63 --- /dev/null +++ b/src/components/layouts/ChartWrapper/ChartWrapper.tsx @@ -0,0 +1,38 @@ +"use client"; +import React from "react"; +import { PropsChartWrapper } from "./interface"; +import clsx from "clsx"; +import Image from "next/image"; +import icons from "@/constant/images/icons"; + +const ChartWrapper = ({ + children, + isEmpty, + message = "Không có dữ liệu", +}: PropsChartWrapper) => { + return ( +
    + {isEmpty ? ( +
    + Ảnh dữ liệu trống + {/* nếu muốn hiện message thì bật dòng dưới */} + {/*

    {message}

    */} +
    + ) : ( + children + )} +
    + ); +}; + +export default ChartWrapper; diff --git a/src/components/layouts/ChartWrapper/index.ts b/src/components/layouts/ChartWrapper/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/components/layouts/ChartWrapper/interface/index.ts b/src/components/layouts/ChartWrapper/interface/index.ts new file mode 100644 index 0000000..acf4283 --- /dev/null +++ b/src/components/layouts/ChartWrapper/interface/index.ts @@ -0,0 +1,5 @@ +export interface PropsChartWrapper { + children: React.ReactNode; + isEmpty?: boolean; + message?: string; +} diff --git a/src/components/layouts/GridColumn/GridColumn.tsx b/src/components/layouts/GridColumn/GridColumn.tsx new file mode 100644 index 0000000..68d696b --- /dev/null +++ b/src/components/layouts/GridColumn/GridColumn.tsx @@ -0,0 +1,69 @@ +"use client"; + +import React from "react"; +import { GridColumnProps } from "./interface"; +import clsx from "clsx"; + +const GridColumn = ({ + children, + col = 3, + sm, + tabletCol3, + mobile2, + scroll20, + scrollMobile85, + className, +}: GridColumnProps) => { + return ( +
    + {children} +
    + ); +}; + +export default GridColumn; diff --git a/src/components/layouts/GridColumn/index.ts b/src/components/layouts/GridColumn/index.ts new file mode 100644 index 0000000..6b7d9a9 --- /dev/null +++ b/src/components/layouts/GridColumn/index.ts @@ -0,0 +1 @@ +export { default } from "./GridColumn"; diff --git a/src/components/layouts/GridColumn/interface/index.ts b/src/components/layouts/GridColumn/interface/index.ts new file mode 100644 index 0000000..9d1c8f9 --- /dev/null +++ b/src/components/layouts/GridColumn/interface/index.ts @@ -0,0 +1,14 @@ +export interface GridColumnProps { + children: React.ReactNode; + + col?: 1 | 2 | 3 | 4 | 5 | 6 | 8 | 12; + sm?: boolean; + + tabletCol3?: boolean; + mobile2?: boolean; + + scroll20?: boolean; + scrollMobile85?: boolean; + + className?: string; +} diff --git a/src/components/layouts/LayoutAuth‎/LayoutAuth‎.tsx b/src/components/layouts/LayoutAuth‎/LayoutAuth‎.tsx new file mode 100644 index 0000000..81f6949 --- /dev/null +++ b/src/components/layouts/LayoutAuth‎/LayoutAuth‎.tsx @@ -0,0 +1,40 @@ +"use client"; + +import RequiredLogout from "@/components/protected/RequiredLogout"; +import React from "react"; + +interface PropsLayoutAuth { + children: React.ReactNode; +} + +const LayoutAuth = ({ children }: PropsLayoutAuth) => { + return ( + +
    + {/* BACKGROUND */} +
    + + {/* MAIN */} +
    + {children} +
    +
    + + ); +}; + +export default LayoutAuth; diff --git a/src/components/layouts/LayoutAuth‎/index.ts b/src/components/layouts/LayoutAuth‎/index.ts new file mode 100644 index 0000000..9756f0a --- /dev/null +++ b/src/components/layouts/LayoutAuth‎/index.ts @@ -0,0 +1 @@ +export { default } from "./LayoutAuth‎"; diff --git a/src/components/layouts/LayoutPages/LayoutPages.tsx b/src/components/layouts/LayoutPages/LayoutPages.tsx new file mode 100644 index 0000000..b263476 --- /dev/null +++ b/src/components/layouts/LayoutPages/LayoutPages.tsx @@ -0,0 +1,57 @@ +"use client"; +import React, { useCallback } from "react"; +import { PropsLayoutPages } from "./interface"; +import { usePathname } from "next/navigation"; +import Link from "next/link"; +import clsx from "clsx"; + +const LayoutPages = ({ children, listPages }: PropsLayoutPages) => { + const pathname = usePathname(); + const checkActive = useCallback( + (url: string) => { + return pathname === url; + }, + [pathname], + ); + return ( + <> +
    + {listPages.map((item, i) => { + const isActive = checkActive(item.url); + return ( + { + if (isActive) e.preventDefault(); + }} + className={clsx( + "inline-block min-w-[60px] text-center font-medium rounded-md px-6 py-3 border select-none cursor-pointer", + "text-[16px] max-[1200px]:text-[14px] max-[768px]:text-[13px]", + isActive + ? "bg-[#3772ff] text-white border-[#3772ff]" + : "bg-white text-[#202939] border-[#eaedf2]", + i !== 0 && "ml-2", + )} + > + {" "} +
    +
    +

    {item.title}

    +
    + + ); + })} +
    + {/* MAIN */} +
    {children}
    + + ); +}; + +export default LayoutPages; diff --git a/src/components/layouts/LayoutPages/index.ts b/src/components/layouts/LayoutPages/index.ts new file mode 100644 index 0000000..14b40c4 --- /dev/null +++ b/src/components/layouts/LayoutPages/index.ts @@ -0,0 +1 @@ +export { default } from "./LayoutPages"; diff --git a/src/components/layouts/LayoutPages/interface/index.ts b/src/components/layouts/LayoutPages/interface/index.ts new file mode 100644 index 0000000..c47ef62 --- /dev/null +++ b/src/components/layouts/LayoutPages/interface/index.ts @@ -0,0 +1,8 @@ +export interface PropsLayoutPages { + children: React.ReactNode; + listPages: { + title: string; + url: string; + color: string; + }[]; +} diff --git a/src/components/page/Home/MainPageHome/MainPageHome.tsx b/src/components/page/Home/MainPageHome/MainPageHome.tsx new file mode 100644 index 0000000..59c3551 --- /dev/null +++ b/src/components/page/Home/MainPageHome/MainPageHome.tsx @@ -0,0 +1,8 @@ +"use client"; +import React from "react"; + +const MainPageHome = () => { + return
    MainPageHome
    ; +}; + +export default MainPageHome; diff --git a/src/components/page/Home/MainPageHome/index.ts b/src/components/page/Home/MainPageHome/index.ts new file mode 100644 index 0000000..310c0db --- /dev/null +++ b/src/components/page/Home/MainPageHome/index.ts @@ -0,0 +1 @@ +export { default } from "./MainPageHome"; diff --git a/src/components/page/auth/MainForgotPassword/MainForgotPassword.tsx b/src/components/page/auth/MainForgotPassword/MainForgotPassword.tsx new file mode 100644 index 0000000..8632ba1 --- /dev/null +++ b/src/components/page/auth/MainForgotPassword/MainForgotPassword.tsx @@ -0,0 +1,66 @@ +"use client"; + +import React, { useState } from "react"; + +import { IFormForgotPassword, TYPE_FORGOT_PASWORD } from "./interface"; + +import { ContextForgotPassword } from "./context"; + +import FormEmail from "./components/FormEmail"; +import FormPassword from "./components/FormPassword/FormPassword"; + +export default function MainForgotPassword() { + const [type, setType] = useState( + TYPE_FORGOT_PASWORD.EMAIL, + ); + + const [form, setForm] = useState({ + email: "", + otp: "", + password: "", + rePassword: "", + }); + + return ( +
    + {/* TITLE */} +

    + Quên mật khẩu +

    + + {/* DESCRIPTION */} +

    + Nhập địa chỉ email liên kết với tài khoản của bạn để lấy lại mật khẩu! +

    + + {/* FORM */} +
    + + {type === TYPE_FORGOT_PASWORD.EMAIL && } + + {type === TYPE_FORGOT_PASWORD.PASSWORD && } + +
    +
    + ); +} diff --git a/src/components/page/auth/MainForgotPassword/components/FormEmail/FormEmail.tsx b/src/components/page/auth/MainForgotPassword/components/FormEmail/FormEmail.tsx new file mode 100644 index 0000000..fc2557a --- /dev/null +++ b/src/components/page/auth/MainForgotPassword/components/FormEmail/FormEmail.tsx @@ -0,0 +1,126 @@ +"use client"; + +import React, { useContext } from "react"; + +import { useRouter, useSearchParams } from "next/navigation"; +import { useMutation } from "@tanstack/react-query"; +import { Mail } from "lucide-react"; + +import FormCustom from "@/components/utils/FormCustom"; +import InputForm from "@/components/utils/FormCustom/components/InputForm"; +import CustomPopup from "@/components/customs/custom-popup"; +import CustomLoading from "@/components/customs/custom-loading"; +import CustomButton from "@/components/customs/custom-button"; + +import { ContextForgotPassword } from "../../context"; + +import FormOTP from "../FormOTP"; + +import { PATH } from "@/constant/config"; + +import { httpRequest } from "@/services"; +import accountServices from "@/services/accountServices"; + +function FormEmail() { + const router = useRouter(); + + const searchParams = useSearchParams(); + + const open = searchParams.get("_open"); + + const { form, setForm } = useContext(ContextForgotPassword); + + const sendOTPMutation = useMutation({ + mutationFn: async () => { + return httpRequest({ + showMessageFailed: true, + showMessageSuccess: true, + msgSuccess: "Mã OTP đã được gửi đến email của bạn", + http: accountServices.sendOTP({ + email: form?.email, + }), + }); + }, + + onSuccess(data) { + if (!data) return; + + const params = new URLSearchParams(searchParams.toString()); + + params.set("_open", "otp"); + + router.replace(`?${params.toString()}`); + }, + }); + + const handleSendOTP = () => { + sendOTPMutation.mutate(); + }; + + const handleClosePopup = () => { + const params = new URLSearchParams(searchParams.toString()); + + params.delete("_open"); + + router.replace(`?${params.toString()}`); + }; + + return ( + + + + {/* INPUT EMAIL */} + + Email + * + + } + placeholder="Nhập email" + type="text" + name="email" + isEmail + onClean + isRequired + isBlur + icon={} + /> + + {/* BUTTON GROUP */} +
    + {/* BUTTON SUBMIT */} + + Lấy lại mật khẩu + + + {/* LINE */} +
    + + {/* LOGIN BUTTON */} + router.push(PATH.LOGIN)} + > + Đăng nhập ngay + +
    + + {/* POPUP OTP */} + + + + + ); +} + +export default FormEmail; diff --git a/src/components/page/auth/MainForgotPassword/components/FormEmail/index.ts b/src/components/page/auth/MainForgotPassword/components/FormEmail/index.ts new file mode 100644 index 0000000..28c8163 --- /dev/null +++ b/src/components/page/auth/MainForgotPassword/components/FormEmail/index.ts @@ -0,0 +1 @@ +export { default } from "./FormEmail"; diff --git a/src/components/page/auth/MainForgotPassword/components/FormOTP/FormOTP.tsx b/src/components/page/auth/MainForgotPassword/components/FormOTP/FormOTP.tsx new file mode 100644 index 0000000..09b0fb8 --- /dev/null +++ b/src/components/page/auth/MainForgotPassword/components/FormOTP/FormOTP.tsx @@ -0,0 +1,241 @@ +"use client"; + +import React, { useContext, useEffect, useState } from "react"; + +import { useRouter, useSearchParams } from "next/navigation"; +import { useMutation } from "@tanstack/react-query"; + +import { X } from "lucide-react"; + +import { ContextForgotPassword } from "../../context"; + +import { TYPE_FORGOT_PASWORD } from "../../interface"; + +import CustomButton from "@/components/customs/custom-button"; +import CustomLoading from "@/components/customs/custom-loading"; + +import { httpRequest } from "@/services"; +import accountServices from "@/services/accountServices"; +import InputSingle from "@/components/customs/custom-input"; +import fancyTimeFormat, { obfuscateEmail } from "@/common/funcs/optionConvert"; + +function FormOTP() { + const TIME_OTP = 60; + + const router = useRouter(); + + const searchParams = useSearchParams(); + + const [countDown, setCountDown] = useState(TIME_OTP); + + const { form, setForm, setType } = useContext(ContextForgotPassword); + + /* ========================= COUNTDOWN ========================= */ + useEffect(() => { + if (countDown <= 0) return; + + const timeout = setTimeout(() => { + setCountDown((prev) => prev - 1); + }, 1000); + + return () => clearTimeout(timeout); + }, [countDown]); + + /* ========================= CLOSE POPUP ========================= */ + const closeForm = () => { + const params = new URLSearchParams(searchParams.toString()); + + params.delete("_open"); + + router.replace(`?${params.toString()}`); + }; + + /* ========================= SEND OTP ========================= */ + const sendOTPMutation = useMutation({ + mutationFn: async () => { + return httpRequest({ + showMessageFailed: true, + showMessageSuccess: true, + msgSuccess: "Mã OTP đã được gửi đến email của bạn", + http: accountServices.sendOTP({ + email: form.email!, + }), + }); + }, + + onSuccess(data) { + if (data) { + setCountDown(TIME_OTP); + } + }, + }); + + /* ========================= VERIFY OTP ========================= */ + const submitOTPMutation = useMutation({ + mutationFn: async () => { + return httpRequest({ + showMessageFailed: true, + showMessageSuccess: true, + msgSuccess: "Xác thực thành công", + http: accountServices.enterOTP({ + email: form.email!, + otp: form.otp!, + }), + }); + }, + + onSuccess(data) { + if (data) { + setType(TYPE_FORGOT_PASWORD.PASSWORD); + + closeForm(); + } + }, + }); + + /* ========================= HANDLERS ========================= */ + const handleSendCode = () => { + sendOTPMutation.mutate(); + }; + + const handleSubmit = () => { + submitOTPMutation.mutate(); + }; + + return ( +
    + {/* LOADING */} + + + {/* TITLE */} +

    + Xác thực mã OTP +

    + + {/* DESCRIPTION */} +

    + Một mã xác thực đã được gửi cho bạn qua địa chỉ email: + {obfuscateEmail(form.email!)} +

    + + {/* FORM */} +
    + {/* LABEL */} +

    + Nhập mã OTP +

    + + {/* INPUT OTP */} +
    + +
    + + {/* COUNTDOWN */} +

    + Bạn chưa nhận được mã. + {countDown > 0 ? ( + + Gửi lại OTP ({fancyTimeFormat(countDown)}) + + ) : ( + + Gửi lại OTP + + )} +

    +
    + + {/* BUTTON */} +
    + + Xác thực Email + +
    + + {/* CLOSE */} + +
    + ); +} + +export default FormOTP; diff --git a/src/components/page/auth/MainForgotPassword/components/FormOTP/index.ts b/src/components/page/auth/MainForgotPassword/components/FormOTP/index.ts new file mode 100644 index 0000000..2b61278 --- /dev/null +++ b/src/components/page/auth/MainForgotPassword/components/FormOTP/index.ts @@ -0,0 +1 @@ +export { default } from "./FormOTP"; diff --git a/src/components/page/auth/MainForgotPassword/components/FormPassword/FormPassword.tsx b/src/components/page/auth/MainForgotPassword/components/FormPassword/FormPassword.tsx new file mode 100644 index 0000000..fa111f2 --- /dev/null +++ b/src/components/page/auth/MainForgotPassword/components/FormPassword/FormPassword.tsx @@ -0,0 +1,169 @@ +"use client"; + +import React, { useContext } from "react"; + +import { useRouter } from "next/navigation"; + +import { useMutation } from "@tanstack/react-query"; + +import { ShieldCheck } from "lucide-react"; + +import { useSelector } from "react-redux"; + +import md5 from "md5"; + +import { RootState, store } from "@/redux/store"; + +import { + setDataLoginStorage, + setStateLogin, + setToken, +} from "@/redux/reducer/auth"; + +import { setInfoUser } from "@/redux/reducer/user"; + +import { PATH } from "@/constant/config"; + +import { httpRequest } from "@/services"; +import authServices from "@/services/authServices"; + +import { ContextForgotPassword, IContextForgotPassword } from "../../context"; + +import InputForm from "@/components/utils/FormCustom/components/InputForm"; + +import CustomButton from "@/components/customs/custom-button"; +import CustomLoading from "@/components/customs/custom-loading"; +import FormCustom from "@/components/utils/FormCustom"; +import { ContextFormCustom } from "@/components/utils/FormCustom/contexts"; + +export default function FormPassword() { + const router = useRouter(); + + const { dataLoginStorage } = useSelector((state: RootState) => state.auth); + + const { isRememberPassword } = useSelector((state: RootState) => state.site); + + const { form, setForm } = useContext( + ContextForgotPassword, + ); + + const loginMutation = useMutation({ + mutationFn: async () => { + return httpRequest({ + showMessageFailed: true, + showMessageSuccess: true, + msgSuccess: "Đăng nhập thành công!", + + http: authServices.login({ + username: dataLoginStorage?.usernameStorage || "", + + password: md5( + `${form.password}_${process.env.NEXT_PUBLIC_KEY_PASSWORD}`, + ), + + ip: "", + address: "", + type: 0, + }), + }); + }, + + onSuccess(data: any) { + if (!data) return; + + store.dispatch(setStateLogin(true)); + + store.dispatch(setToken(data.accessToken)); + + store.dispatch( + setInfoUser({ + accessToken: data?.accessToken || "", + refreshToken: data?.refreshToken || "", + accessExpiresAt: data?.accessExpiresAt || "", + refreshExpiresAt: data?.refreshExpiresAt || "", + avatar: data?.avatar || "", + fullname: data?.fullname || "", + }), + ); + + if (isRememberPassword) { + store.dispatch( + setDataLoginStorage({ + usernameStorage: dataLoginStorage?.usernameStorage || "", + + passwordStorage: form.password, + }), + ); + } else { + store.dispatch(setDataLoginStorage(null)); + } + + router.replace(PATH.HOME); + }, + }); + + const handleSubmit = () => { + loginMutation.mutate(); + }; + + return ( + + + + {/* PASSWORD */} + + Mật khẩu mới + * + + } + placeholder="Nhập mật khẩu mới" + type="password" + name="password" + onClean + isRequired + isBlur + showDone + icon={} + /> + + {/* RE PASSWORD */} +
    + + Xác nhận mật khẩu mới + * + + } + placeholder="Xác nhận mật khẩu mới" + type="password" + name="rePassword" + valueConfirm={form.password} + onClean + isRequired + isBlur + showDone + icon={} + /> +
    + + {/* BUTTON */} +
    + + {({ isDone }) => ( + + Lấy lại mật khẩu + + )} + +
    +
    + ); +} diff --git a/src/components/page/auth/MainForgotPassword/components/FormPassword/index.ts b/src/components/page/auth/MainForgotPassword/components/FormPassword/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/components/page/auth/MainForgotPassword/context/index.ts b/src/components/page/auth/MainForgotPassword/context/index.ts new file mode 100644 index 0000000..8878104 --- /dev/null +++ b/src/components/page/auth/MainForgotPassword/context/index.ts @@ -0,0 +1,16 @@ +import { createContext, Dispatch, SetStateAction } from "react"; +import { IFormForgotPassword, TYPE_FORGOT_PASWORD } from "../interface"; + +export interface IContextForgotPassword { + form: IFormForgotPassword; + setForm: Dispatch>; + type: number; + setType: Dispatch>; +} + +export const ContextForgotPassword = createContext({ + form: { email: "", otp: "", password: "", rePassword: "" }, + setForm: () => null, + type: TYPE_FORGOT_PASWORD.EMAIL, + setType: () => null, +}); diff --git a/src/components/page/auth/MainForgotPassword/index.ts b/src/components/page/auth/MainForgotPassword/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/components/page/auth/MainForgotPassword/interface/index.ts b/src/components/page/auth/MainForgotPassword/interface/index.ts new file mode 100644 index 0000000..f531e95 --- /dev/null +++ b/src/components/page/auth/MainForgotPassword/interface/index.ts @@ -0,0 +1,11 @@ +export interface IFormForgotPassword { + email: string; + otp: string; + password: string; + rePassword: string; +} + +export enum TYPE_FORGOT_PASWORD { + EMAIL = 1, + PASSWORD, +} diff --git a/src/components/page/auth/MainLogin/MainLogin.tsx b/src/components/page/auth/MainLogin/MainLogin.tsx new file mode 100644 index 0000000..8e77298 --- /dev/null +++ b/src/components/page/auth/MainLogin/MainLogin.tsx @@ -0,0 +1,269 @@ +"use client"; + +import React, { useEffect, useState } from "react"; + +import { useRouter } from "next/navigation"; +import { useMutation } from "@tanstack/react-query"; +import { LockKeyhole, ShieldPlus, User } from "lucide-react"; +import { useSelector } from "react-redux"; + +import { RootState, store } from "@/redux/store"; + +import { + setDataLoginStorage, + setStateLogin, + setToken, +} from "@/redux/reducer/auth"; + +import { setRememberPassword } from "@/redux/reducer/site"; +import { setInfoUser } from "@/redux/reducer/user"; + +import authServices from "@/services/authServices"; +import { httpRequest } from "@/services"; + +import { PATH } from "@/constant/config"; + +import FormCustom from "@/components/utils/FormCustom"; +import InputForm from "@/components/utils/FormCustom/components/InputForm"; +import CustomLoading from "@/components/customs/custom-loading"; +// import FormCustom, { InputForm } from "@/components/common/FormCustom"; + +export default function MainLogin() { + const router = useRouter(); + + const { dataLoginStorage } = useSelector((state: RootState) => state.auth); + + const { isRememberPassword } = useSelector((state: RootState) => state.site); + + const [form, setForm] = useState({ + username: isRememberPassword ? dataLoginStorage?.usernameStorage || "" : "", + + password: isRememberPassword ? dataLoginStorage?.passwordStorage || "" : "", + }); + + const loginMutation = useMutation({ + mutationFn: async () => { + return httpRequest({ + showMessageFailed: true, + showMessageSuccess: true, + msgSuccess: "Đăng nhập thành công!", + http: authServices.login({ + username: form.username, + password: form.password, + ip: "", + address: "", + type: 0, + }), + }); + }, + + onSuccess(data: any) { + if (!data) return; + + store.dispatch(setStateLogin(true)); + + store.dispatch(setToken(data.accessToken)); + + store.dispatch( + setInfoUser({ + accessToken: data?.accessToken || "", + refreshToken: data?.refreshToken || "", + accessExpiresAt: data?.accessExpiresAt || "", + refreshExpiresAt: data?.refreshExpiresAt || "", + avatar: data?.avatar || "", + fullname: data?.fullname || "", + }), + ); + + if (isRememberPassword) { + store.dispatch( + setDataLoginStorage({ + usernameStorage: form.username, + passwordStorage: form.password, + }), + ); + } else { + store.dispatch(setDataLoginStorage(null)); + } + + router.replace(PATH.HOME); + }, + }); + + const handleLogin = () => { + loginMutation.mutate(); + }; + + return ( +
    + + + + {/* TITLE */} +

    + Đăng nhập +

    + +

    + Chào mừng bạn đến với hệ thống quản lý +

    + + {/* FORM */} +
    + {/* USERNAME */} + + Tài khoản + * + + } + placeholder="Tài khoản" + type="text" + name="username" + onClean + isRequired + isBlur + showDone + icon={} + /> + + {/* PASSWORD */} +
    + + Mật khẩu + * + + } + placeholder="Mật khẩu" + type="password" + name="password" + onClean + isRequired + isBlur + showDone + icon={} + /> +
    + + {/* REMEMBER PASSWORD */} +
    + + store.dispatch(setRememberPassword(!isRememberPassword)) + } + className=" + h-5 + w-5 + cursor-pointer + accent-[#0011AB] + " + /> + + +
    + + {/* BUTTONS */} +
    + {/* LOGIN */} + + + {/* LINE */} +
    + + {/* FORGOT PASSWORD */} + +
    +
    + +
    + ); +} diff --git a/src/components/page/auth/MainLogin/index.ts b/src/components/page/auth/MainLogin/index.ts new file mode 100644 index 0000000..efa3c86 --- /dev/null +++ b/src/components/page/auth/MainLogin/index.ts @@ -0,0 +1 @@ +export { default } from "./MainLogin"; diff --git a/src/components/protected/LoadingTopBar/LoadingTopBar.tsx b/src/components/protected/LoadingTopBar/LoadingTopBar.tsx new file mode 100644 index 0000000..ea88219 --- /dev/null +++ b/src/components/protected/LoadingTopBar/LoadingTopBar.tsx @@ -0,0 +1,35 @@ +"use client"; + +import { useEffect } from "react"; +import NProgress from "nprogress"; +import "nprogress/nprogress.css"; + +function LoadingTopBar() { + useEffect(() => { + NProgress.configure({ showSpinner: false }); + + const handleClick = (e: any) => { + const target = e.target.closest("a"); + + if (target?.href && target.href.startsWith(window.location.origin)) { + NProgress.start(); + } + }; + + const handleStop = () => { + NProgress.done(); + }; + + window.addEventListener("click", handleClick); + window.addEventListener("load", handleStop); + + return () => { + window.removeEventListener("click", handleClick); + window.removeEventListener("load", handleStop); + }; + }, []); + + return null; +} + +export default LoadingTopBar; diff --git a/src/components/protected/LoadingTopBar/index.ts b/src/components/protected/LoadingTopBar/index.ts new file mode 100644 index 0000000..6219428 --- /dev/null +++ b/src/components/protected/LoadingTopBar/index.ts @@ -0,0 +1 @@ +export { default } from "./LoadingTopBar"; diff --git a/src/components/protected/RequiredAuth/index.tsx b/src/components/protected/RequiredAuth/index.tsx new file mode 100644 index 0000000..011c0ed --- /dev/null +++ b/src/components/protected/RequiredAuth/index.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { PATH } from "@/constant/config"; +import { RootState } from "@/redux/store"; +import { useEffect } from "react"; +import { useRouter } from "next/navigation"; +import { useSelector } from "react-redux"; + +interface IRequireAuthProps { + children: React.ReactNode; +} + +export default function RequireAuth(props: IRequireAuthProps) { + const router = useRouter(); + + const { loading } = useSelector((state: RootState) => state.site); + const { isLogin } = useSelector((state: RootState) => state.auth); + + useEffect(() => { + if (!isLogin && !loading) { + router.replace(PATH.LOGIN); + } + }, [isLogin, loading, router]); + + if (isLogin && !loading) { + return <>{props.children}; + } + + return
    ; +} diff --git a/src/components/protected/RequiredLogout/index.tsx b/src/components/protected/RequiredLogout/index.tsx new file mode 100644 index 0000000..cc8b369 --- /dev/null +++ b/src/components/protected/RequiredLogout/index.tsx @@ -0,0 +1,34 @@ +//********************** +//* COMPONENT PROTECTED SCREEN THEN LOGIN +//********************** + +"use client"; + +import React, { useEffect } from "react"; + +import { RootState } from "@/redux/store"; +import { useRouter } from "next/navigation"; +import { useSelector } from "react-redux"; +import { PATH } from "@/constant/config"; + +interface props { + children: React.ReactNode; +} + +function RequiredLogout({ children }: props) { + const { replace } = useRouter(); + const { isLogin } = useSelector((state: RootState) => state.auth); + const { loading } = useSelector((state: RootState) => state.site); + + useEffect(() => { + if (isLogin && !loading) replace(PATH.HOME); + }, [isLogin, loading, replace]); + + if (!isLogin && !loading) { + return <>{children}; + } + + return
    ; +} + +export default RequiredLogout; diff --git a/src/components/protected/SplashScreen/SplashScreen.tsx b/src/components/protected/SplashScreen/SplashScreen.tsx new file mode 100644 index 0000000..a78c6a1 --- /dev/null +++ b/src/components/protected/SplashScreen/SplashScreen.tsx @@ -0,0 +1,111 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { RootState, store } from "@/redux/store"; +import { useSelector } from "react-redux"; +import clsx from "clsx"; +import Lottie from "lottie-react"; + +import { getItemStorage, setItemStorage } from "@/common/funcs/localStorage"; +import { KEY_STORE } from "@/constant/config"; + +import { setLoading, setRememberPassword } from "@/redux/reducer/site"; +import { + setDataLoginStorage, + setStateLogin, + setToken, +} from "@/redux/reducer/auth"; +import { setInfoUser } from "@/redux/reducer/user"; + +import loadingAnim from "@/../public/static/anim/loading_screen.json"; + +function SplashScreen() { + const [isClosing, setIsClosing] = useState(false); + + const { loading, isRememberPassword } = useSelector( + (state: RootState) => state.site, + ); + const { infoUser } = useSelector((state: RootState) => state.user); + const { token, isLogin, dataLoginStorage } = useSelector( + (state: RootState) => state.auth, + ); + + // 🔥 Load state từ localStorage + useEffect(() => { + (async () => { + const encryptedState = await getItemStorage(KEY_STORE); + const SECRET_KEY = process.env.NEXT_PUBLIC_SECRET_KEY || "default_key"; + + const decode = (data: string): any => { + try { + const decoded = decodeURIComponent(atob(data)); + const clean = decoded.startsWith(SECRET_KEY) + ? decoded.slice(SECRET_KEY.length) + : null; + return clean ? JSON.parse(clean) : null; + } catch (err) { + console.error("Decode error:", err); + return null; + } + }; + + const state = + typeof encryptedState === "string" ? decode(encryptedState) : null; + + if (state) { + store.dispatch(setToken(state.token)); + store.dispatch(setStateLogin(state.isLogin)); + store.dispatch(setInfoUser(state.infoUser)); + store.dispatch(setRememberPassword(state.isRememberPassword)); + store.dispatch(setDataLoginStorage(state.dataLoginStorage)); + } + + store.dispatch(setLoading(false)); + })(); + }, []); + + // 🔥 Save lại state + useEffect(() => { + if (!loading) { + const SECRET_KEY = process.env.NEXT_PUBLIC_SECRET_KEY || "default_key"; + + const encode = (data: string): string => { + const textToEncode = SECRET_KEY + data; + return btoa(encodeURIComponent(textToEncode)); + }; + + const hashedData = encode( + JSON.stringify({ + isLogin, + token, + infoUser, + isRememberPassword, + dataLoginStorage, + }), + ); + + setItemStorage(KEY_STORE, hashedData); + + // 👉 trigger animation fade out + setTimeout(() => setIsClosing(true), 0); + } + }, [loading, isLogin, token, infoUser, isRememberPassword, dataLoginStorage]); + + return ( +
    +
    + +
    +
    + ); +} + +export default SplashScreen; diff --git a/src/components/protected/SplashScreen/index.ts b/src/components/protected/SplashScreen/index.ts new file mode 100644 index 0000000..076d132 --- /dev/null +++ b/src/components/protected/SplashScreen/index.ts @@ -0,0 +1 @@ +export { default } from "./SplashScreen"; diff --git a/src/components/utils/FormCustom/FormCustom.tsx b/src/components/utils/FormCustom/FormCustom.tsx new file mode 100644 index 0000000..36c61cf --- /dev/null +++ b/src/components/utils/FormCustom/FormCustom.tsx @@ -0,0 +1,70 @@ +"use client"; + +import React, { useCallback, useMemo, useState } from "react"; + +import { PropsFormCustom } from "./interface"; +import { ContextFormCustom } from "./contexts"; + +function FormCustom>({ + form, + setForm, + onSubmit, + children, +}: PropsFormCustom) { + // 👉 tạo object error ban đầu + const initialError = useMemo(() => { + return Object.fromEntries( + Object.keys(form).map((key) => [key, null]), + ) as Record; + }, [form]); + + const [countValidate, setCountValidate] = useState(0); + const [errorText, setErrorText] = + useState>(initialError); + + const [validate, setValidate] = useState | null>( + null, + ); + + // 👉 check form hợp lệ + const isDone = useMemo(() => { + if (!validate) return false; + + return Object.values(validate).every(Boolean); + }, [validate]); + + // 👉 submit form + const handleSubmit = useCallback( + (e: React.FormEvent) => { + e.preventDefault(); + + if (isDone) { + onSubmit?.(); + } else { + setCountValidate((prev) => prev + 1); + } + }, + [isDone, onSubmit], + ); + + return ( + +
    + {children} +
    +
    + ); +} + +export default FormCustom; diff --git a/src/components/utils/FormCustom/components/InputForm/InputForm.tsx b/src/components/utils/FormCustom/components/InputForm/InputForm.tsx new file mode 100644 index 0000000..0d26197 --- /dev/null +++ b/src/components/utils/FormCustom/components/InputForm/InputForm.tsx @@ -0,0 +1,401 @@ +"use client"; + +import React, { useContext, useMemo, useState } from "react"; + +import clsx from "clsx"; + +import { Check, CircleX, Eye, EyeOff } from "lucide-react"; + +import { validateEmail, validatePhoneNumber } from "@/common/funcs/validate"; + +import { convertCoin, price } from "@/common/funcs/convertCoin"; + +import { ContextFormCustom, IContextFormCustom } from "../../contexts"; + +import { PropsInputForm } from "./interface"; + +function InputForm({ + type, + name, + placeholder, + icon, + label, + value, + unit, + note, + textRequired, + textConfirm, + valueConfirm, + isBlur = true, + onClean, + showDone, + readOnly, + max, + min, + isRequired, + isNumber, + isUppercase, + isPhone, + isEmail, + isMoney, + onBlur, + onChangeValue, +}: PropsInputForm) { + const isPassword = type === "password"; + + const [showPass, setShowPass] = useState(false); + + const [isFocus, setIsFocus] = useState(false); + + const { + form, + countValidate, + errorText, + isDone, + setForm, + setValidate, + setErrorText, + } = useContext>(ContextFormCustom); + + // ========================= + // VALUE + // ========================= + + const currentValue = useMemo(() => { + return value ?? form?.[name] ?? ""; + }, [value, form, name]); + + // ========================= + // VALIDATE + // ========================= + + const handleValidate = (inputValue?: string | number) => { + const finalValue = String(inputValue ?? currentValue ?? ""); + + if (isRequired && finalValue.trim() === "") { + return { + valid: false, + message: textRequired || "Vui lòng nhập trường này", + }; + } + + if (finalValue) { + if (isNumber && isNaN(Number(finalValue))) { + return { + valid: false, + message: "Vui lòng chỉ nhập số", + }; + } + + if (isPhone && !validatePhoneNumber(finalValue)) { + return { + valid: false, + message: "Định dạng số điện thoại không đúng", + }; + } + + if (isEmail && !validateEmail(finalValue)) { + return { + valid: false, + message: "Định dạng email không chính xác", + }; + } + + if (valueConfirm && finalValue !== valueConfirm) { + return { + valid: false, + message: textConfirm || "Mật khẩu không trùng khớp", + }; + } + + if (max && finalValue.length > max) { + return { + valid: false, + message: `Nhập tối đa ${max} kí tự`, + }; + } + + if (min && finalValue.length < min) { + return { + valid: false, + message: `Nhập tối thiểu ${min} kí tự`, + }; + } + } + + return { + valid: true, + message: null, + }; + }; + + // ========================= + // ERROR + // ========================= + + const handleSetMessage = (inputValue?: string | number) => { + const result = handleValidate(inputValue); + + setErrorText((prev) => ({ + ...prev, + [name]: result.message, + })); + + setValidate((prev) => ({ + ...prev, + [name]: result.valid, + })); + }; + + // ========================= + // HANDLERS + // ========================= + + const handleFocus = () => { + setIsFocus(true); + + setErrorText((prev) => ({ + ...prev, + [name]: null, + })); + }; + + const handleBlur = () => { + setIsFocus(false); + + onBlur?.(); + + if (isBlur) { + handleSetMessage(); + } + }; + + const handleChange = (e: React.ChangeEvent) => { + let inputValue = e.target.value; + + if (isUppercase) { + inputValue = inputValue.toUpperCase(); + } + + // MONEY + if (isMoney) { + const numericValue = Number(price(inputValue)); + + inputValue = numericValue ? convertCoin(numericValue) : "0"; + } + + // CUSTOM CHANGE + if (onChangeValue) { + onChangeValue(inputValue); + return; + } + + // UPDATE FORM + setForm((prev: any) => ({ + ...prev, + [name]: inputValue, + })); + + // VALIDATE LIVE + if (countValidate > 0) { + handleSetMessage(inputValue); + } + }; + + const handleClean = () => { + setForm((prev: any) => ({ + ...prev, + [name]: "", + })); + + setErrorText((prev) => ({ + ...prev, + [name]: null, + })); + + setValidate((prev) => ({ + ...prev, + [name]: !isRequired, + })); + }; + + // ========================= + // RENDER + // ========================= + + return ( +
    + {/* LABEL */} + {label && ( + + )} + + {/* INPUT WRAPPER */} +
    + {/* ICON */} + {icon && ( +
    + {icon} +
    + )} + + {/* INPUT */} + + + {/* UNIT */} + {unit ? ( +
    + {unit} +
    + ) : ( +
    + {/* CLEAN */} + {onClean && !!currentValue && ( + + )} + + {/* DONE */} + {!isPassword && + showDone && + isDone && + !errorText?.[name] && + !!currentValue && } + + {/* PASSWORD */} + {isPassword && ( + + )} +
    + )} +
    + + {/* ERROR */} + {errorText?.[name] && ( +

    + {errorText[name]} +

    + )} + + {/* NOTE */} + {note && ( +

    + {note} +

    + )} +
    + ); +} + +export default InputForm; diff --git a/src/components/utils/FormCustom/components/InputForm/index.ts b/src/components/utils/FormCustom/components/InputForm/index.ts new file mode 100644 index 0000000..2d6bc1c --- /dev/null +++ b/src/components/utils/FormCustom/components/InputForm/index.ts @@ -0,0 +1 @@ +export { default } from "./InputForm"; diff --git a/src/components/utils/FormCustom/components/InputForm/interface/index.ts b/src/components/utils/FormCustom/components/InputForm/interface/index.ts new file mode 100644 index 0000000..7942454 --- /dev/null +++ b/src/components/utils/FormCustom/components/InputForm/interface/index.ts @@ -0,0 +1,35 @@ +import { Dispatch, SetStateAction } from "react"; + +export interface PropsInputForm { + type: string; + name: string; + placeholder: string; + + icon?: React.ReactNode; + label?: string | React.ReactNode; + + value?: string | number; + unit?: string; + note?: string; + textRequired?: string; + valueConfirm?: string; + textConfirm?: string; + + isBlur?: boolean; + onClean?: boolean; + showDone?: boolean; + readOnly?: boolean; + + max?: number; + min?: number; + + isRequired?: boolean; + isNumber?: boolean; + isPhone?: boolean; + isEmail?: boolean; + isMoney?: boolean; + isUppercase?: boolean; + + onBlur?: () => void; + onChangeValue?: (val: string | number) => void; +} diff --git a/src/components/utils/FormCustom/components/SelectForm/SelectForm.tsx b/src/components/utils/FormCustom/components/SelectForm/SelectForm.tsx new file mode 100644 index 0000000..791eb36 --- /dev/null +++ b/src/components/utils/FormCustom/components/SelectForm/SelectForm.tsx @@ -0,0 +1,175 @@ +"use client"; + +import React, { useEffect, useRef, useState } from "react"; +import clsx from "clsx"; +import { ChevronDown, Search, CircleX } from "lucide-react"; +import Image from "next/image"; + +import { removeVietnameseTones } from "@/common/funcs/optionConvert"; +import icons from "@/constant/images/icons"; +import { PropsSelectForm } from "./interface"; + +function SelectForm({ + label, + placeholder = "Chọn...", + isSearch = true, + readOnly, + value, + options = [], // ✅ FIX NULL + onClean, + onSelect, + getOptionLabel, + getOptionValue, +}: PropsSelectForm) { + const refWrapper = useRef(null); + const refInput = useRef(null); + + const [open, setOpen] = useState(false); + const [keyword, setKeyword] = useState(""); + + /* ================= CLICK OUTSIDE ================= */ + useEffect(() => { + const handleClickOutside = (e: MouseEvent) => { + if (!refWrapper.current?.contains(e.target as Node)) { + setOpen(false); + setKeyword(""); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + + /* ================= FOCUS SEARCH ================= */ + useEffect(() => { + if (open && isSearch) { + setTimeout(() => { + refInput.current?.focus(); + }, 0); + } + }, [open, isSearch]); + + /* ================= DATA ================= */ + const safeOptions = options ?? []; + + const selectedOption = safeOptions.find( + (opt) => getOptionValue(opt) === value, + ); + + const filteredOptions = safeOptions.filter((opt) => { + const label = removeVietnameseTones(getOptionLabel(opt) || ""); + const key = removeVietnameseTones(keyword || ""); + return label.includes(key); + }); + + /* ================= HANDLER ================= */ + const handleSelect = (opt: OptionType) => { + onSelect?.(opt); + setOpen(false); + setKeyword(""); + }; + + return ( +
    + {/* LABEL */} + {label && ( + + )} + + {/* SELECT BOX */} +
    !readOnly && setOpen((prev) => !prev)} + className={clsx( + "flex items-center justify-between px-4 h-12 border rounded-full cursor-pointer transition", + "border-gray-300 bg-white hover:border-blue-600", + open && "border-blue-600", + readOnly && + "bg-gray-100 border-gray-200 cursor-not-allowed hover:border-gray-200", + )} + > + {/* VALUE */} +

    + {selectedOption ? getOptionLabel(selectedOption) : placeholder} +

    + + {/* CLEAR */} + {onClean && !!value && !readOnly && ( +
    { + e.stopPropagation(); + onClean(); + }} + className="mr-2 hover:opacity-70" + > + +
    + )} + + {/* ARROW */} + +
    + + {/* DROPDOWN */} + {open && ( +
    + {/* SEARCH */} + {isSearch && ( +
    + + setKeyword(e.target.value)} + className="flex-1 bg-transparent outline-none text-sm" + /> +
    + )} + + {/* LIST */} + {filteredOptions.length > 0 ? ( +
    + {filteredOptions.map((opt) => { + const active = getOptionValue(opt) === value; + + return ( +
    handleSelect(opt)} + className={clsx( + "px-4 py-2 text-sm font-medium rounded-2xl cursor-pointer transition", + active + ? "bg-blue-700 text-white" + : "hover:bg-blue-700 hover:text-white", + )} + > + {getOptionLabel(opt)} +
    + ); + })} +
    + ) : ( +
    + empty +

    Danh sách lựa chọn rỗng!

    +
    + )} +
    + )} +
    + ); +} + +export default SelectForm; diff --git a/src/components/utils/FormCustom/components/SelectForm/index.ts b/src/components/utils/FormCustom/components/SelectForm/index.ts new file mode 100644 index 0000000..0470c2e --- /dev/null +++ b/src/components/utils/FormCustom/components/SelectForm/index.ts @@ -0,0 +1 @@ +export { default } from "./SelectForm"; diff --git a/src/components/utils/FormCustom/components/SelectForm/interface/index.ts b/src/components/utils/FormCustom/components/SelectForm/interface/index.ts new file mode 100644 index 0000000..d674375 --- /dev/null +++ b/src/components/utils/FormCustom/components/SelectForm/interface/index.ts @@ -0,0 +1,15 @@ +export interface PropsSelectForm { + placeholder: string; + label?: string | React.ReactNode; + + isSearch?: boolean; + readOnly?: boolean; + + value: string | number; + options: OptionType[]; + + onClean?: () => void; + onSelect: (option: OptionType) => void; + getOptionLabel: (option: OptionType) => string; + getOptionValue: (option: OptionType) => string | number; +} diff --git a/src/components/utils/FormCustom/components/SelectMany/SelectMany.tsx b/src/components/utils/FormCustom/components/SelectMany/SelectMany.tsx new file mode 100644 index 0000000..1d4cec2 --- /dev/null +++ b/src/components/utils/FormCustom/components/SelectMany/SelectMany.tsx @@ -0,0 +1,261 @@ +"use client"; + +import React, { Fragment, useEffect, useRef, useState } from "react"; +import clsx from "clsx"; +import { CirclePlus, Trash, Search, X } from "lucide-react"; +import { removeVietnameseTones } from "@/common/funcs/optionConvert"; +import Image from "next/image"; +import icons from "@/constant/images/icons"; +import Popup from "@/components/customs/custom-popup"; +import CustomButton from "@/components/customs/custom-button"; + +import { PropsSelectMany } from "./interface"; + +function SelectMany({ + text, + label, + placeholder, + isSearch = true, + showSelectedItems = false, + readOnly, + disabledItems = [], + selectedItems = [], + options = [], + title, + onClickSelect, + setSelectedItems, + getOptionLabel, + getOptionValue, + onRemove, + action, + renderOption, + selectedItemFields, + selectedItemFieldLabels, +}: PropsSelectMany) { + const refInputSearch = useRef(null); + + const [keyword, setKeyword] = useState(""); + const [isFocus, setIsFocus] = useState(false); + const [listDataTerm, setListDataTerm] = + useState>(selectedItems); + + useEffect(() => { + if (isFocus) { + setTimeout(() => refInputSearch.current?.focus(), 0); + } + }, [isFocus]); + + const filteredOptions = options.filter((opt) => + removeVietnameseTones(String(getOptionLabel(opt) || "")).includes( + removeVietnameseTones(keyword), + ), + ); + + const handleOptionClick = (option: OptionType) => { + const value = getOptionValue(option); + if (disabledItems.includes(value)) return; + + setListDataTerm((prev) => + prev.includes(value) ? prev.filter((v) => v !== value) : [...prev, value], + ); + + setKeyword(""); + }; + + const handleRemoveSelected = (value: string | number) => { + const updated = listDataTerm.filter((v) => v !== value); + setListDataTerm(updated); + + onRemove ? onRemove(value) : setSelectedItems?.(updated); + }; + + const handleClose = () => { + setKeyword(""); + setIsFocus(false); + + // reset lại data cũ + setTimeout(() => { + setListDataTerm(selectedItems); + }, 0); + }; + + const handleSaveData = () => { + setIsFocus(false); + setSelectedItems?.(listDataTerm); + }; + + return ( + +
    + {label && ( + + )} + + {/* Select box */} +
    !readOnly && setIsFocus(true)} + className={clsx( + "flex items-center justify-between h-12 px-4 rounded-full border cursor-pointer transition", + "border-gray-300 bg-white hover:border-blue-700", + isFocus && "border-blue-700", + readOnly && "bg-gray-100 border-gray-200 cursor-not-allowed", + )} + > +

    + {selectedItems.length > 0 + ? `Đã chọn ${selectedItems.length} ${text}` + : placeholder} +

    + + +
    + + {/* Selected chips */} + {showSelectedItems === "default" && ( +
    + {selectedItems + .map((v) => options.find((o) => getOptionValue(o) === v)) + .filter(Boolean) + .map((item) => { + const value = getOptionValue(item!); + const disabled = disabledItems.includes(value); + + return ( +
    + {getOptionLabel(item!)} + handleRemoveSelected(value)} + /> +
    + ); + })} +
    + )} +
    + + {/* Popup */} + +
    + {/* Title */} +

    {title || "Danh sách"}

    + + {/* Search */} + {isSearch && ( +
    + + setKeyword(e.target.value)} + placeholder="Tìm kiếm..." + className="flex-1 outline-none bg-transparent text-sm" + /> +
    + )} + + {/* Select all */} + {filteredOptions.length > 0 && ( +
    +
    + + setListDataTerm( + e.target.checked ? options.map(getOptionValue) : [], + ) + } + className="w-5 h-5 accent-blue-500" + /> + Chọn tất cả +
    + +

    + Đã chọn:{" "} + {listDataTerm.length} +

    +
    + )} + + {/* Options */} + {filteredOptions.length > 0 ? ( +
    + {filteredOptions.map((opt) => { + const value = getOptionValue(opt); + const checked = listDataTerm.includes(value); + + return ( +
    + handleOptionClick(opt)} + disabled={disabledItems.includes(value)} + className="w-5 h-5 accent-blue-500" + /> + +
    + ); + })} +
    + ) : ( +
    + empty +

    Danh sách lựa chọn rỗng!

    +
    + )} + + {/* Buttons */} +
    + + Hủy bỏ + + + + Xác nhận + +
    + + {/* Close */} +
    + +
    +
    +
    +
    + ); +} + +export default SelectMany; diff --git a/src/components/utils/FormCustom/components/SelectMany/index.ts b/src/components/utils/FormCustom/components/SelectMany/index.ts new file mode 100644 index 0000000..ed5c741 --- /dev/null +++ b/src/components/utils/FormCustom/components/SelectMany/index.ts @@ -0,0 +1 @@ +export { default } from "./SelectMany"; diff --git a/src/components/utils/FormCustom/components/SelectMany/interface/index.ts b/src/components/utils/FormCustom/components/SelectMany/interface/index.ts new file mode 100644 index 0000000..35749c3 --- /dev/null +++ b/src/components/utils/FormCustom/components/SelectMany/interface/index.ts @@ -0,0 +1,27 @@ +import { Dispatch, SetStateAction } from "react"; + +export interface PropsSelectMany { + text: string; + placeholder: string; + label?: string | React.ReactNode; + + isSearch?: boolean; + readOnly?: boolean; + showSelectedItems?: false | "default" | "input"; + + disabledItems?: Array; + selectedItems: Array; + options: OptionType[]; + title?: string; + + onClickSelect?: () => void; + setSelectedItems?: Dispatch>>; + getOptionLabel: (option: OptionType) => string | React.ReactNode; + getOptionValue: (option: OptionType) => string | number; + onRemove?: (item: string | number) => void; + + action?: React.ReactNode; + renderOption?: (option: OptionType) => React.ReactNode; + selectedItemFields?: (keyof OptionType)[]; + selectedItemFieldLabels?: Record; +} diff --git a/src/components/utils/FormCustom/components/SelectSingle/SelectSingle.tsx b/src/components/utils/FormCustom/components/SelectSingle/SelectSingle.tsx new file mode 100644 index 0000000..60033ef --- /dev/null +++ b/src/components/utils/FormCustom/components/SelectSingle/SelectSingle.tsx @@ -0,0 +1,206 @@ +"use client"; + +import React, { useEffect, useRef, useState } from "react"; +import clsx from "clsx"; +import { X, Search } from "lucide-react"; + +import Image from "next/image"; + +import { removeVietnameseTones } from "@/common/funcs/optionConvert"; +import icons from "@/constant/images/icons"; +import Popup from "@/components/customs/custom-popup"; +import CustomButton from "@/components/customs/custom-button"; +import { PropsSelectSingle } from "./interface"; + +function SelectSingle({ + text = "", + label, + placeholder = "Chọn 1 giá trị", + isSearch = true, + readOnly, + disabledItems = [], + selectedItem, + options, + title, + action, + getOptionLabel, + getOptionValue, + setSelectedItem, + onClickSelect, + renderOption, +}: PropsSelectSingle) { + const refInputSearch = useRef(null); + + const [keyword, setKeyword] = useState(""); + const [isFocus, setIsFocus] = useState(false); + const [selectedTemp, setSelectedTemp] = useState( + null, + ); + + useEffect(() => { + if (isFocus && refInputSearch.current) { + setTimeout(() => refInputSearch.current?.focus(), 0); + } + }, [isFocus]); + + const handlerFocused = () => { + if (onClickSelect) { + onClickSelect(); + return; + } + + if (readOnly) return; + + // sync dữ liệu tại đây + setSelectedTemp(selectedItem ?? null); + + setIsFocus(true); + }; + + const handleSelect = (value: string | number) => { + if (!disabledItems.includes(value)) { + setSelectedTemp(value); + } + }; + + const handleSave = () => { + setSelectedItem?.(selectedTemp); + setIsFocus(false); + }; + + const handleClose = () => { + setIsFocus(false); + setKeyword(""); + setSelectedTemp(selectedItem ?? null); + }; + + const selectedLabel = (() => { + const selectedOption = options.find( + (opt) => getOptionValue(opt) === selectedItem, + ); + return selectedOption + ? getOptionLabel(selectedOption) + : `Đã chọn 1 ${text}`; + })(); + + const filteredOptions = options.filter((opt) => + removeVietnameseTones(getOptionLabel(opt)).includes( + removeVietnameseTones(keyword), + ), + ); + + return ( +
    + {label && ( + + )} + + {/* Select box */} +
    +

    + {selectedItem ? selectedLabel : placeholder} +

    +
    + + {/* Popup */} + +
    + {/* Header */} +
    +

    + {title || "Chọn 1 giá trị"} +

    + + {isSearch && ( +
    +
    + + setKeyword(e.target.value)} + placeholder="Tìm kiếm..." + className="flex-1 ml-2 outline-none bg-transparent" + /> +
    + + {action &&
    {action}
    } +
    + )} +
    + + {/* List */} + {filteredOptions.length > 0 ? ( +
    + {filteredOptions.map((opt) => { + const value = getOptionValue(opt); + return ( +
    + handleSelect(value)} + disabled={disabledItems.includes(value)} + className="w-5 h-5 accent-[#0011AB]" + /> + +
    + ); + })} +
    + ) : ( +
    + empty +

    Danh sách lựa chọn rỗng!

    +
    + )} + + {/* Footer */} +
    + + Hủy bỏ + + + + Xác nhận + +
    + + {/* Close */} +
    + +
    +
    +
    +
    + ); +} + +export default SelectSingle; diff --git a/src/components/utils/FormCustom/components/SelectSingle/index.ts b/src/components/utils/FormCustom/components/SelectSingle/index.ts new file mode 100644 index 0000000..5e9f46e --- /dev/null +++ b/src/components/utils/FormCustom/components/SelectSingle/index.ts @@ -0,0 +1 @@ +export { default } from "./SelectSingle"; diff --git a/src/components/utils/FormCustom/components/SelectSingle/interface/index.ts b/src/components/utils/FormCustom/components/SelectSingle/interface/index.ts new file mode 100644 index 0000000..99c80ab --- /dev/null +++ b/src/components/utils/FormCustom/components/SelectSingle/interface/index.ts @@ -0,0 +1,17 @@ +export interface PropsSelectSingle { + text?: string; + label?: React.ReactNode; + placeholder?: string; + isSearch?: boolean; + readOnly?: boolean; + disabledItems?: (string | number)[]; + selectedItem?: string | number | null; + options: OptionType[]; + title?: string; + action?: React.ReactNode; + getOptionLabel: (opt: OptionType) => string; + getOptionValue: (opt: OptionType) => string | number; + setSelectedItem?: (value: string | number | null) => void; + renderOption?: (option: OptionType) => React.ReactNode; + onClickSelect?: () => void; +} diff --git a/src/components/utils/FormCustom/components/TextArea/TextArea.tsx b/src/components/utils/FormCustom/components/TextArea/TextArea.tsx new file mode 100644 index 0000000..85f19d2 --- /dev/null +++ b/src/components/utils/FormCustom/components/TextArea/TextArea.tsx @@ -0,0 +1,192 @@ +"use client"; + +import React, { useContext, useEffect, useState } from "react"; +import clsx from "clsx"; + +import { PropsTextArea } from "./interface"; +import { ContextFormCustom, IContextFormCustom } from "../../contexts"; + +function TextArea({ + name, + value, + placeholder, + label, + isBlur = true, + showDone, + readOnly, + max, + min, + isRequired, + textRequired, +}: PropsTextArea) { + const [isFocus, setIsFocus] = useState(false); + + const { + form, + countValidate, + errorText, + isDone, + setForm, + setValidate, + setErrorText, + } = useContext>(ContextFormCustom); + + // ========================= + // VALIDATE + // ========================= + + const handleValidate = (): boolean => { + const currentValue = `${form[name] || ""}`.trim(); + + if (isRequired && currentValue === "") return false; + + if (min && currentValue.length < min) return false; + + if (max && currentValue.length > max) return false; + + return true; + }; + + // ========================= + // ERROR MESSAGE + // ========================= + + const handleSetMessage = () => { + const currentValue = `${form[name] || ""}`.trim(); + + setErrorText((prev) => ({ + ...prev, + [name]: null, + })); + + if (isRequired && currentValue === "") { + return setErrorText((prev) => ({ + ...prev, + [name]: textRequired || "Vui lòng nhập trường này", + })); + } + + if (max && currentValue.length > max) { + return setErrorText((prev) => ({ + ...prev, + [name]: `Nhập tối đa ${max} kí tự`, + })); + } + + if (min && currentValue.length < min) { + return setErrorText((prev) => ({ + ...prev, + [name]: `Nhập tối thiểu ${min} kí tự`, + })); + } + }; + + // ========================= + // EFFECTS + // ========================= + + useEffect(() => { + if (countValidate > 0) { + handleSetMessage(); + } + }, [countValidate]); + + useEffect(() => { + setValidate((prev) => ({ + ...prev, + [name]: handleValidate(), + })); + }, [form[name]]); + + // ========================= + // EVENTS + // ========================= + + const handlerFocused = () => { + setIsFocus(true); + + setErrorText((prev) => ({ + ...prev, + [name]: null, + })); + }; + + const handlerBlur = () => { + setIsFocus(false); + + if (isBlur) { + handleSetMessage(); + + setValidate((prev) => ({ + ...prev, + [name]: handleValidate(), + })); + } + }; + + const handleChange = (e: React.ChangeEvent) => { + const { value } = e.target; + + setForm((prev: any) => ({ + ...prev, + [name]: value, + })); + }; + + return ( +
    + {/* LABEL */} + {label && ( + + )} + + {/* TEXTAREA */} +