This commit is contained in:
unknown
2025-04-01 22:38:44 +07:00
44 changed files with 3079 additions and 466 deletions

2
.gitignore vendored
View File

@ -28,3 +28,5 @@ yarn.lock
*.sw? *.sw?
**/src/*.js **/src/*.js
.env .env
package-lock.json

143
package-lock.json generated
View File

@ -71,6 +71,7 @@
"styled-components": "^6.1.13", "styled-components": "^6.1.13",
"stylis": "^4.3.4", "stylis": "^4.3.4",
"stylis-plugin-rtl": "^2.1.1", "stylis-plugin-rtl": "^2.1.1",
"tabs": "^0.2.0",
"tailwind-merge": "^2.5.4", "tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"vite-plugin-windicss": "^1.9.3", "vite-plugin-windicss": "^1.9.3",
@ -106,7 +107,6 @@
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
"dev": true,
"engines": { "engines": {
"node": ">=10" "node": ">=10"
}, },
@ -1593,7 +1593,6 @@
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"aix" "aix"
@ -1609,7 +1608,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
@ -1625,7 +1623,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
@ -1641,7 +1638,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
@ -1657,7 +1653,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
@ -1673,7 +1668,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
@ -1689,7 +1683,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"freebsd" "freebsd"
@ -1705,7 +1698,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"freebsd" "freebsd"
@ -1721,7 +1713,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -1737,7 +1728,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -1753,7 +1743,6 @@
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -1769,7 +1758,6 @@
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -1785,7 +1773,6 @@
"cpu": [ "cpu": [
"mips64el" "mips64el"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -1801,7 +1788,6 @@
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -1817,7 +1803,6 @@
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -1833,7 +1818,6 @@
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -1849,7 +1833,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -1865,7 +1848,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"netbsd" "netbsd"
@ -1881,7 +1863,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"openbsd" "openbsd"
@ -1897,7 +1878,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"sunos" "sunos"
@ -1913,7 +1893,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@ -1929,7 +1908,6 @@
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@ -1945,7 +1923,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@ -2448,7 +2425,6 @@
"version": "8.0.2", "version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"dependencies": { "dependencies": {
"string-width": "^5.1.2", "string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0", "string-width-cjs": "npm:string-width@^4.2.0",
@ -2465,7 +2441,6 @@
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"dev": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@ -2477,7 +2452,6 @@
"version": "6.2.1", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@ -2488,14 +2462,12 @@
"node_modules/@isaacs/cliui/node_modules/emoji-regex": { "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
"version": "9.2.2", "version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
"dev": true
}, },
"node_modules/@isaacs/cliui/node_modules/string-width": { "node_modules/@isaacs/cliui/node_modules/string-width": {
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"dependencies": { "dependencies": {
"eastasianwidth": "^0.2.0", "eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2", "emoji-regex": "^9.2.2",
@ -2512,7 +2484,6 @@
"version": "7.1.0", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"dependencies": { "dependencies": {
"ansi-regex": "^6.0.1" "ansi-regex": "^6.0.1"
}, },
@ -2527,7 +2498,6 @@
"version": "8.1.0", "version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"dependencies": { "dependencies": {
"ansi-styles": "^6.1.0", "ansi-styles": "^6.1.0",
"string-width": "^5.0.1", "string-width": "^5.0.1",
@ -2920,7 +2890,6 @@
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"dev": true,
"optional": true, "optional": true,
"engines": { "engines": {
"node": ">=14" "node": ">=14"
@ -3939,7 +3908,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
@ -3952,7 +3920,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
@ -3965,7 +3932,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
@ -3978,7 +3944,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
@ -3991,7 +3956,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"freebsd" "freebsd"
@ -4004,7 +3968,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"freebsd" "freebsd"
@ -4017,7 +3980,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -4030,7 +3992,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -4043,7 +4004,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -4056,7 +4016,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -4069,7 +4028,6 @@
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -4082,7 +4040,6 @@
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -4095,7 +4052,6 @@
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -4108,7 +4064,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -4121,7 +4076,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@ -4134,7 +4088,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@ -4147,7 +4100,6 @@
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@ -4160,7 +4112,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@ -4265,8 +4216,7 @@
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
"dev": true
}, },
"node_modules/@types/geojson": { "node_modules/@types/geojson": {
"version": "7946.0.14", "version": "7946.0.14",
@ -4330,7 +4280,7 @@
"version": "18.3.1", "version": "18.3.1",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==",
"dev": true, "devOptional": true,
"dependencies": { "dependencies": {
"@types/react": "*" "@types/react": "*"
} }
@ -4668,14 +4618,12 @@
"node_modules/any-promise": { "node_modules/any-promise": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="
"dev": true
}, },
"node_modules/anymatch": { "node_modules/anymatch": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"dependencies": { "dependencies": {
"normalize-path": "^3.0.0", "normalize-path": "^3.0.0",
"picomatch": "^2.0.4" "picomatch": "^2.0.4"
@ -4701,8 +4649,7 @@
"node_modules/arg": { "node_modules/arg": {
"version": "5.0.2", "version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="
"dev": true
}, },
"node_modules/argparse": { "node_modules/argparse": {
"version": "2.0.1", "version": "2.0.1",
@ -4803,7 +4750,6 @@
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
"dev": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
}, },
@ -4815,7 +4761,6 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0"
} }
@ -4907,7 +4852,6 @@
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
"dev": true,
"engines": { "engines": {
"node": ">= 6" "node": ">= 6"
} }
@ -4959,7 +4903,6 @@
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"dependencies": { "dependencies": {
"anymatch": "~3.1.2", "anymatch": "~3.1.2",
"braces": "~3.0.2", "braces": "~3.0.2",
@ -4983,7 +4926,6 @@
"version": "5.1.2", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"dependencies": { "dependencies": {
"is-glob": "^4.0.1" "is-glob": "^4.0.1"
}, },
@ -5077,7 +5019,6 @@
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
"dev": true,
"engines": { "engines": {
"node": ">= 6" "node": ">= 6"
} }
@ -5111,7 +5052,6 @@
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"path-key": "^3.1.0", "path-key": "^3.1.0",
@ -5326,14 +5266,12 @@
"node_modules/didyoumean": { "node_modules/didyoumean": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="
"dev": true
}, },
"node_modules/dlv": { "node_modules/dlv": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
"dev": true
}, },
"node_modules/dom-helpers": { "node_modules/dom-helpers": {
"version": "5.2.1", "version": "5.2.1",
@ -5347,8 +5285,7 @@
"node_modules/eastasianwidth": { "node_modules/eastasianwidth": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
"dev": true
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.48", "version": "1.5.48",
@ -5391,7 +5328,6 @@
"version": "0.21.5", "version": "0.21.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
"dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"bin": { "bin": {
"esbuild": "bin/esbuild" "esbuild": "bin/esbuild"
@ -5853,7 +5789,6 @@
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
"integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
"dev": true,
"dependencies": { "dependencies": {
"cross-spawn": "^7.0.0", "cross-spawn": "^7.0.0",
"signal-exit": "^4.0.1" "signal-exit": "^4.0.1"
@ -5923,7 +5858,6 @@
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"optional": true, "optional": true,
"os": [ "os": [
@ -6008,7 +5942,6 @@
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
"dependencies": { "dependencies": {
"is-glob": "^4.0.3" "is-glob": "^4.0.3"
}, },
@ -6221,7 +6154,6 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"dependencies": { "dependencies": {
"binary-extensions": "^2.0.0" "binary-extensions": "^2.0.0"
}, },
@ -6281,14 +6213,12 @@
"node_modules/isexe": { "node_modules/isexe": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
"dev": true
}, },
"node_modules/jackspeak": { "node_modules/jackspeak": {
"version": "3.4.3", "version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"dev": true,
"dependencies": { "dependencies": {
"@isaacs/cliui": "^8.0.2" "@isaacs/cliui": "^8.0.2"
}, },
@ -6411,7 +6341,6 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
"integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
"dev": true,
"engines": { "engines": {
"node": ">=10" "node": ">=10"
} }
@ -6563,7 +6492,6 @@
"version": "9.0.5", "version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
"dependencies": { "dependencies": {
"brace-expansion": "^2.0.1" "brace-expansion": "^2.0.1"
}, },
@ -6578,7 +6506,6 @@
"version": "7.1.2", "version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"dev": true,
"engines": { "engines": {
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"
} }
@ -6601,7 +6528,6 @@
"version": "2.7.0", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
"dev": true,
"dependencies": { "dependencies": {
"any-promise": "^1.0.0", "any-promise": "^1.0.0",
"object-assign": "^4.0.1", "object-assign": "^4.0.1",
@ -6658,7 +6584,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -6712,7 +6637,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
"dev": true,
"engines": { "engines": {
"node": ">= 6" "node": ">= 6"
} }
@ -6791,8 +6715,7 @@
"node_modules/package-json-from-dist": { "node_modules/package-json-from-dist": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="
"dev": true
}, },
"node_modules/parent-module": { "node_modules/parent-module": {
"version": "1.0.1", "version": "1.0.1",
@ -6843,7 +6766,6 @@
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@ -6857,7 +6779,6 @@
"version": "1.11.1", "version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"dev": true,
"dependencies": { "dependencies": {
"lru-cache": "^10.2.0", "lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
@ -6872,8 +6793,7 @@
"node_modules/path-scurry/node_modules/lru-cache": { "node_modules/path-scurry/node_modules/lru-cache": {
"version": "10.4.3", "version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
"dev": true
}, },
"node_modules/path-type": { "node_modules/path-type": {
"version": "4.0.0", "version": "4.0.0",
@ -6903,7 +6823,6 @@
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -6912,7 +6831,6 @@
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
"integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
"dev": true,
"engines": { "engines": {
"node": ">= 6" "node": ">= 6"
} }
@ -6921,7 +6839,6 @@
"version": "8.4.49", "version": "8.4.49",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
"integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -7299,7 +7216,6 @@
"version": "15.1.0", "version": "15.1.0",
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
"dev": true,
"dependencies": { "dependencies": {
"postcss-value-parser": "^4.0.0", "postcss-value-parser": "^4.0.0",
"read-cache": "^1.0.0", "read-cache": "^1.0.0",
@ -7316,7 +7232,6 @@
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
"dev": true,
"dependencies": { "dependencies": {
"camelcase-css": "^2.0.1" "camelcase-css": "^2.0.1"
}, },
@ -7363,7 +7278,6 @@
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -7398,7 +7312,6 @@
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz",
"integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==",
"dev": true,
"engines": { "engines": {
"node": ">=14" "node": ">=14"
}, },
@ -7410,7 +7323,6 @@
"version": "2.6.0", "version": "2.6.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz",
"integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==",
"dev": true,
"bin": { "bin": {
"yaml": "bin.mjs" "yaml": "bin.mjs"
}, },
@ -7446,7 +7358,6 @@
"version": "6.2.0", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
"dev": true,
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -7471,7 +7382,6 @@
"version": "6.1.2", "version": "6.1.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
"dev": true,
"dependencies": { "dependencies": {
"cssesc": "^3.0.0", "cssesc": "^3.0.0",
"util-deprecate": "^1.0.2" "util-deprecate": "^1.0.2"
@ -8025,6 +7935,7 @@
"version": "5.4.3", "version": "5.4.3",
"resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.3.tgz", "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.3.tgz",
"integrity": "sha512-VCY5hFg/soBighAoGcdE+GagkJq0230qN6jcS5sp8wQX1qy1fYN/RX7/BXkrs0oyzzwqR8/+eSUrqXbGeywdUQ==", "integrity": "sha512-VCY5hFg/soBighAoGcdE+GagkJq0230qN6jcS5sp8wQX1qy1fYN/RX7/BXkrs0oyzzwqR8/+eSUrqXbGeywdUQ==",
"license": "MIT",
"peerDependencies": { "peerDependencies": {
"react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "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" "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", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
"dev": true,
"dependencies": { "dependencies": {
"pify": "^2.3.0" "pify": "^2.3.0"
} }
@ -8197,7 +8107,6 @@
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"dependencies": { "dependencies": {
"picomatch": "^2.2.1" "picomatch": "^2.2.1"
}, },
@ -8275,7 +8184,6 @@
"version": "4.24.2", "version": "4.24.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.2.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.2.tgz",
"integrity": "sha512-do/DFGq5g6rdDhdpPq5qb2ecoczeK6y+2UAjdJ5trjQJj5f1AiVdLRWRc9A9/fFukfvJRgM0UXzxBIYMovm5ww==", "integrity": "sha512-do/DFGq5g6rdDhdpPq5qb2ecoczeK6y+2UAjdJ5trjQJj5f1AiVdLRWRc9A9/fFukfvJRgM0UXzxBIYMovm5ww==",
"dev": true,
"dependencies": { "dependencies": {
"@types/estree": "1.0.6" "@types/estree": "1.0.6"
}, },
@ -8375,7 +8283,6 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"dependencies": { "dependencies": {
"shebang-regex": "^3.0.0" "shebang-regex": "^3.0.0"
}, },
@ -8387,7 +8294,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@ -8413,7 +8319,6 @@
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
"engines": { "engines": {
"node": ">=14" "node": ">=14"
}, },
@ -8464,7 +8369,6 @@
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": { "dependencies": {
"emoji-regex": "^8.0.0", "emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0", "is-fullwidth-code-point": "^3.0.0",
@ -8490,7 +8394,6 @@
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": { "dependencies": {
"ansi-regex": "^5.0.1" "ansi-regex": "^5.0.1"
}, },
@ -8617,7 +8520,6 @@
"version": "3.35.0", "version": "3.35.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
"integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
"dev": true,
"dependencies": { "dependencies": {
"@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/gen-mapping": "^0.3.2",
"commander": "^4.0.0", "commander": "^4.0.0",
@ -8639,7 +8541,6 @@
"version": "10.4.5", "version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"dev": true,
"dependencies": { "dependencies": {
"foreground-child": "^3.1.0", "foreground-child": "^3.1.0",
"jackspeak": "^3.1.2", "jackspeak": "^3.1.2",
@ -8777,6 +8678,12 @@
"url": "https://opencollective.com/unts" "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": { "node_modules/tailwind-merge": {
"version": "2.5.4", "version": "2.5.4",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.4.tgz", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.4.tgz",
@ -8790,7 +8697,6 @@
"version": "3.4.14", "version": "3.4.14",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.14.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.14.tgz",
"integrity": "sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==", "integrity": "sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==",
"dev": true,
"dependencies": { "dependencies": {
"@alloc/quick-lru": "^5.2.0", "@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2", "arg": "^5.0.2",
@ -8835,7 +8741,6 @@
"version": "6.1.2", "version": "6.1.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
"dev": true,
"dependencies": { "dependencies": {
"cssesc": "^3.0.0", "cssesc": "^3.0.0",
"util-deprecate": "^1.0.2" "util-deprecate": "^1.0.2"
@ -8854,7 +8759,6 @@
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
"dev": true,
"dependencies": { "dependencies": {
"any-promise": "^1.0.0" "any-promise": "^1.0.0"
} }
@ -8863,7 +8767,6 @@
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
"dev": true,
"dependencies": { "dependencies": {
"thenify": ">= 3.1.0 < 4" "thenify": ">= 3.1.0 < 4"
}, },
@ -8912,8 +8815,7 @@
"node_modules/ts-interface-checker": { "node_modules/ts-interface-checker": {
"version": "0.1.13", "version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="
"dev": true
}, },
"node_modules/tslib": { "node_modules/tslib": {
"version": "2.8.0", "version": "2.8.0",
@ -8947,7 +8849,7 @@
"version": "5.6.3", "version": "5.6.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
"dev": true, "devOptional": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@ -9089,7 +8991,6 @@
"version": "5.4.11", "version": "5.4.11",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
"integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
"dev": true,
"dependencies": { "dependencies": {
"esbuild": "^0.21.3", "esbuild": "^0.21.3",
"postcss": "^8.4.43", "postcss": "^8.4.43",
@ -9165,7 +9066,6 @@
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"dependencies": { "dependencies": {
"isexe": "^2.0.0" "isexe": "^2.0.0"
}, },
@ -9217,7 +9117,6 @@
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"dependencies": { "dependencies": {
"ansi-styles": "^4.0.0", "ansi-styles": "^4.0.0",
"string-width": "^4.1.0", "string-width": "^4.1.0",

View File

@ -77,6 +77,7 @@
"styled-components": "^6.1.13", "styled-components": "^6.1.13",
"stylis": "^4.3.4", "stylis": "^4.3.4",
"stylis-plugin-rtl": "^2.1.1", "stylis-plugin-rtl": "^2.1.1",
"tabs": "^0.2.0",
"tailwind-merge": "^2.5.4", "tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"vite-plugin-windicss": "^1.9.3", "vite-plugin-windicss": "^1.9.3",

View File

@ -181,6 +181,7 @@ export const DataGridProvider = <TData extends object>(props: TDataGridProps<TDa
reload: loadData reload: loadData
}} }}
> >
{loading && <div className="fixed inset-0 flex items-center justify-center z-50"></div>}
{props.children ? props.children : <DataGridInner />} {props.children ? props.children : <DataGridInner />}
</DataGridContext.Provider> </DataGridContext.Provider>
); );

View File

@ -5,10 +5,10 @@ export const DataGridLoader = () => {
const { props } = useDataGrid(); const { props } = useDataGrid();
return ( return (
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"> <div className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex items-center gap-6 min-w-[160px] bg-card text-muted-foreground px-4 py-2 font-medium leading-none text-sm border shadow-sm rounded-md">
<div className="text-muted-foreground bg-card flex items-center gap-2 px-4 py-2 font-medium leading-none text-sm border shadow-sm rounded-md"> <div className="text-muted-foreground bg-card flex items-center gap-2 px-4 py-2 font-medium leading-none text-sm border shadow-sm rounded-md">
<svg <svg
className="animate-spin -ml-1 h-5 w-5 text-muted-foreground" className="animate-spin -ml-1 h-5 w-5 text-muted-foreground flex-shrink-0"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"

View File

@ -1,6 +1,12 @@
import { Container, DataGridInner } from '@/components'; import { Container, DataGridInner } from '@/components';
import { ManageConversionContextProvider } from './hooks/ManageConversionContext'; import { ManageConversionContextProvider } from './hooks/ManageConversionContext';
import { Breadcrumbs, Link } from '@mui/material'; import { Breadcrumbs, Link } from '@mui/material';
import AddDialog from './blocks/AddDialog';
import EditDialog from './blocks/EditDialog';
import { Delete } from 'lucide-react';
import DeleteDialog from './blocks/DeleteDialog';
// import EditDialog from './blocks/EditDialog';
const ConversionMaster = () => { const ConversionMaster = () => {
return ( return (
@ -24,6 +30,10 @@ const ConversionMaster = () => {
<div className="grid gap-5 lg:gap-7.5"> <div className="grid gap-5 lg:gap-7.5">
<DataGridInner /> <DataGridInner />
</div> </div>
<AddDialog />
<EditDialog/>
<DeleteDialog/>
</Container> </Container>
</ManageConversionContextProvider> </ManageConversionContextProvider>
); );

View File

@ -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<any | null>(null);
const { showAddDialog, handleAddDialog, selectedConversion } = useManageConversionContext();
const { reload } = useDataGrid();
const { PostData, GetData } = useCallApi();
const parsedUser = getAuth()?.user;
const [currencies, setCurrencies] = useState<CurrencyProps[]>([]);
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<HTMLFormElement>) => {
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<HTMLFormElement>) => {
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 (
<Dialog open={showAddDialog} onOpenChange={(open) => handleAddDialog(open)}>
<DialogContent className="container-fixed max-w-[768px] flex flex-col p-5 overflow-hidden">
<DialogHeader>
<DialogTitle>Conversion - Create</DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<DialogBody ref={parentRef}>
<div className="flex flex-col">
{alert.show && (
<Alert variant="danger">
<h3>{alert.message}</h3>
</Alert>
)}
<form onSubmit={handleSubmit}>
<div className="card-body grid gap-5">
<div className="w-full">
<label className="form-label">
Currency Origin <span className="text-red-500">*</span>
</label>
<Select
value={formField.id_currency_origin}
onValueChange={(id_currency_origin) =>
setFormField((prev) => ({ ...prev, id_currency_origin }))
}
>
<SelectTrigger>
<SelectValue placeholder="Select Currency" />
</SelectTrigger>
<SelectContent>
{currencies.map((currency, idx) => (
<SelectItem value={currency.ID} key={currency.ID}>
{currency.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="w-full">
<label className="form-label">
Currency Destination <span className="text-red-500">*</span>
</label>
<Select
value={formField.id_currency_destination}
onValueChange={(id_currency_destination) =>
setFormField((prev) => ({ ...prev, id_currency_destination }))
}
>
<SelectTrigger>
<SelectValue placeholder="Select Currency" />
</SelectTrigger>
<SelectContent>
{currencies.map((currency, idx) => (
<SelectItem value={currency.ID} key={currency.ID}>
{currency.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="w-full">
<label className="form-label">
Buy<span className="text-red-500">*</span>
</label>
<NumericFormat
className="input"
value={formField.buy}
thousandSeparator="."
decimalSeparator=","
allowNegative={false}
onValueChange={(values) => {
setFormField((prev) => ({
...prev,
buy: values.floatValue || 0
}));
}}
placeholder="Enter Buy"
/>
</div>
<div className="w-full">
<label className="form-label">
Sell
<span className="text-red-500">*</span>
</label>
<NumericFormat
className="input"
value={formField.sell}
thousandSeparator="."
decimalSeparator=","
allowNegative={false}
onValueChange={(values) => {
setFormField((prev) => ({
...prev,
sell: values.floatValue || 0
}));
}}
placeholder="Enter Sell"
/>
</div>
<div className="w-full">
<label className="form-label">
Status
<span className="text-red-500">*</span>
</label>
<div className="grow">
<Select
value={formField.status}
onValueChange={(value) =>
setFormField((prev) => ({ ...prev, status: value }))
}
>
<SelectTrigger>
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Y">Active</SelectItem>
<SelectItem value="N">Inactive</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="flex justify-end gap-5">
<Button type="button" variant="outline" onClick={resetForm}>
Reset
</Button>
<Button variant="default">Save Changes</Button>
</div>
</div>
</form>
</div>
</DialogBody>
</DialogContent>
</Dialog>
);
};
export default AddDialog;

View File

@ -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 (
<Dialog open={showDeleteDialog} onOpenChange={(open) => handleDeleteDialog(open, null)}>
<DialogContent className="container-fixed max-w-md flex flex-col p-5 overflow-hidden [&>button]:hidden">
<DialogHeader className="p-0 border-0 block">
<DialogTitle></DialogTitle>
<DialogDescription></DialogDescription>
<Alert variant="warning">
<h3 className="text-lg">Are you sure?</h3>
<span className="text-sm">You will delete this data!</span>
</Alert>
{alert.show && (
<Alert variant="danger">
<h3>{alert.message}</h3>
</Alert>
)}
</DialogHeader>
<DialogFooter className="flex justify-end items-center gap-4 mt-3">
<Button variant="outline" onClick={() => handleDeleteDialog(false, null)}>
Cancel
</Button>
<Button variant="destructive" onClick={doDeleteConversion}>
Delete
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};
export default DeleteDialog;

View File

@ -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<any | null>(null);
const { showEditDialog, handleEditDialog, selectedConversion } = useManageConversionContext();
const { reload } = useDataGrid();
const { PostData, GetData, PutData } = useCallApi();
const parsedUser = getAuth()?.user;
const [currencies, setCurrencies] = useState<CurrencyProps[]>([]);
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<HTMLFormElement>) => {
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 (
<Dialog open={showEditDialog} onOpenChange={(open) => handleEditDialog(open,null)}>
<DialogContent className="container-fixed max-w-[768px] flex flex-col p-5 overflow-hidden">
<DialogHeader>
<DialogTitle>Conversion - Update</DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<DialogBody ref={parentRef}>
<div className="flex flex-col">
{alert.show && (
<Alert variant="danger">
<h3>{alert.message}</h3>
</Alert>
)}
<form onSubmit={doUpdateConversion}>
<div className="card-body grid gap-5">
<div className="w-full">
<label className="form-label">
Currency Origin <span className="text-red-500">*</span>
</label>
<Select
value={formField.id_currency_origin}
onValueChange={(id_currency_origin) =>
setFormField((prev) => ({ ...prev, id_currency_origin }))
}
>
<SelectTrigger>
<SelectValue placeholder="Select Currency" />
</SelectTrigger>
<SelectContent>
{currencies.map((currency, idx) => (
<SelectItem value={currency.ID} key={currency.ID}>
{currency.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="w-full">
<label className="form-label">
Currency Destination <span className="text-red-500">*</span>
</label>
<Select
value={formField.id_currency_destination}
onValueChange={(id_currency_destination) =>
setFormField((prev) => ({ ...prev, id_currency_destination }))
}
>
<SelectTrigger>
<SelectValue placeholder="Select Currency" />
</SelectTrigger>
<SelectContent>
{currencies.map((currency, idx) => (
<SelectItem value={currency.ID} key={currency.ID}>
{currency.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="w-full">
<label className="form-label">
Buy<span className="text-red-500">*</span>
</label>
<NumericFormat
className="input"
value={formField.buy}
thousandSeparator="."
decimalSeparator=","
allowNegative={false}
onValueChange={(values) => {
setFormField((prev) => ({
...prev,
buy: values.floatValue || 0
}));
}}
placeholder="Enter Buy"
/>
</div>
<div className="w-full">
<label className="form-label">
Sell
<span className="text-red-500">*</span>
</label>
<NumericFormat
className="input"
value={formField.sell}
thousandSeparator="."
decimalSeparator=","
allowNegative={false}
onValueChange={(values) => {
setFormField((prev) => ({
...prev,
sell: values.floatValue || 0
}));
}}
placeholder="Enter Sell"
/>
</div>
<div className="w-full">
<label className="form-label">
Status
<span className="text-red-500">*</span>
</label>
<div className="grow">
<Select
value={formField.status}
onValueChange={(value) =>
setFormField((prev) => ({ ...prev, status: value }))
}
>
<SelectTrigger>
<SelectValue placeholder="Select" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Y">Active</SelectItem>
<SelectItem value="N">Inactive</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="flex justify-end gap-5">
<Button type="button" variant="outline" onClick={resetForm}>
Reset
</Button>
<Button variant="default">Save Changes</Button>
</div>
</div>
</form>
</div>
</DialogBody>
</DialogContent>
</Dialog>
);
};
export default EditDialog;

View File

@ -11,7 +11,7 @@ const ListToolbar = () => {
<div className="flex flex-wrap gap-2 lg:gap-5 w-full"> <div className="flex flex-wrap gap-2 lg:gap-5 w-full">
<div className="flex justify-between w-full items-center"> <div className="flex justify-between w-full items-center">
<div className="flex w-[50%] gap-3 items-center"> <div className="flex w-[50%] gap-3 items-center">
<label className="input input-sm w-1/3"> {/* <label className="input input-sm w-1/3">
<KeenIcon icon="magnifier" /> <KeenIcon icon="magnifier" />
<input <input
type="text" type="text"
@ -19,7 +19,7 @@ const ListToolbar = () => {
value={(table.getColumn('name')?.getFilterValue() as string) ?? ''} value={(table.getColumn('name')?.getFilterValue() as string) ?? ''}
onChange={(event) => table.getColumn('name')?.setFilterValue(event.target.value)} onChange={(event) => table.getColumn('name')?.setFilterValue(event.target.value)}
/> />
</label> </label> */}
{/* <DefaultTooltip title={'Filter'} placement={'top'}> {/* <DefaultTooltip title={'Filter'} placement={'top'}>
<Button <Button
variant="outline" variant="outline"

View File

@ -3,79 +3,123 @@ import { Toaster } from '@/components/ui/sonner';
import { apiConfig } from '@/config/api.config'; import { apiConfig } from '@/config/api.config';
import { useCallApi } from '@/hooks'; import { useCallApi } from '@/hooks';
import { ColumnDef } from '@tanstack/react-table'; import { ColumnDef } from '@tanstack/react-table';
import { createContext, useCallback, useMemo, useState } from 'react'; import { createContext, useCallback, useEffect, useMemo, useState } from 'react';
import ListToolbar from '../blocks/ListToolbar'; import ListToolbar from '../blocks/ListToolbar';
interface ConversionProps { interface CurrencyProps {
id: string; ID: string;
name: string; name: string;
} }
interface Conversion {
id: string;
id_currency_origin: string;
id_currency_destination: string;
buy: number;
sell: number;
status: string;
}
interface ContextProps { interface ContextProps {
conversion: ConversionProps[];
showAddDialog: boolean;
handleAddDialog: (show: boolean) => void;
showEditDialog: boolean; showEditDialog: boolean;
handleEditDialog: (show: boolean, selected_conversion: string | null) => void; handleEditDialog: (show: boolean, selected_conversion: string | null) => void;
showAddDialog: boolean;
handleAddDialog: (show: boolean) => void;
showDeleteDialog: boolean; showDeleteDialog: boolean;
handleDeleteDialog: (show: boolean, selected_conversion: string | null) => void; handleDeleteDialog: (show: boolean, selected_conversion: string | null) => void;
selectedConversion: string | null; selectedConversion: string | null;
getConversionLists: ( conversion: string | null;
limit: number,
page: number,
with_deleted: boolean,
order_field: any,
order_direction: any
) => Promise<{ data: ConversionProps[]; totalCount: number } | undefined>;
} }
const initialProps: ContextProps = { const initialProps: ContextProps = {
conversion: [],
showAddDialog: false,
handleAddDialog: () => {},
showEditDialog: false, showEditDialog: false,
handleEditDialog: () => {}, handleEditDialog: () => {},
showAddDialog: false,
handleAddDialog: () => {},
showDeleteDialog: false, showDeleteDialog: false,
handleDeleteDialog: () => {}, handleDeleteDialog: () => {},
selectedConversion: null, selectedConversion: null,
getConversionLists: async () => undefined conversion: null,
}; };
const ManageConversionContext = createContext<ContextProps>(initialProps); const ManageConversionContext = createContext<ContextProps>(initialProps);
const API_URL_WALLET = apiConfig.service_wallet; const API_URL = apiConfig.service_wallet;
const ManageConversionContextProvider = ({ children }: { children: React.ReactNode }) => { const ManageConversionContextProvider = ({ children }: { children: React.ReactNode }) => {
const [conversions, setConversions] = useState<ConversionProps[]>([]);
const [showAddDialog, setShowAddDialog] = useState(false);
const [showEditDialog, setShowEditDialog] = useState(false); const [showEditDialog, setShowEditDialog] = useState(false);
const [showAddDialog, setShowAddDialog] = useState(false);
const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const [selectedConversion, setSelectedConversion] = useState<string | null>(null);
const { GetData } = useCallApi(); const { GetData } = useCallApi();
const [selectedConversion, setSelectedConversion] = useState<string | null>(null);
const [conversion, setConversion] = useState<string | null>(null);
const handleEditDialog = useCallback((show: boolean, selected_conversion: string | null) => {
setSelectedConversion(show ? selected_conversion : null);
setShowEditDialog(show);
}, []);
const handleAddDialog = useCallback((show: boolean) => { const handleAddDialog = useCallback((show: boolean) => {
setShowAddDialog(show); setShowAddDialog(show);
}, []); }, []);
const handleEditDialog = useCallback((show: boolean, selected_conversion: string | null) => {
setShowEditDialog(show);
setSelectedConversion(show ? selected_conversion : null);
}, []);
const handleDeleteDialog = useCallback((show: boolean, selected_conversion: string | null) => { const handleDeleteDialog = useCallback((show: boolean, selected_conversion: string | null) => {
setShowEditDialog(show); setShowDeleteDialog(show);
setSelectedConversion(show ? selected_conversion : null); setSelectedConversion(show ? selected_conversion : null);
}, []); }, []);
const columns = useMemo<ColumnDef<any>[]>( const columns = useMemo<ColumnDef<any>[]>(
() => [ () => [
{ {
accessorFn: (row) => row.name, accessorFn: (row) => row.currency_origin.name,
id: 'name', id: 'id_currency_origin',
header: ({ column }) => <DataGridColumnHeader title="Name" column={column} />, header: ({ column }) => <DataGridColumnHeader title="Currency Origin" column={column} />,
enableSorting: true, enableSorting: true,
enableHiding: false, enableHiding: false,
meta: { meta: { headerClassName: 'w-[150px]' }
headerClassName: 'w-[250px]' },
{
accessorFn: (row) => row.currency_destination.name,
id: 'id_currency_destination',
header: ({ column }) => <DataGridColumnHeader title="Currency Destination" column={column} />,
enableSorting: true,
enableHiding: false,
meta: { headerClassName: 'w-[150px]' }
},
{
accessorFn: (row) => row.buy,
id: 'buy',
header: ({ column }) => <DataGridColumnHeader title="Buy" column={column} />,
enableSorting: true,
enableHiding: false,
meta: { headerClassName: 'w-[150px]' }
},
{
accessorFn: (row) => row.sell,
id: 'sell',
header: ({ column }) => <DataGridColumnHeader title="Sell" column={column} />,
enableSorting: true,
enableHiding: false,
meta: { headerClassName: 'w-[150px]' }
},
{
accessorFn: (row) => row.status,
id: 'status',
header: ({ column }) => <DataGridColumnHeader title="Status" column={column} />,
enableSorting: true,
enableHiding: false,
meta: { headerClassName: 'w-[150px]' },
cell: ({ row }) => {
const isActive = row.original.status === 'Y';
return (
<span
className={`px-2 py-1 text-xs font-semibold rounded-full ${
isActive ? 'bg-green-100 text-green-600' : 'bg-red-100 text-red-600'
}`}
>
{isActive ? 'Active' : 'Inactive'}
</span>
);
} }
}, },
{ {
@ -89,79 +133,80 @@ const ManageConversionContextProvider = ({ children }: { children: React.ReactNo
<> <>
<button <button
className="btn btn-sm btn-icon btn-clear btn-light" className="btn btn-sm btn-icon btn-clear btn-light"
onClick={() => handleEditDialog(true, row.id)} onClick={() => handleEditDialog(true, row.ID)}
> >
<KeenIcon icon="notepad-edit" /> <KeenIcon icon="notepad-edit" />
</button> </button>
<button <button
className="btn btn-sm btn-icon btn-clear btn-light" className="btn btn-sm btn-icon btn-clear btn-light"
onClick={() => handleDeleteDialog(true, row.id)} onClick={() => handleDeleteDialog(true, row.ID)}
> >
<KeenIcon icon="trash" /> <KeenIcon icon="trash" />
</button> </button>
</> </>
); );
}, },
meta: { meta: { headerClassName: 'w-[100px]', cellClassName: 'text-center' }
headerClassName: 'w-[100px]',
cellClassName: 'text-center'
}
} }
], ],
[] [handleEditDialog, handleDeleteDialog]
); );
const doGetConversion = async (
const getConversionLists = async (page: number, limit: number, sorting: any, filter: any) => { page: number,
try { limit: number,
sorting = sorting.length == 0 ? [{ id: 'name', desc: false }] : sorting; sorting: any,
filter = filter.length == 0 ? {} : { any: filter[0].value?.toLowerCase() }; filter: any
const response = await GetData(`${API_URL_WALLET}/dashboard/conversion`, { ) => {
limit, sorting = sorting.length == 0 ? [{ id: 'name', desc: true }] : sorting;
page: page + 1, filter = filter.length == 0 ? {} : { any: filter[0].value?.toLowerCase() };
with_deleted: false, console.log(sorting);
order_field: sorting[0].id, const response = await GetData(`${API_URL}/dashboard/conversion/`, {
order_direction: sorting[0].desc == false ? 'ASC' : 'DESC', limit: limit,
filter: JSON.stringify(filter) page: page + 1,
}); with_deleted: false,
console.log(response?.data); order_field: sorting[0].id,
setConversions(response?.data.list); order_direction: sorting[0].desc ? 'ASC' : 'DESC',
return { data: response?.data.list, totalCount: response?.data.total_count }; filter: JSON.stringify(filter)
} catch (error) { });
console.error('Error fetching Conversion', error); console.log(response?.data);
} return { data: response?.data.list, totalCount: response?.data.total_count };
}; };
return ( return (
<ManageConversionContext.Provider <div className="min-h-screen">
value={{ <ManageConversionContext.Provider
conversion: conversions, value={{
showAddDialog, showEditDialog,
handleAddDialog, handleEditDialog,
showEditDialog, showAddDialog,
handleEditDialog, handleAddDialog,
showDeleteDialog, showDeleteDialog,
handleDeleteDialog, handleDeleteDialog,
selectedConversion, selectedConversion,
getConversionLists conversion
}} }}
>
<Toaster expand visibleToasts={9} duration={3000} />
<DataGridProvider
columns={columns}
pagination={{ size: 10 }}
toolbar={<ListToolbar />}
layout={{ card: true }}
sorting={[{ id: 'id', desc: false }]}
serverSide={true}
onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) =>
getConversionLists(pageIndex, pageSize, sorting, columnFilters)
}
> >
{children} <Toaster expand visibleToasts={9} duration={3000} />
</DataGridProvider>
</ManageConversionContext.Provider> <div className="px-4">
<DataGridProvider
columns={columns}
pagination={{ size: 10 }}
layout={{ card: true }}
toolbar={<ListToolbar />}
sorting={[{ id: 'ID', desc: true }]}
serverSide={true}
onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) =>
doGetConversion(pageIndex, pageSize, sorting, columnFilters)
}
>
{children}
</DataGridProvider>
</div>
</ManageConversionContext.Provider>
</div>
); );
}; };
export { ManageConversionContext, ManageConversionContextProvider }; export { ManageConversionContext, ManageConversionContextProvider };
export type { ConversionProps }; export type { Conversion };

View File

@ -109,8 +109,22 @@ const ManageProviderContextProvider = ({ children }: { children: React.ReactNode
header: ({ column }) => <DataGridColumnHeader title="Status" column={column} />, header: ({ column }) => <DataGridColumnHeader title="Status" column={column} />,
enableSorting: false, enableSorting: false,
enableHiding: false, enableHiding: false,
cell: ({ row }) => {
const isActive = row.original.provider_status === 'Y';
return (
<span
className={`px-2 py-1 text-xs font-semibold rounded-full ${
isActive ? 'bg-green-100 text-green-600' : 'bg-red-100 text-red-600'
}`}
>
{isActive ? 'Active' : 'Inactive'}
</span>
);
},
meta: { 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', order_direction: sorting[0].desc == false ? 'ASC' : 'DESC',
filter: JSON.stringify(filter) filter: JSON.stringify(filter)
}); });
// console.log(response?.data); console.log(response?.data);
setProvider(response?.data.list); setProvider(response?.data.list);
return { data: response?.data.list, totalCount: response?.data.total_count }; return { data: response?.data.list, totalCount: response?.data.total_count };
} catch (error) { } catch (error) {

View File

@ -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 (
<ManageWalletContextProvider>
<Container>
<h1 className="text-xl font-medium leading-none text-gray-900 mb-3">Manage Wallet</h1>
<Breadcrumbs sx={{ mb: 2 }}>
<Link underline="none" color="inherit" href="/">
<span className="text-sm hover:underline">Dashboard</span>
</Link>
<Link underline="none" color="inherit">
<span className="text-sm">Master Data</span>
</Link>
<Link underline="none" color="inherit">
<span className="text-sm">Manage Wallet</span>
</Link>
</Breadcrumbs>
<div className="grid gap-5 lg:gap-7.5">
<DataGridInner />
</div>
<AddDialog />
<EditDialog />
<DeleteDialog />
</Container>
</ManageWalletContextProvider>
);
};
export default WalletMaster;

View File

@ -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<CurrencyProps[]>([]);
const [groups, setGroups] = useState<GroupProps[]>([]);
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<HTMLFormElement>) => {
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<HTMLFormElement>) => {
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 (
<Dialog open={showAddDialog} onOpenChange={(open) => handleAddDialog(open)}>
<DialogContent className="container-fixed max-w-[768px] flex flex-col p-5 overflow-hidden">
<DialogHeader>
<DialogTitle>Wallet - Create</DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<DialogBody className="scrollable">
<div className="flex flex-col">
{alert.show && (
<Alert variant="danger">
<h3>{alert.message}</h3>
</Alert>
)}
<form onSubmit={handleSubmit}>
<div className="card-body grid gap-5">
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Wallet Name<span className="text-red-500">*</span>
</label>
<Input
type="text"
value={formField.name}
onChange={(e) => setFormField({ ...formField, name: e.target.value })}
placeholder="Wallet Name"
/>
</div>
</div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Description<span className="text-red-500">*</span>
</label>
<Input
type="text"
value={formField.description}
onChange={(e) => setFormField({ ...formField, description: e.target.value })}
placeholder="Description"
/>
</div>
</div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Status<span className="text-red-500">*</span>
</label>
<Select
value={formField.status}
onValueChange={(value) => setFormField({ ...formField, status: value })}
>
<SelectTrigger>
<SelectValue placeholder="Select Status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Y">Active</SelectItem>
<SelectItem value="N">Inactive</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Currency<span className="text-red-500">*</span>
</label>
<Select
value={formField.currency_id}
onValueChange={(value) => setFormField({ ...formField, currency_id: value })}
>
<SelectTrigger>
<SelectValue placeholder="Select Currency Type" />
</SelectTrigger>
<SelectContent>
{currencies.map((currency) => (
<SelectItem key={currency.ID} value={currency.ID}>
{currency.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Groups<span className="text-red-500">*</span>
</label>
<div className="flex flex-wrap gap-3">
{groups.map((group) => (
<label key={group.id} className="inline-flex items-center">
<input
type="checkbox"
className="h-4 w-4"
checked={formField.group.includes(group.id)}
onChange={() => handleGroupChange(group.id)}
/>
<span className="ml-2">{group.name}</span>
</label>
))}
</div>
</div>
</div>
<div className="flex justify-end gap-5">
<Button type="button" variant="outline" onClick={resetForm}>
Reset
</Button>
<Button variant="default">Create</Button>
</div>
</div>
</form>
</div>
</DialogBody>
</DialogContent>
</Dialog>
);
};
export default AddDialog;

View File

@ -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 (
<Dialog open={showDeleteDialog} onOpenChange={(open) => handleDeleteDialog(open, null)}>
<DialogContent className="container-fixed max-w-md flex flex-col p-5 overflow-hidden">
<DialogHeader className="p-0 border-0 block">
<DialogTitle></DialogTitle>
<DialogDescription></DialogDescription>
<Alert variant="warning">
<h3 className="text-lg">Are you sure?</h3>
<span className="text-sm">You will delete this data!</span>
</Alert>
{alert.show && (
<Alert variant="danger">
<h3>{alert.message}</h3>
</Alert>
)}
</DialogHeader>
<DialogFooter className="flex justify-end items-center gap-4 mt-3">
<Button variant="outline" onClick={() => handleDeleteDialog(false, null)}>
Cancel
</Button>
<Button variant="destructive" onClick={doDeleteWallet}>
Delete
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};
export default DeleteDialog;

View File

@ -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<CurrencyProps[]>([]);
const [groups, setGroups] = useState<GroupProps[]>([]);
const resetForm = () => {
setFormField(initialState);
setAlert({ show: false, message: '' });
};
const doUpdateWallet = useCallback(
async (e: React.FormEvent<HTMLFormElement>) => {
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<HTMLFormElement>) => {
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 (
<Dialog open={showEditDialog} onOpenChange={(open) => handleEditDialog(open, null)}>
<DialogContent className="container-fixed max-w-[768px] flex flex-col p-5 overflow-hidden">
<DialogHeader>
<DialogTitle>Wallet - Update</DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
<DialogBody className="scrollable">
<div className="flex flex-col">
{alert.show && (
<Alert variant="danger">
<h3>{alert.message}</h3>
</Alert>
)}
<form onSubmit={handleSubmit}>
<div className="card-body grid gap-5">
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Wallet Name
</label>
<Input
type="text"
value={formField.name}
onChange={(e) => setFormField({ ...formField, name: e.target.value })}
placeholder="Wallet Name"
/>
</div>
</div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Description
</label>
<Input
type="text"
value={formField.description}
onChange={(e) => setFormField({ ...formField, description: e.target.value })}
placeholder="Description"
/>
</div>
</div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">Status</label>
<Select
value={formField.status}
onValueChange={(value) => setFormField({ ...formField, status: value })}
>
<SelectTrigger>
<SelectValue placeholder="Select Status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Y">Active</SelectItem>
<SelectItem value="N">Inactive</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">Currency</label>
<Input type="text" placeholder='Empty' value={selectedCurrency?.name || ''} readOnly />
</div>
</div>
<div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">Groups</label>
<Input type="text" placeholder='Empty' value={selectedGroupNames} readOnly />
</div>
</div>
<div className="flex justify-end gap-5">
<Button type="button" variant="outline" onClick={resetForm}>
Reset
</Button>
<Button variant="default">Update</Button>
</div>
</div>
</form>
</div>
</DialogBody>
</DialogContent>
</Dialog>
);
};
export default EditDialog;

View File

@ -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 (
<div className="card-header flex-wrap gap-2 border-b-0 px-5">
<div className="flex flex-wrap gap-2 lg:gap-5 w-full">
<div className="flex justify-between w-full items-center">
<div className="flex w-[50%] gap-3 items-center">
<label className="input input-sm w-1/3">
<KeenIcon icon="magnifier" />
<input
type="text"
placeholder="Search Provider"
value={(table.getColumn('name')?.getFilterValue() as string) ?? ''}
onChange={(event) => table.getColumn('name')?.setFilterValue(event.target.value)}
/>
</label>
{/* <DefaultTooltip title={'Filter'} placement={'top'}>
<Button
variant="outline"
className="h-7.5 disabled:bg-gray-400"
// disabled={isLoading}
// onClick={handleFilterData}
>
{loadingButton === 'filter' ? <ContentLoader /> : <KeenIcon icon="filter" />}
<KeenIcon icon="filter" />
</Button>
</DefaultTooltip> */}
</div>
<div className="flex gap-3 items-center">
<Button
variant="outline"
className="h-7.5 text-[0.8rem]"
onClick={() => handleAddDialog(true)}
>
Add Data
</Button>
<DefaultTooltip title={'Refresh'} placement={'top'}>
<Button variant="outline" className="h-7.5" onClick={() => reload()}>
<KeenIcon icon="arrows-circle" />
</Button>
</DefaultTooltip>
</div>
</div>
</div>
</div>
);
};
export default ListToolbar;

