diff --git a/.gitignore b/.gitignore index 8d6f61e..cff2732 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,6 @@ yarn.lock *.sln *.sw? **/src/*.js -.env \ No newline at end of file +.env + +package-lock.json diff --git a/package-lock.json b/package-lock.json index 3c55c14..f0ff65e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -71,6 +71,7 @@ "styled-components": "^6.1.13", "stylis": "^4.3.4", "stylis-plugin-rtl": "^2.1.1", + "tabs": "^0.2.0", "tailwind-merge": "^2.5.4", "tailwindcss-animate": "^1.0.7", "vite-plugin-windicss": "^1.9.3", @@ -106,7 +107,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, "engines": { "node": ">=10" }, @@ -1593,7 +1593,6 @@ "cpu": [ "ppc64" ], - "dev": true, "optional": true, "os": [ "aix" @@ -1609,7 +1608,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "android" @@ -1625,7 +1623,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "android" @@ -1641,7 +1638,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "android" @@ -1657,7 +1653,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -1673,7 +1668,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -1689,7 +1683,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -1705,7 +1698,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -1721,7 +1713,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "linux" @@ -1737,7 +1728,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -1753,7 +1743,6 @@ "cpu": [ "ia32" ], - "dev": true, "optional": true, "os": [ "linux" @@ -1769,7 +1758,6 @@ "cpu": [ "loong64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -1785,7 +1773,6 @@ "cpu": [ "mips64el" ], - "dev": true, "optional": true, "os": [ "linux" @@ -1801,7 +1788,6 @@ "cpu": [ "ppc64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -1817,7 +1803,6 @@ "cpu": [ "riscv64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -1833,7 +1818,6 @@ "cpu": [ "s390x" ], - "dev": true, "optional": true, "os": [ "linux" @@ -1849,7 +1833,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -1865,7 +1848,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "netbsd" @@ -1881,7 +1863,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "openbsd" @@ -1897,7 +1878,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "sunos" @@ -1913,7 +1893,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -1929,7 +1908,6 @@ "cpu": [ "ia32" ], - "dev": true, "optional": true, "os": [ "win32" @@ -1945,7 +1923,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -2448,7 +2425,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -2465,7 +2441,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, "engines": { "node": ">=12" }, @@ -2477,7 +2452,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "engines": { "node": ">=12" }, @@ -2488,14 +2462,12 @@ "node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -2512,7 +2484,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -2527,7 +2498,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -2920,7 +2890,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "optional": true, "engines": { "node": ">=14" @@ -3939,7 +3908,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "android" @@ -3952,7 +3920,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "android" @@ -3965,7 +3932,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -3978,7 +3944,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -3991,7 +3956,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -4004,7 +3968,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -4017,7 +3980,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "linux" @@ -4030,7 +3992,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "linux" @@ -4043,7 +4004,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -4056,7 +4016,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -4069,7 +4028,6 @@ "cpu": [ "ppc64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -4082,7 +4040,6 @@ "cpu": [ "riscv64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -4095,7 +4052,6 @@ "cpu": [ "s390x" ], - "dev": true, "optional": true, "os": [ "linux" @@ -4108,7 +4064,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -4121,7 +4076,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -4134,7 +4088,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -4147,7 +4100,6 @@ "cpu": [ "ia32" ], - "dev": true, "optional": true, "os": [ "win32" @@ -4160,7 +4112,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -4265,8 +4216,7 @@ "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" }, "node_modules/@types/geojson": { "version": "7946.0.14", @@ -4330,7 +4280,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", - "dev": true, + "devOptional": true, "dependencies": { "@types/react": "*" } @@ -4668,14 +4618,12 @@ "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -4701,8 +4649,7 @@ "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "dev": true + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" }, "node_modules/argparse": { "version": "2.0.1", @@ -4803,7 +4750,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, "engines": { "node": ">=8" }, @@ -4815,7 +4761,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -4907,7 +4852,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, "engines": { "node": ">= 6" } @@ -4959,7 +4903,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -4983,7 +4926,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -5077,7 +5019,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, "engines": { "node": ">= 6" } @@ -5111,7 +5052,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -5326,14 +5266,12 @@ "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "dev": true + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, "node_modules/dom-helpers": { "version": "5.2.1", @@ -5347,8 +5285,7 @@ "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "node_modules/electron-to-chromium": { "version": "1.5.48", @@ -5391,7 +5328,6 @@ "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" @@ -5853,7 +5789,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", - "dev": true, "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -5923,7 +5858,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -6008,7 +5942,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -6221,7 +6154,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -6281,14 +6213,12 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -6411,7 +6341,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true, "engines": { "node": ">=10" } @@ -6563,7 +6492,6 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -6578,7 +6506,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "engines": { "node": ">=16 || 14 >=14.17" } @@ -6601,7 +6528,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", @@ -6658,7 +6584,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -6712,7 +6637,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "dev": true, "engines": { "node": ">= 6" } @@ -6791,8 +6715,7 @@ "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" }, "node_modules/parent-module": { "version": "1.0.1", @@ -6843,7 +6766,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -6857,7 +6779,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -6872,8 +6793,7 @@ "node_modules/path-scurry/node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" }, "node_modules/path-type": { "version": "4.0.0", @@ -6903,7 +6823,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -6912,7 +6831,6 @@ "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", - "dev": true, "engines": { "node": ">= 6" } @@ -6921,7 +6839,6 @@ "version": "8.4.49", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", - "dev": true, "funding": [ { "type": "opencollective", @@ -7299,7 +7216,6 @@ "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "dev": true, "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", @@ -7316,7 +7232,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, "dependencies": { "camelcase-css": "^2.0.1" }, @@ -7363,7 +7278,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -7398,7 +7312,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", - "dev": true, "engines": { "node": ">=14" }, @@ -7410,7 +7323,6 @@ "version": "2.6.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", - "dev": true, "bin": { "yaml": "bin.mjs" }, @@ -7446,7 +7358,6 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -7471,7 +7382,6 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dev": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -8025,6 +7935,7 @@ "version": "5.4.3", "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.3.tgz", "integrity": "sha512-VCY5hFg/soBighAoGcdE+GagkJq0230qN6jcS5sp8wQX1qy1fYN/RX7/BXkrs0oyzzwqR8/+eSUrqXbGeywdUQ==", + "license": "MIT", "peerDependencies": { "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -8188,7 +8099,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "dev": true, "dependencies": { "pify": "^2.3.0" } @@ -8197,7 +8107,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -8275,7 +8184,6 @@ "version": "4.24.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.2.tgz", "integrity": "sha512-do/DFGq5g6rdDhdpPq5qb2ecoczeK6y+2UAjdJ5trjQJj5f1AiVdLRWRc9A9/fFukfvJRgM0UXzxBIYMovm5ww==", - "dev": true, "dependencies": { "@types/estree": "1.0.6" }, @@ -8375,7 +8283,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -8387,7 +8294,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -8413,7 +8319,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "engines": { "node": ">=14" }, @@ -8464,7 +8369,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -8490,7 +8394,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -8617,7 +8520,6 @@ "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", - "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", @@ -8639,7 +8541,6 @@ "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -8777,6 +8678,12 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/tabs": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/tabs/-/tabs-0.2.0.tgz", + "integrity": "sha512-q6mo8KWq/Zi+zGR3ZvGq6S6ckc3JNbGRGtAwIhtnp2GlS1goTqFnWX3MkyDKYSQuzwBqBAy2fYGHQI+3HMnXZQ==", + "license": "MIT" + }, "node_modules/tailwind-merge": { "version": "2.5.4", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.4.tgz", @@ -8790,7 +8697,6 @@ "version": "3.4.14", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.14.tgz", "integrity": "sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==", - "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -8835,7 +8741,6 @@ "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "dev": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -8854,7 +8759,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, "dependencies": { "any-promise": "^1.0.0" } @@ -8863,7 +8767,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, "dependencies": { "thenify": ">= 3.1.0 < 4" }, @@ -8912,8 +8815,7 @@ "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" }, "node_modules/tslib": { "version": "2.8.0", @@ -8947,7 +8849,7 @@ "version": "5.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9089,7 +8991,6 @@ "version": "5.4.11", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", - "dev": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -9165,7 +9066,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -9217,7 +9117,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", diff --git a/package.json b/package.json index 8121a93..4dd81d2 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "styled-components": "^6.1.13", "stylis": "^4.3.4", "stylis-plugin-rtl": "^2.1.1", + "tabs": "^0.2.0", "tailwind-merge": "^2.5.4", "tailwindcss-animate": "^1.0.7", "vite-plugin-windicss": "^1.9.3", diff --git a/src/components/data-grid/DataGridContext.tsx b/src/components/data-grid/DataGridContext.tsx index 9e95494..4b54947 100644 --- a/src/components/data-grid/DataGridContext.tsx +++ b/src/components/data-grid/DataGridContext.tsx @@ -181,6 +181,7 @@ export const DataGridProvider = (props: TDataGridProps + {loading &&
} {props.children ? props.children : } ); diff --git a/src/components/data-grid/DataGridLoader.tsx b/src/components/data-grid/DataGridLoader.tsx index 03c08ca..77e71fb 100644 --- a/src/components/data-grid/DataGridLoader.tsx +++ b/src/components/data-grid/DataGridLoader.tsx @@ -5,10 +5,10 @@ export const DataGridLoader = () => { const { props } = useDataGrid(); return ( -
+
{ return ( @@ -24,6 +30,10 @@ const ConversionMaster = () => {
+ + + + ); diff --git a/src/pages/master/conversion/blocks/AddDialog.tsx b/src/pages/master/conversion/blocks/AddDialog.tsx index e69de29..b9f0e60 100644 --- a/src/pages/master/conversion/blocks/AddDialog.tsx +++ b/src/pages/master/conversion/blocks/AddDialog.tsx @@ -0,0 +1,287 @@ +import { apiConfig } from '@/config/api.config'; +import { Alert, useDataGrid } from '@/components'; +import { useCallApi } from '@/hooks'; +import { getAuth } from '@/auth'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { toast } from 'sonner'; +import { + Dialog, + DialogBody, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { NumericFormat } from 'react-number-format'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList +} from '@/components/ui/command'; +import { set } from 'date-fns'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select'; +import { useManageConversionContext } from '../hooks/useManageConversionContext'; +interface CurrencyProps { + ID: string; + name: string; +} + +const API_URL = apiConfig.service_wallet; + +const AddDialog = () => { + const parentRef = useRef(null); + const { showAddDialog, handleAddDialog, selectedConversion } = useManageConversionContext(); + const { reload } = useDataGrid(); + const { PostData, GetData } = useCallApi(); + const parsedUser = getAuth()?.user; + const [currencies, setCurrencies] = useState([]); + const [open, setOpen] = useState(false); + const [alert, setAlert] = useState({ + show: false, + message: '' + }); + const initialState = { + status: '', + id_currency_origin: '', + id_currency_destination: '', + buy: 0, + sell: 0, + created_by: '', + created_at: '' + }; + const [formField, setFormField] = useState(initialState); + const created_time = new Date(); + const formattedTime = created_time.toISOString().slice(0, 19).replace('T', ' '); + + const resetForm = () => { + setFormField(initialState); + setAlert({ show: false, message: '' }); + }; + + const doCreateConversion = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + + const response = await PostData(`${API_URL}/dashboard/conversion`, formField); + + if (response?.status) { + resetForm(); + handleAddDialog(false); + toast.success('Success Create Conversion'); + reload(); + } else { + toast.error('Error Create Conversion'); + setAlert({ show: true, message: 'Failed to create Conversion. Please try again.' }); + } + }, + [formField] + ); + + const doFetchCurrency = async (sorting: any) => { + try { + sorting = sorting.length == 0 ? [{ id: 'name', desc: false }] : sorting; + const response = await GetData(`${API_URL}/dashboard/currency/`, { + limit: 1000, + page: 1, + with_deleted: false, + order_field: sorting[0].id, + order_direction: sorting[0].desc == false ? 'ASC' : 'DESC' + }); + + setCurrencies(response?.data.list); + } catch (error) { + console.error('Error fetching currency', error); + } + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + if ( + formField.id_currency_origin === '' || + formField.id_currency_destination === '' || + formField.buy === 0 || + formField.sell === 0 || + formField.status === '' + ) { + setAlert({ show: true, message: 'Please fill in all required fields.' }); + return; + } + + doCreateConversion(e); + console.log(formField); + setAlert({ show: false, message: '' }); + }; + + useEffect(() => { + if (showAddDialog) { + setFormField({ + ...formField, + created_by: parsedUser.username, + created_at: formattedTime + }); + } + }, [formattedTime]); + + useEffect(() => { + doFetchCurrency([{ id: 'name', desc: false }]); + }, []); + + useEffect(() => { + if (showAddDialog === false) { + resetForm(); + } + }, [showAddDialog]); + + return ( + handleAddDialog(open)}> + + + Conversion - Create + + + +
+ {alert.show && ( + +

{alert.message}

+
+ )} + +
+
+
+ + +
+
+ + +
+
+ + { + setFormField((prev) => ({ + ...prev, + buy: values.floatValue || 0 + })); + }} + placeholder="Enter Buy" + /> +
+
+ + { + setFormField((prev) => ({ + ...prev, + sell: values.floatValue || 0 + })); + }} + placeholder="Enter Sell" + /> +
+
+ + +
+ +
+ +
+
+ + +
+
+
+
+
+
+
+ ); +}; + +export default AddDialog; diff --git a/src/pages/master/conversion/blocks/DeleteDialog.tsx b/src/pages/master/conversion/blocks/DeleteDialog.tsx new file mode 100644 index 0000000..1c84e02 --- /dev/null +++ b/src/pages/master/conversion/blocks/DeleteDialog.tsx @@ -0,0 +1,79 @@ +import { Alert, useDataGrid } from '@/components'; +import { useManageConversionContext } from '../hooks/useManageConversionContext'; +import { useCallback, useState } from 'react'; +import { apiConfig } from '@/config/api.config'; +import { useCallApi } from '@/hooks'; +import { toast } from 'sonner'; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { DialogDescription } from '@radix-ui/react-dialog'; + +const API_URL = apiConfig.service_wallet; + +const DeleteDialog = () => { + const { showDeleteDialog, handleDeleteDialog, selectedConversion } = useManageConversionContext(); + const { reload } = useDataGrid(); + const { DeleteData } = useCallApi(); + const [alert, setAlert] = useState({ + show: false, + message: '' + }); + + const doDeleteConversion = useCallback(async () => { + if (!selectedConversion) { + toast.error('No Conversion selected'); + return; + } + console.log(selectedConversion); + const response = await DeleteData( + `${API_URL}/dashboard/conversion/${selectedConversion}`, + { id: selectedConversion } + ); + + if (response?.status) { + setAlert({ show: false, message: '' }); + handleDeleteDialog(false, null); + toast.success('Success Delete Conversion'); + reload(); + } else { + setAlert({ show: true, message: response?.message }); + toast.error('Failed Delete Conversion'); + } + }, [selectedConversion, DeleteData, handleDeleteDialog, reload]); + console.log(selectedConversion); + return ( + handleDeleteDialog(open, null)}> + + + + + +

Are you sure?

+ You will delete this data! +
+ {alert.show && ( + +

{alert.message}

+
+ )} +
+ + + + +
+
+ ); +}; + +export default DeleteDialog; diff --git a/src/pages/master/conversion/blocks/EditDialog.tsx b/src/pages/master/conversion/blocks/EditDialog.tsx new file mode 100644 index 0000000..05f4d68 --- /dev/null +++ b/src/pages/master/conversion/blocks/EditDialog.tsx @@ -0,0 +1,299 @@ +import { apiConfig } from '@/config/api.config'; +import { Alert, useDataGrid } from '@/components'; +import { useCallApi } from '@/hooks'; +import { getAuth } from '@/auth'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { toast } from 'sonner'; +import { + Dialog, + DialogBody, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { NumericFormat } from 'react-number-format'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList +} from '@/components/ui/command'; +import { set } from 'date-fns'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select'; +import { useManageConversionContext } from '../hooks/useManageConversionContext'; +interface CurrencyProps { + ID: string; + name: string; +} + +const API_URL = apiConfig.service_wallet; + +const EditDialog = () => { + const parentRef = useRef(null); + const { showEditDialog, handleEditDialog, selectedConversion } = useManageConversionContext(); + const { reload } = useDataGrid(); + const { PostData, GetData, PutData } = useCallApi(); + const parsedUser = getAuth()?.user; + const [currencies, setCurrencies] = useState([]); + const [open, setOpen] = useState(false); + const [alert, setAlert] = useState({ + show: false, + message: '' + }); + const initialState = { + status: '', + id_currency_origin: '', + id_currency_destination: '', + buy: 0, + sell: 0, + created_by: '', + created_at: '' + }; + const [formField, setFormField] = useState(initialState); + const updated_time = new Date(); + const formattedTime = updated_time.toISOString().slice(0, 19).replace('T', ' '); + + const resetForm = () => { + setFormField(initialState); + setAlert({ show: false, message: '' }); + }; + + const doUpdateConversion = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + if(!showEditDialog) return; + const response = await PutData(`${API_URL}/dashboard/conversion/${selectedConversion}`,{ + ...formField + }); + + if (response?.status) { + resetForm(); + handleEditDialog(false, null); + toast.success('Success Update Conversion'); + reload(); + } else { + toast.error('Error Create Conversion'); + setAlert({ show: true, message: 'Failed to Update Conversion. Please try again.' }); + } + }, + [formField] + ); + + const doGetCurrency = async (sorting: any) => { + if (!showEditDialog)return; + try { + sorting = sorting.length == 0 ? [{ id: 'name', desc: false }] : sorting; + const response = await GetData(`${API_URL}/dashboard/currency/`, { + limit: 1000, + page: 1, + with_deleted: false, + order_field: sorting[0].id, + order_direction: sorting[0].desc == false ? 'ASC' : 'DESC' + }); + + setCurrencies(response?.data.list); + } catch (error) { + console.error('Error fetching currency', error); + } + }; + + const doGetConversionById = useCallback(async (id: string) => { + const response = await GetData(`${API_URL}/dashboard/conversion/${id}`, { id }); + console.log('Transaction Type: ', response?.data); + if (response?.status) { + setFormField((prev) => ({ + ...prev, + status: response.data.status, + id_currency_origin: response.data.id_currency_origin, + id_currency_destination: response.data.id_currency_destination, + buy: response.data.buy, + sell: response.data.sell, + })); + } + // console.log('form fieldd Transaction Type: ', formField); + }, []); + + useEffect(() => { + if (selectedConversion) { + doGetConversionById(selectedConversion); + } + }, [selectedConversion]); + + + + + useEffect(() => { + if (showEditDialog) { + setFormField({ + ...formField, + created_by: parsedUser.username, + created_at: formattedTime + }); + } + }, [formattedTime]); + + useEffect(() => { + if (showEditDialog) { + doGetCurrency([{ id: 'name', desc: false }]); + } + }, [showEditDialog]); + + + useEffect(() => { + if (showEditDialog === false) { + resetForm(); + } + }, [showEditDialog]); + + return ( + handleEditDialog(open,null)}> + + + Conversion - Update + + + +
+ {alert.show && ( + +

{alert.message}

+
+ )} + +
+
+
+ + +
+
+ + +
+
+ + { + setFormField((prev) => ({ + ...prev, + buy: values.floatValue || 0 + })); + }} + placeholder="Enter Buy" + /> +
+
+ + { + setFormField((prev) => ({ + ...prev, + sell: values.floatValue || 0 + })); + }} + placeholder="Enter Sell" + /> +
+
+ + +
+ +
+ +
+
+ + +
+
+
+
+
+
+
+ ); +}; + +export default EditDialog; diff --git a/src/pages/master/conversion/blocks/ListToolbar.tsx b/src/pages/master/conversion/blocks/ListToolbar.tsx index 1cacece..f356e29 100644 --- a/src/pages/master/conversion/blocks/ListToolbar.tsx +++ b/src/pages/master/conversion/blocks/ListToolbar.tsx @@ -11,7 +11,7 @@ const ListToolbar = () => {
- */} {/* ); }, - meta: { - headerClassName: 'w-[100px]', - cellClassName: 'text-center' - } + meta: { headerClassName: 'w-[100px]', cellClassName: 'text-center' } } ], - [] + [handleEditDialog, handleDeleteDialog] ); - - const getConversionLists = async (page: number, limit: number, sorting: any, filter: any) => { - try { - sorting = sorting.length == 0 ? [{ id: 'name', desc: false }] : sorting; - filter = filter.length == 0 ? {} : { any: filter[0].value?.toLowerCase() }; - const response = await GetData(`${API_URL_WALLET}/dashboard/conversion`, { - limit, - page: page + 1, - with_deleted: false, - order_field: sorting[0].id, - order_direction: sorting[0].desc == false ? 'ASC' : 'DESC', - filter: JSON.stringify(filter) - }); - console.log(response?.data); - setConversions(response?.data.list); - return { data: response?.data.list, totalCount: response?.data.total_count }; - } catch (error) { - console.error('Error fetching Conversion', error); - } + const doGetConversion = async ( + page: number, + limit: number, + sorting: any, + filter: any + ) => { + sorting = sorting.length == 0 ? [{ id: 'name', desc: true }] : sorting; + filter = filter.length == 0 ? {} : { any: filter[0].value?.toLowerCase() }; + console.log(sorting); + const response = await GetData(`${API_URL}/dashboard/conversion/`, { + limit: limit, + page: page + 1, + with_deleted: false, + order_field: sorting[0].id, + order_direction: sorting[0].desc ? 'ASC' : 'DESC', + filter: JSON.stringify(filter) + }); + console.log(response?.data); + return { data: response?.data.list, totalCount: response?.data.total_count }; }; return ( - - - } - layout={{ card: true }} - sorting={[{ id: 'id', desc: false }]} - serverSide={true} - onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => - getConversionLists(pageIndex, pageSize, sorting, columnFilters) - } +
+ - {children} - - + + +
+ } + sorting={[{ id: 'ID', desc: true }]} + serverSide={true} + onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => + doGetConversion(pageIndex, pageSize, sorting, columnFilters) + } + > + {children} + +
+ +
); }; export { ManageConversionContext, ManageConversionContextProvider }; -export type { ConversionProps }; +export type { Conversion }; diff --git a/src/pages/master/provider/hooks/ManageProviderContext.tsx b/src/pages/master/provider/hooks/ManageProviderContext.tsx index f68d8bc..6dc7ea4 100644 --- a/src/pages/master/provider/hooks/ManageProviderContext.tsx +++ b/src/pages/master/provider/hooks/ManageProviderContext.tsx @@ -109,8 +109,22 @@ const ManageProviderContextProvider = ({ children }: { children: React.ReactNode header: ({ column }) => , enableSorting: false, enableHiding: false, + cell: ({ row }) => { + const isActive = row.original.provider_status === 'Y'; + + return ( + + {isActive ? 'Active' : 'Inactive'} + + ); + }, meta: { - headerClassName: 'w-[100px]' + headerClassName: 'w-[100px]', + cellClassName: 'text-center' } }, { @@ -158,7 +172,7 @@ const ManageProviderContextProvider = ({ children }: { children: React.ReactNode order_direction: sorting[0].desc == false ? 'ASC' : 'DESC', filter: JSON.stringify(filter) }); - // console.log(response?.data); + console.log(response?.data); setProvider(response?.data.list); return { data: response?.data.list, totalCount: response?.data.total_count }; } catch (error) { diff --git a/src/pages/master/wallet/WalletMaster.tsx b/src/pages/master/wallet/WalletMaster.tsx new file mode 100644 index 0000000..d319707 --- /dev/null +++ b/src/pages/master/wallet/WalletMaster.tsx @@ -0,0 +1,37 @@ +import { Container, DataGridInner } from '@/components'; +import { ManageWalletContextProvider } from './hooks/ManageWalletContext'; +import { Breadcrumbs, Link } from '@mui/material'; +import AddDialog from './blocks/AddDialog'; +import EditDialog from './blocks/EditDialog'; +import DeleteDialog from './blocks/DeleteDialog'; + +const WalletMaster = () => { + return ( + + +

Manage Wallet

+ + + Dashboard + + + + Master Data + + + + Manage Wallet + + +
+ +
+ + + +
+
+ ); +}; + +export default WalletMaster; diff --git a/src/pages/master/wallet/blocks/AddDialog.tsx b/src/pages/master/wallet/blocks/AddDialog.tsx new file mode 100644 index 0000000..b8100ba --- /dev/null +++ b/src/pages/master/wallet/blocks/AddDialog.tsx @@ -0,0 +1,298 @@ +import { useCallApi } from '@/hooks'; +import { useManageWalletContext } from '../hooks/useManageWalletContext'; +import { Alert, useDataGrid } from '@/components'; +import React, { useCallback, useEffect, useState } from 'react'; +import { apiConfig } from '@/config/api.config'; +import { toast } from 'sonner'; +import { + Dialog, + DialogBody, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select'; +import { Button } from '@/components/ui/button'; +import { Checkbox } from '@/components/ui/checkbox'; + +interface CurrencyProps { + ID: string; + code: string; + name: string; + prefix: string; + status: string; +} + +interface GroupProps { + id: string; + name: string; + description: string; + status: string; +} + +const API_URL_WALLET = apiConfig.service_wallet; +const API_URL_MASTER_DATA = apiConfig.service_master_data; + +const AddDialog = () => { + const { showAddDialog, handleAddDialog } = useManageWalletContext(); + const { GetData, PostData } = useCallApi(); + const { reload } = useDataGrid(); + const [alert, setAlert] = useState({ + show: false, + message: '' + }); + const initialState: { + name: string; + description: string; + status: string; + group: string[]; + currency_id: string; + } = { + name: '', + description: '', + status: '', + group: [], + currency_id: '' + }; + const [formField, setFormField] = useState(initialState); + const [currencies, setCurrencies] = useState([]); + const [groups, setGroups] = useState([]); + + const resetForm = () => { + setFormField(initialState); + setAlert({ show: false, message: '' }); + }; + + const handleGroupChange = (groupId: string) => { + setFormField((prevState) => { + const isSelected = prevState.group.includes(groupId); + + if (isSelected) { + // Remove the group if already selected + return { + ...prevState, + group: prevState.group.filter((id) => id !== groupId) + }; + } else { + // Add the group if not selected + return { + ...prevState, + group: [...prevState.group, groupId] + }; + } + }); + }; + + const doCreateWallet = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + + const response = await PostData(`${API_URL_MASTER_DATA}/wallet/create`, formField); + + if (response?.status) { + handleAddDialog(false); + toast.success('Success Create Wallet'); + reload(); + } else { + toast.error('Failed Create Wallet'); + setAlert({ show: true, message: 'Failed Create Wallet' }); + } + }, + [formField] + ); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + if ( + formField.name.trim() === '' || + formField.description.trim() === '' || + formField.status.trim() === '' || + formField.group.length === 0 || + formField.currency_id.trim() === '' + ) { + setAlert({ show: true, message: 'Please fill in all required fields.' }); + return; + } + + console.log(formField); + doCreateWallet(e); + setAlert({ show: false, message: '' }); + }; + + const getCurrencyLists = async (sorting: any) => { + try { + const response = await GetData(`${API_URL_WALLET}/dashboard/currency`, { + limit: 100, + page: 1, + with_deleted: false, + order_field: sorting[0].id, + order_direction: sorting[0].desc == false ? 'ASC' : 'DESC' + }); + // console.log('Currency: ', response?.data); + setCurrencies(response?.data.list); + } catch (error) { + console.error('Error fetching currency', error); + } + }; + + const getGroupLists = async (sorting: any) => { + try { + const response = await GetData(`${API_URL_MASTER_DATA}/groups/list`, { + limit: 100, + page: 1, + with_deleted: false, + order_field: sorting[0].id, + order_direction: sorting[0].desc == false ? 'ASC' : 'DESC' + }); + // console.log('Group: ', response?.data); + setGroups(response?.data.list); + } catch (error) { + console.error('Error fetching group', error); + } + }; + + useEffect(() => { + getCurrencyLists([{ id: 'name', desc: false }]); + getGroupLists([{ id: 'name', desc: false }]); + }, []); + + useEffect(() => { + if (showAddDialog === false) { + resetForm(); + } + }, [showAddDialog]); + + return ( + handleAddDialog(open)}> + + + Wallet - Create + + + +
+ {alert.show && ( + +

{alert.message}

+
+ )} + +
+
+
+
+ + setFormField({ ...formField, name: e.target.value })} + placeholder="Wallet Name" + /> +
+
+ +
+
+ + setFormField({ ...formField, description: e.target.value })} + placeholder="Description" + /> +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ +
+ {groups.map((group) => ( + + ))} +
+
+
+ +
+ + +
+
+
+
+
+
+
+ ); +}; + +export default AddDialog; diff --git a/src/pages/master/wallet/blocks/DeleteDialog.tsx b/src/pages/master/wallet/blocks/DeleteDialog.tsx new file mode 100644 index 0000000..9166356 --- /dev/null +++ b/src/pages/master/wallet/blocks/DeleteDialog.tsx @@ -0,0 +1,78 @@ +import { useCallApi } from '@/hooks'; +import { useManageWalletContext } from '../hooks/useManageWalletContext'; +import { Alert, useDataGrid } from '@/components'; +import { useCallback, useState } from 'react'; +import { toast } from 'sonner'; +import { apiConfig } from '@/config/api.config'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; + +const API_URL = apiConfig.service_master_data; + +const DeleteDialog = () => { + const { showDeleteDialog, handleDeleteDialog, selectedWallet } = useManageWalletContext(); + const { DeleteData } = useCallApi(); + const { reload } = useDataGrid(); + const [alert, setAlert] = useState({ show: false, message: '' }); + + const doDeleteWallet = useCallback(async () => { + if (!selectedWallet) { + toast.error('No wallet selected'); + return; + } + + const response = await DeleteData( + `${API_URL}/wallet/delete/${selectedWallet.Wallet_id}/false`, + { + id: selectedWallet.Wallet_id + } + ); + + if (response?.status) { + setAlert({ show: false, message: '' }); + handleDeleteDialog(false, null); + toast.success('Success Delete Wallet'); + reload(); + } else { + setAlert({ show: true, message: response?.message }); + toast.error('Failed Delete Wallet'); + } + }, [selectedWallet, handleDeleteDialog, DeleteData, reload]); + + return ( + handleDeleteDialog(open, null)}> + + + + + +

Are you sure?

+ You will delete this data! +
+ {alert.show && ( + +

{alert.message}

+
+ )} +
+ + + + +
+
+ ); +}; + +export default DeleteDialog; diff --git a/src/pages/master/wallet/blocks/EditDialog.tsx b/src/pages/master/wallet/blocks/EditDialog.tsx new file mode 100644 index 0000000..c5faae1 --- /dev/null +++ b/src/pages/master/wallet/blocks/EditDialog.tsx @@ -0,0 +1,278 @@ +import { Alert, useDataGrid } from '@/components'; +import { useManageWalletContext } from '../hooks/useManageWalletContext'; +import { useCallApi } from '@/hooks'; +import React, { useCallback, useEffect, useState } from 'react'; +import { apiConfig } from '@/config/api.config'; +import { toast } from 'sonner'; +import { + Dialog, + DialogBody, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select'; +import { Button } from '@/components/ui/button'; + +interface CurrencyProps { + ID: string; + code: string; + name: string; + prefix: string; + status: string; +} + +interface GroupProps { + id: string; + name: string; + description: string; + status: string; +} + +const API_URL_WALLET = apiConfig.service_wallet; +const API_URL_MASTER_DATA = apiConfig.service_master_data; + +const EditDialog = () => { + const { showEditDialog, handleEditDialog, selectedWallet } = useManageWalletContext(); + const { reload } = useDataGrid(); + const { GetData, PutData } = useCallApi(); + const [alert, setAlert] = useState({ + show: false, + message: '' + }); + const initialState: { + name: string; + description: string; + status: string; + group: string[]; + currency_id: string; + } = { + name: '', + description: '', + status: '', + group: [], + currency_id: '' + }; + const [formField, setFormField] = useState(initialState); + const [currencies, setCurrencies] = useState([]); + const [groups, setGroups] = useState([]); + + const resetForm = () => { + setFormField(initialState); + setAlert({ show: false, message: '' }); + }; + + const doUpdateWallet = useCallback( + async (e: React.FormEvent) => { + e.preventDefault(); + + const response = await PutData( + `${API_URL_MASTER_DATA}/wallet/update/${selectedWallet?.Wallet_id}`, + formField + ); + + if (response?.status) { + handleEditDialog(false, null); + toast.success('Success Update Wallet'); + reload(); + } else { + toast.error('Failed Update Wallet'); + setAlert({ show: true, message: 'Failed Update Wallet' }); + } + }, + [formField] + ); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + console.log(formField); + doUpdateWallet(e); + setAlert({ show: false, message: '' }); + }; + + const doFetchData = useCallback(async (id: string) => { + const response = await GetData(`${API_URL_MASTER_DATA}/wallet/detail/${id}`, { + id + }); + + console.log(response); + if (response?.status) { + setFormField((prev) => ({ + ...prev, + name: response?.data.name, + description: response?.data.description, + status: response?.data.status, + currency_id: response?.data.id_currency, + group: Array.isArray(response?.data.group) + ? response?.data.group.map((target: GroupProps) => target.id) + : [] + })); + } + }, []); + + const getCurrencyLists = async (sorting: any) => { + try { + const response = await GetData(`${API_URL_WALLET}/dashboard/currency`, { + limit: 100, + page: 1, + with_deleted: false, + order_field: sorting[0].id, + order_direction: sorting[0].desc == false ? 'ASC' : 'DESC' + }); + // console.log('Currency: ', response?.data); + setCurrencies(response?.data.list); + } catch (error) { + console.error('Error fetching currency', error); + } + }; + + const getGroupLists = async (sorting: any) => { + try { + const response = await GetData(`${API_URL_MASTER_DATA}/groups/list`, { + limit: 100, + page: 1, + with_deleted: false, + order_field: sorting[0].id, + order_direction: sorting[0].desc == false ? 'ASC' : 'DESC' + }); + // console.log('Group: ', response?.data); + setGroups(response?.data.list); + } catch (error) { + console.error('Error fetching group', error); + } + }; + + const selectedGroupNames = groups + .filter((g) => formField.group.includes(g.id)) + .map((g) => g.name) + .join(', '); + + const selectedCurrency = currencies.find((currency) => currency.ID === formField.currency_id); + + useEffect(() => { + getCurrencyLists([{ id: 'name', desc: false }]); + getGroupLists([{ id: 'name', desc: false }]); + }, []); + + useEffect(() => { + if (selectedWallet) { + // setFormField((prev) => ({ + // ...prev, + // name: selectedWallet?.Wallet_name, + // description: selectedWallet?.Wallet_description, + // status: selectedWallet?.Wallet_status, + // currency_id: selectedWallet?.Wallet_currency_id, + // group: selectedWallet?.Wallet_group + // })); + doFetchData(selectedWallet?.Wallet_id); + } + }, [selectedWallet]); + + useEffect(() => { + if (showEditDialog === false) { + resetForm(); + } + }, [showEditDialog]); + // console.log(selectedWallet); + return ( + handleEditDialog(open, null)}> + + + Wallet - Update + + + +
+ {alert.show && ( + +

{alert.message}

+
+ )} + +
+
+
+
+ + setFormField({ ...formField, name: e.target.value })} + placeholder="Wallet Name" + /> +
+
+ +
+
+ + setFormField({ ...formField, description: e.target.value })} + placeholder="Description" + /> +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+ + +
+
+
+
+
+
+
+ ); +}; + +export default EditDialog; diff --git a/src/pages/master/wallet/blocks/ListToolbar.tsx b/src/pages/master/wallet/blocks/ListToolbar.tsx new file mode 100644 index 0000000..0d2a427 --- /dev/null +++ b/src/pages/master/wallet/blocks/ListToolbar.tsx @@ -0,0 +1,55 @@ +import { DefaultTooltip, KeenIcon, useDataGrid } from '@/components'; +import { Button } from '@/components/ui/button'; +import { useManageWalletContext } from '../hooks/useManageWalletContext'; + +const ListToolbar = () => { + const { reload, table } = useDataGrid(); + const { handleAddDialog } = useManageWalletContext(); + + return ( +
+
+
+
+ + {/* + + */} +
+
+ + + + +
+
+
+
+ ); +}; + +export default ListToolbar; diff --git a/src/pages/master/wallet/hooks/ManageWalletContext.tsx b/src/pages/master/wallet/hooks/ManageWalletContext.tsx new file mode 100644 index 0000000..d92f73d --- /dev/null +++ b/src/pages/master/wallet/hooks/ManageWalletContext.tsx @@ -0,0 +1,205 @@ +import { DataGridColumnHeader, DataGridProvider, KeenIcon } from '@/components'; +import { apiConfig } from '@/config/api.config'; +import { useCallApi } from '@/hooks'; +import { ColumnDef } from '@tanstack/react-table'; +import React, { createContext, useCallback, useMemo, useState } from 'react'; +import { Toaster } from 'sonner'; +import ListToolbar from '../blocks/ListToolbar'; + +interface WalletProps { + Wallet_id: string; + Wallet_name: string; + Wallet_status: string; + Wallet_description: string; + Wallet_group: string[]; + Wallet_currency_id: string; +} + +interface ContextProps { + wallet: WalletProps[]; + showAddDialog: boolean; + handleAddDialog: (show: boolean) => void; + showEditDialog: boolean; + handleEditDialog: (show: boolean, selected_wallet: WalletProps | null) => void; + showDeleteDialog: boolean; + handleDeleteDialog: (show: boolean, selected_wallet: WalletProps | null) => void; + selectedWallet: WalletProps | null; + getWalletLists: ( + limit: number, + page: number, + with_deleted: boolean, + order_field: any, + order_direction: any + ) => Promise<{ data: WalletProps[]; totalCount: number } | undefined>; +} + +const initialProps: ContextProps = { + wallet: [], + showAddDialog: false, + handleAddDialog: (show: boolean) => {}, + showEditDialog: false, + handleEditDialog: (show: boolean, selected_wallet: WalletProps | null) => {}, + showDeleteDialog: false, + handleDeleteDialog: (show: boolean, selected_wallet: WalletProps | null) => {}, + selectedWallet: null, + getWalletLists: async () => ({ data: [], totalCount: 0 }) +}; + +const ManageWalletContext = createContext(initialProps); +const API_URL_MASTER_DATA = apiConfig.service_master_data; + +const ManageWalletContextProvider = ({ children }: { children: React.ReactNode }) => { + const [wallets, setWallets] = useState([]); + const [showAddDialog, setShowAddDialog] = useState(false); + const [showEditDialog, setShowEditDialog] = useState(false); + const [showDeleteDialog, setShowDeleteDialog] = useState(false); + const [selectedWallet, setSelectedWallet] = useState(null); + const { GetData } = useCallApi(); + + const handleAddDialog = useCallback((show: boolean) => { + setShowAddDialog(show); + }, []); + + const handleEditDialog = useCallback((show: boolean, selected_wallet: WalletProps | null) => { + setShowEditDialog(show); + setSelectedWallet(show ? selected_wallet : null); + }, []); + + const handleDeleteDialog = useCallback((show: boolean, selected_wallet: WalletProps | null) => { + setShowDeleteDialog(show); + setSelectedWallet(show ? selected_wallet : null); + }, []); + + const columns = useMemo[]>( + () => [ + { + accessorFn: (row) => row.Wallet_name, + id: 'name', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, + { + accessorFn: (row) => row.Wallet_description, + id: 'description', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + meta: { + headerClassName: 'w-[250px]' + } + }, + { + accessorFn: (row) => row.Wallet_status, + id: 'status', + header: ({ column }) => , + enableSorting: true, + enableHiding: false, + cell: ({ row }) => { + const isActive = row.original.Wallet_status === 'Y'; + + return ( + + {isActive ? 'Active' : 'Inactive'} + + ); + }, + meta: { + headerClassName: 'w-[100px] text-center', + cellClassName: 'text-center' + } + }, + { + id: 'actions', + header: ({ column }) => , + enableSorting: false, + enableHiding: false, + cell: (data) => { + const row = data.row.original; + return ( + <> + + + + ); + }, + meta: { + headerClassName: 'w-[100px] text-center', + cellClassName: 'text-center' + } + } + ], + [] + ); + + const getWalletLists = async (page: number, limit: number, sorting: any, filter: any) => { + try { + sorting = sorting.length == 0 ? [{ id: 'name', desc: false }] : sorting; + filter = filter.length == 0 ? {} : { any: filter[0].value?.toLowerCase() }; + const response = await GetData(`${API_URL_MASTER_DATA}/wallet/list`, { + limit, + page: page + 1, + with_deleted: false, + order_field: sorting[0].id, + order_direction: sorting[0].desc == false ? 'ASC' : 'DESC', + filter: JSON.stringify(filter) + }); + // console.log(response?.data); + setWallets(response?.data.list); + return { data: response?.data.list, totalCount: response?.data.total_count }; + } catch (error) { + console.error('Error fetching Wallet', error); + } + }; + + return ( + + + } + layout={{ card: true }} + sorting={[{ id: 'id', desc: false }]} + serverSide={true} + onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) => + getWalletLists(pageIndex, pageSize, sorting, columnFilters) + } + > + {children} + + + ); +}; + +export { ManageWalletContext, ManageWalletContextProvider }; +export type { WalletProps }; diff --git a/src/pages/master/wallet/hooks/useManageWalletContext.tsx b/src/pages/master/wallet/hooks/useManageWalletContext.tsx new file mode 100644 index 0000000..e6bcaec --- /dev/null +++ b/src/pages/master/wallet/hooks/useManageWalletContext.tsx @@ -0,0 +1,12 @@ +import { useContext } from 'react'; +import { ManageWalletContext } from './ManageWalletContext'; + +const useManageWalletContext = () => { + const context = useContext(ManageWalletContext); + if (!context) { + throw new Error('useManageWalletContext must be used within a ManageWalletContextProvider'); + } + return context; +}; + +export { useManageWalletContext }; diff --git a/src/pages/master/walletRule/blocks/AddDialog.tsx b/src/pages/master/walletRule/blocks/AddDialog.tsx index 885c15a..f18cd89 100644 --- a/src/pages/master/walletRule/blocks/AddDialog.tsx +++ b/src/pages/master/walletRule/blocks/AddDialog.tsx @@ -105,15 +105,7 @@ const AddDialog = () => { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - if ( - formField.id_group.trim() === '' || - formField.max_transaction_per_day === 0 || - formField.balance_minimum === 0 || - formField.balance_maximum === 0 || - formField.credit_limit === 0 || - formField.monthly_limit === 0 || - formField.status.trim() === '' - ) { + if (formField.id_group.trim() === '' || formField.status.trim() === '') { setAlert({ show: true, message: 'Please fill in all required fields.' }); return; } diff --git a/src/pages/master/walletRule/blocks/EditDialog.tsx b/src/pages/master/walletRule/blocks/EditDialog.tsx index 9b870c4..8529c3f 100644 --- a/src/pages/master/walletRule/blocks/EditDialog.tsx +++ b/src/pages/master/walletRule/blocks/EditDialog.tsx @@ -108,15 +108,7 @@ const EditDialog = () => { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - if ( - formField.id_group.trim() === '' || - formField.max_transaction_per_day === 0 || - formField.balance_minimum === 0 || - formField.balance_maximum === 0 || - formField.credit_limit === 0 || - formField.monthly_limit === 0 || - formField.status.trim() === '' - ) { + if (formField.id_group.trim() === '' || formField.status.trim() === '') { setAlert({ show: true, message: 'Please fill in all required fields.' }); return; } diff --git a/src/pages/master/walletRule/hooks/ManageWalletRuleContext.tsx b/src/pages/master/walletRule/hooks/ManageWalletRuleContext.tsx index bbe1798..20f699f 100644 --- a/src/pages/master/walletRule/hooks/ManageWalletRuleContext.tsx +++ b/src/pages/master/walletRule/hooks/ManageWalletRuleContext.tsx @@ -137,10 +137,24 @@ const ManageWalletRuleContextProvider = ({ children }: { children: React.ReactNo accessorFn: (row) => row.status, id: 'status', header: ({ column }) => , - enableSorting: true, + enableSorting: false, enableHiding: false, + cell: ({ row }) => { + const isActive = row.original.status === 'Y'; + + return ( + + {isActive ? 'Active' : 'Inactive'} + + ); + }, meta: { - headerClassName: 'w-[250px]' + headerClassName: 'w-[100px]', + cellClassName: 'text-center' } }, { diff --git a/src/pages/menu/manage-menu/blocks/AddDIalog.tsx b/src/pages/menu/manage-menu/blocks/AddDIalog.tsx index 15d9a74..beb6cc4 100644 --- a/src/pages/menu/manage-menu/blocks/AddDIalog.tsx +++ b/src/pages/menu/manage-menu/blocks/AddDIalog.tsx @@ -12,16 +12,22 @@ import { DialogHeader, DialogTitle } from '@/components/ui/dialog'; -import { FormControl,NativeSelect } from "@mui/material"; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select'; const API_URL = apiConfig.service_dashboard; const AddDialog = () => { - const parentRef = useRef(null); const { showAddDialog, handleAddDialog, parents } = useManageMenusContext(); const { reload } = useDataGrid(); const { PostData } = useCallApi(); + const [open, setOpen] = useState(false); const [alert, setAlert] = useState({ show: false, message: '' @@ -51,8 +57,11 @@ const AddDialog = () => { return; } + console.log('Data dikirim ke API:', formField); const response = await PostData(`${API_URL}/menus/create`, formField); + console.log('Response from API:', response); + if (response?.status) { handleAddDialog(false); resetForm(); @@ -67,7 +76,10 @@ const AddDialog = () => { }; const resetForm = () => { - setFormField(initialState); + setFormField({ + ...initialState, + status: formField.status // Pertahankan nilai status terpilih + }); setAlert({ show: false, message: '' }); }; @@ -78,7 +90,7 @@ const AddDialog = () => { Menu - Create - +
{alert.show && ( @@ -133,24 +145,21 @@ const AddDialog = () => {
- - setFormField({ ...formField, id_parent: e.target.value })} - inputProps={{ - name: 'id_parent', - id: 'uncontrolled-native', - }} - > - - { - parents ? parents.map((el: any) => ( - - )) : '' - } - - +
@@ -190,21 +199,18 @@ const AddDialog = () => { - - setFormField({ ...formField, status: e.target.value })} - inputProps={{ - name: 'status', - id: 'uncontrolled-native', - }} - > - - - - - +
diff --git a/src/pages/menu/manage-menu/blocks/DeleteDialog.tsx b/src/pages/menu/manage-menu/blocks/DeleteDialog.tsx index 4f4818c..b5250eb 100644 --- a/src/pages/menu/manage-menu/blocks/DeleteDialog.tsx +++ b/src/pages/menu/manage-menu/blocks/DeleteDialog.tsx @@ -4,7 +4,14 @@ import { Alert, useDataGrid } from '@/components'; import { useCallApi } from '@/hooks'; import { ChangeEvent, useCallback, useState } from 'react'; import { toast } from 'sonner'; -import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; import { EnforceSwitch } from '@/components/switch'; import { Button } from '@/components/ui/button'; @@ -13,14 +20,13 @@ const DeleteDialog = () => { const { showDeleteDialog, handleDeleteDialog, selectedMenu }: any = useManageMenusContext(); const { reload } = useDataGrid(); const { DeleteData } = useCallApi(); - const [enforce, setEnforce] = useState(false); const [alert, setAlert] = useState({ show: false, message: '' }); const doDeleteMenu = async () => { - const response = await DeleteData(`${API_URL}/menus/delete/${selectedMenu.id}/${enforce}`, { + const response = await DeleteData(`${API_URL}/menus/delete/${selectedMenu.id}/false`, { id: selectedMenu.id }); @@ -44,15 +50,6 @@ const DeleteDialog = () => {

Are you sure?

you will delete this data! -
- - ) => { - setEnforce(e.target.checked); - }} - /> -
{alert.show && ( diff --git a/src/pages/menu/manage-menu/blocks/EditDialog.tsx b/src/pages/menu/manage-menu/blocks/EditDialog.tsx index 78a5ea2..6a40e87 100644 --- a/src/pages/menu/manage-menu/blocks/EditDialog.tsx +++ b/src/pages/menu/manage-menu/blocks/EditDialog.tsx @@ -12,15 +12,25 @@ import { DialogHeader, DialogTitle } from '@/components/ui/dialog'; -import { FormControl,NativeSelect } from "@mui/material"; +import { FormControl, NativeSelect } from '@mui/material'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select'; + const API_URL = apiConfig.service_dashboard; const EditDialog = () => { - const { showEditDialog, handleEditDialog, selectedMenu, setSelectedMenu, parents }: any = useManageMenusContext(); + const { showEditDialog, handleEditDialog, selectedMenu, setSelectedMenu, parents }: any = + useManageMenusContext(); const { reload } = useDataGrid(); const { PutData } = useCallApi(); + const [open, setOpen] = useState(false); const [alert, setAlert] = useState({ show: false, message: '' @@ -38,11 +48,12 @@ const EditDialog = () => { const handleUpdate = async (e: React.FormEvent) => { e.preventDefault(); - + if ( selectedMenu.module === '' || selectedMenu.name === '' || selectedMenu.link === '' || + selectedMenu.id_parent === '' || selectedMenu.order_number === 0 || selectedMenu.status === '' ) { @@ -51,8 +62,8 @@ const EditDialog = () => { } const updateMenu = selectedMenu; - if (updateMenu.id_parent === null) updateMenu.id_parent = ""; - delete updateMenu.parentName + if (updateMenu.id_parent === null) updateMenu.id_parent = ''; + delete updateMenu.parentName; const response = await PutData(`${API_URL}/menus/update/${selectedMenu.id}`, selectedMenu); if (response?.status) { handleEditDialog(false, null); @@ -67,7 +78,7 @@ const EditDialog = () => { }; const resetForm = () => { - setSelectedMenu(initialState) + setSelectedMenu(initialState); setAlert({ show: false, message: '' }); }; @@ -78,7 +89,7 @@ const EditDialog = () => { Menu - Update - +
{alert.show && ( @@ -132,24 +143,27 @@ const EditDialog = () => {
- - - setSelectedMenu({ ...selectedMenu, id_parent: e.target.value })} - inputProps={{ - name: 'id_parent', - id: 'uncontrolled-native', - }} - > - { - parents ? parents.map((el: any) => ( - - )) : '' - } - - + +
@@ -165,7 +179,10 @@ const EditDialog = () => { value={selectedMenu.order_number === 0 ? '' : selectedMenu.order_number} onChange={(e) => { const value = parseInt(e.target.value, 10); - setSelectedMenu({ ...selectedMenu, order_number: isNaN(value) ? 0 : value }); + setSelectedMenu({ + ...selectedMenu, + order_number: isNaN(value) ? 0 : value + }); }} />
@@ -184,6 +201,26 @@ const EditDialog = () => {
+
+ + +
+
+ + {/*
-
+
*/}
diff --git a/src/pages/menu/manage-menu/hooks/ManageMenusContext.tsx b/src/pages/menu/manage-menu/hooks/ManageMenusContext.tsx index 2d76541..3ff2f54 100644 --- a/src/pages/menu/manage-menu/hooks/ManageMenusContext.tsx +++ b/src/pages/menu/manage-menu/hooks/ManageMenusContext.tsx @@ -133,12 +133,27 @@ const ManageMenusContextProvider = ({ children }: { children: React.ReactNode }) meta: { headerClassName: 'w-[250px]' } }, { - accessorFn: (row) => row.status, - id: 'status', + accessorKey: 'status', header: ({ column }) => , enableSorting: false, enableHiding: false, - meta: { headerClassName: 'w-[100px]', cellClassName: 'text-center' } + cell: ({ row }) => { + const isActive = row.original.status === 'Y'; + + return ( + + {isActive ? 'Active' : 'Inactive'} + + ); + }, + meta: { + headerClassName: 'w-[100px]', + cellClassName: 'text-center' + } }, { id: 'actions', @@ -211,7 +226,7 @@ const ManageMenusContextProvider = ({ children }: { children: React.ReactNode }) link: child.link, id_parent: child.id_parent, status: child.status, - order_number: child.order_number + order_number: parent.order_number }; }); @@ -242,7 +257,7 @@ const ManageMenusContextProvider = ({ children }: { children: React.ReactNode }) const total_count = transformedData.length; const paginatedData = transformedData.slice(page * limit, (page + 1) * limit); - console.log(response.data); + console.log('data', paginatedData); // setMenus(transformedData); return { data: paginatedData, totalCount: total_count }; } catch (error) { diff --git a/src/pages/settings/user/manage-user/hooks/ManageUserContext.tsx b/src/pages/settings/user/manage-user/hooks/ManageUserContext.tsx index efae604..231cbc6 100644 --- a/src/pages/settings/user/manage-user/hooks/ManageUserContext.tsx +++ b/src/pages/settings/user/manage-user/hooks/ManageUserContext.tsx @@ -8,6 +8,8 @@ import { ListToolBar } from '../blocks'; import { useCallApi } from '@/hooks'; interface ContextProps { + showSearchDialog: boolean; + handleSearchDialog: (show: boolean) => void; showEditDialog: boolean; handleEditDialog: (show: boolean, selected_user: string | null) => void; showAddDialog: boolean; @@ -35,6 +37,8 @@ interface RoleListProps { } const initialProps: ContextProps = { + showSearchDialog: false, + handleSearchDialog: (show: boolean) => {}, showEditDialog: false, handleEditDialog: () => {}, showAddDialog: false, @@ -52,6 +56,7 @@ const API_URL = apiConfig.service_dashboard; const ManageUserContextProvider = ({ children }: { children: React.ReactNode }) => { /* state */ const [showEditDialog, setShowEditDialog] = useState(false); + const [showSearchDialog, setShowSearchDialog] = useState(false); const [showAddDialog, setShowAddDialog] = useState(false); const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [selectedUser, setSelectedUser] = useState(null); @@ -59,6 +64,10 @@ const ManageUserContextProvider = ({ children }: { children: React.ReactNode }) const { GetData } = useCallApi(); /* action */ + const handleSearchDialog = useCallback((show: boolean) => { + setShowSearchDialog(show); + }, []); + const handleEditDialog = useCallback((show: boolean, selected_user: string | null) => { setSelectedUser(show ? selected_user : null); setShowEditDialog(show); @@ -219,6 +228,8 @@ const ManageUserContextProvider = ({ children }: { children: React.ReactNode }) return ( { - Master Data + Transaction diff --git a/src/pages/transaction/blocks/DetailTransaction.tsx b/src/pages/transaction/blocks/DetailTransaction.tsx new file mode 100644 index 0000000..ec5dd9f --- /dev/null +++ b/src/pages/transaction/blocks/DetailTransaction.tsx @@ -0,0 +1,538 @@ +import { useTransactionContext } from '../hooks/useTransactionContext'; +import { useCallApi } from '@/hooks'; +import { apiConfig } from '@/config/api.config'; +import { useEffect, useState } from 'react'; +import { + Dialog, + DialogBody, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog'; + +const API_URL = apiConfig.transaction; + +const DetailTransaction = () => { + const { GetData } = useCallApi(); + const { + showDetailDialog, + setShowDetailDialog, + selectedTransactionId + } = useTransactionContext(); + + const [transactionDetails, setTransactionDetails] = useState(null); + + useEffect(() => { + const fetchTransactionDetails = async () => { + if (selectedTransactionId) { + try { + const response = await GetData(`${API_URL}/transaction/history/detail/${selectedTransactionId}`, { + id: selectedTransactionId + }); + // console.log(response?.data); + setTransactionDetails(response?.data); + } catch (error) { + console.error('Error fetching transaction', error); + } + } + }; + + if (showDetailDialog && selectedTransactionId) { + fetchTransactionDetails(); + } + }, [showDetailDialog, selectedTransactionId, GetData]); + + const [activeTab, setActiveTab] = useState('detail'); // 'detail', 'log', 'approve' + + return ( + + + + Transaction Details + + + {/* Tabs Navigation */} +
+ + + + + +
+ + {/* Tab Content */} +
+ {activeTab === 'detail' && transactionDetails?.kind === 'P' && ( +
+

+ Transaction Information + + Info + +

+
+
+

Transaction Date

+

{transactionDetails?.transaction_date}

+
+
+

Full Name

+

{transactionDetails?.origin_customer.fullname}

+
+
+

Amount

+

{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(transactionDetails?.purchase.amount)}

+
+
+

Fee

+

+ {transactionDetails?.kind === 'P' + ? transactionDetails?.purchase.fee_amount.toLocaleString('en-US', { style: 'currency', currency: 'USD' }) + : transactionDetails?.transfer.fee_amount.toLocaleString('en-US', { style: 'currency', currency: 'USD' })} +

+
+
+

Status

+

+ {(() => { + let status; + if (transactionDetails?.status === 'C') { + status = 'COMPLETE'; + } else if (transactionDetails?.status === 'F') { + status = 'FAILED'; + } else if (transactionDetails?.status === 'O') { + status = 'ON PROCESS'; + } else { + status = 'PENDING'; + } + return status; + })()} +

+
+
+

Transaction Type

+

+ {(() => { + let kind; + if (transactionDetails?.kind === 'T') { + kind = 'TRANSFER'; + } else if (transactionDetails?.kind === 'P') { + kind = 'PURCHASE'; + } else if (transactionDetails?.kind === 'W') { + kind = 'WITHDRAW'; + } else if (transactionDetails?.kind === 'U') { + kind = 'TOP UP'; + } else if (transactionDetails?.kind === 'R') { + kind = 'RETURN'; + } + return kind; + })()} +

+
+
+

Description

+

{transactionDetails?.description}

+
+
+

Name

+

{transactionDetails?.type.name}

+
+
+ +

+ Origin Wallet + + Wallet + +

+
+
+

Name

+

{transactionDetails?.origin_wallet.name}

+
+
+

Description

+

{transactionDetails?.origin_wallet.description}

+
+
+ +

+ Purchase + + Purchase + +

+
+
+

Amount

+

{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(transactionDetails?.purchase.amount)}

+
+
+

Cashback

+

{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(transactionDetails?.purchase.cashback)}

+
+
+

Cashback Point

+

{transactionDetails?.purchase.cashback_point}

+
+
+

Fee Amount

+

{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(transactionDetails?.purchase.fee_amount)}

+
+
+
+ )} + + {activeTab === 'detail' && transactionDetails?.kind != 'P' && transactionDetails?.transfer!=null && ( +
+

+ Transaction Information + + Info + +

+
+
+

Transaction Date

+

{transactionDetails?.transaction_date}

+
+
+

Full Name

+

{transactionDetails?.origin_customer.fullname}

+
+
+

Amount

+

{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(transactionDetails?.transfer.amount)}

+
+
+

Fee

+

+ {transactionDetails?.transfer.fee_amount.toLocaleString('en-US', { style: 'currency', currency: 'USD' })} +

+
+
+

Status

+

+ {(() => { + let status; + if (transactionDetails?.status === 'C') { + status = 'COMPLETE'; + } else if (transactionDetails?.status === 'F') { + status = 'FAILED'; + } else if (transactionDetails?.status === 'O') { + status = 'ON PROCESS'; + } else { + status = 'PENDING'; + } + return status; + })()} +

+
+
+

Transaction Type

+

+ {(() => { + let kind; + if (transactionDetails?.kind === 'T') { + kind = 'TRANSFER'; + } else if (transactionDetails?.kind === 'P') { + kind = 'PURCHASE'; + } else if (transactionDetails?.kind === 'W') { + kind = 'WITHDRAW'; + } else if (transactionDetails?.kind === 'U') { + kind = 'TOP UP'; + } else if (transactionDetails?.kind === 'R') { + kind = 'RETURN'; + } + return kind; + })()} +

+
+
+

Description

+

{transactionDetails?.description}

+
+
+

Name

+

{transactionDetails?.type.name}

+
+
+

Reference

+

{transactionDetails?.transfer.reference}

+
+
+

Destination Iban

+

{transactionDetails?.transfer.destination_iban}

+
+
+ +

+ Destination Wallet + + Wallet + +

+
+
+

Name

+

{transactionDetails?.transfer.destination_wallet.name}

+
+
+

Description

+

{transactionDetails?.transfer.destination_wallet.description}

+
+
+ +

+ Destination Customer + + Destination Customer + +

+
+
+

Name

+

{transactionDetails?.transfer.destination_customer.fullname}

+
+
+

MSISDN

+

{transactionDetails?.transfer.destination_customer.msisdn}

+
+
+

Email

+

{transactionDetails?.transfer.destination_customer.email}

+
+
+

MSISDN

+

{transactionDetails?.transfer.destination_customer.username}

+
+
+ +

+ Origin Wallet + + Wallet + +

+
+
+

Name

+

{transactionDetails?.origin_wallet.name}

+
+
+

Description

+

{transactionDetails?.origin_wallet.description}

+
+
+
+ )} + + + {activeTab === 'origincustomer' && ( +
+

Origin Customer

+
+
+

Full Name

+

{transactionDetails?.origin_customer.fullname}

+
+
+

Phone Number

+

{transactionDetails?.origin_customer.msisdn}

+
+
+

Email

+

{transactionDetails?.origin_customer.email}

+
+
+

Username

+

{transactionDetails?.origin_customer.username}

+
+
+
+ )} + + {activeTab === 'log' && ( +
+

Transaction Logs

+
+ + + + + + + + + + + + + {transactionDetails?.log && transactionDetails?.log.length > 0 ? ( + transactionDetails.log.map((log: { request_endpoint: string, status: string; request_date: string; response_date: string; request_body: string; response_body: string; response_code: number }, index: number) => ( + + + + + + + + + )) + ) : ( + + + + )} + +
StatusRequest DateRequest BodyResponse BodyResponse CodeRequest End Point
+ {(() => { + let status; + if (log.status === 'P') { + status = 'PENDING'; + } else if (log.status === 'O') { + status = 'ON PROCESS'; + } else if (log.status === 'F') { + status = 'FAILED'; + } else if (log.status === 'C') { + status = 'COMPLETE'; + } + return status; + })()} + {log.request_date ?? '-'}{log.response_date ?? '-'}{log.request_body ?? '-'}{log.response_body ?? '-'}{log.request_endpoint ?? '-'}
+ No logs available +
+
+
+ )} + + {activeTab === 'approve' && ( +
+

Approval Logs

+ {transactionDetails?.log_approve.length === 0 ? ( +

No data available

+ ) : ( +
+ + + + + + + + + + {transactionDetails?.log_approve && transactionDetails?.log_approve.length > 0 ? ( + transactionDetails.log_approve.map((log: { created_at: string; status: string; updated_at: string }, index: number) => ( + + + + + + )) + ) : ( + + + + )} + +
StatusCreated AtUpdated At
+ {(() => { + let status; + if (log.status === 'W') { + status = 'WAITING'; + } else if (log.status === 'Y') { + status = 'APPROVE'; + } else if (log.status === 'N') { + status = 'REJECT'; + } else if (log.status === 'T') { + status = 'NO NEED'; + } + return status; + })()} + {log.created_at}{log.updated_at}
+ No logs available +
+
+ )} +
+ )} + + {activeTab === 'p24' && ( +
+

P24 Logs

+ {transactionDetails?.p24.length === 0 ? ( +

No data available

+ ) : ( +
+ + + + + + + + + + + + + {transactionDetails?.p24 && transactionDetails?.p24.length > 0 ? ( + transactionDetails.p24.map((log: { request_endpoint: string, type: string; request_date: string; response_date: string; request_body: string; response_body: string; response_code: number }, index: number) => ( + + + + + + + + + )) + ) : ( + + + + )} + +
TypeRequest DateRequest BodyResponse BodyResponse CodeRequest Endpoint
{log.type}{log.request_date}{log.response_date}{log.request_body}{log.response_body}{log.request_endpoint ?? '-'}
+ No logs available +
+
+ )} +
+ )} + +
+
+
+
+ ); +}; + +export default DetailTransaction; diff --git a/src/pages/transaction/hooks/TransactionContext.tsx b/src/pages/transaction/hooks/TransactionContext.tsx index c8b4943..5e28d8f 100644 --- a/src/pages/transaction/hooks/TransactionContext.tsx +++ b/src/pages/transaction/hooks/TransactionContext.tsx @@ -8,6 +8,7 @@ import { useCallApi } from '@/hooks'; import ListToolbar from '../blocks/ListToolbar'; import { Button } from '@/components/ui/button'; import { useNavigate } from 'react-router'; +import DetailTransaction from '../blocks/DetailTransaction'; interface TransactionProps { id: number; @@ -23,16 +24,26 @@ interface ContextProps { order_direction: any, filter: any ) => Promise<{ data: TransactionProps[]; totalCount: number } | undefined>; + showDetailDialog: boolean; + setShowDetailDialog: React.Dispatch>; + selectedTransactionId: number | null; + setSelectedTransactionId: React.Dispatch>; } const initialProps: ContextProps = { - getTransactionLists: async () => ({ data: [], totalCount: 0 }) + getTransactionLists: async () => ({ data: [], totalCount: 0 }), + showDetailDialog: false, + setShowDetailDialog: () => { }, + selectedTransactionId: null, + setSelectedTransactionId: () => { } }; const ManageTransactionContext = createContext(initialProps); const API_URL = apiConfig.transaction; const TransactionProvider = ({ children }: { children: React.ReactNode }) => { + const [showDetailDialog, setShowDetailDialog] = useState(false); + const [selectedTransactionId, setSelectedTransactionId] = useState(null); const [transaction, setTransaction] = useState([]); const { GetData } = useCallApi(); const navigate = useNavigate(); @@ -42,6 +53,15 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { const columns = useMemo[]>( () => [ + // { + // accessorKey: 'transaction_date', + // header: ({ column }) => , + // enableSorting: false, + // enableHiding: false, + // meta: { + // headerClassName: 'w-[250px]' + // } + // }, { accessorKey: 'transaction_date', header: ({ column }) => , @@ -49,8 +69,22 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { enableHiding: false, meta: { headerClassName: 'w-[250px]' + }, + cell: ({ row }) => { + // Memformat tanggal dan waktu dari ISO ke format biasa (DD-MM-YYYY HH:MM:SS) + const transactionDate = new Date(row.original.transaction_date); + const formattedDateTime = transactionDate.toLocaleString('en-GB', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false // Gunakan format 24 jam + }); + return formattedDateTime; // Format DD-MM-YYYY HH:MM:SS (menggunakan waktu yang sudah ada) } - }, + }, { accessorKey: 'origin_customer.fullname', header: ({ column }) => , @@ -61,8 +95,12 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { } }, { - accessorFn: (row) => - new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(row.purchase.amount), + accessorFn: (row) => { + const purchaseAmount = row?.purchase?.amount; + const transferAmount = row?.transfer?.amount; + + return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(purchaseAmount ?? transferAmount ?? 0); + }, id: 'amount', header: ({ column }) => , enableSorting: false, @@ -73,19 +111,13 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { }, { accessorFn: (row) => { - let fee; - if (row.kind === 'P') { - fee = row.purchase.fee_amount; - } else { - fee = row.transfer.fee_amount; - } - return fee.toLocaleString('en-US', { - style: 'currency', - currency: 'USD', - }); + const purchaseAmount = row?.purchase?.fee_amount; + const transferAmount = row?.transfer?.fee_amount; + + return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(purchaseAmount ?? transferAmount ?? 0); }, - accessorKey: 'fee', - header: ({ column }) => , + id: 'feeamount', + header: ({ column }) => , enableSorting: false, enableHiding: false, meta: { @@ -97,9 +129,9 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { let status; if (row.status === 'C') { status = 'COMPLETE'; - } else if(row.status === 'F') { + } else if (row.status === 'F') { status = 'FAILED'; - } else if(row.status === 'O') { + } else if (row.status === 'O') { status = 'ON PROCESS'; } else { status = 'PENDING'; @@ -138,13 +170,19 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { enableSorting: false, enableHiding: false, cell: (data) => { - const row = data.row.original.id; + const row = data.row.original; return ( - <> - - +
); }, meta: { @@ -161,25 +199,25 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { let enddate; let formattedFilter; - if (filter == undefined || filter.length==0) { + if (filter == undefined || filter.length == 0) { const today = new Date(); const nextWeek = new Date(); nextWeek.setDate(today.getDate() + 7); - - startdate = today.toISOString().split('T')[0]; + + startdate = today.toISOString().split('T')[0]; enddate = nextWeek.toISOString().split('T')[0]; - }else if (filter != undefined || filter.length!=0) { + } else if (filter != undefined || filter.length != 0) { startdate = filter[0].value.from; enddate = filter[0].value.to; } formattedFilter = { "Transactions.transaction_date": { - from: startdate+" 00:00:00", - to: enddate+" 23:59:59" + from: startdate + " 00:00:00", + to: enddate + " 23:59:59" } }; - + const response = await GetData(`${API_URL}/transaction/history`, { limit, page: page + 1, @@ -199,10 +237,15 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => { return ( + { e.preventDefault(); for (const key in formField) { if ( - formField[key as keyof typeof formField] === '' || - formField[key as keyof typeof formField] === 0 - ) { + formField[key as keyof typeof formField] === '' ) { setAlert({ show: true, message: 'All fields must be filled out' }); return; } @@ -163,7 +161,7 @@ const AddFeeDialog = () => { ...formField }); if (response?.status) { - toast.success('Success Create Transfer Fee'); + // toast.success('Success Create Transfer Fee'); reload(); resetForm(); handleAddFeeDialog(false); @@ -225,7 +223,7 @@ const AddFeeDialog = () => { />
- + { />
- + { />
- + { />
- + { if (response?.status) { setAlert({ show: false, message: '' }); handleDeleteFeeDialog(false, null); - toast.success('Success Delete Product'); + // toast.success('Success Delete Transaction Fee'); reload(); } else { - toast.error('Failed Delete Product'); + // toast.error('Failed Delete Transaction Fee'); setAlert({ show: true, message: response?.message }); } }, [selectedTransferFee]); diff --git a/src/pages/transfer/transferfee/blocks/EditDialog.tsx b/src/pages/transfer/transferfee/blocks/EditDialog.tsx index 6d82790..5bcedcd 100644 --- a/src/pages/transfer/transferfee/blocks/EditDialog.tsx +++ b/src/pages/transfer/transferfee/blocks/EditDialog.tsx @@ -96,10 +96,11 @@ const EditFeeDialog = () => { async (e: React.FormEvent) => { e.preventDefault(); const response = await PutData(`${API_URL}/transactionfees/update/${selectedTransferFee}`, { - ...formField, }); + ...formField + }); if (response?.status) { handleEditFeeDialog(false, null); - toast.success('Success Update User'); + // toast.success('Success Update Transaction Fee'); reload(); } else { setAlert((prev) => ({ ...prev, show: true, message: response?.message })); @@ -252,7 +253,9 @@ const EditFeeDialog = () => {
- + { />
- + { />
- + { />
- + { />
- + { />
- + { />
- + { />
- + { />
- +
- +
- +
- - -
+ + +
{/*
diff --git a/src/pages/transfer/transferfee/hooks/ManageTransferFeeContext.tsx b/src/pages/transfer/transferfee/hooks/ManageTransferFeeContext.tsx index 8703234..2510b2d 100644 --- a/src/pages/transfer/transferfee/hooks/ManageTransferFeeContext.tsx +++ b/src/pages/transfer/transferfee/hooks/ManageTransferFeeContext.tsx @@ -10,10 +10,10 @@ import { EditFeeDialog } from '../blocks/EditDialog'; interface ContextProps { showEditFeeDialog: boolean; - handleEditFeeDialog: (show: boolean, selectedTransferFee: string | null) => void; + handleEditFeeDialog: (show: boolean, selected_TransferFee: string | null) => void; showAddFeeDialog: boolean; handleAddFeeDialog: (show: boolean) => void; - handleDeleteFeeDialog: (show: boolean, selectedTransferFee: string | null) => void; + handleDeleteFeeDialog: (show: boolean, selected_TransferFee: string | null) => void; showDeleteFeeDialog: boolean; selectedTransferFee: string | null; } @@ -39,8 +39,8 @@ const ManageTransferFeeContextProvider = ({ children }: { children: React.ReactN const [selectedTransferFee, setSelectedTransferFee] = useState(null); const { GetData } = useCallApi(); - const handleEditFeeDialog = useCallback((show: boolean, selectedTransferFee: string | null) => { - setSelectedTransferFee(show ? selectedTransferFee : null); + const handleEditFeeDialog = useCallback((show: boolean, selected_TransferFee: string | null) => { + setSelectedTransferFee(show ? selected_TransferFee : null); setShowEditFeeDialog(show); }, []); @@ -48,8 +48,8 @@ const ManageTransferFeeContextProvider = ({ children }: { children: React.ReactN setShowAddFeeDialog(show); }, []); - const handleDeleteFeeDialog = useCallback((show: boolean, selectedTransferFee: string | null) => { - setSelectedTransferFee(show ? selectedTransferFee : null); + const handleDeleteFeeDialog = useCallback((show: boolean, selected_TransferFee: string | null) => { + setSelectedTransferFee(show ? selected_TransferFee : null); setShowDeleteFeeDialog(show); }, []); const doGetTransferFeeListData = async ( @@ -62,7 +62,7 @@ const ManageTransferFeeContextProvider = ({ children }: { children: React.ReactN filter = filter.length == 0 ? {} : { any: filter[0].value.toLowerCase() }; const response = await GetData(`${API_URL}/transactionfees/list`, { limit: limit, - page: 1, + page: page+1, with_deleted: false, order_field: sorting[0].id, order_direction: sorting[0].desc ? 'ASC' : 'DESC', @@ -203,9 +203,9 @@ const ManageTransferFeeContextProvider = ({ children }: { children: React.ReactN return (
-
-

Manage Transaction Fee

-
+
+

Manage Transaction Fee

+
{ async (e: React.FormEvent) => { e.preventDefault(); for (const key in formField) { - if ( - formField[key as keyof typeof formField] === '' || - formField[key as keyof typeof formField] === 0 - ) { + if (formField[key as keyof typeof formField] === '') { setAlert({ show: true, message: 'All fields must be filled out' }); return; } @@ -229,6 +226,7 @@ const AddDialog = () => {
{
{
- +
{
- +
{
- +
{ setAlert({ show: false, message: '' }); handleDeleteDialog(false, null); reload(); - setTimeout(() => toast.success('Success Delete Product'), 0); + setTimeout(() => toast.success('Success Delete Transaction Type'), 0); } else { setAlert({ show: true, message: response?.message }); setTimeout(() => toast.error('Failed Delete Product'), 0); diff --git a/src/pages/transfer/transfertype/blocks/EditDialog.tsx b/src/pages/transfer/transfertype/blocks/EditDialog.tsx index 0061789..23236f5 100644 --- a/src/pages/transfer/transfertype/blocks/EditDialog.tsx +++ b/src/pages/transfer/transfertype/blocks/EditDialog.tsx @@ -124,7 +124,7 @@ const EditDialog = () => { }); if (response?.status) { handleEditDialog(false, null); - toast.success('Success Update User'); + toast.success('Success Update Transfer Type'); reload(); } else { setAlert((prev) => ({ ...prev, show: true, message: response?.message })); @@ -240,7 +240,7 @@ const EditDialog = () => {
{
{
{ onValueChange={(values) => { setFormField((prev) => ({ ...prev, - maximum_amount: values.floatValue || 0 + minimum_amount: values.floatValue || 0 })); }} placeholder="Enter Minimum Amount" @@ -299,7 +298,6 @@ const EditDialog = () => {
{
{
-