View File

@ -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<ContextProps>(initialProps);
const API_URL_MASTER_DATA = apiConfig.service_master_data;
const ManageWalletContextProvider = ({ children }: { children: React.ReactNode }) => {
const [wallets, setWallets] = useState<WalletProps[]>([]);
const [showAddDialog, setShowAddDialog] = useState(false);
const [showEditDialog, setShowEditDialog] = useState(false);
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const [selectedWallet, setSelectedWallet] = useState<WalletProps | null>(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<ColumnDef<any>[]>(
() => [
{
accessorFn: (row) => row.Wallet_name,
id: 'name',
header: ({ column }) => <DataGridColumnHeader title="Name" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
accessorFn: (row) => row.Wallet_description,
id: 'description',
header: ({ column }) => <DataGridColumnHeader title="Description" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[250px]'
}
},
{
accessorFn: (row) => row.Wallet_status,
id: 'status',
header: ({ column }) => <DataGridColumnHeader title="Status" column={column} />,
enableSorting: true,
enableHiding: false,
cell: ({ row }) => {
const isActive = row.original.Wallet_status === 'Y';
return (
<span
className={`px-2 py-1 text-xs font-semibold rounded-full ${
isActive ? 'bg-green-100 text-green-600' : 'bg-red-100 text-red-600'
}`}
>
{isActive ? 'Active' : 'Inactive'}
</span>
);
},
meta: {
headerClassName: 'w-[100px] text-center',
cellClassName: 'text-center'
}
},
{
id: 'actions',
header: ({ column }) => <DataGridColumnHeader title="Actions" column={column} />,
enableSorting: false,
enableHiding: false,
cell: (data) => {
const row = data.row.original;
return (
<>
<button
className="btn btn-sm btn-icon btn-clear btn-light"
onClick={() => handleEditDialog(true, row)}
>
<KeenIcon icon="notepad-edit" />
</button>
<button
className="btn btn-sm btn-icon btn-clear btn-light"
onClick={() => handleDeleteDialog(true, row)}
>
<KeenIcon icon="trash" />
</button>
</>
);
},
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 (
<ManageWalletContext.Provider
value={{
wallet: wallets,
showAddDialog,
handleAddDialog,
showEditDialog,
handleEditDialog,
showDeleteDialog,
handleDeleteDialog,
selectedWallet,
getWalletLists
}}
>
<Toaster expand visibleToasts={9} duration={3000} />
<DataGridProvider
columns={columns}
pagination={{ size: 25 }}
toolbar={<ListToolbar />}
layout={{ card: true }}
sorting={[{ id: 'id', desc: false }]}
serverSide={true}
onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) =>
getWalletLists(pageIndex, pageSize, sorting, columnFilters)
}
>
{children}
</DataGridProvider>
</ManageWalletContext.Provider>
);
};
export { ManageWalletContext, ManageWalletContextProvider };
export type { WalletProps };

View File

@ -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 };

View File

@ -105,15 +105,7 @@ const AddDialog = () => {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
if ( if (formField.id_group.trim() === '' || formField.status.trim() === '') {
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() === ''
) {
setAlert({ show: true, message: 'Please fill in all required fields.' }); setAlert({ show: true, message: 'Please fill in all required fields.' });
return; return;
} }

View File

@ -108,15 +108,7 @@ const EditDialog = () => {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
if ( if (formField.id_group.trim() === '' || formField.status.trim() === '') {
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() === ''
) {
setAlert({ show: true, message: 'Please fill in all required fields.' }); setAlert({ show: true, message: 'Please fill in all required fields.' });
return; return;
} }

View File

@ -137,10 +137,24 @@ const ManageWalletRuleContextProvider = ({ children }: { children: React.ReactNo
accessorFn: (row) => row.status, accessorFn: (row) => row.status,
id: 'status', id: 'status',
header: ({ column }) => <DataGridColumnHeader title="Status" column={column} />, header: ({ column }) => <DataGridColumnHeader title="Status" column={column} />,
enableSorting: true, enableSorting: false,
enableHiding: false, enableHiding: false,
cell: ({ row }) => {
const isActive = row.original.status === 'Y';
return (
<span
className={`px-2 py-1 text-xs font-semibold rounded-full ${
isActive ? 'bg-green-100 text-green-600' : 'bg-red-100 text-red-600'
}`}
>
{isActive ? 'Active' : 'Inactive'}
</span>
);
},
meta: { meta: {
headerClassName: 'w-[250px]' headerClassName: 'w-[100px]',
cellClassName: 'text-center'
} }
}, },
{ {

View File

@ -12,16 +12,22 @@ import {
DialogHeader, DialogHeader,
DialogTitle DialogTitle
} from '@/components/ui/dialog'; } from '@/components/ui/dialog';
import { FormControl,NativeSelect } from "@mui/material";
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
} from '@/components/ui/select';
const API_URL = apiConfig.service_dashboard; const API_URL = apiConfig.service_dashboard;
const AddDialog = () => { const AddDialog = () => {
const parentRef = useRef<any | null>(null);
const { showAddDialog, handleAddDialog, parents } = useManageMenusContext(); const { showAddDialog, handleAddDialog, parents } = useManageMenusContext();
const { reload } = useDataGrid(); const { reload } = useDataGrid();
const { PostData } = useCallApi(); const { PostData } = useCallApi();
const [open, setOpen] = useState(false);
const [alert, setAlert] = useState({ const [alert, setAlert] = useState({
show: false, show: false,
message: '' message: ''
@ -51,8 +57,11 @@ const AddDialog = () => {
return; return;
} }
console.log('Data dikirim ke API:', formField);
const response = await PostData(`${API_URL}/menus/create`, formField); const response = await PostData(`${API_URL}/menus/create`, formField);
console.log('Response from API:', response);
if (response?.status) { if (response?.status) {
handleAddDialog(false); handleAddDialog(false);
resetForm(); resetForm();
@ -67,7 +76,10 @@ const AddDialog = () => {
}; };
const resetForm = () => { const resetForm = () => {
setFormField(initialState); setFormField({
...initialState,
status: formField.status // Pertahankan nilai status terpilih
});
setAlert({ show: false, message: '' }); setAlert({ show: false, message: '' });
}; };
@ -78,7 +90,7 @@ const AddDialog = () => {
<DialogTitle>Menu - Create</DialogTitle> <DialogTitle>Menu - Create</DialogTitle>
<DialogDescription></DialogDescription> <DialogDescription></DialogDescription>
</DialogHeader> </DialogHeader>
<DialogBody ref={parentRef}> <DialogBody className="scrollable">
<div className="flex flex-col"> <div className="flex flex-col">
{alert.show && ( {alert.show && (
<Alert variant="danger"> <Alert variant="danger">
@ -133,24 +145,21 @@ const AddDialog = () => {
<div className="w-full"> <div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5"> <div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">Parent</label> <label className="form-label flex items-center gap-1 max-w-56">Parent</label>
<FormControl fullWidth margin="dense"> <Select
<NativeSelect value={formField.id_parent}
defaultValue={30} onValueChange={(value) => setFormField({ ...formField, id_parent: value })}
value={formField.id_parent} >
onChange={(e: any) => setFormField({ ...formField, id_parent: e.target.value })} <SelectTrigger>
inputProps={{ <SelectValue placeholder="Select As Parent" />
name: 'id_parent', </SelectTrigger>
id: 'uncontrolled-native', <SelectContent>
}} {parents.map((parent: any) => (
> <SelectItem key={parent.id} value={parent.id}>
<option value={''}>As Parent</option> {parent.name}
{ </SelectItem>
parents ? parents.map((el: any) => ( ))}
<option key={el.id} value={el.id}>{el.name}</option> </SelectContent>
)) : '' </Select>
}
</NativeSelect>
</FormControl>
</div> </div>
</div> </div>
@ -190,21 +199,18 @@ const AddDialog = () => {
<label className="form-label flex items-center gap-1 max-w-56"> <label className="form-label flex items-center gap-1 max-w-56">
Status<span className="text-red-500">*</span> Status<span className="text-red-500">*</span>
</label> </label>
<FormControl fullWidth margin="dense"> <Select
<NativeSelect value={formField.status}
defaultValue={30} onValueChange={(value) => setFormField({ ...formField, status: value })}
value={formField.status} >
onChange={(e: any) => setFormField({ ...formField, status: e.target.value })} <SelectTrigger>
inputProps={{ <SelectValue placeholder="Select Status" />
name: 'status', </SelectTrigger>
id: 'uncontrolled-native', <SelectContent>
}} <SelectItem value="Y">Active</SelectItem>
> <SelectItem value="N">Inactive</SelectItem>
<option value={''}>Select</option> </SelectContent>
<option value={"Y"}>Active</option> </Select>
<option value={"N"}>Inactive</option>
</NativeSelect>
</FormControl>
</div> </div>
</div> </div>

View File

@ -4,7 +4,14 @@ import { Alert, useDataGrid } from '@/components';
import { useCallApi } from '@/hooks'; import { useCallApi } from '@/hooks';
import { ChangeEvent, useCallback, useState } from 'react'; import { ChangeEvent, useCallback, useState } from 'react';
import { toast } from 'sonner'; 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 { EnforceSwitch } from '@/components/switch';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
@ -13,14 +20,13 @@ const DeleteDialog = () => {
const { showDeleteDialog, handleDeleteDialog, selectedMenu }: any = useManageMenusContext(); const { showDeleteDialog, handleDeleteDialog, selectedMenu }: any = useManageMenusContext();
const { reload } = useDataGrid(); const { reload } = useDataGrid();
const { DeleteData } = useCallApi(); const { DeleteData } = useCallApi();
const [enforce, setEnforce] = useState(false);
const [alert, setAlert] = useState({ const [alert, setAlert] = useState({
show: false, show: false,
message: '' message: ''
}); });
const doDeleteMenu = async () => { 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 id: selectedMenu.id
}); });
@ -44,15 +50,6 @@ const DeleteDialog = () => {
<Alert variant="warning"> <Alert variant="warning">
<h3 className="text-lg">Are you sure?</h3> <h3 className="text-lg">Are you sure?</h3>
<span className="text-sm">you will delete this data!</span> <span className="text-sm">you will delete this data!</span>
<div className="mt-2 flex items-center gap-x-2">
<label className="form-label max-w-56">Hard Delete</label>
<EnforceSwitch
enforce={enforce}
onChange={(e: ChangeEvent<HTMLInputElement>) => {
setEnforce(e.target.checked);
}}
/>
</div>
</Alert> </Alert>
{alert.show && ( {alert.show && (
<Alert variant="danger"> <Alert variant="danger">

View File

@ -12,15 +12,25 @@ import {
DialogHeader, DialogHeader,
DialogTitle DialogTitle
} from '@/components/ui/dialog'; } from '@/components/ui/dialog';
import { FormControl,NativeSelect } from "@mui/material"; import { FormControl, NativeSelect } from '@mui/material';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
} from '@/components/ui/select';
const API_URL = apiConfig.service_dashboard; const API_URL = apiConfig.service_dashboard;
const EditDialog = () => { const EditDialog = () => {
const { showEditDialog, handleEditDialog, selectedMenu, setSelectedMenu, parents }: any = useManageMenusContext(); const { showEditDialog, handleEditDialog, selectedMenu, setSelectedMenu, parents }: any =
useManageMenusContext();
const { reload } = useDataGrid(); const { reload } = useDataGrid();
const { PutData } = useCallApi(); const { PutData } = useCallApi();
const [open, setOpen] = useState(false);
const [alert, setAlert] = useState({ const [alert, setAlert] = useState({
show: false, show: false,
message: '' message: ''
@ -43,6 +53,7 @@ const EditDialog = () => {
selectedMenu.module === '' || selectedMenu.module === '' ||
selectedMenu.name === '' || selectedMenu.name === '' ||
selectedMenu.link === '' || selectedMenu.link === '' ||
selectedMenu.id_parent === '' ||
selectedMenu.order_number === 0 || selectedMenu.order_number === 0 ||
selectedMenu.status === '' selectedMenu.status === ''
) { ) {
@ -51,8 +62,8 @@ const EditDialog = () => {
} }
const updateMenu = selectedMenu; const updateMenu = selectedMenu;
if (updateMenu.id_parent === null) updateMenu.id_parent = ""; if (updateMenu.id_parent === null) updateMenu.id_parent = '';
delete updateMenu.parentName delete updateMenu.parentName;
const response = await PutData(`${API_URL}/menus/update/${selectedMenu.id}`, selectedMenu); const response = await PutData(`${API_URL}/menus/update/${selectedMenu.id}`, selectedMenu);
if (response?.status) { if (response?.status) {
handleEditDialog(false, null); handleEditDialog(false, null);
@ -67,7 +78,7 @@ const EditDialog = () => {
}; };
const resetForm = () => { const resetForm = () => {
setSelectedMenu(initialState) setSelectedMenu(initialState);
setAlert({ show: false, message: '' }); setAlert({ show: false, message: '' });
}; };
@ -78,7 +89,7 @@ const EditDialog = () => {
<DialogTitle>Menu - Update</DialogTitle> <DialogTitle>Menu - Update</DialogTitle>
<DialogDescription></DialogDescription> <DialogDescription></DialogDescription>
</DialogHeader> </DialogHeader>
<DialogBody> <DialogBody className="scrollable">
<div className="flex flex-col"> <div className="flex flex-col">
{alert.show && ( {alert.show && (
<Alert variant="danger"> <Alert variant="danger">
@ -132,24 +143,27 @@ const EditDialog = () => {
<div className="w-full"> <div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5"> <div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">ID Parent</label> <label className="form-label flex items-center gap-1 max-w-56">Parent</label>
<FormControl fullWidth margin="dense"> <Select
<NativeSelect value={selectedMenu.id_parent || ''}
defaultValue={30} onValueChange={(value) => {
value={selectedMenu.id_parent} setSelectedMenu({
onChange={(e: any) => setSelectedMenu({ ...selectedMenu, id_parent: e.target.value })} ...selectedMenu,
inputProps={{ id_parent: value === '' ? null : value
name: 'id_parent', });
id: 'uncontrolled-native', }}
}} >
> <SelectTrigger>
{ <SelectValue placeholder="Select As Parent" />
parents ? parents.map((el: any) => ( </SelectTrigger>
<option key={el.id} value={el.id}>{el.name}</option> <SelectContent>
)) : '' {parents.map((parent: any) => (
} <SelectItem key={parent.id} value={parent.id}>
</NativeSelect> {parent.name}
</FormControl> </SelectItem>
))}
</SelectContent>
</Select>
</div> </div>
</div> </div>
@ -165,7 +179,10 @@ const EditDialog = () => {
value={selectedMenu.order_number === 0 ? '' : selectedMenu.order_number} value={selectedMenu.order_number === 0 ? '' : selectedMenu.order_number}
onChange={(e) => { onChange={(e) => {
const value = parseInt(e.target.value, 10); const value = parseInt(e.target.value, 10);
setSelectedMenu({ ...selectedMenu, order_number: isNaN(value) ? 0 : value }); setSelectedMenu({
...selectedMenu,
order_number: isNaN(value) ? 0 : value
});
}} }}
/> />
</div> </div>
@ -184,6 +201,26 @@ const EditDialog = () => {
</div> </div>
<div className="w-full"> <div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56">
Status<span className="text-red-500">*</span>
</label>
<Select
value={selectedMenu.status}
onValueChange={(value) => setSelectedMenu({ ...selectedMenu, status: value })}
>
<SelectTrigger>
<SelectValue placeholder="Select Status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="Y">Active</SelectItem>
<SelectItem value="N">Inactive</SelectItem>
</SelectContent>
</Select>
</div>
</div>
{/* <div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5"> <div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56"> <label className="form-label flex items-center gap-1 max-w-56">
Status<span className="text-red-500">*</span> Status<span className="text-red-500">*</span>
@ -203,7 +240,7 @@ const EditDialog = () => {
</NativeSelect> </NativeSelect>
</FormControl> </FormControl>
</div> </div>
</div> </div> */}
<div className="flex justify-end"> <div className="flex justify-end">
<Button className="btn btn-primary">Save Changes</Button> <Button className="btn btn-primary">Save Changes</Button>

View File

@ -133,12 +133,27 @@ const ManageMenusContextProvider = ({ children }: { children: React.ReactNode })
meta: { headerClassName: 'w-[250px]' } meta: { headerClassName: 'w-[250px]' }
}, },
{ {
accessorFn: (row) => row.status, accessorKey: 'status',
id: 'status',
header: ({ column }) => <DataGridColumnHeader title="Status" column={column} />, header: ({ column }) => <DataGridColumnHeader title="Status" column={column} />,
enableSorting: false, enableSorting: false,
enableHiding: false, enableHiding: false,
meta: { headerClassName: 'w-[100px]', cellClassName: 'text-center' } cell: ({ row }) => {
const isActive = row.original.status === 'Y';
return (
<span
className={`px-2 py-1 text-xs font-semibold rounded-full ${
isActive ? 'bg-green-100 text-green-600' : 'bg-red-100 text-red-600'
}`}
>
{isActive ? 'Active' : 'Inactive'}
</span>
);
},
meta: {
headerClassName: 'w-[100px]',
cellClassName: 'text-center'
}
}, },
{ {
id: 'actions', id: 'actions',
@ -211,7 +226,7 @@ const ManageMenusContextProvider = ({ children }: { children: React.ReactNode })
link: child.link, link: child.link,
id_parent: child.id_parent, id_parent: child.id_parent,
status: child.status, 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 total_count = transformedData.length;
const paginatedData = transformedData.slice(page * limit, (page + 1) * limit); const paginatedData = transformedData.slice(page * limit, (page + 1) * limit);
console.log(response.data); console.log('data', paginatedData);
// setMenus(transformedData); // setMenus(transformedData);
return { data: paginatedData, totalCount: total_count }; return { data: paginatedData, totalCount: total_count };
} catch (error) { } catch (error) {

View File

@ -8,6 +8,8 @@ import { ListToolBar } from '../blocks';
import { useCallApi } from '@/hooks'; import { useCallApi } from '@/hooks';
interface ContextProps { interface ContextProps {
showSearchDialog: boolean;
handleSearchDialog: (show: boolean) => void;
showEditDialog: boolean; showEditDialog: boolean;
handleEditDialog: (show: boolean, selected_user: string | null) => void; handleEditDialog: (show: boolean, selected_user: string | null) => void;
showAddDialog: boolean; showAddDialog: boolean;
@ -35,6 +37,8 @@ interface RoleListProps {
} }
const initialProps: ContextProps = { const initialProps: ContextProps = {
showSearchDialog: false,
handleSearchDialog: (show: boolean) => {},
showEditDialog: false, showEditDialog: false,
handleEditDialog: () => {}, handleEditDialog: () => {},
showAddDialog: false, showAddDialog: false,
@ -52,6 +56,7 @@ const API_URL = apiConfig.service_dashboard;
const ManageUserContextProvider = ({ children }: { children: React.ReactNode }) => { const ManageUserContextProvider = ({ children }: { children: React.ReactNode }) => {
/* state */ /* state */
const [showEditDialog, setShowEditDialog] = useState(false); const [showEditDialog, setShowEditDialog] = useState(false);
const [showSearchDialog, setShowSearchDialog] = useState(false);
const [showAddDialog, setShowAddDialog] = useState(false); const [showAddDialog, setShowAddDialog] = useState(false);
const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const [selectedUser, setSelectedUser] = useState<string | null>(null); const [selectedUser, setSelectedUser] = useState<string | null>(null);
@ -59,6 +64,10 @@ const ManageUserContextProvider = ({ children }: { children: React.ReactNode })
const { GetData } = useCallApi(); const { GetData } = useCallApi();
/* action */ /* action */
const handleSearchDialog = useCallback((show: boolean) => {
setShowSearchDialog(show);
}, []);
const handleEditDialog = useCallback((show: boolean, selected_user: string | null) => { const handleEditDialog = useCallback((show: boolean, selected_user: string | null) => {
setSelectedUser(show ? selected_user : null); setSelectedUser(show ? selected_user : null);
setShowEditDialog(show); setShowEditDialog(show);
@ -219,6 +228,8 @@ const ManageUserContextProvider = ({ children }: { children: React.ReactNode })
return ( return (
<ManageUserContext.Provider <ManageUserContext.Provider
value={{ value={{
showSearchDialog,
handleSearchDialog,
showEditDialog, showEditDialog,
handleEditDialog, handleEditDialog,
selectedUser, selectedUser,

View File

@ -13,7 +13,7 @@ const Transaction = () => {
</Link> </Link>
<Link underline="none" color="inherit"> <Link underline="none" color="inherit">
<span className="text-sm">Master Data</span> <span className="text-sm">Transaction</span>
</Link> </Link>
<Link underline="none" color="inherit"> <Link underline="none" color="inherit">

View File

@ -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<any>(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 (
<Dialog open={showDetailDialog} onOpenChange={setShowDetailDialog}>
<DialogContent className="container-fixed max-w-[1024px] flex flex-col p-5 overflow-hidden">
<DialogHeader>
<DialogTitle>Transaction Details</DialogTitle>
</DialogHeader>
<DialogBody>
{/* Tabs Navigation */}
<div className="flex border-b border-gray-200">
<button
className={`py-2 px-4 font-medium text-sm focus:outline-none ${activeTab === 'detail' ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700'}`}
onClick={() => setActiveTab('detail')}
>
Detail Transaction
</button>
<button
className={`py-2 px-4 font-medium text-sm focus:outline-none ${activeTab === 'origincustomer' ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700'}`}
onClick={() => setActiveTab('origincustomer')}
>
Origin Customer
</button>
<button
className={`py-2 px-4 font-medium text-sm focus:outline-none ${activeTab === 'log' ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700'}`}
onClick={() => setActiveTab('log')}
>
Transaction Log
</button>
<button
className={`py-2 px-4 font-medium text-sm focus:outline-none ${activeTab === 'approve' ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700'}`}
onClick={() => setActiveTab('approve')}
>
Approval Log
</button>
<button
className={`py-2 px-4 font-medium text-sm focus:outline-none ${activeTab === 'p24' ? 'text-blue-600 border-b-2 border-blue-600' : 'text-gray-500 hover:text-gray-700'}`}
onClick={() => setActiveTab('p24')}
>
Log P24
</button>
</div>
{/* Tab Content */}
<div className="py-4 overflow-y-auto max-h-[400px]">
{activeTab === 'detail' && transactionDetails?.kind === 'P' && (
<div className="space-y-4">
<h3 className="font-semibold flex items-center">
Transaction Information
<span className="ml-2 bg-blue-100 text-blue-800 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">
Info
</span>
</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm text-gray-500">Transaction Date</p>
<p className="font-medium">{transactionDetails?.transaction_date}</p>
</div>
<div>
<p className="text-sm text-gray-500">Full Name</p>
<p className="font-medium">{transactionDetails?.origin_customer.fullname}</p>
</div>
<div>
<p className="text-sm text-gray-500">Amount</p>
<p className="font-medium">{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(transactionDetails?.purchase.amount)}</p>
</div>
<div>
<p className="text-sm text-gray-500">Fee</p>
<p className="font-medium">
{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' })}
</p>
</div>
<div>
<p className="text-sm text-gray-500">Status</p>
<p className="font-medium">
{(() => {
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;
})()}
</p>
</div>
<div>
<p className="text-sm text-gray-500">Transaction Type</p>
<p className="font-medium">
{(() => {
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;
})()}
</p>
</div>
<div>
<p className="text-sm text-gray-500">Description</p>
<p className="font-medium">{transactionDetails?.description}</p>
</div>
<div>
<p className="text-sm text-gray-500">Name</p>
<p className="font-medium">{transactionDetails?.type.name}</p>
</div>
</div>
<h3 className="font-semibold flex items-center">
Origin Wallet
<span className="ml-2 bg-blue-100 text-blue-800 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">
Wallet
</span>
</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm text-gray-500">Name</p>
<p className="font-medium">{transactionDetails?.origin_wallet.name}</p>
</div>
<div>
<p className="text-sm text-gray-500">Description</p>
<p className="font-medium">{transactionDetails?.origin_wallet.description}</p>
</div>
</div>
<h3 className="font-semibold flex items-center">
Purchase
<span className="ml-2 bg-blue-100 text-blue-800 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">
Purchase
</span>
</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm text-gray-500">Amount</p>
<p className="font-medium">{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(transactionDetails?.purchase.amount)}</p>
</div>
<div>
<p className="text-sm text-gray-500">Cashback</p>
<p className="font-medium">{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(transactionDetails?.purchase.cashback)}</p>
</div>
<div>
<p className="text-sm text-gray-500">Cashback Point</p>
<p className="font-medium">{transactionDetails?.purchase.cashback_point}</p>
</div>
<div>
<p className="text-sm text-gray-500">Fee Amount</p>
<p className="font-medium">{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(transactionDetails?.purchase.fee_amount)}</p>
</div>
</div>
</div>
)}
{activeTab === 'detail' && transactionDetails?.kind != 'P' && transactionDetails?.transfer!=null && (
<div className="space-y-4">
<h3 className="font-semibold flex items-center">
Transaction Information
<span className="ml-2 bg-blue-100 text-blue-800 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">
Info
</span>
</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm text-gray-500">Transaction Date</p>
<p className="font-medium">{transactionDetails?.transaction_date}</p>
</div>
<div>
<p className="text-sm text-gray-500">Full Name</p>
<p className="font-medium">{transactionDetails?.origin_customer.fullname}</p>
</div>
<div>
<p className="text-sm text-gray-500">Amount</p>
<p className="font-medium">{new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(transactionDetails?.transfer.amount)}</p>
</div>
<div>
<p className="text-sm text-gray-500">Fee</p>
<p className="font-medium">
{transactionDetails?.transfer.fee_amount.toLocaleString('en-US', { style: 'currency', currency: 'USD' })}
</p>
</div>
<div>
<p className="text-sm text-gray-500">Status</p>
<p className="font-medium">
{(() => {
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;
})()}
</p>
</div>
<div>
<p className="text-sm text-gray-500">Transaction Type</p>
<p className="font-medium">
{(() => {
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;
})()}
</p>
</div>
<div>
<p className="text-sm text-gray-500">Description</p>
<p className="font-medium">{transactionDetails?.description}</p>
</div>
<div>
<p className="text-sm text-gray-500">Name</p>
<p className="font-medium">{transactionDetails?.type.name}</p>
</div>
<div>
<p className="text-sm text-gray-500">Reference</p>
<p className="font-medium">{transactionDetails?.transfer.reference}</p>
</div>
<div>
<p className="text-sm text-gray-500">Destination Iban</p>
<p className="font-medium">{transactionDetails?.transfer.destination_iban}</p>
</div>
</div>
<h3 className="font-semibold flex items-center">
Destination Wallet
<span className="ml-2 bg-blue-100 text-blue-800 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">
Wallet
</span>
</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm text-gray-500">Name</p>
<p className="font-medium">{transactionDetails?.transfer.destination_wallet.name}</p>
</div>
<div>
<p className="text-sm text-gray-500">Description</p>
<p className="font-medium">{transactionDetails?.transfer.destination_wallet.description}</p>
</div>
</div>
<h3 className="font-semibold flex items-center">
Destination Customer
<span className="ml-2 bg-blue-100 text-blue-800 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">
Destination Customer
</span>
</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm text-gray-500">Name</p>
<p className="font-medium">{transactionDetails?.transfer.destination_customer.fullname}</p>
</div>
<div>
<p className="text-sm text-gray-500">MSISDN</p>
<p className="font-medium">{transactionDetails?.transfer.destination_customer.msisdn}</p>
</div>
<div>
<p className="text-sm text-gray-500">Email</p>
<p className="font-medium">{transactionDetails?.transfer.destination_customer.email}</p>
</div>
<div>
<p className="text-sm text-gray-500">MSISDN</p>
<p className="font-medium">{transactionDetails?.transfer.destination_customer.username}</p>
</div>
</div>
<h3 className="font-semibold flex items-center">
Origin Wallet
<span className="ml-2 bg-blue-100 text-blue-800 text-xs font-semibold mr-2 px-2.5 py-0.5 rounded">
Wallet
</span>
</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm text-gray-500">Name</p>
<p className="font-medium">{transactionDetails?.origin_wallet.name}</p>
</div>
<div>
<p className="text-sm text-gray-500">Description</p>
<p className="font-medium">{transactionDetails?.origin_wallet.description}</p>
</div>
</div>
</div>
)}
{activeTab === 'origincustomer' && (
<div className="space-y-4">
<h3 className="font-semibold">Origin Customer</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm text-gray-500">Full Name</p>
<p className="font-medium">{transactionDetails?.origin_customer.fullname}</p>
</div>
<div>
<p className="text-sm text-gray-500">Phone Number</p>
<p className="font-medium">{transactionDetails?.origin_customer.msisdn}</p>
</div>
<div>
<p className="text-sm text-gray-500">Email</p>
<p className="font-medium">{transactionDetails?.origin_customer.email}</p>
</div>
<div>
<p className="text-sm text-gray-500">Username</p>
<p className="font-medium">{transactionDetails?.origin_customer.username}</p>
</div>
</div>
</div>
)}
{activeTab === 'log' && (
<div className="space-y-4">
<h3 className="font-semibold">Transaction Logs</h3>
<div className="border rounded-lg overflow-x-auto">
<table className="min-w-full table-auto">
<thead>
<tr className="bg-gray-100">
<th className="px-4 py-2 text-left text-sm text-gray-500">Status</th>
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Date</th>
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Body</th>
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Body</th>
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Code</th>
<th className="px-4 py-2 text-left text-sm text-gray-500">Request End Point</th>
</tr>
</thead>
<tbody>
{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) => (
<tr key={index} className="border-t">
<td className="px-4 py-2 font-medium">
{(() => {
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;
})()}
</td>
<td className="px-4 py-2 text-sm text-gray-500">{log.request_date ?? '-'}</td>
<td className="px-4 py-2 text-sm text-gray-500">{log.response_date ?? '-'}</td>
<td className="px-4 py-2 text-sm text-gray-500">{log.request_body ?? '-'}</td>
<td className="px-4 py-2 text-sm text-gray-500">{log.response_body ?? '-'}</td>
<td className="px-4 py-2 text-sm text-gray-500">{log.request_endpoint ?? '-'}</td>
</tr>
))
) : (
<tr>
<td colSpan={3} className="px-4 py-2 text-center text-sm text-gray-500">
No logs available
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
)}
{activeTab === 'approve' && (
<div className="space-y-4">
<h3 className="font-semibold">Approval Logs</h3>
{transactionDetails?.log_approve.length === 0 ? (
<p className="text-sm text-gray-500">No data available</p>
) : (
<div className="border rounded-lg overflow-x-auto">
<table className="min-w-full table-auto">
<thead>
<tr className="bg-gray-100">
<th className="px-4 py-2 text-left text-sm text-gray-500">Status</th>
<th className="px-4 py-2 text-left text-sm text-gray-500">Created At</th>
<th className="px-4 py-2 text-left text-sm text-gray-500">Updated At</th>
</tr>
</thead>
<tbody>
{transactionDetails?.log_approve && transactionDetails?.log_approve.length > 0 ? (
transactionDetails.log_approve.map((log: { created_at: string; status: string; updated_at: string }, index: number) => (
<tr key={index} className="border-t">
<td className="px-4 py-2 font-medium">
{(() => {
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;
})()}
</td>
<td className="px-4 py-2 text-sm text-gray-500">{log.created_at}</td>
<td className="px-4 py-2 text-sm text-gray-500">{log.updated_at}</td>
</tr>
))
) : (
<tr>
<td colSpan={3} className="px-4 py-2 text-center text-sm text-gray-500">
No logs available
</td>
</tr>
)}
</tbody>
</table>
</div>
)}
</div>
)}
{activeTab === 'p24' && (
<div className="space-y-4">
<h3 className="font-semibold">P24 Logs</h3>
{transactionDetails?.p24.length === 0 ? (
<p className="text-sm text-gray-500">No data available</p>
) : (
<div className="border rounded-lg overflow-x-auto">
<table className="min-w-full table-auto">
<thead>
<tr className="bg-gray-100">
<th className="px-4 py-2 text-left text-sm text-gray-500">Type</th>
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Date</th>
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Body</th>
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Body</th>
<th className="px-4 py-2 text-left text-sm text-gray-500">Response Code</th>
<th className="px-4 py-2 text-left text-sm text-gray-500">Request Endpoint</th>
</tr>
</thead>
<tbody>
{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) => (
<tr key={index} className="border-t">
<td className="px-4 py-2 text-sm text-gray-500">{log.type}</td>
<td className="px-4 py-2 text-sm text-gray-500">{log.request_date}</td>
<td className="px-4 py-2 text-sm text-gray-500">{log.response_date}</td>
<td className="px-4 py-2 text-sm text-gray-500">{log.request_body}</td>
<td className="px-4 py-2 text-sm text-gray-500">{log.response_body}</td>
<td className="px-4 py-2 text-sm text-gray-500">{log.request_endpoint ?? '-'}</td>
</tr>
))
) : (
<tr>
<td colSpan={3} className="px-4 py-2 text-center text-sm text-gray-500">
No logs available
</td>
</tr>
)}
</tbody>
</table>
</div>
)}
</div>
)}
</div>
</DialogBody>
</DialogContent>
</Dialog>
);
};
export default DetailTransaction;

View File

@ -8,6 +8,7 @@ import { useCallApi } from '@/hooks';
import ListToolbar from '../blocks/ListToolbar'; import ListToolbar from '../blocks/ListToolbar';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router';
import DetailTransaction from '../blocks/DetailTransaction';
interface TransactionProps { interface TransactionProps {
id: number; id: number;
@ -23,16 +24,26 @@ interface ContextProps {
order_direction: any, order_direction: any,
filter: any filter: any
) => Promise<{ data: TransactionProps[]; totalCount: number } | undefined>; ) => Promise<{ data: TransactionProps[]; totalCount: number } | undefined>;
showDetailDialog: boolean;
setShowDetailDialog: React.Dispatch<React.SetStateAction<boolean>>;
selectedTransactionId: number | null;
setSelectedTransactionId: React.Dispatch<React.SetStateAction<number | null>>;
} }
const initialProps: ContextProps = { const initialProps: ContextProps = {
getTransactionLists: async () => ({ data: [], totalCount: 0 }) getTransactionLists: async () => ({ data: [], totalCount: 0 }),
showDetailDialog: false,
setShowDetailDialog: () => { },
selectedTransactionId: null,
setSelectedTransactionId: () => { }
}; };
const ManageTransactionContext = createContext<ContextProps>(initialProps); const ManageTransactionContext = createContext<ContextProps>(initialProps);
const API_URL = apiConfig.transaction; const API_URL = apiConfig.transaction;
const TransactionProvider = ({ children }: { children: React.ReactNode }) => { const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
const [showDetailDialog, setShowDetailDialog] = useState(false);
const [selectedTransactionId, setSelectedTransactionId] = useState<number | null>(null);
const [transaction, setTransaction] = useState<TransactionProps[]>([]); const [transaction, setTransaction] = useState<TransactionProps[]>([]);
const { GetData } = useCallApi(); const { GetData } = useCallApi();
const navigate = useNavigate(); const navigate = useNavigate();
@ -42,6 +53,15 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
const columns = useMemo<ColumnDef<any>[]>( const columns = useMemo<ColumnDef<any>[]>(
() => [ () => [
// {
// accessorKey: 'transaction_date',
// header: ({ column }) => <DataGridColumnHeader title="Transaction Date" column={column} />,
// enableSorting: false,
// enableHiding: false,
// meta: {
// headerClassName: 'w-[250px]'
// }
// },
{ {
accessorKey: 'transaction_date', accessorKey: 'transaction_date',
header: ({ column }) => <DataGridColumnHeader title="Transaction Date" column={column} />, header: ({ column }) => <DataGridColumnHeader title="Transaction Date" column={column} />,
@ -49,6 +69,20 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
enableHiding: false, enableHiding: false,
meta: { meta: {
headerClassName: 'w-[250px]' 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)
} }
}, },
{ {
@ -61,8 +95,12 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
} }
}, },
{ {
accessorFn: (row) => accessorFn: (row) => {
new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(row.purchase.amount), 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', id: 'amount',
header: ({ column }) => <DataGridColumnHeader title="Amount" column={column} />, header: ({ column }) => <DataGridColumnHeader title="Amount" column={column} />,
enableSorting: false, enableSorting: false,
@ -73,19 +111,13 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
}, },
{ {
accessorFn: (row) => { accessorFn: (row) => {
let fee; const purchaseAmount = row?.purchase?.fee_amount;
if (row.kind === 'P') { const transferAmount = row?.transfer?.fee_amount;
fee = row.purchase.fee_amount;
} else { return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(purchaseAmount ?? transferAmount ?? 0);
fee = row.transfer.fee_amount;
}
return fee.toLocaleString('en-US', {
style: 'currency',
currency: 'USD',
});
}, },
accessorKey: 'fee', id: 'feeamount',
header: ({ column }) => <DataGridColumnHeader title="Fee" column={column} />, header: ({ column }) => <DataGridColumnHeader title="Fee Amount" column={column} />,
enableSorting: false, enableSorting: false,
enableHiding: false, enableHiding: false,
meta: { meta: {
@ -97,9 +129,9 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
let status; let status;
if (row.status === 'C') { if (row.status === 'C') {
status = 'COMPLETE'; status = 'COMPLETE';
} else if(row.status === 'F') { } else if (row.status === 'F') {
status = 'FAILED'; status = 'FAILED';
} else if(row.status === 'O') { } else if (row.status === 'O') {
status = 'ON PROCESS'; status = 'ON PROCESS';
} else { } else {
status = 'PENDING'; status = 'PENDING';
@ -138,13 +170,19 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
enableSorting: false, enableSorting: false,
enableHiding: false, enableHiding: false,
cell: (data) => { cell: (data) => {
const row = data.row.original.id; const row = data.row.original;
return ( return (
<> <div key={`actions-${row.id}`}>
<button className="btn btn-sm btn-icon btn-clear btn-light"> <button
className="btn btn-sm btn-icon btn-clear btn-light"
onClick={() => {
setSelectedTransactionId(row.id);
setShowDetailDialog(true);
}}
>
<KeenIcon icon="eye" /> <KeenIcon icon="eye" />
</button> </button>
</> </div>
); );
}, },
meta: { meta: {
@ -161,22 +199,22 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
let enddate; let enddate;
let formattedFilter; let formattedFilter;
if (filter == undefined || filter.length==0) { if (filter == undefined || filter.length == 0) {
const today = new Date(); const today = new Date();
const nextWeek = new Date(); const nextWeek = new Date();
nextWeek.setDate(today.getDate() + 7); nextWeek.setDate(today.getDate() + 7);
startdate = today.toISOString().split('T')[0]; startdate = today.toISOString().split('T')[0];
enddate = nextWeek.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; startdate = filter[0].value.from;
enddate = filter[0].value.to; enddate = filter[0].value.to;
} }
formattedFilter = { formattedFilter = {
"Transactions.transaction_date": { "Transactions.transaction_date": {
from: startdate+" 00:00:00", from: startdate + " 00:00:00",
to: enddate+" 23:59:59" to: enddate + " 23:59:59"
} }
}; };
@ -199,10 +237,15 @@ const TransactionProvider = ({ children }: { children: React.ReactNode }) => {
return ( return (
<ManageTransactionContext.Provider <ManageTransactionContext.Provider
value={{ value={{
getTransactionLists getTransactionLists,
showDetailDialog,
setShowDetailDialog,
selectedTransactionId,
setSelectedTransactionId
}} }}
> >
<Toaster expand visibleToasts={9} duration={3000} /> <Toaster expand visibleToasts={9} duration={3000} />
<DetailTransaction />
<DataGridProvider <DataGridProvider
columns={columns} columns={columns}

View File

@ -150,9 +150,7 @@ const AddFeeDialog = () => {
e.preventDefault(); e.preventDefault();
for (const key in formField) { for (const key in formField) {
if ( if (
formField[key as keyof typeof formField] === '' || formField[key as keyof typeof formField] === '' ) {
formField[key as keyof typeof formField] === 0
) {
setAlert({ show: true, message: 'All fields must be filled out' }); setAlert({ show: true, message: 'All fields must be filled out' });
return; return;
} }
@ -163,7 +161,7 @@ const AddFeeDialog = () => {
...formField ...formField
}); });
if (response?.status) { if (response?.status) {
toast.success('Success Create Transfer Fee'); // toast.success('Success Create Transfer Fee');
reload(); reload();
resetForm(); resetForm();
handleAddFeeDialog(false); handleAddFeeDialog(false);
@ -225,7 +223,7 @@ const AddFeeDialog = () => {
/> />
</div> </div>
<div className="w-full"> <div className="w-full">
<label className="form-label">Minimum Amount <span className="text-red-500">*</span></label> <label className="form-label">Minimum Amount</label>
<NumericFormat <NumericFormat
className="input" className="input"
value={formField.minimum_amount} value={formField.minimum_amount}
@ -242,7 +240,7 @@ const AddFeeDialog = () => {
/> />
</div> </div>
<div className="w-full"> <div className="w-full">
<label className="form-label">Maximum Amount <span className="text-red-500">*</span></label> <label className="form-label">Maximum Amount</label>
<NumericFormat <NumericFormat
className="input" className="input"
value={formField.maximum_amount} value={formField.maximum_amount}
@ -283,7 +281,7 @@ const AddFeeDialog = () => {
/> />
</div> </div>
<div className="w-full"> <div className="w-full">
<label className="form-label">Deduct Amount <span className="text-red-500">*</span></label> <label className="form-label">Deduct Amount</label>
<NumericFormat <NumericFormat
className="input" className="input"
value={formField.deduct_amount} value={formField.deduct_amount}
@ -300,7 +298,7 @@ const AddFeeDialog = () => {
/> />
</div> </div>
<div className="w-full"> <div className="w-full">
<label className="form-label">Deduct Percentage <span className="text-red-500">*</span></label> <label className="form-label">Deduct Percentage</label>
<NumericFormat <NumericFormat
className="input" className="input"
value={formField.deduct_percentage} value={formField.deduct_percentage}

View File

@ -1,4 +1,4 @@
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle , DialogDescription} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Alert, useDataGrid } from '@/components'; import { Alert, useDataGrid } from '@/components';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
@ -6,7 +6,6 @@ import { apiConfig } from '@/config/api.config';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { useCallApi } from '@/hooks'; import { useCallApi } from '@/hooks';
import { useManageTransferFeeContext } from '../hooks/useManageTransferFeeContext'; import { useManageTransferFeeContext } from '../hooks/useManageTransferFeeContext';
import { DialogDescription } from '@radix-ui/react-dialog';
const API_URL = apiConfig.service_transaction; const API_URL = apiConfig.service_transaction;
@ -27,10 +26,10 @@ const DeleteDialog = () => {
if (response?.status) { if (response?.status) {
setAlert({ show: false, message: '' }); setAlert({ show: false, message: '' });
handleDeleteFeeDialog(false, null); handleDeleteFeeDialog(false, null);
toast.success('Success Delete Product'); // toast.success('Success Delete Transaction Fee');
reload(); reload();
} else { } else {
toast.error('Failed Delete Product'); // toast.error('Failed Delete Transaction Fee');
setAlert({ show: true, message: response?.message }); setAlert({ show: true, message: response?.message });
} }
}, [selectedTransferFee]); }, [selectedTransferFee]);

View File

@ -96,10 +96,11 @@ const EditFeeDialog = () => {
async (e: React.FormEvent<HTMLFormElement>) => { async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
const response = await PutData(`${API_URL}/transactionfees/update/${selectedTransferFee}`, { const response = await PutData(`${API_URL}/transactionfees/update/${selectedTransferFee}`, {
...formField, }); ...formField
});
if (response?.status) { if (response?.status) {
handleEditFeeDialog(false, null); handleEditFeeDialog(false, null);
toast.success('Success Update User'); // toast.success('Success Update Transaction Fee');
reload(); reload();
} else { } else {
setAlert((prev) => ({ ...prev, show: true, message: response?.message })); setAlert((prev) => ({ ...prev, show: true, message: response?.message }));
@ -252,7 +253,9 @@ const EditFeeDialog = () => {
<div className="card flex flex-col gap-5"> <div className="card flex flex-col gap-5">
<div className="card-body grid gap-5 p-0"> <div className="card-body grid gap-5 p-0">
<div className="w-full"> <div className="w-full">
<label className="form-label">Transfer Free Name <span className="text-red-500">*</span></label> <label className="form-label">
Transfer Free Name <span className="text-red-500">*</span>
</label>
<Input <Input
className="input" className="input"
type="text" type="text"
@ -264,7 +267,9 @@ const EditFeeDialog = () => {
/> />
</div> </div>
<div className="w-full"> <div className="w-full">
<label className="form-label">Description <span className="text-red-500">*</span></label> <label className="form-label">
Description <span className="text-red-500">*</span>
</label>
<Input <Input
className="input" className="input"
type="text" type="text"
@ -276,7 +281,9 @@ const EditFeeDialog = () => {
/> />
</div> </div>
<div className="w-full"> <div className="w-full">
<label className="form-label">Minimum Amount <span className="text-red-500">*</span></label> <label className="form-label">
Minimum Amount
</label>
<NumericFormat <NumericFormat
className="input" className="input"
value={formField.minimum_amount} value={formField.minimum_amount}
@ -293,7 +300,9 @@ const EditFeeDialog = () => {
/> />
</div> </div>
<div className="w-full"> <div className="w-full">
<label className="form-label">Maximum Amount <span className="text-red-500">*</span></label> <label className="form-label">
Maximum Amount
</label>
<NumericFormat <NumericFormat
className="input" className="input"
value={formField.maximum_amount} value={formField.maximum_amount}
@ -310,7 +319,9 @@ const EditFeeDialog = () => {
/> />
</div> </div>
<div className="w-full"> <div className="w-full">
<label className="form-label">Period Start <span className="text-red-500">*</span></label> <label className="form-label">
Period Start <span className="text-red-500">*</span>
</label>
<Input <Input
className="input" className="input"
type="date" type="date"
@ -322,7 +333,9 @@ const EditFeeDialog = () => {
/> />
</div> </div>
<div className="w-full"> <div className="w-full">
<label className="form-label">Period End <span className="text-red-500">*</span></label> <label className="form-label">
Period End <span className="text-red-500">*</span>
</label>
<Input <Input
className="input" className="input"
type="date" type="date"
@ -334,7 +347,8 @@ const EditFeeDialog = () => {
/> />
</div> </div>
<div className="w-full"> <div className="w-full">
<label className="form-label">Deduct Amount <span className="text-red-500">*</span></label> <label className="form-label">
Deduct Amount</label>
<NumericFormat <NumericFormat
className="input" className="input"
value={formField.deduct_amount} value={formField.deduct_amount}
@ -351,7 +365,8 @@ const EditFeeDialog = () => {
/> />
</div> </div>
<div className="w-full"> <div className="w-full">
<label className="form-label">Deduct Percentage <span className="text-red-500">*</span></label> <label className="form-label">
Deduct Percentage </label>
<NumericFormat <NumericFormat
className="input" className="input"
value={formField.deduct_percentage} value={formField.deduct_percentage}
@ -368,7 +383,9 @@ const EditFeeDialog = () => {
/> />
</div> </div>
<div className="w-full"> <div className="w-full">
<label className="form-label">Transacsion Type ID <span className="text-red-500">*</span></label> <label className="form-label">
Transacsion Type ID <span className="text-red-500">*</span>
</label>
<Select <Select
value={formField.transaction_type} value={formField.transaction_type}
onValueChange={(transaction_type) => onValueChange={(transaction_type) =>
@ -388,7 +405,9 @@ const EditFeeDialog = () => {
</Select> </Select>
</div> </div>
<div className="w-full"> <div className="w-full">
<label className="form-label">Status <span className="text-red-500">*</span></label> <label className="form-label">
Status <span className="text-red-500">*</span>
</label>
<Select <Select
value={formField.status} value={formField.status}
onValueChange={(value) => setFormField({ ...formField, status: value })} onValueChange={(value) => setFormField({ ...formField, status: value })}
@ -403,7 +422,9 @@ const EditFeeDialog = () => {
</Select> </Select>
</div> </div>
<div className="w-full"> <div className="w-full">
<label className="form-label">Status Included <span className="text-red-500">*</span></label> <label className="form-label">
Status Included <span className="text-red-500">*</span>
</label>
<Select <Select
value={formField.status_include} value={formField.status_include}
onValueChange={(value) => onValueChange={(value) =>
@ -420,20 +441,22 @@ const EditFeeDialog = () => {
</Select> </Select>
</div> </div>
<div className="w-full"> <div className="w-full">
<label className="form-label">Priority <span className="text-red-500">*</span></label> <label className="form-label">
<Select Priority <span className="text-red-500">*</span>
value={formField.priority} </label>
onValueChange={(value) => setFormField({ ...formField, priority: value })} <Select
> value={formField.priority}
<SelectTrigger> onValueChange={(value) => setFormField({ ...formField, priority: value })}
<SelectValue placeholder="Select Priority" /> >
</SelectTrigger> <SelectTrigger>
<SelectContent> <SelectValue placeholder="Select Priority" />
<SelectItem value="Y">Yes</SelectItem> </SelectTrigger>
<SelectItem value="N">No</SelectItem> <SelectContent>
</SelectContent> <SelectItem value="Y">Yes</SelectItem>
</Select> <SelectItem value="N">No</SelectItem>
</div> </SelectContent>
</Select>
</div>
{/* <div className="w-full"> {/* <div className="w-full">
<label className="form-label">Priotity <span className="text-red-500">*</span></label> <label className="form-label">Priotity <span className="text-red-500">*</span></label>

View File

@ -10,10 +10,10 @@ import { EditFeeDialog } from '../blocks/EditDialog';
interface ContextProps { interface ContextProps {
showEditFeeDialog: boolean; showEditFeeDialog: boolean;
handleEditFeeDialog: (show: boolean, selectedTransferFee: string | null) => void; handleEditFeeDialog: (show: boolean, selected_TransferFee: string | null) => void;
showAddFeeDialog: boolean; showAddFeeDialog: boolean;
handleAddFeeDialog: (show: boolean) => void; handleAddFeeDialog: (show: boolean) => void;
handleDeleteFeeDialog: (show: boolean, selectedTransferFee: string | null) => void; handleDeleteFeeDialog: (show: boolean, selected_TransferFee: string | null) => void;
showDeleteFeeDialog: boolean; showDeleteFeeDialog: boolean;
selectedTransferFee: string | null; selectedTransferFee: string | null;
} }
@ -39,8 +39,8 @@ const ManageTransferFeeContextProvider = ({ children }: { children: React.ReactN
const [selectedTransferFee, setSelectedTransferFee] = useState<string | null>(null); const [selectedTransferFee, setSelectedTransferFee] = useState<string | null>(null);
const { GetData } = useCallApi(); const { GetData } = useCallApi();
const handleEditFeeDialog = useCallback((show: boolean, selectedTransferFee: string | null) => { const handleEditFeeDialog = useCallback((show: boolean, selected_TransferFee: string | null) => {
setSelectedTransferFee(show ? selectedTransferFee : null); setSelectedTransferFee(show ? selected_TransferFee : null);
setShowEditFeeDialog(show); setShowEditFeeDialog(show);
}, []); }, []);
@ -48,8 +48,8 @@ const ManageTransferFeeContextProvider = ({ children }: { children: React.ReactN
setShowAddFeeDialog(show); setShowAddFeeDialog(show);
}, []); }, []);
const handleDeleteFeeDialog = useCallback((show: boolean, selectedTransferFee: string | null) => { const handleDeleteFeeDialog = useCallback((show: boolean, selected_TransferFee: string | null) => {
setSelectedTransferFee(show ? selectedTransferFee : null); setSelectedTransferFee(show ? selected_TransferFee : null);
setShowDeleteFeeDialog(show); setShowDeleteFeeDialog(show);
}, []); }, []);
const doGetTransferFeeListData = async ( const doGetTransferFeeListData = async (
@ -62,7 +62,7 @@ const ManageTransferFeeContextProvider = ({ children }: { children: React.ReactN
filter = filter.length == 0 ? {} : { any: filter[0].value.toLowerCase() }; filter = filter.length == 0 ? {} : { any: filter[0].value.toLowerCase() };
const response = await GetData(`${API_URL}/transactionfees/list`, { const response = await GetData(`${API_URL}/transactionfees/list`, {
limit: limit, limit: limit,
page: 1, page: page+1,
with_deleted: false, with_deleted: false,
order_field: sorting[0].id, order_field: sorting[0].id,
order_direction: sorting[0].desc ? 'ASC' : 'DESC', order_direction: sorting[0].desc ? 'ASC' : 'DESC',
@ -203,9 +203,9 @@ const ManageTransferFeeContextProvider = ({ children }: { children: React.ReactN
return ( return (
<div className="container mx-auto py-5"> <div className="container mx-auto py-5">
<div className="flex justify-between items-center mb-4"> <div className="flex justify-between items-center mt-6">
<h1 className="text-2xl font-semibold">Manage Transaction Fee</h1> <h1 className="text-xl font-semibold text-gray-900">Manage Transaction Fee</h1>
</div> </div>
<ManageTransferFeeContext.Provider <ManageTransferFeeContext.Provider
value={{ value={{
showEditFeeDialog, showEditFeeDialog,

View File

@ -165,10 +165,7 @@ const AddDialog = () => {
async (e: React.FormEvent<HTMLFormElement>) => { async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault(); e.preventDefault();
for (const key in formField) { for (const key in formField) {
if ( if (formField[key as keyof typeof formField] === '') {
formField[key as keyof typeof formField] === '' ||
formField[key as keyof typeof formField] === 0
) {
setAlert({ show: true, message: 'All fields must be filled out' }); setAlert({ show: true, message: 'All fields must be filled out' });
return; return;
} }
@ -229,6 +226,7 @@ const AddDialog = () => {
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5"> <div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56"> <label className="form-label flex items-center gap-1 max-w-56">
Transfer Type Name Transfer Type Name
<span className="text-red-500">*</span>
</label> </label>
<Input <Input
className="input" className="input"
@ -246,6 +244,7 @@ const AddDialog = () => {
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5"> <div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56"> <label className="form-label flex items-center gap-1 max-w-56">
Description Description
<span className="text-red-500">*</span>
</label> </label>
<Input <Input
className="input" className="input"
@ -325,7 +324,10 @@ const AddDialog = () => {
</div> </div>
<div className="w-full"> <div className="w-full">
<div className="flex items-center flex-wrap gap-2.5"> <div className="flex items-center flex-wrap gap-2.5">
<label className="form-label max-w-56">From Account</label> <label className="form-label flex items-center gap-1 max-w-56">
From Account
<span className="text-red-500">*</span>
</label>
<div className="grow"> <div className="grow">
<Select <Select
value={formField.wallet_origin} value={formField.wallet_origin}
@ -350,7 +352,10 @@ const AddDialog = () => {
<div className="w-full"> <div className="w-full">
<div className="flex items-center flex-wrap gap-2.5"> <div className="flex items-center flex-wrap gap-2.5">
<label className="form-label max-w-56">To Account</label> <label className="form-label flex items-center gap-1 max-w-56">
To Account
<span className="text-red-500">*</span>
</label>
<div className="grow"> <div className="grow">
<Select <Select
value={formField.wallet_destination} value={formField.wallet_destination}
@ -374,7 +379,10 @@ const AddDialog = () => {
</div> </div>
<div className="w-full"> <div className="w-full">
<div className="flex items-center flex-wrap gap-2.5"> <div className="flex items-center flex-wrap gap-2.5">
<label className="form-label max-w-56">Wallet Destination Fee</label> <label className="form-label flex items-center gap-1 max-w-56">
Wallet Destination Fee
<span className="text-red-500">*</span>
</label>
<div className="grow"> <div className="grow">
<Select <Select
value={formField.wallet_fee_destination} value={formField.wallet_fee_destination}
@ -398,7 +406,10 @@ const AddDialog = () => {
</div> </div>
<div className="w-full"> <div className="w-full">
<div className="flex items-center flex-wrap gap-2.5"> <div className="flex items-center flex-wrap gap-2.5">
<label className="form-label max-w-56">Customer Fee Destination</label> <label className="form-label flex items-center gap-1 max-w-56">
Customer Fee Destination
<span className="text-red-500">*</span>
</label>
<div className="grow"> <div className="grow">
<Select <Select
value={formField.customer_fee_destination} value={formField.customer_fee_destination}
@ -423,7 +434,10 @@ const AddDialog = () => {
<div className="w-full"> <div className="w-full">
<div className="flex items-center flex-wrap gap-2.5"> <div className="flex items-center flex-wrap gap-2.5">
<label className="form-label max-w-56">Status Approval</label> <label className="form-label flex items-center gap-1 max-w-56">
Status Approval
<span className="text-red-500">*</span>
</label>
<div className="grow"> <div className="grow">
<Select <Select
@ -446,7 +460,10 @@ const AddDialog = () => {
<div className="w-full"> <div className="w-full">
<div className="flex items-center flex-wrap gap-2.5"> <div className="flex items-center flex-wrap gap-2.5">
<label className="form-label max-w-56">Status</label> <label className="form-label flex items-center gap-1 max-w-56">
Status
<span className="text-red-500"> *</span>
</label>
<div className="grow"> <div className="grow">
<Select <Select

View File

@ -34,7 +34,7 @@ const DeleteDialog = () => {
setAlert({ show: false, message: '' }); setAlert({ show: false, message: '' });
handleDeleteDialog(false, null); handleDeleteDialog(false, null);
reload(); reload();
setTimeout(() => toast.success('Success Delete Product'), 0); setTimeout(() => toast.success('Success Delete Transaction Type'), 0);
} else { } else {
setAlert({ show: true, message: response?.message }); setAlert({ show: true, message: response?.message });
setTimeout(() => toast.error('Failed Delete Product'), 0); setTimeout(() => toast.error('Failed Delete Product'), 0);

View File

@ -124,7 +124,7 @@ const EditDialog = () => {
}); });
if (response?.status) { if (response?.status) {
handleEditDialog(false, null); handleEditDialog(false, null);
toast.success('Success Update User'); toast.success('Success Update Transfer Type');
reload(); reload();
} else { } else {
setAlert((prev) => ({ ...prev, show: true, message: response?.message })); setAlert((prev) => ({ ...prev, show: true, message: response?.message }));
@ -240,7 +240,7 @@ const EditDialog = () => {
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5"> <div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56"> <label className="form-label flex items-center gap-1 max-w-56">
Transfer Type Name Transfer Type Name
<span className="text-red-500">*</span> <span className="text-red-500">*</span>
</label> </label>
<Input <Input
className="input" className="input"
@ -258,7 +258,7 @@ const EditDialog = () => {
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5"> <div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56"> <label className="form-label flex items-center gap-1 max-w-56">
Description Description
<span className="text-red-500">*</span> <span className="text-red-500">*</span>
</label> </label>
<Input <Input
className="input" className="input"
@ -276,7 +276,6 @@ const EditDialog = () => {
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5"> <div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56"> <label className="form-label flex items-center gap-1 max-w-56">
Minimum Amount Minimum Amount
<span className="text-red-500">*</span>
</label> </label>
<NumericFormat <NumericFormat
className="input" className="input"
@ -287,7 +286,7 @@ const EditDialog = () => {
onValueChange={(values) => { onValueChange={(values) => {
setFormField((prev) => ({ setFormField((prev) => ({
...prev, ...prev,
maximum_amount: values.floatValue || 0 minimum_amount: values.floatValue || 0
})); }));
}} }}
placeholder="Enter Minimum Amount" placeholder="Enter Minimum Amount"
@ -299,7 +298,6 @@ const EditDialog = () => {
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5"> <div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56"> <label className="form-label flex items-center gap-1 max-w-56">
Maximum Amount Maximum Amount
<span className="text-red-500">*</span>
</label> </label>
<NumericFormat <NumericFormat
className="input" className="input"
@ -320,8 +318,7 @@ const EditDialog = () => {
<div className="w-full"> <div className="w-full">
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5"> <div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label className="form-label flex items-center gap-1 max-w-56"> <label className="form-label flex items-center gap-1 max-w-56">
Maximum Transaction per day Max Transaction per day
<span className="text-red-500">*</span>
</label> </label>
<NumericFormat <NumericFormat
className="input" className="input"
@ -341,7 +338,9 @@ const EditDialog = () => {
</div> </div>
<div className="w-full"> <div className="w-full">
<div className="flex items-center flex-wrap gap-2.5"> <div className="flex items-center flex-wrap gap-2.5">
<label className="form-label max-w-56">From Account<span className="text-red-500">*</span> <label className="form-label flex items-center gap-1 max-w-56">
From Account
<span className="text-red-500">*</span>
</label> </label>
<div className="grow"> <div className="grow">
<Select <Select
@ -354,8 +353,8 @@ const EditDialog = () => {
<SelectValue placeholder="Select Wallet" /> <SelectValue placeholder="Select Wallet" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{wallets.map((wallet) => ( {wallets.map((wallet, idx) => (
<SelectItem value={wallet.Wallet_id} key={wallet.Wallet_id}> <SelectItem value={wallet.Wallet_id} key={wallet.Wallet_name}>
{wallet.Wallet_name} {wallet.Wallet_name}
</SelectItem> </SelectItem>
))} ))}
@ -367,7 +366,9 @@ const EditDialog = () => {
<div className="w-full"> <div className="w-full">
<div className="flex items-center flex-wrap gap-2.5"> <div className="flex items-center flex-wrap gap-2.5">
<label className="form-label max-w-56">To Account<span className="text-red-500">*</span> <label className="form-label flex items-center gap-1 max-w-56">
To Account
<span className="text-red-500">*</span>
</label> </label>
<div className="grow"> <div className="grow">
<Select <Select
@ -381,7 +382,7 @@ const EditDialog = () => {
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{wallets.map((wallet, idx) => ( {wallets.map((wallet, idx) => (
<SelectItem value={wallet.Wallet_id} key={wallet.Wallet_name}> <SelectItem value={wallet.Wallet_id} key={wallet.Wallet_id}>
{wallet.Wallet_name} {wallet.Wallet_name}
</SelectItem> </SelectItem>
))} ))}
@ -392,7 +393,9 @@ const EditDialog = () => {
</div> </div>
<div className="w-full"> <div className="w-full">
<div className="flex items-center flex-wrap gap-2.5"> <div className="flex items-center flex-wrap gap-2.5">
<label className="form-label max-w-56">Wallet Fee Destination<span className="text-red-500">*</span> <label className="form-label flex items-center gap-1 max-w-56">
Wallet Destination Fee
<span className="text-red-500">*</span>
</label> </label>
<div className="grow"> <div className="grow">
<Select <Select
@ -406,7 +409,7 @@ const EditDialog = () => {
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{wallets.map((wallet, idx) => ( {wallets.map((wallet, idx) => (
<SelectItem value={wallet.Wallet_id} key={wallet.Wallet_name}> <SelectItem value={wallet.Wallet_id} key={wallet.Wallet_id}>
{wallet.Wallet_name} {wallet.Wallet_name}
</SelectItem> </SelectItem>
))} ))}
@ -417,7 +420,9 @@ const EditDialog = () => {
</div> </div>
<div className="w-full"> <div className="w-full">
<div className="flex items-center flex-wrap gap-2.5"> <div className="flex items-center flex-wrap gap-2.5">
<label className="form-label max-w-56">Select Customer<span className="text-red-500">*</span> <label className="form-label flex items-center gap-1 max-w-56">
Customer Fee Destination
<span className="text-red-500">*</span>
</label> </label>
<div className="grow"> <div className="grow">
<Select <Select
@ -427,7 +432,7 @@ const EditDialog = () => {
} }
> >
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select" /> <SelectValue placeholder="Select Customer" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{customers.map((customer, idx) => ( {customers.map((customer, idx) => (
@ -440,9 +445,12 @@ const EditDialog = () => {
</div> </div>
</div> </div>
</div> </div>
<div className="w-full"> <div className="w-full">
<div className="flex items-center flex-wrap gap-2.5"> <div className="flex items-center flex-wrap gap-2.5">
<label className="form-label max-w-56">Status Approval<span className="text-red-500">*</span> <label className="form-label flex items-center gap-1 max-w-56">
Status Approval
<span className="text-red-500">*</span>
</label> </label>
<div className="grow"> <div className="grow">
@ -466,7 +474,9 @@ const EditDialog = () => {
<div className="w-full"> <div className="w-full">
<div className="flex items-center flex-wrap gap-2.5"> <div className="flex items-center flex-wrap gap-2.5">
<label className="form-label max-w-56">Status<span className="text-red-500">*</span> <label className="form-label flex items-center gap-1 max-w-56">
Status
<span className="text-red-500"> *</span>
</label> </label>
<div className="grow"> <div className="grow">
@ -487,6 +497,7 @@ const EditDialog = () => {
</div> </div>
</div> </div>
</div> </div>
<div className="flex justify-end pt-2.5 gap-5"> <div className="flex justify-end pt-2.5 gap-5">
<Button <Button
variant={'outline'} variant={'outline'}

View File

@ -85,6 +85,14 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React
enableHiding: false, enableHiding: false,
meta: { headerClassName: 'w-[250px]' } meta: { headerClassName: 'w-[250px]' }
}, },
{
accessorFn: (row) => row.description,
id: 'description',
header: ({ column }) => <DataGridColumnHeader title="Description" column={column} />,
enableSorting: true,
enableHiding: false,
meta: { headerClassName: 'w-[250px]' }
},
{ {
accessorFn: (row) => row.wallet_origin?.name || 'N/A', accessorFn: (row) => row.wallet_origin?.name || 'N/A',
id: 'wallet_origin', id: 'wallet_origin',
@ -127,14 +135,6 @@ const ManageTransferTypeContextProvider = ({ children }: { children: React.React
enableHiding: false, enableHiding: false,
meta: { headerClassName: 'w-[250px]' } meta: { headerClassName: 'w-[250px]' }
}, },
{
accessorFn: (row) => row.description,
id: 'description',
header: ({ column }) => <DataGridColumnHeader title="Description" column={column} />,
enableSorting: true,
enableHiding: false,
meta: { headerClassName: 'w-[250px]' }
},
{ {
accessorFn: (row) => row.wallet_fee_destination.name, accessorFn: (row) => row.wallet_fee_destination.name,
id: 'wallet_fee_destination', id: 'wallet_fee_destination',

View File

@ -0,0 +1,32 @@
import { Container, DataGridInner } from '@/components';
import { ManageWalletContextProvider } from './hooks/ManageWalletHistoryContext';
import { Breadcrumbs, Link } from '@mui/material';
const WalletHistory = () => {
return (
<ManageWalletContextProvider>
<Container>
<h1 className="text-xl font-medium leading-none text-gray-900 mb-5">Wallet History</h1>
<Breadcrumbs sx={{ mb: 2 }}>
<Link underline="none" color="inherit" href="/">
<span className="text-sm hover:underline">Dashboard</span>
</Link>
<Link underline="none" color="inherit">
<span className="text-sm">Wallet</span>
</Link>
<Link underline="none" color="inherit">
<span className="text-sm">Wallet History</span>
</Link>
</Breadcrumbs>
<div className="grid gap-5 lg:gap-7.5">
<DataGridInner />
</div>
</Container>
</ManageWalletContextProvider>
);
};
export default WalletHistory;

View File

@ -0,0 +1,48 @@
import { DefaultTooltip, KeenIcon, useDataGrid } from '@/components';
import { useManageWalletContext } from '../hooks/useManageWalletHistoryContext';
import { Button } from '@/components/ui/button';
const ListToolbar = () => {
const { table, reload } = useDataGrid();
const { handleAddDialog } = useManageWalletContext();
return (
<div className="card-header flex-wrap gap-2 border-b-0 px-5">
<div className="flex flex-wrap gap-2 lg:gap-5 w-full">
<div className="flex justify-between w-full items-center">
<div className="flex w-[50%] gap-3 items-center">
<label className="input input-sm w-1/3">
<KeenIcon icon="magnifier" />
<input
type="text"
placeholder="Search Wallet"
value={(table.getColumn('name')?.getFilterValue() as string) ?? ''}
onChange={(event) => table.getColumn('name')?.setFilterValue(event.target.value)}
/>
</label>
{/* <DefaultTooltip title={'Filter'} placement={'top'}>
<Button
variant="outline"
className="h-7.5 disabled:bg-gray-400"
// disabled={isLoading}
// onClick={handleFilterData}
>
{loadingButton === 'filter' ? <ContentLoader /> : <KeenIcon icon="filter" />}
<KeenIcon icon="filter" />
</Button>
</DefaultTooltip> */}
</div>
<div className="flex gap-3 items-center">
<DefaultTooltip title={'Refresh'} placement={'top'}>
<Button variant="outline" className="h-7.5" onClick={() => reload()}>
<KeenIcon icon="arrows-circle" />
</Button>
</DefaultTooltip>
</div>
</div>
</div>
</div>
);
};
export default ListToolbar;

View File

@ -0,0 +1,211 @@
import { DataGridColumnHeader, DataGridProvider, KeenIcon } from '@/components';
import { Toaster } from '@/components/ui/sonner';
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 ListToolbar from '../blocks/ListToolbar';
interface WalletProps {
id: string;
name: string;
id_currency: string;
status: string;
}
interface ContextProps {
wallets: 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 = {
wallets: [],
showAddDialog: false,
handleAddDialog: (show: boolean) => {},
showEditDialog: false,
handleEditDialog: (show: boolean, selected_wallet: object | null) => {},
showDeleteDialog: false,
handleDeleteDialog: (show: boolean, selected_wallet: object | null) => {},
selectedWallet: null,
getWalletLists: async () => undefined
};
const ManageWalletContext = createContext<ContextProps>(initialProps);
const API_URL_WALLET = apiConfig.service_wallet;
const ManageWalletContextProvider = ({ children }: { children: React.ReactNode }) => {
const [wallets, setWallets] = useState<WalletProps[]>([]);
const [showAddDialog, setShowAddDialog] = useState(false);
const [showEditDialog, setShowEditDialog] = useState(false);
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const [selectedWallet, setSelectedWallet] = useState<WalletProps | null>(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<ColumnDef<any>[]>(
() => [
{
accessorFn: (row) => row.name,
id: 'name',
header: ({ column }) => <DataGridColumnHeader title="Wallet Name" column={column} />,
enableSorting: true,
enableHiding: false,
meta: {
headerClassName: 'w-[550px]',
cellClassName: 'p-[20px]'
}
},
{
accessorFn: (row) => row.currency.name,
id: 'currency_name',
header: ({ column }) => <DataGridColumnHeader title="Currency" column={column} />,
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[200px]'
}
},
{
accessorFn: (row) => row.currency.prefix,
id: 'currency_prefix',
header: ({ column }) => <DataGridColumnHeader title="Prefix" column={column} />,
enableSorting: false,
enableHiding: false,
meta: {
headerClassName: 'w-[200px]'
}
},
{
accessorFn: (row) => row.status,
id: 'status',
header: ({ column }) => <DataGridColumnHeader title="Status" column={column} />,
enableSorting: false,
enableHiding: false,
cell: ({ row }) => {
const isActive = row.original.status === 'Y';
return (
<span
className={`px-2 py-1 text-xs font-semibold rounded-full ${
isActive ? 'bg-green-100 text-green-600' : 'bg-red-100 text-red-600'
}`}
>
{isActive ? 'Active' : 'Inactive'}
</span>
);
},
meta: {
headerClassName: 'w-[100px]',
cellClassName: 'text-center'
}
}
// {
// id: 'actions',
// header: ({ column }) => <DataGridColumnHeader title="Actions" column={column} />,
// cell: (data) => {
// const row = data.row.original;
// return (
// <>
// <button
// className="btn btn-sm btn-icon btn-clear btn-light"
// onClick={() => handleEditDialog(true, row)}
// >
// <KeenIcon icon="notepad-edit" />
// </button>
// <button
// className="btn btn-sm btn-icon btn-clear btn-light"
// onClick={() => handleDeleteDialog(true, row)}
// >
// <KeenIcon icon="trash" />
// </button>
// </>
// );
// },
// meta: {
// headerClassName: 'w-[150px]'
// }
// }
],
[]
);
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 ? {} : { name: filter[0].value?.toLowerCase() };
const response = await GetData(`${API_URL_WALLET}/dashboard/wallet`, {
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 (
<ManageWalletContext.Provider
value={{
wallets,
showAddDialog,
handleAddDialog,
showEditDialog,
handleEditDialog,
showDeleteDialog,
handleDeleteDialog,
selectedWallet,
getWalletLists
}}
>
<Toaster expand visibleToasts={9} duration={3000} />
<DataGridProvider
columns={columns}
pagination={{ size: 5 }}
toolbar={<ListToolbar />}
layout={{ card: true }}
sorting={[{ id: 'id', desc: false }]}
serverSide={true}
onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) =>
getWalletLists(pageIndex, pageSize, sorting, columnFilters)
}
>
{children}
</DataGridProvider>
</ManageWalletContext.Provider>
);
};
export { ManageWalletContext, ManageWalletContextProvider };
export type { WalletProps };

View File

@ -0,0 +1,12 @@
import { useContext } from 'react';
import { ManageWalletContext } from './ManageWalletHistoryContext';
const useManageWalletContext = () => {
const context = useContext(ManageWalletContext);
if (!context) {
throw new Error('useManageWalletContext must be used within a ManageWalletContextProvider');
}
return context;
};
export { useManageWalletContext };

View File

@ -34,6 +34,8 @@ import ProductsMaster from '@/pages/master/products/ProductsMaster';
import ProviderMaster from '@/pages/master/provider/ProviderMaster'; import ProviderMaster from '@/pages/master/provider/ProviderMaster';
import ConversionMaster from '@/pages/master/conversion/ConversionMaster'; import ConversionMaster from '@/pages/master/conversion/ConversionMaster';
import WalletRuleMaster from '@/pages/master/walletRule/WalletRuleMaster'; import WalletRuleMaster from '@/pages/master/walletRule/WalletRuleMaster';
import WalletHistory from '@/pages/wallet/wallet-history/WalletHistory';
import WalletMaster from '@/pages/master/wallet/WalletMaster';
const AppRoutingSetup = (): ReactElement => { const AppRoutingSetup = (): ReactElement => {
return ( return (
@ -55,6 +57,9 @@ const AppRoutingSetup = (): ReactElement => {
element={<PostoAdmsMaster />} element={<PostoAdmsMaster />}
/> />
<Route path="/master-data/conversion" element={<ConversionMaster />} /> <Route path="/master-data/conversion" element={<ConversionMaster />} />
<Route path="/master-data/wallet" element={<WalletMaster />} />
<Route path="/master-data/wallet-rule" element={<WalletRuleMaster />} /> <Route path="/master-data/wallet-rule" element={<WalletRuleMaster />} />
<Route path="/account/home/user-profile" element={<AccountUserProfilePage />} /> <Route path="/account/home/user-profile" element={<AccountUserProfilePage />} />
@ -72,6 +77,8 @@ const AppRoutingSetup = (): ReactElement => {
<Route path="/transfer-type/transfer-type-management" element={<TransferType />} /> <Route path="/transfer-type/transfer-type-management" element={<TransferType />} />
<Route path="/wallet/wallet-history" element={<WalletHistory />} />
<Route path="/notification/notification-management" element={<ManageNotification />} /> <Route path="/notification/notification-management" element={<ManageNotification />} />
<Route path="/transaction" element={<Transaction />} /> <Route path="/transaction" element={<Transaction />} />

View File

@ -39,7 +39,7 @@
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.0.tgz" resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.0.tgz"
integrity sha512-qETICbZSLe7uXv9VE8T/RWOdIE5qqyTucOt4zLYMafj2MRO271VGgLd4RACJMeBO37UPWhXiKMBk7YlJ0fOzQA== integrity sha512-qETICbZSLe7uXv9VE8T/RWOdIE5qqyTucOt4zLYMafj2MRO271VGgLd4RACJMeBO37UPWhXiKMBk7YlJ0fOzQA==
"@babel/core@^7.25.2": "@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.25.2":
version "7.26.0" version "7.26.0"
resolved "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz" resolved "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz"
integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg== integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==
@ -564,7 +564,7 @@
resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz" resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz"
integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==
"@emotion/react@^11.13.3": "@emotion/react@^11.0.0-rc.0", "@emotion/react@^11.13.3", "@emotion/react@^11.4.1", "@emotion/react@^11.5.0":
version "11.13.3" version "11.13.3"
resolved "https://registry.npmjs.org/@emotion/react/-/react-11.13.3.tgz" resolved "https://registry.npmjs.org/@emotion/react/-/react-11.13.3.tgz"
integrity sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg== integrity sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==
@ -594,7 +594,7 @@
resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz" resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz"
integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg== integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==
"@emotion/styled@^11.13.0": "@emotion/styled@^11.13.0", "@emotion/styled@^11.3.0":
version "11.13.0" version "11.13.0"
resolved "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz" resolved "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz"
integrity sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA== integrity sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==
@ -631,10 +631,10 @@
resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz" resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz"
integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg== integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==
"@esbuild/win32-x64@0.21.5": "@esbuild/darwin-arm64@0.21.5":
version "0.21.5" version "0.21.5"
resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz" resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz"
integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
version "4.4.1" version "4.4.1"
@ -704,7 +704,7 @@
resolved "https://registry.npmjs.org/@faker-js/faker/-/faker-9.1.0.tgz" resolved "https://registry.npmjs.org/@faker-js/faker/-/faker-9.1.0.tgz"
integrity sha512-GJvX9iM9PBtKScJVlXQ0tWpihK3i0pha/XAhzQa1hPK/ILLa1Wq3I63Ij7lRtqTwmdTxRCyrUhLC5Sly9SLbug== integrity sha512-GJvX9iM9PBtKScJVlXQ0tWpihK3i0pha/XAhzQa1hPK/ILLa1Wq3I63Ij7lRtqTwmdTxRCyrUhLC5Sly9SLbug==
"@firebase/app@^0.10.15": "@firebase/app@^0.10.15", "@firebase/app@0.x":
version "0.10.15" version "0.10.15"
resolved "https://registry.npmjs.org/@firebase/app/-/app-0.10.15.tgz" resolved "https://registry.npmjs.org/@firebase/app/-/app-0.10.15.tgz"
integrity sha512-he6qlG3pmwL+LHdG/BrSMBQeJzzutciq4fpXN3lGa1uSwYSijJ24VtakS/bP2X9SiDf8jGywJ4u+OgXAenJsNg== integrity sha512-he6qlG3pmwL+LHdG/BrSMBQeJzzutciq4fpXN3lGa1uSwYSijJ24VtakS/bP2X9SiDf8jGywJ4u+OgXAenJsNg==
@ -991,7 +991,7 @@
dependencies: dependencies:
"@babel/runtime" "^7.26.0" "@babel/runtime" "^7.26.0"
"@mui/material@^6.1.6": "@mui/material@^6.1.6", "@mui/material@^6.4.6":
version "6.4.8" version "6.4.8"
resolved "https://registry.npmjs.org/@mui/material/-/material-6.4.8.tgz" resolved "https://registry.npmjs.org/@mui/material/-/material-6.4.8.tgz"
integrity sha512-5S9UTjKZZBd9GfbcYh/nYfD9cv6OXmj5Y7NgKYfk7JcSoshp8/pW5zP4wecRiroBSZX8wcrywSgogpVNO+5W0Q== integrity sha512-5S9UTjKZZBd9GfbcYh/nYfD9cv6OXmj5Y7NgKYfk7JcSoshp8/pW5zP4wecRiroBSZX8wcrywSgogpVNO+5W0Q==
@ -1580,10 +1580,10 @@
resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.21.0.tgz" resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.21.0.tgz"
integrity sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA== integrity sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==
"@rollup/rollup-win32-x64-msvc@4.24.2": "@rollup/rollup-darwin-arm64@4.24.2":
version "4.24.2" version "4.24.2"
resolved "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.2.tgz" resolved "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.2.tgz"
integrity sha512-2mLH46K1u3r6uwc95hU+OR9q/ggYMpnS7pSp83Ece1HUQgF9Nh/QwTK5rcgbFnV9j+08yBrU5sA/P0RK2MSBNA== integrity sha512-/UhrIxobHYCBfhi5paTkUDQ0w+jckjRZDZ1kcBL132WeHZQ6+S5v9jQPVGLVrLbNUebdIRpIt00lQ+4Z7ys4Rg==
"@tanstack/query-core@5.59.20": "@tanstack/query-core@5.59.20":
version "5.59.20" version "5.59.20"
@ -1672,7 +1672,7 @@
dependencies: dependencies:
"@types/geojson" "*" "@types/geojson" "*"
"@types/node@^22.9.0", "@types/node@>=12.12.47", "@types/node@>=13.7.0": "@types/node@^18.0.0 || >=20.0.0", "@types/node@^22.9.0", "@types/node@>=12.12.47", "@types/node@>=13.7.0":
version "22.9.0" version "22.9.0"
resolved "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz" resolved "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz"
integrity sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ== integrity sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==
@ -1689,7 +1689,7 @@
resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz" resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz"
integrity sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ== integrity sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==
"@types/react-dom@^18.3.1": "@types/react-dom@*", "@types/react-dom@^18.3.1":
version "18.3.1" version "18.3.1"
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz" resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz"
integrity sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ== integrity sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==
@ -1708,7 +1708,7 @@
resolved "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz" resolved "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz"
integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w== integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==
"@types/react@*", "@types/react@^18.3.12", "@types/react@16 || 17 || 18": "@types/react@*", "@types/react@^16.8.0 || ^17.0.0 || ^18.0.0", "@types/react@^16.9.0 || ^17.0.0 || ^18.0.0", "@types/react@^17.0.0 || ^18.0.0", "@types/react@^17.0.0 || ^18.0.0 || ^19.0.0", "@types/react@^18.3.12", "@types/react@16 || 17 || 18":
version "18.3.12" version "18.3.12"
resolved "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz" resolved "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz"
integrity sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw== integrity sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==
@ -1750,7 +1750,7 @@
natural-compare "^1.4.0" natural-compare "^1.4.0"
ts-api-utils "^1.3.0" ts-api-utils "^1.3.0"
"@typescript-eslint/parser@^8.14.0", "@typescript-eslint/parser@8.14.0": "@typescript-eslint/parser@^8.0.0 || ^8.0.0-alpha.0", "@typescript-eslint/parser@^8.14.0", "@typescript-eslint/parser@8.14.0":
version "8.14.0" version "8.14.0"
resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.14.0.tgz" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.14.0.tgz"
integrity sha512-2p82Yn9juUJq0XynBXtFCyrBDb6/dJombnz6vbo6mgQEtWHfvHbQuEa9kAOVIt1c9YFwi7H6WxtPj1kg+80+RA== integrity sha512-2p82Yn9juUJq0XynBXtFCyrBDb6/dJombnz6vbo6mgQEtWHfvHbQuEa9kAOVIt1c9YFwi7H6WxtPj1kg+80+RA==
@ -1859,7 +1859,7 @@ acorn-jsx@^5.3.2:
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
acorn@^8.12.0: "acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.12.0:
version "8.14.0" version "8.14.0"
resolved "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz" resolved "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz"
integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==
@ -1909,7 +1909,7 @@ anymatch@~3.1.2:
normalize-path "^3.0.0" normalize-path "^3.0.0"
picomatch "^2.0.4" picomatch "^2.0.4"
apexcharts@3.52.0: apexcharts@^3.41.0, apexcharts@3.52.0:
version "3.52.0" version "3.52.0"
resolved "https://registry.npmjs.org/apexcharts/-/apexcharts-3.52.0.tgz" resolved "https://registry.npmjs.org/apexcharts/-/apexcharts-3.52.0.tgz"
integrity sha512-7dg0ADKs8AA89iYMZMe2sFDG0XK5PfqllKV9N+i3hKHm3vEtdhwz8AlXGm+/b0nJ6jKiaXsqci5LfVxNhtB+dA== integrity sha512-7dg0ADKs8AA89iYMZMe2sFDG0XK5PfqllKV9N+i3hKHm3vEtdhwz8AlXGm+/b0nJ6jKiaXsqci5LfVxNhtB+dA==
@ -2025,7 +2025,7 @@ broadcast-channel@^3.4.1:
rimraf "3.0.2" rimraf "3.0.2"
unload "2.2.0" unload "2.2.0"
browserslist@^4.23.1, browserslist@^4.23.3, browserslist@^4.24.0: browserslist@^4.23.1, browserslist@^4.23.3, browserslist@^4.24.0, "browserslist@>= 4.21.0":
version "4.24.2" version "4.24.2"
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz"
integrity sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg== integrity sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==
@ -2239,12 +2239,12 @@ cssjanus@^2.0.1:
resolved "https://registry.npmjs.org/cssjanus/-/cssjanus-2.3.0.tgz" resolved "https://registry.npmjs.org/cssjanus/-/cssjanus-2.3.0.tgz"
integrity sha512-ZZXXn51SnxRxAZ6fdY7mBDPmA4OZd83q/J9Gdqz3YmE9TUq+9tZl+tdOnCi7PpNygI6PEkehj9rgifv5+W8a5A== integrity sha512-ZZXXn51SnxRxAZ6fdY7mBDPmA4OZd83q/J9Gdqz3YmE9TUq+9tZl+tdOnCi7PpNygI6PEkehj9rgifv5+W8a5A==
csstype@^3.0.2, csstype@^3.1.3, csstype@3.1.3: csstype@^3.0.10, csstype@^3.0.2, csstype@^3.1.3, csstype@3.1.3:
version "3.1.3" version "3.1.3"
resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz" resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz"
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
date-fns@^3.0.0: "date-fns@^2.28.0 || ^3.0.0", date-fns@^3.0.0:
version "3.6.0" version "3.6.0"
resolved "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz" resolved "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz"
integrity sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww== integrity sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==
@ -2386,7 +2386,7 @@ escape-string-regexp@^4.0.0:
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
eslint-config-prettier@^9.1.0: eslint-config-prettier@*, eslint-config-prettier@^9.1.0:
version "9.1.0" version "9.1.0"
resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz" resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz"
integrity sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw== integrity sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==
@ -2427,7 +2427,7 @@ eslint-visitor-keys@^4.1.0:
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz" resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz"
integrity sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg== integrity sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==
eslint@^9.13.0: "eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^8.57.0 || ^9.0.0", eslint@^9.13.0, eslint@>=7, eslint@>=7.0.0, eslint@>=8.0.0:
version "9.13.0" version "9.13.0"
resolved "https://registry.npmjs.org/eslint/-/eslint-9.13.0.tgz" resolved "https://registry.npmjs.org/eslint/-/eslint-9.13.0.tgz"
integrity sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA== integrity sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==
@ -2625,6 +2625,11 @@ fs.realpath@^1.0.0:
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
fsevents@~2.3.2, fsevents@~2.3.3:
version "2.3.3"
resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
function-bind@^1.1.2: function-bind@^1.1.2:
version "1.1.2" version "1.1.2"
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
@ -2877,7 +2882,7 @@ jackspeak@^3.1.2:
optionalDependencies: optionalDependencies:
"@pkgjs/parseargs" "^0.11.0" "@pkgjs/parseargs" "^0.11.0"
jiti@^1.18.2, jiti@^1.21.0: jiti@*, jiti@^1.18.2, jiti@^1.21.0:
version "1.21.6" version "1.21.6"
resolved "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz" resolved "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz"
integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w== integrity sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==
@ -2941,7 +2946,7 @@ kolorist@^1.8.0:
resolved "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz" resolved "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz"
integrity sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ== integrity sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==
leaflet@^1.9.4: leaflet@^1.9.0, leaflet@^1.9.4:
version "1.9.4" version "1.9.4"
resolved "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz" resolved "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz"
integrity sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA== integrity sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==
@ -3612,7 +3617,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^
resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8.4.23, postcss@^8.4.43, postcss@^8.4.49: postcss@^8, postcss@^8.0.0, postcss@^8.0.3, postcss@^8.1.0, postcss@^8.2.14, postcss@^8.4, postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.43, postcss@^8.4.49, postcss@^8.4.6, postcss@>=8.0.9:
version "8.4.49" version "8.4.49"
resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz" resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz"
integrity sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA== integrity sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==
@ -3642,7 +3647,7 @@ prettier-linter-helpers@^1.0.0:
dependencies: dependencies:
fast-diff "^1.1.2" fast-diff "^1.1.2"
prettier@^3.3.3: prettier@^3.3.3, prettier@>=3.0.0:
version "3.3.3" version "3.3.3"
resolved "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz" resolved "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz"
integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==
@ -3713,7 +3718,7 @@ react-day-picker@^8.10.1:
resolved "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz" resolved "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz"
integrity sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA== integrity sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==
react-dom@^18.3.1: "react-dom@^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom@^17.0.0 || ^18.0.0", "react-dom@^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^18 || ^19 || ^19.0.0-rc", react-dom@^18.0.0, "react-dom@^18.0.0 || ^19.0.0 || ^19.0.0-rc", react-dom@^18.3.1, "react-dom@>= 16.8.0", react-dom@>=16.6.0, react-dom@>=16.8, react-dom@>=16.8.0:
version "18.3.1" version "18.3.1"
resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz"
integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
@ -3887,7 +3892,7 @@ react-transition-group@^4.4.5:
loose-envify "^1.4.0" loose-envify "^1.4.0"
prop-types "^15.6.2" prop-types "^15.6.2"
react@^18.3.1: "react@^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.3.0 || ^17.0.0 || ^18.0.0", "react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc", "react@^16.6.0 || ^17.0.0 || ^18.0.0", "react@^16.6.0 || 17 || 18", "react@^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^17.0.0 || ^18.0.0", "react@^17.0.0 || ^18.0.0 || ^19.0.0", "react@^18 || ^19", "react@^18 || ^19 || ^19.0.0-rc", react@^18.0.0, "react@^18.0.0 || ^19.0.0 || ^19.0.0-rc", react@^18.3.1, "react@>= 16.8.0", react@>=0.13, react@>=16.3.0, react@>=16.6.0, react@>=16.8, react@>=16.8.0, "react@16.8 - 18":
version "18.3.1" version "18.3.1"
resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz" resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
@ -4134,7 +4139,7 @@ stylis-plugin-rtl@^2.1.1:
dependencies: dependencies:
cssjanus "^2.0.1" cssjanus "^2.0.1"
stylis@^4.3.4: stylis@^4.3.4, stylis@4.x:
version "4.3.4" version "4.3.4"
resolved "https://registry.npmjs.org/stylis/-/stylis-4.3.4.tgz" resolved "https://registry.npmjs.org/stylis/-/stylis-4.3.4.tgz"
integrity sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now== integrity sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now==
@ -4237,6 +4242,11 @@ synckit@^0.9.1:
"@pkgr/core" "^0.1.0" "@pkgr/core" "^0.1.0"
tslib "^2.6.2" tslib "^2.6.2"
tabs@^0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/tabs/-/tabs-0.2.0.tgz"
integrity sha512-q6mo8KWq/Zi+zGR3ZvGq6S6ckc3JNbGRGtAwIhtnp2GlS1goTqFnWX3MkyDKYSQuzwBqBAy2fYGHQI+3HMnXZQ==
tailwind-merge@^2.5.4: tailwind-merge@^2.5.4:
version "2.5.4" version "2.5.4"
resolved "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.4.tgz" resolved "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.4.tgz"
@ -4247,7 +4257,7 @@ tailwindcss-animate@^1.0.7:
resolved "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz" resolved "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz"
integrity sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA== integrity sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==
tailwindcss@^3.4.14: tailwindcss@^3.4.14, "tailwindcss@>=3.0.0 || insiders":
version "3.4.14" version "3.4.14"
resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.14.tgz" resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.14.tgz"
integrity sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA== integrity sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==
@ -4357,7 +4367,7 @@ typescript-eslint@^8.14.0:
"@typescript-eslint/parser" "8.14.0" "@typescript-eslint/parser" "8.14.0"
"@typescript-eslint/utils" "8.14.0" "@typescript-eslint/utils" "8.14.0"
typescript@^5.6.3: "typescript@^4.7 || 5", typescript@^5.6.3, typescript@>=4.2.0:
version "5.6.3" version "5.6.3"
resolved "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz" resolved "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz"
integrity sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw== integrity sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==
@ -4425,7 +4435,7 @@ vite-plugin-windicss@^1.9.3:
kolorist "^1.8.0" kolorist "^1.8.0"
windicss "^3.5.6" windicss "^3.5.6"
vite@^5.4.11: "vite@^2.0.1 || ^3.0.0 || ^4.0.0 || ^5.0.0", "vite@^4.2.0 || ^5.0.0", vite@^5.4.11:
version "5.4.11" version "5.4.11"
resolved "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz" resolved "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz"
integrity sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q== integrity sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==