done
24
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
pnpm-lock.ymal
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-lock.yaml
|
||||||
76
db.json
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
"example": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "ibrahim",
|
||||||
|
"email": "ibrahim@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "gregr",
|
||||||
|
"email": "ibrahimgmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "mhmad",
|
||||||
|
"email": "mhmad@gmial.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "soso",
|
||||||
|
"email": "soso@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"name": "few",
|
||||||
|
"email": "jfpwrej"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"name": "sos",
|
||||||
|
"email": "fdwf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"name": "sos",
|
||||||
|
"email": "fdwf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"name": "sos",
|
||||||
|
"email": "fdwf"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"test2":[
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
"id":1,
|
||||||
|
"email":"admin@adamin.com"
|
||||||
|
}
|
||||||
|
,
|
||||||
|
{
|
||||||
|
"id":1,
|
||||||
|
"email":"admin@adamin.com"
|
||||||
|
}
|
||||||
|
,
|
||||||
|
{
|
||||||
|
"id":1,
|
||||||
|
"email":"admin@adamin.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
, {
|
||||||
|
"id":1,
|
||||||
|
"email":"admin@adamin.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
],
|
||||||
|
"users": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"email": "admin@adamin.com",
|
||||||
|
"password": "password",
|
||||||
|
"token": "token"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
33874
package-lock.json
generated
Normal file
100
package.json
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
{
|
||||||
|
"name": "my-app",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/styled": "^11.11.0",
|
||||||
|
"@mui/icons-material": "^5.14.19",
|
||||||
|
"@react-google-maps/api": "^2.19.2",
|
||||||
|
"@szhsin/react-menu": "github:szhsin/react-menu",
|
||||||
|
"@testing-library/jest-dom": "^5.17.0",
|
||||||
|
"@testing-library/react": "^13.4.0",
|
||||||
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"@tinymce/tinymce-react": "^4.3.2",
|
||||||
|
"@types/jest": "^27.5.2",
|
||||||
|
"@types/node": "^16.18.60",
|
||||||
|
"@types/react": "^18.2.33",
|
||||||
|
"@types/react-dom": "^18.2.14",
|
||||||
|
"@types/socket.io-client": "^3.0.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||||
|
"@typescript-eslint/parser": "^5.62.0",
|
||||||
|
"@wojtekmaj/react-daterange-picker": "^5.4.4",
|
||||||
|
"antd": "^5.12.1",
|
||||||
|
"apexcharts": "^3.44.2",
|
||||||
|
"axios": "^1.6.0",
|
||||||
|
"bootstrap": "^5.3.2",
|
||||||
|
"chart.js": "^4.4.0",
|
||||||
|
"dayjs": "^1.11.10",
|
||||||
|
"eslint": "^8.56.0",
|
||||||
|
"formik": "^2.4.5",
|
||||||
|
"history": "^5.3.0",
|
||||||
|
"i18next": "^23.6.0",
|
||||||
|
"i18next-browser-languagedetector": "^7.1.0",
|
||||||
|
"json-server": "^0.17.4",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-apexcharts": "^1.4.1",
|
||||||
|
"react-bootstrap": "^2.9.1",
|
||||||
|
"react-bootstrap-sweetalert": "^5.2.0",
|
||||||
|
"react-chartjs-2": "^5.2.0",
|
||||||
|
"react-confirm-alert": "^3.0.6",
|
||||||
|
"react-data-table-component": "^7.5.4",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-feather": "^2.0.10",
|
||||||
|
"react-i18next": "^13.3.1",
|
||||||
|
"react-icons": "^4.11.0",
|
||||||
|
"react-query": "^3.39.3",
|
||||||
|
"react-redux": "^8.1.3",
|
||||||
|
"react-router-dom": "^6.18.0",
|
||||||
|
"react-scripts": "^5.0.1",
|
||||||
|
"react-select": "^5.7.7",
|
||||||
|
"react-simple-star-rating": "^5.1.7",
|
||||||
|
"react-switch": "^7.0.0",
|
||||||
|
"react-tabs": "^6.0.2",
|
||||||
|
"react-toastify": "^9.0.0",
|
||||||
|
"react-toggle": "^4.1.3",
|
||||||
|
"reactstrap": "^9.2.0",
|
||||||
|
"redux": "^4.2.1",
|
||||||
|
"sass": "^1.69.5",
|
||||||
|
"socket.io-client": "^4.7.2",
|
||||||
|
"styled-components": "5.3.3",
|
||||||
|
"ts-xlsx": "^0.0.11",
|
||||||
|
"typescript": "^4.9.5",
|
||||||
|
"web-vitals": "^2.1.4",
|
||||||
|
"xlsx": "^0.18.5",
|
||||||
|
"yup": "^1.3.2",
|
||||||
|
"zustand": "^4.4.5"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject",
|
||||||
|
"g:api": "node src/Extensions/FileGenerator/generateApi.js",
|
||||||
|
"g:column": "node src/Extensions/FileGenerator/generateColumn.js",
|
||||||
|
"g:formutil": "node src/Extensions/FileGenerator/generateformUtils.js",
|
||||||
|
"g:page": "node src/Extensions/FileGenerator/generatePage.js",
|
||||||
|
"g:dashboard": "node src/Extensions/FileGenerator/generateDashboard.js ",
|
||||||
|
"g:modal:add": "node src/Extensions/FileGenerator/generateEditModal.js ",
|
||||||
|
"g:model:edit": "node src/Extensions/FileGenerator/generateEditModal.js "
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react-toggle": "^4.0.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
public/Layout/Ar.svg
Normal file
|
After Width: | Height: | Size: 10 KiB |
1
public/Layout/En.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="42.67" height="32" viewBox="0 0 640 480"><defs><clipPath id="flagUm4x30"><path fill-opacity=".7" d="M0 0h682.7v512H0z"/></clipPath></defs><g fill-rule="evenodd" clip-path="url(#flagUm4x30)" transform="scale(.9375)"><g stroke-width="1pt"><path fill="#bd3d44" d="M0 0h972.8v39.4H0zm0 78.8h972.8v39.4H0zm0 78.7h972.8V197H0zm0 78.8h972.8v39.4H0zm0 78.8h972.8v39.4H0zm0 78.7h972.8v39.4H0zm0 78.8h972.8V512H0z"/><path fill="#fff" d="M0 39.4h972.8v39.4H0zm0 78.8h972.8v39.3H0zm0 78.7h972.8v39.4H0zm0 78.8h972.8v39.4H0zm0 78.8h972.8v39.4H0zm0 78.7h972.8v39.4H0z"/></g><path fill="#192f5d" d="M0 0h389.1v275.7H0z"/><path fill="#fff" d="M32.4 11.8L36 22.7h11.4l-9.2 6.7l3.5 11l-9.3-6.8l-9.2 6.7l3.5-10.9l-9.3-6.7H29zm64.9 0l3.5 10.9h11.5l-9.3 6.7l3.5 11l-9.2-6.8l-9.3 6.7l3.5-10.9l-9.2-6.7h11.4zm64.8 0l3.6 10.9H177l-9.2 6.7l3.5 11l-9.3-6.8l-9.2 6.7l3.5-10.9l-9.3-6.7h11.5zm64.9 0l3.5 10.9H242l-9.3 6.7l3.6 11l-9.3-6.8l-9.3 6.7l3.6-10.9l-9.3-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.2 6.7l3.5 11l-9.3-6.8l-9.2 6.7l3.5-10.9l-9.2-6.7h11.4zm64.9 0l3.5 10.9h11.5l-9.3 6.7l3.6 11l-9.3-6.8l-9.3 6.7l3.6-10.9l-9.3-6.7h11.5zM64.9 39.4l3.5 10.9h11.5L70.6 57L74 67.9l-9-6.7l-9.3 6.7L59 57l-9-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.3 6.7l3.6 10.9l-9.3-6.7l-9.3 6.7L124 57l-9.3-6.7h11.5zm64.9 0l3.5 10.9h11.5l-9.3 6.7l3.5 10.9l-9.2-6.7l-9.3 6.7l3.5-10.9l-9.2-6.7H191zm64.8 0l3.6 10.9h11.4l-9.3 6.7l3.6 10.9l-9.3-6.7l-9.2 6.7l3.5-10.9l-9.3-6.7H256zm64.9 0l3.5 10.9h11.5L330 57l3.5 10.9l-9.2-6.7l-9.3 6.7l3.5-10.9l-9.2-6.7h11.4zM32.4 66.9L36 78h11.4l-9.2 6.7l3.5 10.9l-9.3-6.8l-9.2 6.8l3.5-11l-9.3-6.7H29zm64.9 0l3.5 11h11.5l-9.3 6.7l3.5 10.9l-9.2-6.8l-9.3 6.8l3.5-11l-9.2-6.7h11.4zm64.8 0l3.6 11H177l-9.2 6.7l3.5 10.9l-9.3-6.8l-9.2 6.8l3.5-11l-9.3-6.7h11.5zm64.9 0l3.5 11H242l-9.3 6.7l3.6 10.9l-9.3-6.8l-9.3 6.8l3.6-11l-9.3-6.7h11.4zm64.8 0l3.6 11h11.4l-9.2 6.7l3.5 10.9l-9.3-6.8l-9.2 6.8l3.5-11l-9.2-6.7h11.4zm64.9 0l3.5 11h11.5l-9.3 6.7l3.6 10.9l-9.3-6.8l-9.3 6.8l3.6-11l-9.3-6.7h11.5zM64.9 94.5l3.5 10.9h11.5l-9.3 6.7l3.5 11l-9.2-6.8l-9.3 6.7l3.5-10.9l-9.2-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.3 6.7l3.6 11l-9.3-6.8l-9.3 6.7l3.6-10.9l-9.3-6.7h11.5zm64.9 0l3.5 10.9h11.5l-9.3 6.7l3.5 11l-9.2-6.8l-9.3 6.7l3.5-10.9l-9.2-6.7H191zm64.8 0l3.6 10.9h11.4l-9.2 6.7l3.5 11l-9.3-6.8l-9.2 6.7l3.5-10.9l-9.3-6.7H256zm64.9 0l3.5 10.9h11.5l-9.3 6.7l3.5 11l-9.2-6.8l-9.3 6.7l3.5-10.9l-9.2-6.7h11.4zM32.4 122.1L36 133h11.4l-9.2 6.7l3.5 11l-9.3-6.8l-9.2 6.7l3.5-10.9l-9.3-6.7H29zm64.9 0l3.5 10.9h11.5l-9.3 6.7l3.5 10.9l-9.2-6.7l-9.3 6.7l3.5-10.9l-9.2-6.7h11.4zm64.8 0l3.6 10.9H177l-9.2 6.7l3.5 11l-9.3-6.8l-9.2 6.7l3.5-10.9l-9.3-6.7h11.5zm64.9 0l3.5 10.9H242l-9.3 6.7l3.6 11l-9.3-6.8l-9.3 6.7l3.6-10.9l-9.3-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.2 6.7l3.5 11l-9.3-6.8l-9.2 6.7l3.5-10.9l-9.2-6.7h11.4zm64.9 0l3.5 10.9h11.5l-9.3 6.7l3.6 11l-9.3-6.8l-9.3 6.7l3.6-10.9l-9.3-6.7h11.5zM64.9 149.7l3.5 10.9h11.5l-9.3 6.7l3.5 10.9l-9.2-6.8l-9.3 6.8l3.5-11l-9.2-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.3 6.7l3.6 10.9l-9.3-6.8l-9.3 6.8l3.6-11l-9.3-6.7h11.5zm64.9 0l3.5 10.9h11.5l-9.3 6.7l3.5 10.9l-9.2-6.8l-9.3 6.8l3.5-11l-9.2-6.7H191zm64.8 0l3.6 10.9h11.4l-9.2 6.7l3.5 10.9l-9.3-6.8l-9.2 6.8l3.5-11l-9.3-6.7H256zm64.9 0l3.5 10.9h11.5l-9.3 6.7l3.5 10.9l-9.2-6.8l-9.3 6.8l3.5-11l-9.2-6.7h11.4zM32.4 177.2l3.6 11h11.4l-9.2 6.7l3.5 10.8l-9.3-6.7l-9.2 6.7l3.5-10.9l-9.3-6.7H29zm64.9 0l3.5 11h11.5l-9.3 6.7l3.6 10.8l-9.3-6.7l-9.3 6.7l3.6-10.9l-9.3-6.7h11.4zm64.8 0l3.6 11H177l-9.2 6.7l3.5 10.8l-9.3-6.7l-9.2 6.7l3.5-10.9l-9.3-6.7h11.5zm64.9 0l3.5 11H242l-9.3 6.7l3.6 10.8l-9.3-6.7l-9.3 6.7l3.6-10.9l-9.3-6.7h11.4zm64.8 0l3.6 11h11.4l-9.2 6.7l3.5 10.8l-9.3-6.7l-9.2 6.7l3.5-10.9l-9.2-6.7h11.4zm64.9 0l3.5 11h11.5l-9.3 6.7l3.6 10.8l-9.3-6.7l-9.3 6.7l3.6-10.9l-9.3-6.7h11.5zM64.9 204.8l3.5 10.9h11.5l-9.3 6.7l3.5 11l-9.2-6.8l-9.3 6.7l3.5-10.9l-9.2-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.3 6.7l3.6 11l-9.3-6.8l-9.3 6.7l3.6-10.9l-9.3-6.7h11.5zm64.9 0l3.5 10.9h11.5l-9.3 6.7l3.5 11l-9.2-6.8l-9.3 6.7l3.5-10.9l-9.2-6.7H191zm64.8 0l3.6 10.9h11.4l-9.2 6.7l3.5 11l-9.3-6.8l-9.2 6.7l3.5-10.9l-9.3-6.7H256zm64.9 0l3.5 10.9h11.5l-9.3 6.7l3.5 11l-9.2-6.8l-9.3 6.7l3.5-10.9l-9.2-6.7h11.4zM32.4 232.4l3.6 10.9h11.4l-9.2 6.7l3.5 10.9l-9.3-6.7l-9.2 6.7l3.5-11l-9.3-6.7H29zm64.9 0l3.5 10.9h11.5L103 250l3.6 10.9l-9.3-6.7l-9.3 6.7l3.6-11l-9.3-6.7h11.4zm64.8 0l3.6 10.9H177l-9 6.7l3.5 10.9l-9.3-6.7l-9.2 6.7l3.5-11l-9.3-6.7h11.5zm64.9 0l3.5 10.9H242l-9.3 6.7l3.6 10.9l-9.3-6.7l-9.3 6.7l3.6-11l-9.3-6.7h11.4zm64.8 0l3.6 10.9h11.4l-9.2 6.7l3.5 10.9l-9.3-6.7l-9.2 6.7l3.5-11l-9.2-6.7h11.4zm64.9 0l3.5 10.9h11.5l-9.3 6.7l3.6 10.9l-9.3-6.7l-9.3 6.7l3.6-11l-9.3-6.7h11.5z"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 4.5 KiB |
BIN
public/Layout/LoginBg.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/Layout/MisbarLogo.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
public/Layout/body_background.png
Normal file
|
After Width: | Height: | Size: 373 KiB |
1
public/Layout/dark.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 64 64"><path fill="currentColor" d="M43.139 2a29.885 29.885 0 0 1 5.121 16.756c0 16.701-13.686 30.24-30.57 30.24a30.656 30.656 0 0 1-15.689-4.285C7.209 54.963 17.93 62 30.318 62C47.816 62 62 47.969 62 30.66C62 17.867 54.246 6.871 43.139 2z"/></svg>
|
||||||
|
After Width: | Height: | Size: 324 B |
2
public/Layout/desktop.ini
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
[LocalizedFileNames]
|
||||||
|
Screenshot 2023-11-29 134655.png=@Screenshot 2023-11-29 134655.png,0
|
||||||
1
public/Layout/light.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 512 512"><path fill="currentColor" d="M256 160c-52.9 0-96 43.1-96 96s43.1 96 96 96s96-43.1 96-96s-43.1-96-96-96zm246.4 80.5l-94.7-47.3l33.5-100.4c4.5-13.6-8.4-26.5-21.9-21.9l-100.4 33.5l-47.4-94.8c-6.4-12.8-24.6-12.8-31 0l-47.3 94.7L92.7 70.8c-13.6-4.5-26.5 8.4-21.9 21.9l33.5 100.4l-94.7 47.4c-12.8 6.4-12.8 24.6 0 31l94.7 47.3l-33.5 100.5c-4.5 13.6 8.4 26.5 21.9 21.9l100.4-33.5l47.3 94.7c6.4 12.8 24.6 12.8 31 0l47.3-94.7l100.4 33.5c13.6 4.5 26.5-8.4 21.9-21.9l-33.5-100.4l94.7-47.3c13-6.5 13-24.7.2-31.1zm-155.9 106c-49.9 49.9-131.1 49.9-181 0c-49.9-49.9-49.9-131.1 0-181c49.9-49.9 131.1-49.9 181 0c49.9 49.9 49.9 131.1 0 181z"/></svg>
|
||||||
|
After Width: | Height: | Size: 715 B |
BIN
public/Layout/mac.webp
Normal file
|
After Width: | Height: | Size: 483 KiB |
17
public/index.html
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Web site created using create-react-app"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<title>Dashboard - Structure</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
public/logo.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
public/logo3.png
Normal file
|
After Width: | Height: | Size: 206 KiB |
25
public/manifest.json
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"short_name": "React App",
|
||||||
|
"name": "Create React App Sample",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
||||||
3
public/robots.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
67
src/App.tsx
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { Fragment, lazy, Suspense } from 'react';
|
||||||
|
import { Route, Routes } from 'react-router-dom'
|
||||||
|
import Loading from './Components/Utils/Loading/Loading';
|
||||||
|
import { RoutesLinks } from './Routes';
|
||||||
|
import Layout from './Layout/app/Layout';
|
||||||
|
import Auth from './Pages/Auth/Page';
|
||||||
|
import useHitRequests from './Hooks/useHitRequests';
|
||||||
|
const Page404 = lazy(() => import("./Layout/app/NotFoundPage"))
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
// hit requests after user status is online
|
||||||
|
// useHitRequests();
|
||||||
|
useHitRequests();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Routes >
|
||||||
|
{/* 404 Page */}
|
||||||
|
<Route path="*" element={<Suspense fallback={<Loading />}> <Page404 /></Suspense>} />
|
||||||
|
{/* Login Page */}
|
||||||
|
<Route path="/auth" element={<Suspense fallback={<Loading />}> <Auth /></Suspense>} />
|
||||||
|
|
||||||
|
{/* route not in navigation */}
|
||||||
|
|
||||||
|
|
||||||
|
{/* All Routes */}
|
||||||
|
{RoutesLinks?.map((item: any, index: number) => (
|
||||||
|
|
||||||
|
<Fragment key={index}>
|
||||||
|
|
||||||
|
if(item?.element){
|
||||||
|
<Route
|
||||||
|
key={index}
|
||||||
|
path={item.href}
|
||||||
|
element={
|
||||||
|
<Suspense fallback={<Loading />} >
|
||||||
|
<Layout>
|
||||||
|
{item?.element ?? "Please Add Element Props in Routes"}
|
||||||
|
</Layout>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}else{
|
||||||
|
item?.children?.map((item:any)=>{
|
||||||
|
return(
|
||||||
|
<Route
|
||||||
|
path={item.href}
|
||||||
|
key={item}
|
||||||
|
element={
|
||||||
|
<Suspense fallback={<Loading />} >
|
||||||
|
<Layout>
|
||||||
|
{item?.element ?? "Please Add Element Props in Routes"}
|
||||||
|
</Layout>
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</Fragment>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</Routes>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App
|
||||||
63
src/Components/Columns/ColumnsImage.tsx
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
import {
|
||||||
|
DownloadOutlined,
|
||||||
|
RotateLeftOutlined,
|
||||||
|
RotateRightOutlined,
|
||||||
|
SwapOutlined,
|
||||||
|
ZoomInOutlined,
|
||||||
|
ZoomOutOutlined,
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import React from 'react';
|
||||||
|
import { Image, Space } from 'antd';
|
||||||
|
import { BaseURL, BaseURL_IMAGE } from '../../api/config';
|
||||||
|
|
||||||
|
|
||||||
|
const ColumnsImage= ({src}:any) => {
|
||||||
|
const imageUrl = BaseURL_IMAGE+src;
|
||||||
|
|
||||||
|
// or you can download flipped and rotated image
|
||||||
|
// https://codesandbox.io/s/zi-ding-yi-gong-ju-lan-antd-5-7-0-forked-c9jvmp
|
||||||
|
const onDownload = () => {
|
||||||
|
fetch(src)
|
||||||
|
.then((response) => response.blob())
|
||||||
|
.then((blob) => {
|
||||||
|
const url = URL.createObjectURL(new Blob([blob]));
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.download = 'image.png';
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
link.remove();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Image
|
||||||
|
width={60}
|
||||||
|
height={50}
|
||||||
|
src={imageUrl}
|
||||||
|
className='p-1 mb-1'
|
||||||
|
preview={{
|
||||||
|
toolbarRender: (
|
||||||
|
_,
|
||||||
|
{
|
||||||
|
transform: { scale },
|
||||||
|
actions: { onFlipY, onFlipX, onRotateLeft, onRotateRight, onZoomOut, onZoomIn },
|
||||||
|
},
|
||||||
|
) => (
|
||||||
|
<Space size={12} className="toolbar-wrapper">
|
||||||
|
<DownloadOutlined onClick={onDownload} />
|
||||||
|
<SwapOutlined rotate={90} onClick={onFlipY} />
|
||||||
|
<SwapOutlined onClick={onFlipX} />
|
||||||
|
<RotateLeftOutlined onClick={onRotateLeft} />
|
||||||
|
<RotateRightOutlined onClick={onRotateRight} />
|
||||||
|
<ZoomOutOutlined disabled={scale === 1} onClick={onZoomOut} />
|
||||||
|
<ZoomInOutlined disabled={scale === 50} onClick={onZoomIn} />
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ColumnsImage;
|
||||||
42
src/Components/Columns/ColumnsSwitch.tsx
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { Switch } from 'antd'
|
||||||
|
import React from 'react'
|
||||||
|
import { CheckOutlined, CloseOutlined } from '@ant-design/icons';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
export interface ColumnsSwitchProps {
|
||||||
|
name: string;
|
||||||
|
Front?: string;
|
||||||
|
Back?: string;
|
||||||
|
onChange?: (checked:any,event:any) => any;
|
||||||
|
icon?: boolean
|
||||||
|
Checked?:boolean
|
||||||
|
|
||||||
|
}
|
||||||
|
const ColumnsSwitch = (props: ColumnsSwitchProps) => {
|
||||||
|
const { name, Front, Back, icon, onChange } = props;
|
||||||
|
const formik = useFormikContext<any>();
|
||||||
|
const onSwitchChange = (checked: boolean, event: Event) => {
|
||||||
|
// formik.setFieldValue("status", checked)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<Switch
|
||||||
|
checkedChildren={icon ? <CheckOutlined /> : Front}
|
||||||
|
unCheckedChildren={icon ? <CloseOutlined /> : Back}
|
||||||
|
onChange={ (checked:any,event:any)=> onChange ? onChange(checked,event) : onSwitchChange(checked,event)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ColumnsSwitch
|
||||||
|
|
||||||
|
|
||||||
|
ColumnsSwitch.defaultProps = {
|
||||||
|
Front: "Front",
|
||||||
|
Back: "Back",
|
||||||
|
onChange: undefined,
|
||||||
|
icon: false,
|
||||||
|
Checked:false
|
||||||
|
|
||||||
|
};
|
||||||
57
src/Components/Karimalden/KarimField.scss
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
.KarimField{
|
||||||
|
>*{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.text,.ant-form-item{
|
||||||
|
margin-bottom:7px !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
>span{
|
||||||
|
margin-bottom: 0px !important;
|
||||||
|
&:focus-within{
|
||||||
|
border-color: var(--primary) ;
|
||||||
|
box-shadow: 0 0 0 1px var(--primary);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
&:has(.is-invalid){
|
||||||
|
border-color: red !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
input{
|
||||||
|
color: var(--text);
|
||||||
|
background: var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:-webkit-autofill,
|
||||||
|
input:-webkit-autofill:hover,
|
||||||
|
input:-webkit-autofill:focus,
|
||||||
|
input:-webkit-autofill:active{
|
||||||
|
-webkit-box-shadow: 0 0 0 30px white inset !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-upload-select{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.Checkboxs{
|
||||||
|
padding: 4%;
|
||||||
|
}
|
||||||
|
.SearchField{
|
||||||
|
button{
|
||||||
|
background: var(--primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.text{
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:disabled{
|
||||||
|
color: var(--text) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.error{
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
55
src/Components/Karimalden/KarimField.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import React from "react";
|
||||||
|
import "./KarimField.scss";
|
||||||
|
import { Date, Time, File, DataRange, SelectField, Default, CheckboxField } from './View';
|
||||||
|
|
||||||
|
export interface KarimFieldProps {
|
||||||
|
name: string;
|
||||||
|
type?: "text" | "Select" | "DataRange" | "Date" | "Time" | "File" | "number" | "Checkbox" | "password";
|
||||||
|
placeholder?: string;
|
||||||
|
label?: string;
|
||||||
|
className?: string;
|
||||||
|
option?: any[];
|
||||||
|
isMulti?: boolean;
|
||||||
|
isDisabled?: boolean;
|
||||||
|
picker?: "data" | "week" | "month" | "quarter" | "year";
|
||||||
|
Format?: "YYYY/MM/DD" | "MM/DD" | "YYYY/MM";
|
||||||
|
accept?:any
|
||||||
|
onChange?: (value: any) => void;
|
||||||
|
Group?: boolean
|
||||||
|
dir?:'ltr' | "rtl"
|
||||||
|
}
|
||||||
|
|
||||||
|
const KarimField = (props: KarimFieldProps) => {
|
||||||
|
switch (props?.type) {
|
||||||
|
case 'Select':
|
||||||
|
return <SelectField {...props} />;
|
||||||
|
case "DataRange":
|
||||||
|
return <DataRange {...props} />;
|
||||||
|
case "Date":
|
||||||
|
return <Date {...props} />;
|
||||||
|
case "Time":
|
||||||
|
return <Time {...props} />;
|
||||||
|
case "File":
|
||||||
|
return <File {...props} />;
|
||||||
|
case "Checkbox":
|
||||||
|
return <CheckboxField {...props} />;
|
||||||
|
default:
|
||||||
|
return <Default {...props} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
KarimField.defaultProps = {
|
||||||
|
type: "text",
|
||||||
|
className: 'default-class',
|
||||||
|
option: [],
|
||||||
|
isMulti: false,
|
||||||
|
isDisabled: false,
|
||||||
|
picker: "date",
|
||||||
|
Format: "YYYY/MM/DD",
|
||||||
|
onChange: undefined,
|
||||||
|
Group:false,
|
||||||
|
dir : "ltr",
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export default KarimField;
|
||||||
29
src/Components/Karimalden/View/CheckboxField.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import React from 'react'
|
||||||
|
import useFormField from '../../../Hooks/useFormField';
|
||||||
|
import { Checkbox } from 'antd';
|
||||||
|
|
||||||
|
const CheckboxField = ({ name, label, placeholder, isDisabled, option, isMulti, onChange,Group, props }: any) => {
|
||||||
|
|
||||||
|
const { t, formik } = useFormField(name, props)
|
||||||
|
const CheckboxhandleChange = (value:any) => {
|
||||||
|
console.log(value.target.checked);
|
||||||
|
|
||||||
|
formik.setFieldValue(name, value.target.checked)
|
||||||
|
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className={Group ? "d-inline mt-3 Checkboxs" :``}>
|
||||||
|
<Checkbox
|
||||||
|
onChange={onChange || CheckboxhandleChange}
|
||||||
|
disabled={isDisabled}
|
||||||
|
checked={formik.getFieldProps(name).value}
|
||||||
|
|
||||||
|
>
|
||||||
|
{t(label)}
|
||||||
|
</Checkbox>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CheckboxField
|
||||||
40
src/Components/Karimalden/View/DataRange.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { Form, DatePicker } from 'antd'
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import useFormField from '../../../Hooks/useFormField';
|
||||||
|
|
||||||
|
const { RangePicker } = DatePicker;
|
||||||
|
|
||||||
|
const DataRange = ({ name, label, isDisabled, option, isMulti,Format ,props }: any) => {
|
||||||
|
|
||||||
|
const { errorMsg, isError, t, formik } = useFormField(name, props)
|
||||||
|
const onCalendarChange = (value: any) => {
|
||||||
|
|
||||||
|
formik.setFieldValue(name, value)
|
||||||
|
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
|
||||||
|
<div className='KarimField'>
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`${label}`)}
|
||||||
|
</label>
|
||||||
|
<Form.Item
|
||||||
|
hasFeedback
|
||||||
|
validateStatus={isError ? "error" : ""}
|
||||||
|
help={isError ? errorMsg : ""}
|
||||||
|
>
|
||||||
|
<RangePicker
|
||||||
|
allowClear
|
||||||
|
className='w-100'
|
||||||
|
format={Format}
|
||||||
|
onCalendarChange={onCalendarChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DataRange
|
||||||
40
src/Components/Karimalden/View/Date.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { Form, DatePicker } from 'antd'
|
||||||
|
import React from 'react'
|
||||||
|
import useFormField from '../../../Hooks/useFormField';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
const Date = ({ name, label, isDisabled, option, isMulti,picker="date" ,props }: any) => {
|
||||||
|
|
||||||
|
const { errorMsg, isError, t, formik } = useFormField(name, props)
|
||||||
|
const onCalendarChange = (value: any) => {
|
||||||
|
|
||||||
|
formik.setFieldValue(name, value)
|
||||||
|
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
|
||||||
|
<div className='KarimField'>
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`${label}`)}
|
||||||
|
</label>
|
||||||
|
<Form.Item
|
||||||
|
hasFeedback
|
||||||
|
validateStatus={isError ? "error" : ""}
|
||||||
|
help={isError ? errorMsg : ""}
|
||||||
|
>
|
||||||
|
<DatePicker
|
||||||
|
picker={picker}
|
||||||
|
allowClear
|
||||||
|
className='w-100'
|
||||||
|
// defaultValue={formik.values[name]}
|
||||||
|
|
||||||
|
onChange={onCalendarChange} />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Date
|
||||||
32
src/Components/Karimalden/View/Default.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { Form, Input } from 'antd'
|
||||||
|
import React from 'react'
|
||||||
|
import useFormField from '../../../Hooks/useFormField';
|
||||||
|
|
||||||
|
const Default = ({ name, label, placeholder, isDisabled, onChange, props }: any) => {
|
||||||
|
const { Field, formik, isError, errorMsg, t } = useFormField(name, props);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="KarimField">
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`${label ? label : name}`)}
|
||||||
|
</label>
|
||||||
|
<Form.Item
|
||||||
|
hasFeedback
|
||||||
|
validateStatus={isError ? 'error' : ''}
|
||||||
|
help={isError ? errorMsg : ''}
|
||||||
|
>
|
||||||
|
<Field
|
||||||
|
as={Input}
|
||||||
|
type="text"
|
||||||
|
placeholder={t(`${placeholder ?placeholder : name}`)}
|
||||||
|
name={name}
|
||||||
|
disabled={isDisabled}
|
||||||
|
// onChange={onChange ? onChange : handleChange}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Default;
|
||||||
59
src/Components/Karimalden/View/File.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { Button, Upload, UploadFile } from 'antd'
|
||||||
|
import useFormField from '../../../Hooks/useFormField';
|
||||||
|
import { UploadOutlined } from '@ant-design/icons';
|
||||||
|
import { BaseURL, BaseURL_IMAGE } from '../../../api/config';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { ErrorMessage } from 'formik';
|
||||||
|
|
||||||
|
|
||||||
|
const File = ({ name, label, onChange, isDisabled,accept, props }: any) => {
|
||||||
|
const { formik, t } = useFormField(name, props)
|
||||||
|
const imageUrl = formik.values[name] ? BaseURL_IMAGE + formik.values[name] : '';
|
||||||
|
|
||||||
|
const fileList: UploadFile[] = [
|
||||||
|
|
||||||
|
{
|
||||||
|
uid: '-1',
|
||||||
|
name: '',
|
||||||
|
status: 'done',
|
||||||
|
url: imageUrl,
|
||||||
|
thumbUrl: imageUrl,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const FilehandleChange = (value:any) => {
|
||||||
|
|
||||||
|
formik.setFieldValue(name, value.file.originFileObj)
|
||||||
|
|
||||||
|
};
|
||||||
|
const customRequest = async ({ onSuccess}: any) => {
|
||||||
|
onSuccess();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="KarimField">
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`${label || name}`)}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<Upload
|
||||||
|
accept={accept ?? undefined}
|
||||||
|
disabled={isDisabled}
|
||||||
|
listType="picture"
|
||||||
|
maxCount={1}
|
||||||
|
className='w-100'
|
||||||
|
defaultFileList={[...fileList]}
|
||||||
|
onChange={onChange || FilehandleChange}
|
||||||
|
customRequest={customRequest}
|
||||||
|
|
||||||
|
>
|
||||||
|
<Button className='w-100' icon={<UploadOutlined />}>{t("upload_image")}</Button>
|
||||||
|
<ErrorMessage name={name}>{msg => <div className='error'>{t(msg)}</div>}</ErrorMessage>
|
||||||
|
|
||||||
|
</Upload>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default File
|
||||||
44
src/Components/Karimalden/View/SearchField.tsx
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { Input } from 'antd';
|
||||||
|
import { SearchProps } from 'antd/es/input'
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
|
||||||
|
const { Search } = Input;
|
||||||
|
|
||||||
|
const SearchField = () => {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const [searchParams,] = useSearchParams();
|
||||||
|
const location =useLocation()
|
||||||
|
const {t} = useTranslation();
|
||||||
|
|
||||||
|
const [searchValue, setSearchValue] = useState(searchParams.get('search')|| "");
|
||||||
|
|
||||||
|
const onSearch: SearchProps['onSearch'] = (value, _e, info) => {
|
||||||
|
// console.log(value);
|
||||||
|
|
||||||
|
navigate(`${location?.pathname}?search=${value}`, { replace: true });
|
||||||
|
}
|
||||||
|
const onChange = (e :any) => {
|
||||||
|
setSearchValue(e.target.value);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='SearchField'>
|
||||||
|
<Search
|
||||||
|
allowClear
|
||||||
|
enterButton={t("search")}
|
||||||
|
size="middle"
|
||||||
|
placeholder={t("search")}
|
||||||
|
onSearch={onSearch}
|
||||||
|
style={{ width: 250 }}
|
||||||
|
value={searchValue}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SearchField
|
||||||
39
src/Components/Karimalden/View/SelectField.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { Form, Select } from 'antd'
|
||||||
|
import React from 'react'
|
||||||
|
import useFormField from '../../../Hooks/useFormField';
|
||||||
|
|
||||||
|
const SelectField = ({ name, label, placeholder, isDisabled,option,isMulti,onChange, props}: any) => {
|
||||||
|
|
||||||
|
const { errorMsg, isError, t ,formik} = useFormField(name, props)
|
||||||
|
const SelecthandleChange = (value: { value: string; label: React.ReactNode }) => {
|
||||||
|
formik.setFieldValue(name, value)
|
||||||
|
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className='KarimField'>
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`${label ? label : name}`)}
|
||||||
|
</label>
|
||||||
|
<Form.Item
|
||||||
|
hasFeedback
|
||||||
|
validateStatus={isError ? "error" : ""}
|
||||||
|
help={isError ? errorMsg : ""}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
placeholder={t(`${placeholder ?placeholder : name}`)}
|
||||||
|
disabled={isDisabled}
|
||||||
|
options={option}
|
||||||
|
defaultValue={formik.values[name]}
|
||||||
|
allowClear
|
||||||
|
{...(isMulti && { mode: "multiple" })}
|
||||||
|
onChange={onChange || SelecthandleChange}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SelectField
|
||||||
38
src/Components/Karimalden/View/Time.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { Form, TimePicker } from 'antd'
|
||||||
|
import React from 'react'
|
||||||
|
import useFormField from '../../../Hooks/useFormField';
|
||||||
|
|
||||||
|
const Time = ({ name, label,props }: any) => {
|
||||||
|
|
||||||
|
const { errorMsg, isError, t, formik } = useFormField(name, props)
|
||||||
|
const onCalendarChange = (value: any) => {
|
||||||
|
|
||||||
|
formik.setFieldValue(name, value)
|
||||||
|
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
|
||||||
|
<div className='KarimField'>
|
||||||
|
<label htmlFor={name} className="text">
|
||||||
|
{t(`${label}`)}
|
||||||
|
</label>
|
||||||
|
<Form.Item
|
||||||
|
hasFeedback
|
||||||
|
validateStatus={isError ? "error" : ""}
|
||||||
|
help={isError ? errorMsg : ""}
|
||||||
|
>
|
||||||
|
<TimePicker
|
||||||
|
allowClear
|
||||||
|
className='w-100'
|
||||||
|
defaultValue={formik.values[name]}
|
||||||
|
onChange={onCalendarChange} />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Time
|
||||||
22
src/Components/Karimalden/View/index.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import Time from "./Time";
|
||||||
|
import SelectField from "./SelectField";
|
||||||
|
import Date from "./Date";
|
||||||
|
import DataRange from "./DataRange";
|
||||||
|
import CheckboxField from "./CheckboxField";
|
||||||
|
import Default from "./Default";
|
||||||
|
import File from "./File";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export {
|
||||||
|
Time,
|
||||||
|
SelectField,
|
||||||
|
Date,
|
||||||
|
DataRange,
|
||||||
|
CheckboxField,
|
||||||
|
Default,
|
||||||
|
File
|
||||||
|
|
||||||
|
}
|
||||||
21
src/Components/Karimalden/index.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { ErrorMessage, useField, Field, useFormikContext } from 'formik';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FaExclamationCircle } from 'react-icons/fa';
|
||||||
|
import Select from 'react-select';
|
||||||
|
import { convert_data_to_select } from '../../Layout/app/Const';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export {
|
||||||
|
useState,
|
||||||
|
ErrorMessage, useField, Field, useFormikContext,
|
||||||
|
useTranslation,
|
||||||
|
FaExclamationCircle,
|
||||||
|
Select,
|
||||||
|
convert_data_to_select,
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
50
src/Components/Ui/Alert.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import React from "react";
|
||||||
|
import { confirmAlert } from "react-confirm-alert";
|
||||||
|
import SweetAlert from "react-bootstrap-sweetalert";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
export default function CustomConfirmAlert(options : any) {
|
||||||
|
confirmAlert({
|
||||||
|
customUI: ({ onClose }) => <CustomUI onClose={onClose} options={options} />,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
type CustomUIProps ={
|
||||||
|
onClose :()=> void
|
||||||
|
options:{
|
||||||
|
title?:string
|
||||||
|
confirmBtnText:string
|
||||||
|
cancelBtnText:string
|
||||||
|
body:string
|
||||||
|
onConfirm:()=>void
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function CustomUI({ onClose, options }:CustomUIProps) {
|
||||||
|
|
||||||
|
|
||||||
|
const [t] = useTranslation()
|
||||||
|
return (
|
||||||
|
<div className="">
|
||||||
|
<SweetAlert
|
||||||
|
title={options.title || t(`delete_are_you_sure`)}
|
||||||
|
customClass="SweetAlert"
|
||||||
|
warning
|
||||||
|
show={true}
|
||||||
|
showCancel
|
||||||
|
reverseButtons
|
||||||
|
cancelBtnBsStyle="danger"
|
||||||
|
confirmBtnText={options.confirmBtnText || t("yes_delete_it")}
|
||||||
|
cancelBtnText={options.cancelBtnText || t("cancel")}
|
||||||
|
onConfirm={() => {
|
||||||
|
options.onConfirm();
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
onCancel={onClose}
|
||||||
|
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
49
src/Components/Ui/CheckboxesVuexy.tsx
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface CheckBoxesVuexyProps {
|
||||||
|
className?: string;
|
||||||
|
color: string;
|
||||||
|
defaultChecked?: boolean;
|
||||||
|
checked?: boolean;
|
||||||
|
value?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
onClick?: () => void;
|
||||||
|
onChange?: () => void;
|
||||||
|
size?: string;
|
||||||
|
icon: React.ReactNode;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CheckBoxesVuexy: React.FC<CheckBoxesVuexyProps> = ({
|
||||||
|
className = '',
|
||||||
|
color,
|
||||||
|
defaultChecked,
|
||||||
|
checked,
|
||||||
|
value,
|
||||||
|
disabled,
|
||||||
|
onClick,
|
||||||
|
onChange,
|
||||||
|
size = 'md',
|
||||||
|
icon,
|
||||||
|
label,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={`vx-checkbox-con ${className} vx-checkbox-${color}`}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
defaultChecked={defaultChecked}
|
||||||
|
checked={checked}
|
||||||
|
value={value}
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={onClick}
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
<span className={`vx-checkbox vx-checkbox-${size}`}>
|
||||||
|
<span className="vx-checkbox--check">{icon}</span>
|
||||||
|
</span>
|
||||||
|
<span>{label}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CheckBoxesVuexy;
|
||||||
27
src/Components/Ui/FileInput.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import React from 'react'
|
||||||
|
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
|
import { Input } from 'reactstrap';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
|
||||||
|
type FileInputProps = {
|
||||||
|
name:string,
|
||||||
|
label:string,
|
||||||
|
accept:string,
|
||||||
|
onChange:any
|
||||||
|
}
|
||||||
|
function FileInput({name , accept="image/*" ,label , onChange} :FileInputProps) {
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="custom-file-input">
|
||||||
|
<label className="custom-file-label" htmlFor="inputGroupFile01">
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
<Input accept={accept} id="inputGroupFile01" name={name} onChange={onChange}
|
||||||
|
type="file" className="custom-file-input" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FileInput
|
||||||
37
src/Components/Ui/HovarableImage.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Tooltip } from "reactstrap";
|
||||||
|
|
||||||
|
const tooltipStyle = {
|
||||||
|
backgroundColor: "white",
|
||||||
|
border: "2px solid lightgrey",
|
||||||
|
maxWidth: "400px",
|
||||||
|
};
|
||||||
|
|
||||||
|
const HovarableImage = ({ id, src, imgPreviewProps = {}, ...props }:any) => {
|
||||||
|
const [isOpen, setIsOpen] = React.useState(false);
|
||||||
|
const ID = `image_hover_tooltip_${id}`;
|
||||||
|
const toggleTooltip = React.useCallback(() => setIsOpen((prev) => !prev), []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{marginInline:"20px"}}>
|
||||||
|
<img id={ID} src={src} alt={props.alt} {...props} style={{ maxWidth:"300px", maxHeight:'50px'}}/>
|
||||||
|
<Tooltip
|
||||||
|
placement="left"
|
||||||
|
isOpen={isOpen}
|
||||||
|
target={ID}
|
||||||
|
toggle={toggleTooltip}
|
||||||
|
style={tooltipStyle}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
style={{ maxWidth:"300px", maxHeight:'150px'}}
|
||||||
|
src={src}
|
||||||
|
alt={props.alt}
|
||||||
|
{...imgPreviewProps}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HovarableImage;
|
||||||
|
|
||||||
34
src/Components/Ui/ImagePreview.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const ImagePreview = ({ preview, height = 200 }:any) => {
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
border: "1px solid lightgray",
|
||||||
|
height: `${height}px`,
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{preview ? (
|
||||||
|
<img
|
||||||
|
className="p-1"
|
||||||
|
style={{
|
||||||
|
maxWidth: "100%",
|
||||||
|
}}
|
||||||
|
height={height}
|
||||||
|
src={preview}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div>{t("image_preview")}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ImagePreview;
|
||||||
14
src/Components/Ui/LoadingButton.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Button, Spinner } from "reactstrap";
|
||||||
|
|
||||||
|
const LoadingButton = ({ handleClickLoadingButton,isLoading = false, ...props }:any) => {
|
||||||
|
return (
|
||||||
|
<Button onClick={handleClickLoadingButton} type="button" className="btn-primary" disabled={isLoading} {...props}>
|
||||||
|
{isLoading ? <Spinner style={{ marginRight: "10px" }} size="sm" /> : null}
|
||||||
|
<span >{props.children}</span>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { LoadingButton };
|
||||||
|
|
||||||
10
src/Components/Ui/LoadingSpinner.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { Spin } from 'antd'
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
function LoadingSpinner() {
|
||||||
|
return (
|
||||||
|
<Spin className='primary' />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LoadingSpinner
|
||||||
57
src/Components/Ui/PasswordField/PasswordField.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import React, { FC, useState } from "react";
|
||||||
|
import { ErrorMessage, useField, Field } from "formik";
|
||||||
|
import { FormGroup } from "reactstrap";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import { Eye, EyeOff } from "react-feather";
|
||||||
|
import "./index.css";
|
||||||
|
|
||||||
|
interface PasswordFieldProps {
|
||||||
|
name: string;
|
||||||
|
label?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PasswordField: FC<PasswordFieldProps> = ({ name, label, ...props }) => {
|
||||||
|
const [field, meta] = useField({ name, ...props });
|
||||||
|
const [showPassword, setShowPassword] = useState<boolean>(false);
|
||||||
|
const EyeIcon = showPassword ? Eye : EyeOff;
|
||||||
|
|
||||||
|
const toggleShow = () => {
|
||||||
|
setShowPassword((prev) => !prev);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{label && <label className="change_password_body" htmlFor={name}>{label}</label>}
|
||||||
|
<FormGroup className={"position-relative has-icon-left"}>
|
||||||
|
<Field
|
||||||
|
className={
|
||||||
|
|
||||||
|
"form-control " + (meta.touched && meta.error ? "is-invalid" : "")
|
||||||
|
}
|
||||||
|
name={name}
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
autoComplete="new-password"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
<div className="form-control-position">
|
||||||
|
<EyeIcon
|
||||||
|
fontSize={12}
|
||||||
|
onClick={toggleShow}
|
||||||
|
className="cursor-pointer change_password_body"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ErrorMessage
|
||||||
|
className="field-error text-danger"
|
||||||
|
component="div"
|
||||||
|
name={field.name}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
PasswordField.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export { PasswordField };
|
||||||
10
src/Components/Ui/PasswordField/index.css
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
.back-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 0;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn {
|
||||||
|
padding-left: 0.8rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
24
src/Components/Ui/ProgressBar.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Progress } from "reactstrap";
|
||||||
|
|
||||||
|
const ProgressBar = ({ value,isLoading, isSuccess, isError }:any) => {
|
||||||
|
|
||||||
|
|
||||||
|
let color = "";
|
||||||
|
if (!isLoading && isSuccess) {
|
||||||
|
color = "success";
|
||||||
|
}
|
||||||
|
if (!isLoading && isError) {
|
||||||
|
color = "danger";
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="my-1">
|
||||||
|
<Progress color={color} style={{ height: "1rem" }} className=" mt-4" value={value}>
|
||||||
|
{value}%
|
||||||
|
</Progress>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default ProgressBar;
|
||||||
29
src/Components/Ui/SelectField.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import React from "react";
|
||||||
|
import Select from "react-select";
|
||||||
|
import { ValidatedField } from "./ValidatedField";
|
||||||
|
import { useFormikContext } from "formik";
|
||||||
|
|
||||||
|
const SelectField = ({ label, name, options, ...props }:any) => {
|
||||||
|
const formik = useFormikContext<any>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ValidatedField
|
||||||
|
CustomField={Select}
|
||||||
|
label={label}
|
||||||
|
name={name}
|
||||||
|
className="React"
|
||||||
|
classNamePrefix="select"
|
||||||
|
options={options || []}
|
||||||
|
value={options?.find((opt:any) => opt.value === formik?.values[name]) || ""}
|
||||||
|
onChange={(opt:any) => formik.setFieldValue(name, opt.value)}
|
||||||
|
onBlur={() => formik.setFieldTouched(name)}
|
||||||
|
key={name}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default SelectField;
|
||||||
|
|
||||||
|
|
||||||
59
src/Components/Ui/StaticsCard/StaticCard.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Card, CardBody } from "reactstrap";
|
||||||
|
import Chart from "react-apexcharts";
|
||||||
|
import { ChartTypeEnum } from "../../../enums/ChartTypeEnum";
|
||||||
|
import { history } from "../../../ProviderContainer";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
interface StatisticsCardProps {
|
||||||
|
className?: string;
|
||||||
|
iconLeft?: boolean;
|
||||||
|
icon: React.ReactNode;
|
||||||
|
count?: string;
|
||||||
|
CardTitle?: string;
|
||||||
|
CardContent?: string;
|
||||||
|
height?: number;
|
||||||
|
pathWhenClick :string ;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const StatisticsCard = (props :StatisticsCardProps) => {
|
||||||
|
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const {
|
||||||
|
className,
|
||||||
|
iconLeft = false ,
|
||||||
|
icon,
|
||||||
|
count,
|
||||||
|
CardTitle,
|
||||||
|
CardContent,
|
||||||
|
pathWhenClick,
|
||||||
|
height,
|
||||||
|
...rest
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card {...rest} onClick={()=>navigate(pathWhenClick , {replace:true})}>
|
||||||
|
<CardBody
|
||||||
|
className={`${
|
||||||
|
className ? className : "stats-card-body"
|
||||||
|
} d-flex justify-content-center flex-column text-center pb-2 pt-2 primary `}
|
||||||
|
>
|
||||||
|
<div className="icon-section">
|
||||||
|
<div
|
||||||
|
className={`avatar avatar-stats p-50 m-0 ${ "bg-rgba-primary"}`}
|
||||||
|
>
|
||||||
|
<p className="mb-0 text-bold-700">{CardTitle}</p>
|
||||||
|
<div className="avatar-content ">{icon}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={"title-section " + (iconLeft ? "ml-2" : "")}>
|
||||||
|
<h2 className="text-bold-600 mb-25 ">{count}</h2>
|
||||||
|
<p className="mb-2 Content">{CardContent}</p>
|
||||||
|
</div>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StatisticsCard;
|
||||||
17
src/Components/Ui/StatusBadge.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Badge } from "reactstrap";
|
||||||
|
|
||||||
|
const StatusBadge = ({ status }:any) => {
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Badge color={status ? "success" : "danger"}>
|
||||||
|
{status ? t("active") : t("inacticve")}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default StatusBadge;
|
||||||
38
src/Components/Ui/TableActions.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import React , {ReactNode} from "react";
|
||||||
|
import confirmAlert from "./Alert";
|
||||||
|
import { FaEdit, FaTrash } from "react-icons/fa";
|
||||||
|
|
||||||
|
type TableActionsProps = {
|
||||||
|
onDelete: () => any;
|
||||||
|
onEdit: () => void;
|
||||||
|
showEdit?: boolean;
|
||||||
|
showDelete?: boolean;
|
||||||
|
children?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const TableActions = ({ onDelete , onEdit,showEdit=true,showDelete=true, children }:TableActionsProps) => {
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="data-list-action TableActions">
|
||||||
|
{showEdit && <FaEdit onClick={onEdit} className="cursor-pointer m-1" size={20} />}
|
||||||
|
{showDelete && (
|
||||||
|
<FaTrash
|
||||||
|
onClick={() =>
|
||||||
|
confirmAlert({
|
||||||
|
onConfirm: () => {
|
||||||
|
onDelete();
|
||||||
|
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="cursor-pointer"
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default TableActions;
|
||||||
30
src/Components/Ui/ThreeSwitchState/TripleSwitch.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import React from 'react';
|
||||||
|
import type { RadioChangeEvent } from 'antd';
|
||||||
|
import { Radio } from 'antd';
|
||||||
|
|
||||||
|
const onChange = (e: RadioChangeEvent) => {
|
||||||
|
};
|
||||||
|
|
||||||
|
const App: React.FC = () => (
|
||||||
|
<>
|
||||||
|
|
||||||
|
|
||||||
|
<Radio.Group onChange={onChange} defaultValue="a" style={{ marginTop: 16 }}>
|
||||||
|
<Radio.Button value="a">Hangzhou</Radio.Button>
|
||||||
|
<Radio.Button value="b" disabled>
|
||||||
|
Shanghai
|
||||||
|
</Radio.Button>
|
||||||
|
<Radio.Button value="c">Beijing</Radio.Button>
|
||||||
|
<Radio.Button value="d">Chengdu</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
|
||||||
|
<Radio.Group disabled onChange={onChange} defaultValue="a" style={{ marginTop: 16 }}>
|
||||||
|
<Radio.Button value="a">Hangzhou</Radio.Button>
|
||||||
|
<Radio.Button value="b">Shanghai</Radio.Button>
|
||||||
|
<Radio.Button value="c">Beijing</Radio.Button>
|
||||||
|
<Radio.Button value="d">Chengdu</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default App;
|
||||||
39
src/Components/Ui/ToggleStatus.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import React from "react";
|
||||||
|
import Toggle from "react-toggle";
|
||||||
|
import "react-toggle/style.css";
|
||||||
|
import StatusBadge from "./StatusBadge";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
interface ToggleStatusProps {
|
||||||
|
|
||||||
|
}
|
||||||
|
export const ToggleStatus = ({ object, toggleMutation, ...props }:any) => {
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
|
const handleSwitch = () => {
|
||||||
|
toggleMutation.mutate({
|
||||||
|
id: object.id,
|
||||||
|
new_status: !object.is_active,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="p-0">
|
||||||
|
<p
|
||||||
|
className="mb-0 p-0"
|
||||||
|
style={{ whiteSpace: "nowrap", textAlign: "center" }}
|
||||||
|
>
|
||||||
|
{object.is_active ? t("active") : t("inactive")}
|
||||||
|
</p>
|
||||||
|
<Toggle
|
||||||
|
{...props}
|
||||||
|
className="custom-switch"
|
||||||
|
disabled={toggleMutation.isLoading}
|
||||||
|
onChange={handleSwitch}
|
||||||
|
checked={object.is_active}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
64
src/Components/Ui/ValidatedField.tsx
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
import React from "react";
|
||||||
|
import { ErrorMessage, useField, Field } from "formik";
|
||||||
|
import { FormGroup } from "reactstrap";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const ValidatedField = ({
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
CustomField,
|
||||||
|
icon: Icon,
|
||||||
|
optional,
|
||||||
|
labelIcon = null,
|
||||||
|
formProps,
|
||||||
|
isRequired,
|
||||||
|
|
||||||
|
...props
|
||||||
|
}:any) => {
|
||||||
|
|
||||||
|
const [field, meta] = useField({ name, ...props });
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
|
let Wrapper = Field;
|
||||||
|
|
||||||
|
if (CustomField) {
|
||||||
|
Wrapper = CustomField;
|
||||||
|
}
|
||||||
|
const fieldProps = props.type === "file" ? {} : { ...field };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{label && (
|
||||||
|
<label htmlFor={name}>
|
||||||
|
{label} {props.required || isRequired ? "*" : ""} {labelIcon}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
<FormGroup
|
||||||
|
className={Icon ? "position-relative has-icon-left" : ""}
|
||||||
|
{...formProps}
|
||||||
|
>
|
||||||
|
<Wrapper
|
||||||
|
className={
|
||||||
|
"form-control " + (meta.touched && meta.error ? "is-invalid" : "")
|
||||||
|
}
|
||||||
|
{...fieldProps}
|
||||||
|
required={props.isRequired}
|
||||||
|
{...props}
|
||||||
|
|
||||||
|
|
||||||
|
/>
|
||||||
|
{Icon && (
|
||||||
|
<div className="form-control-position">
|
||||||
|
<Icon size={15} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<ErrorMessage name={field.name}>
|
||||||
|
{(msg) => <span className="field-error text-danger">{t(msg)}</span>}
|
||||||
|
</ErrorMessage>
|
||||||
|
</FormGroup>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export { ValidatedField };
|
||||||
17
src/Components/Ui/index.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import Checkbox from './CheckboxesVuexy'
|
||||||
|
import ImagePreview from './ImagePreview'
|
||||||
|
import SelectField from './SelectField'
|
||||||
|
import { useImagePreview } from './useImagePreview'
|
||||||
|
import {ValidatedField} from './ValidatedField'
|
||||||
|
import StatusBadge from './StatusBadge'
|
||||||
|
import HovarableImage from './HovarableImage'
|
||||||
|
|
||||||
|
export {
|
||||||
|
Checkbox,
|
||||||
|
ImagePreview,
|
||||||
|
SelectField,
|
||||||
|
useImagePreview,
|
||||||
|
ValidatedField,
|
||||||
|
StatusBadge,
|
||||||
|
HovarableImage
|
||||||
|
}
|
||||||
57
src/Components/Ui/tables/Actions.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import React , {ReactNode} from "react";
|
||||||
|
import { FaEdit, FaEye, FaTrash } from "react-icons/fa";
|
||||||
|
import CustomConfirmAlert from "../Alert";
|
||||||
|
import { usePageState } from "../../../lib/state mangment/LayoutPagestate";
|
||||||
|
|
||||||
|
type TableActionsProps = {
|
||||||
|
onDelete?: () => any;
|
||||||
|
onEdit?: () => any;
|
||||||
|
onView?:() => any;
|
||||||
|
showView?: boolean;
|
||||||
|
showEdit?: boolean;
|
||||||
|
showDelete?: boolean;
|
||||||
|
children?: ReactNode;
|
||||||
|
objectToEdit:any
|
||||||
|
className?:string
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const TableActions = ({ onDelete=()=>{} , objectToEdit,onEdit=()=>{},onView,showEdit=true,showDelete=true,showView=true,children,className }:TableActionsProps) => {
|
||||||
|
// const TableActions = ({ onDelete=()=>{} , objectToEdit,onEdit=()=>{},onView,showEdit=true,showDelete=true,showView=true,children }:TableActionsProps) => {
|
||||||
|
|
||||||
|
// console.log(objectToEdit);
|
||||||
|
|
||||||
|
const {setObjectToEdit , setIsOpenEditModel} = usePageState()
|
||||||
|
return (
|
||||||
|
<div className={`data-list-action TableActions ${className}`}>
|
||||||
|
{showEdit && <FaEdit onClick={()=>{
|
||||||
|
setObjectToEdit(objectToEdit)
|
||||||
|
setIsOpenEditModel()
|
||||||
|
onEdit()
|
||||||
|
|
||||||
|
}} className="cursor-pointer m-2" size={20} />}
|
||||||
|
{showView && <FaEye onClick={onView} className="cursor-pointer m-2" size={25} />}
|
||||||
|
|
||||||
|
{showDelete && (
|
||||||
|
<FaTrash
|
||||||
|
onClick={() =>
|
||||||
|
CustomConfirmAlert({
|
||||||
|
onConfirm: () => {
|
||||||
|
|
||||||
|
|
||||||
|
onDelete();
|
||||||
|
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="cursor-pointer"
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default TableActions;
|
||||||
40
src/Components/Ui/tables/ConfirmAlert.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import React from "react";
|
||||||
|
import { confirmAlert } from "react-confirm-alert";
|
||||||
|
import SweetAlert from "react-bootstrap-sweetalert";
|
||||||
|
|
||||||
|
interface CustomUIProps {
|
||||||
|
onClose: () => void;
|
||||||
|
options: {
|
||||||
|
title?: string;
|
||||||
|
confirmBtnText?: string;
|
||||||
|
cancelBtnText?: string;
|
||||||
|
onConfirm: () => void;
|
||||||
|
body?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CustomConfirmAlert(options: any) {
|
||||||
|
confirmAlert({
|
||||||
|
customUI: ({ onClose }) => <CustomUI onClose={onClose} options={options} />,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function CustomUI({ onClose, options }: CustomUIProps) {
|
||||||
|
const sweetAlertProps: any = {
|
||||||
|
title: options.title || `DELETE, Are you sure?`,
|
||||||
|
warning: true,
|
||||||
|
show: true,
|
||||||
|
showCancel: true,
|
||||||
|
reverseButtons: true,
|
||||||
|
cancelBtnBsStyle: "danger",
|
||||||
|
confirmBtnText: options.confirmBtnText || "Yes, delete it!",
|
||||||
|
cancelBtnText: options.cancelBtnText || "Cancel",
|
||||||
|
onConfirm: () => {
|
||||||
|
options.onConfirm();
|
||||||
|
onClose();
|
||||||
|
},
|
||||||
|
onCancel: onClose,
|
||||||
|
};
|
||||||
|
|
||||||
|
return <SweetAlert {...sweetAlertProps}>{options.body || "You won't be able to revert this!"}</SweetAlert>;
|
||||||
|
}
|
||||||
24
src/Components/Ui/useImagePreview.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
export const useImagePreview = (defaultValue:any = null) => {
|
||||||
|
const [preview, setPreview] = useState(defaultValue || null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
URL.revokeObjectURL(preview);
|
||||||
|
|
||||||
|
};
|
||||||
|
}, [preview]);
|
||||||
|
|
||||||
|
const handleImageChange = (event:any) => {
|
||||||
|
|
||||||
|
setPreview(URL.createObjectURL(event.target.files[0]));
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
preview,
|
||||||
|
handleImageChange,
|
||||||
|
setPreview,
|
||||||
|
};
|
||||||
|
};
|
||||||
93
src/Components/Utils/Loading/Loading.scss
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
.Loading{
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
width: 200px;
|
||||||
|
height: 60px;
|
||||||
|
position: relative;
|
||||||
|
left: 40%;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--primary);
|
||||||
|
left: 15%;
|
||||||
|
transform-origin: 50%;
|
||||||
|
animation: circle7124 .5s alternate infinite ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes circle7124 {
|
||||||
|
0% {
|
||||||
|
top: 60px;
|
||||||
|
height: 5px;
|
||||||
|
border-radius: 50px 50px 25px 25px;
|
||||||
|
transform: scaleX(1.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
40% {
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
transform: scaleX(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
top: 0%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle:nth-child(2) {
|
||||||
|
left: 45%;
|
||||||
|
animation-delay: .2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle:nth-child(3) {
|
||||||
|
left: auto;
|
||||||
|
right: 15%;
|
||||||
|
animation-delay: .3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadow {
|
||||||
|
width: 20px;
|
||||||
|
height: 4px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: rgba(0,0,0,0.9);
|
||||||
|
position: absolute;
|
||||||
|
top: 62px;
|
||||||
|
transform-origin: 50%;
|
||||||
|
z-index: -1;
|
||||||
|
left: 15%;
|
||||||
|
filter: blur(1px);
|
||||||
|
animation: shadow046 .5s alternate infinite ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shadow046 {
|
||||||
|
0% {
|
||||||
|
transform: scaleX(1.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
40% {
|
||||||
|
transform: scaleX(1);
|
||||||
|
opacity: .7;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: scaleX(.2);
|
||||||
|
opacity: .4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadow:nth-child(4) {
|
||||||
|
left: 45%;
|
||||||
|
animation-delay: .2s
|
||||||
|
}
|
||||||
|
|
||||||
|
.shadow:nth-child(5) {
|
||||||
|
left: auto;
|
||||||
|
right: 15%;
|
||||||
|
animation-delay: .3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
19
src/Components/Utils/Loading/Loading.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import React from 'react'
|
||||||
|
import './Loading.scss'
|
||||||
|
const Loading = () => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="Loading">
|
||||||
|
<div className="wrapper">
|
||||||
|
<div className="circle"></div>
|
||||||
|
<div className="circle"></div>
|
||||||
|
<div className="circle"></div>
|
||||||
|
<div className="shadow"></div>
|
||||||
|
<div className="shadow"></div>
|
||||||
|
<div className="shadow"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Loading
|
||||||
43
src/Components/Utils/SearchBar/SearchBar.scss
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
.SearchBar{
|
||||||
|
.group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
max-width: 190px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0 1rem;
|
||||||
|
padding-left: 2.5rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
outline: none;
|
||||||
|
font-weight: 500;
|
||||||
|
background: var(--primary);
|
||||||
|
color: var(--bg);
|
||||||
|
border: none;
|
||||||
|
box-shadow: 2px 2px 7px 0 var(--primary);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.input::placeholder {
|
||||||
|
color: var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 1rem;
|
||||||
|
fill: var(--bg);
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
14
src/Components/Utils/SearchBar/SearchBar.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import React from 'react'
|
||||||
|
import './SearchBar.scss'
|
||||||
|
const SearchBar = () => {
|
||||||
|
return (
|
||||||
|
<div className='SearchBar'>
|
||||||
|
<div className="group">
|
||||||
|
<svg className="icon" viewBox="0 0 24 24"><g><path d="M21.53 20.47l-3.66-3.66C19.195 15.24 20 13.214 20 11c0-4.97-4.03-9-9-9s-9 4.03-9 9 4.03 9 9 9c2.215 0 4.24-.804 5.808-2.13l3.66 3.66c.147.146.34.22.53.22s.385-.073.53-.22c.295-.293.295-.767.002-1.06zM3.5 11c0-4.135 3.365-7.5 7.5-7.5s7.5 3.365 7.5 7.5-3.365 7.5-7.5 7.5-7.5-3.365-7.5-7.5z"></path></g></svg>
|
||||||
|
<input placeholder="Search" type="search" className="input"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SearchBar
|
||||||
84
src/Components/Utils/Theme.tsx
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
import { Menu, MenuItem, MenuButton } from '@szhsin/react-menu';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { usePageState } from '../../lib/state mangment/LayoutPagestate';
|
||||||
|
import { BsFillMoonStarsFill, BsFillSunFill, BsSunglasses } from 'react-icons/bs';
|
||||||
|
|
||||||
|
|
||||||
|
let What_the_Theme = localStorage.getItem('theme') ?? "light";
|
||||||
|
|
||||||
|
if (What_the_Theme === "dark") {
|
||||||
|
|
||||||
|
document.body.classList.add('dark')}
|
||||||
|
else if (What_the_Theme === "glass") {
|
||||||
|
|
||||||
|
document.body.classList.add('glass')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default function Theme() {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
|
||||||
|
const {setThemChange} = usePageState()
|
||||||
|
|
||||||
|
const changeTheme = (newTheme : any) => {
|
||||||
|
|
||||||
|
|
||||||
|
if(newTheme === "dark"){
|
||||||
|
document.body.classList.remove('glass');
|
||||||
|
document.body.classList.add('dark');localStorage.setItem("theme", "dark");
|
||||||
|
What_the_Theme = "dark"
|
||||||
|
}
|
||||||
|
else if(newTheme === "light"){
|
||||||
|
document.body.classList.remove('glass');
|
||||||
|
document.body.classList.remove('dark');localStorage.setItem("theme", "light");
|
||||||
|
What_the_Theme = "light"
|
||||||
|
|
||||||
|
}
|
||||||
|
else if(newTheme === "glass"){
|
||||||
|
document.body.classList.remove('dark'); document.body.classList.add('glass'); localStorage.setItem("theme", "glass");
|
||||||
|
What_the_Theme = "glass"
|
||||||
|
|
||||||
|
}
|
||||||
|
setThemChange()
|
||||||
|
};
|
||||||
|
/// BsSunglasses BsFillSunFill BsFillMoonStarsFill
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='Theme'>
|
||||||
|
<Menu menuButton={<MenuButton>
|
||||||
|
{(What_the_Theme === "light") ?
|
||||||
|
<>
|
||||||
|
<BsFillSunFill/>
|
||||||
|
{t("light")}
|
||||||
|
</>
|
||||||
|
|
||||||
|
: (What_the_Theme === "dark") ?
|
||||||
|
<>
|
||||||
|
<BsFillMoonStarsFill/>
|
||||||
|
{t("dark")}
|
||||||
|
</>
|
||||||
|
:
|
||||||
|
<>
|
||||||
|
<BsSunglasses/>
|
||||||
|
{t("glass")}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</MenuButton>} transition>
|
||||||
|
<MenuItem onClick={() => changeTheme('light')}>
|
||||||
|
<BsFillSunFill/>
|
||||||
|
{t("light")}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={() => changeTheme('dark')}>
|
||||||
|
<BsFillMoonStarsFill/>
|
||||||
|
{t("dark")} </MenuItem>
|
||||||
|
<MenuItem onClick={() => changeTheme('glass')}>
|
||||||
|
<BsSunglasses/>
|
||||||
|
{t("glass")} </MenuItem>
|
||||||
|
</Menu>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
81
src/Components/Utils/Translate.tsx
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
import { Menu, MenuItem, MenuButton } from '@szhsin/react-menu';
|
||||||
|
import { useTranslation, initReactI18next } from 'react-i18next';
|
||||||
|
import i18n from 'i18next'; // Make sure this import is correct
|
||||||
|
import translationEN from '../../translate/en.json';
|
||||||
|
import translationAR from '../../translate/ar.json';
|
||||||
|
|
||||||
|
i18n.use(initReactI18next).init({
|
||||||
|
resources: {
|
||||||
|
en: {
|
||||||
|
translation: translationEN
|
||||||
|
},
|
||||||
|
ar: {
|
||||||
|
translation: translationAR
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lng: 'en',
|
||||||
|
interpolation: {
|
||||||
|
escapeValue: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let What_the_language = localStorage.getItem('language') ?? "en";
|
||||||
|
|
||||||
|
if (What_the_language === "en") {
|
||||||
|
|
||||||
|
i18n.changeLanguage('en');
|
||||||
|
document.body.setAttribute('dir', 'ltr'); document.body.classList.add('en')}
|
||||||
|
else{
|
||||||
|
i18n.changeLanguage('ar');
|
||||||
|
document.body.setAttribute('dir', 'rtl'); document.body.classList.add('ar');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default function Translate() {
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
|
||||||
|
|
||||||
|
const changeLanguage = (newLanguage : any) => {
|
||||||
|
i18n.changeLanguage(newLanguage);
|
||||||
|
if(newLanguage === "Ar"){
|
||||||
|
i18n.changeLanguage('ar');
|
||||||
|
document.body.setAttribute('dir', 'rtl'); document.body.classList.add('ar');localStorage.setItem("language", "ar");
|
||||||
|
What_the_language = "ar"
|
||||||
|
}
|
||||||
|
else if(newLanguage === "En"){
|
||||||
|
i18n.changeLanguage('en');
|
||||||
|
document.body.setAttribute('dir', 'ltr'); document.body.classList.remove('ar');localStorage.setItem("language", "en");
|
||||||
|
What_the_language = "en"
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='Translate'>
|
||||||
|
<Menu menuButton={<MenuButton>
|
||||||
|
{What_the_language === "ar" ?
|
||||||
|
<>
|
||||||
|
{/* <img alt='' src={`/Layout/Ar.svg`} width={20} height={20} /> */}
|
||||||
|
{t("Arabic")}
|
||||||
|
</>
|
||||||
|
|
||||||
|
:
|
||||||
|
<>
|
||||||
|
{/* <img alt='' src={`/Layout/En.svg`} width={20} height={20} /> */}
|
||||||
|
{t("English")}
|
||||||
|
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</MenuButton>} transition>
|
||||||
|
<MenuItem onClick={() => changeLanguage('Ar')}>
|
||||||
|
{/* <img alt='' src='/Layout/Ar.svg' width={20} height={20} /> */}
|
||||||
|
{t("Arabic")}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={() => changeLanguage('En')}>
|
||||||
|
{/* <img alt='' src='/Layout/En.svg' width={20} height={20} /> */}
|
||||||
|
{t("English")}
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
42
src/Extensions/Editor/HtmlEditor.tsx
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import React, { FC } from "react";
|
||||||
|
import { Editor } from '@tinymce/tinymce-react';
|
||||||
|
import { useFormikContext } from "formik";
|
||||||
|
|
||||||
|
interface HtmlEditorProps {
|
||||||
|
langCode: number;
|
||||||
|
name: string;
|
||||||
|
editorState: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const HtmlEditor: FC<HtmlEditorProps> = ({ langCode, name, editorState, ...props }) => {
|
||||||
|
const formik = useFormikContext();
|
||||||
|
|
||||||
|
const ar: boolean = langCode === 2;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Editor
|
||||||
|
apiKey='6xf0byrgd7m2j28p9dfjittsq884x9j0d3e6dsterqrvtvez'
|
||||||
|
value={editorState}
|
||||||
|
init={{
|
||||||
|
height: 500,
|
||||||
|
menubar: false,
|
||||||
|
directionality: ar ? "rtl" : "ltr",
|
||||||
|
plugins: [
|
||||||
|
'advlist autolink lists link image charmap print preview anchor',
|
||||||
|
'searchreplace visualblocks code fullscreen',
|
||||||
|
'insertdatetime media table paste code help wordcount'
|
||||||
|
],
|
||||||
|
toolbar: 'undo redo | formatselect | ' +
|
||||||
|
'bold italic backcolor | alignleft aligncenter ' +
|
||||||
|
'alignright alignjustify | bullist numlist outdent indent | ' +
|
||||||
|
'removeformat | help',
|
||||||
|
content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }'
|
||||||
|
}}
|
||||||
|
onEditorChange={(newValue, editor) => {
|
||||||
|
formik.setFieldValue(name, newValue)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { HtmlEditor };
|
||||||
43
src/Extensions/Editor/SingleLangEditor.tsx
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import React, { FC } from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import { HtmlEditor } from "./HtmlEditor";
|
||||||
|
import { useFormikContext } from "formik";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
interface SingleLangEditorProps {
|
||||||
|
langCode: number;
|
||||||
|
property: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PROPERTY_TYPES: string[] = [
|
||||||
|
"privacy_description",
|
||||||
|
"conditions_description",
|
||||||
|
"about_us_description",
|
||||||
|
"product_description",
|
||||||
|
"auction_description"
|
||||||
|
];
|
||||||
|
|
||||||
|
const SingleLangEditor: FC<SingleLangEditorProps> = ({ langCode, property }) => {
|
||||||
|
const formik:any = useFormikContext();
|
||||||
|
const {t} = useTranslation();
|
||||||
|
|
||||||
|
const label = `${t(property)} (${t(`lang_${langCode}`)})`;
|
||||||
|
const fieldName = `translated_fields[${langCode}][${property}]`;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h5 className="Information_title">{label}</h5>
|
||||||
|
<HtmlEditor
|
||||||
|
langCode={langCode}
|
||||||
|
name={fieldName}
|
||||||
|
editorState={formik.values.translated_fields[langCode][property]}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
SingleLangEditor.propTypes = {
|
||||||
|
langCode: PropTypes.oneOf([1, 2]).isRequired,
|
||||||
|
property: PropTypes.oneOf(PROPERTY_TYPES).isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SingleLangEditor;
|
||||||
23
src/Extensions/Editor/StatusCard.tsx
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import React, { FC } from "react";
|
||||||
|
import { Card, CardBody, Spinner } from "reactstrap";
|
||||||
|
|
||||||
|
interface StatusCardProps {
|
||||||
|
isLoading: boolean;
|
||||||
|
isError: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StatusCard= ({ isLoading, isError }:StatusCardProps) => {
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardBody
|
||||||
|
className="d-flex align-items-center justify-content-center"
|
||||||
|
style={{ height: "15rem" }}
|
||||||
|
>
|
||||||
|
{isLoading && <Spinner size="lg" color="primary" />}
|
||||||
|
{isError && <h4>Failed !</h4>}
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StatusCard;
|
||||||
65
src/Extensions/FileGenerator/generateAddModal.js
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// Get the file name from the command line arguments
|
||||||
|
const fileName = process.argv[2];
|
||||||
|
|
||||||
|
// Check if a file name is provided
|
||||||
|
if (!fileName) {
|
||||||
|
console.error('Please provide a file name.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let FileContiner = `
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import LayoutModal from '../../Layout/Dashboard/LayoutModal'
|
||||||
|
import Form${capitalizeFirstLetter(fileName)} from './Form${capitalizeFirstLetter(fileName)}'
|
||||||
|
import { useAdd${capitalizeFirstLetter(fileName)} } from '../../api/${(fileName)}'
|
||||||
|
import { getDataToSend, getInitialValues, getValidationSchema } from './formUtil'
|
||||||
|
import { QueryStatusEnum } from '../../config/QueryStatus'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
function Add${capitalizeFirstLetter(fileName)}Modal() {
|
||||||
|
|
||||||
|
|
||||||
|
const [t] = useTranslation()
|
||||||
|
const {mutate , status} = useAdd${capitalizeFirstLetter(fileName)}()
|
||||||
|
const handelSubmit = (values:any )=>{
|
||||||
|
|
||||||
|
const dataToSend = getDataToSend(values)
|
||||||
|
|
||||||
|
mutate(dataToSend)
|
||||||
|
// Submit Value
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<LayoutModal
|
||||||
|
|
||||||
|
isAddModal={true}
|
||||||
|
getInitialValues={getInitialValues()}
|
||||||
|
handleSubmit={handelSubmit}
|
||||||
|
status={status as QueryStatusEnum}
|
||||||
|
headerText={t('Add') +t('${(fileName)}')}
|
||||||
|
|
||||||
|
getValidationSchema={getValidationSchema()}>
|
||||||
|
|
||||||
|
<Form${capitalizeFirstLetter(fileName)} />
|
||||||
|
</LayoutModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Add${capitalizeFirstLetter(fileName)}Modal
|
||||||
|
|
||||||
|
`
|
||||||
|
fs.writeFileSync('src/Pages/'+fileName+"/"+"Add"+ capitalizeFirstLetter(fileName)+"Modal.tsx",
|
||||||
|
FileContiner
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`File "${fileName}" generated successfully.`);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function capitalizeFirstLetter(word) {
|
||||||
|
return (word).charAt(0).toUpperCase() + (word).slice(1);
|
||||||
|
}
|
||||||
40
src/Extensions/FileGenerator/generateApi.js
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const fileName = process.argv[2]
|
||||||
|
|
||||||
|
|
||||||
|
if (!fileName) {
|
||||||
|
console.error('Please provide a file name.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let FileContiner = `
|
||||||
|
import useAddMutation from "./helper/useAddMutation"
|
||||||
|
import useDeleteMutation from "./helper/useDeleteMutation"
|
||||||
|
import useGetQuery from "./helper/useGetQuery"
|
||||||
|
import useUpdateMutation from "./helper/useUpdateMutation"
|
||||||
|
|
||||||
|
const API = {
|
||||||
|
GET: "/api/admin/${fileName}",
|
||||||
|
ADD: "/api/admin/${fileName}/create",
|
||||||
|
UPDATE: "/api/admin/${fileName}/update",
|
||||||
|
DELETE: "/api/admin/${fileName}/delete",
|
||||||
|
};
|
||||||
|
|
||||||
|
const KEY = "${fileName.toUpperCase()}";
|
||||||
|
export const useGet${capitalizeFirstLetter(fileName)} = (params?:any) => useGetQuery(KEY, API.GET, params);
|
||||||
|
export const useAdd${capitalizeFirstLetter(fileName)} = () => useAddMutation(KEY, API.ADD);
|
||||||
|
export const useUpdate${capitalizeFirstLetter(fileName)} = () => useUpdateMutation(KEY, API.UPDATE);
|
||||||
|
export const useDelete${capitalizeFirstLetter(fileName)} = () =>useDeleteMutation(KEY, API.DELETE);
|
||||||
|
`
|
||||||
|
fs.writeFileSync('src/api/'+fileName+".ts",
|
||||||
|
FileContiner
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`File "${fileName}" generated successfully.`);
|
||||||
|
|
||||||
|
|
||||||
|
function capitalizeFirstLetter(word) {
|
||||||
|
return (word).charAt(0).toUpperCase() + (word).slice(1);
|
||||||
|
}
|
||||||
63
src/Extensions/FileGenerator/generateColumn.js
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// Get the file name from the command line arguments
|
||||||
|
const fileName = process.argv[2];
|
||||||
|
|
||||||
|
// Check if a file name is provided
|
||||||
|
if (!fileName) {
|
||||||
|
console.error('Please provide a file name.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let FileContiner = `
|
||||||
|
import React, { useMemo } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Actions from "../../Components/Ui/tables/Actions";
|
||||||
|
|
||||||
|
function fnDelete(props :any ){}
|
||||||
|
|
||||||
|
const useTableColumns :any = () => {
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => [
|
||||||
|
|
||||||
|
{
|
||||||
|
name: t("email"),
|
||||||
|
sortable: false,
|
||||||
|
center: "true",
|
||||||
|
cell: (row:any) => row?.email
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "#",
|
||||||
|
sortable: false,
|
||||||
|
center: "true",
|
||||||
|
cell: (row) => (
|
||||||
|
<Actions
|
||||||
|
|
||||||
|
// importnat to return the row in on Edit Function to store in objectToEdit That Upper in Edit Modal
|
||||||
|
onEdit={() => row}
|
||||||
|
onView={()=>{}}
|
||||||
|
objectToEdit={row}
|
||||||
|
showEdit={true}
|
||||||
|
// showDelete={false}
|
||||||
|
onDelete={() => fnDelete({ id: row.id })}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[t]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useTableColumns;
|
||||||
|
|
||||||
|
`
|
||||||
|
fs.writeFileSync('src/Pages/'+fileName+"/useTableColumns"+".tsx",
|
||||||
|
FileContiner
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`File "${fileName}" generated successfully.`);
|
||||||
30
src/Extensions/FileGenerator/generateDashboard.js
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
const { exec } = require('child_process');
|
||||||
|
|
||||||
|
|
||||||
|
const fileName = process.argv[2]
|
||||||
|
|
||||||
|
|
||||||
|
const CreateApi = `node src/Extensions/FileGenerator/generateApi.js ${fileName}`
|
||||||
|
const CreatePage = `node src/Extensions/FileGenerator/generatePage.js ${fileName}`
|
||||||
|
const CreateColumn = `node src/Extensions/FileGenerator/generateColumn.js ${fileName}`
|
||||||
|
const CreateformUtil= `node src/Extensions/FileGenerator/generateformUtils.js ${fileName}`
|
||||||
|
const CreateAddModal= `node src/Extensions/FileGenerator/generateAddModal.js ${fileName}`
|
||||||
|
const CreateEditModal= `node src/Extensions/FileGenerator/generateEditModal.js ${fileName}`
|
||||||
|
const CreateForm= `node src/Extensions/FileGenerator/generateForm.js ${fileName}`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const RunCommand = async()=>{
|
||||||
|
|
||||||
|
exec(CreatePage)
|
||||||
|
exec(CreateApi)
|
||||||
|
setTimeout(()=>{},100)
|
||||||
|
exec(CreateColumn)
|
||||||
|
exec(CreateformUtil)
|
||||||
|
exec(CreateAddModal)
|
||||||
|
exec(CreateEditModal)
|
||||||
|
exec(CreateForm)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
RunCommand()
|
||||||
58
src/Extensions/FileGenerator/generateEditModal.js
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// Get the file name from the command line arguments
|
||||||
|
const fileName = process.argv[2];
|
||||||
|
|
||||||
|
// Check if a file name is provided
|
||||||
|
if (!fileName) {
|
||||||
|
console.error('Please provide a file name.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let FileContiner = `
|
||||||
|
import React from 'react'
|
||||||
|
import LayoutModal from '../../Layout/Dashboard/LayoutModal'
|
||||||
|
import Form${capitalizeFirstLetter(fileName)} from './Form${capitalizeFirstLetter(fileName)}'
|
||||||
|
import { InitialValues, getDataToSend, getInitialValues, getValidationSchema } from './formUtil'
|
||||||
|
import { usePageState } from '../../lib/state mangment/LayoutPagestate'
|
||||||
|
import { QueryStatusEnum } from '../../config/QueryStatus'
|
||||||
|
import { useUpdate${capitalizeFirstLetter(fileName)} } from '../../api/${capitalizeFirstLetter(fileName)}'
|
||||||
|
|
||||||
|
function Edit${capitalizeFirstLetter(fileName)}Modal() {
|
||||||
|
const {objectToEdit} = usePageState()
|
||||||
|
const {status, mutate} = useUpdate${capitalizeFirstLetter(fileName)};
|
||||||
|
|
||||||
|
const handleSubmit = (value:InitialValues)=>{
|
||||||
|
const dataToSend = getDataToSend({...value})
|
||||||
|
|
||||||
|
mutate(dataToSend)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LayoutModal
|
||||||
|
isAddModal={false}
|
||||||
|
getInitialValues={getInitialValues(objectToEdit)}
|
||||||
|
handleSubmit={() => { }}
|
||||||
|
status={status as QueryStatusEnum}
|
||||||
|
headerText='Edit Modal'
|
||||||
|
getValidationSchema={getValidationSchema(objectToEdit)}>
|
||||||
|
<Form${capitalizeFirstLetter(fileName)} />
|
||||||
|
</LayoutModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Edit${capitalizeFirstLetter(fileName)}Modal
|
||||||
|
`
|
||||||
|
fs.writeFileSync('src/Pages/'+fileName+"/"+"Edit"+ capitalizeFirstLetter(fileName)+"Modal.tsx",
|
||||||
|
FileContiner
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`File "${fileName}" generated successfully.`);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function capitalizeFirstLetter(word) {
|
||||||
|
return (word).charAt(0).toUpperCase() + (word).slice(1);
|
||||||
|
}
|
||||||
58
src/Extensions/FileGenerator/generateForm.js
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// Get the file name from the command line arguments
|
||||||
|
const fileName = process.argv[2];
|
||||||
|
|
||||||
|
// Check if a file name is provided
|
||||||
|
if (!fileName) {
|
||||||
|
console.error('Please provide a file name.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let FileContiner = `
|
||||||
|
import React from 'react'
|
||||||
|
import { Col, Row } from 'reactstrap';
|
||||||
|
import KarimField from '../../Components/Karimalden/KarimField';
|
||||||
|
import { FakeSelectData } from '../../Layout/app/Const';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
|
||||||
|
import { DatePicker } from 'antd';
|
||||||
|
|
||||||
|
function Form${capitalizeFirstLetter(fileName)}() {
|
||||||
|
const formik = useFormikContext<any>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row xs={1} sm={1} md={1} lg={2} xl={2}>
|
||||||
|
<Col>
|
||||||
|
{/* name from form utils */}
|
||||||
|
<KarimField name="name" type="text"label='name' placeholder='placeholder' />
|
||||||
|
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Form${capitalizeFirstLetter(fileName)}
|
||||||
|
|
||||||
|
|
||||||
|
`
|
||||||
|
fs.writeFileSync('src/Pages/'+fileName+'/Form'+ capitalizeFirstLetter(fileName)+".tsx",
|
||||||
|
FileContiner
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`File "${fileName}" generated successfully.`);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function capitalizeFirstLetter(word) {
|
||||||
|
return (word).charAt(0).toUpperCase() + (word).slice(1);
|
||||||
|
}
|
||||||
64
src/Extensions/FileGenerator/generateModel.js
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// Get the file name from the command line arguments
|
||||||
|
const fileName = process.argv[2];
|
||||||
|
|
||||||
|
// Check if a file name is provided
|
||||||
|
if (!fileName) {
|
||||||
|
console.error('Please provide a file name.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let FileContiner = `
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import LayoutModal from '../../Layout/Dashboard/LayoutModal'
|
||||||
|
import Form${capitalizeFirstLetter(fileName)} from './Form${capitalizeFirstLetter(fileName)}'
|
||||||
|
import { useAdd${capitalizeFirstLetter(fileName)} } from '../../api/${(fileName)}'
|
||||||
|
import { getDataToSend, getInitialValues, getValidationSchema } from './formUtil'
|
||||||
|
import { QueryStatusEnum } from '../../config/QueryStatus'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
function Add${capitalizeFirstLetter(fileName)}Modal() {
|
||||||
|
|
||||||
|
|
||||||
|
const [t] = useTranslation()
|
||||||
|
const {mutate , status} = useAdd${capitalizeFirstLetter(fileName)}()
|
||||||
|
const handelSubmit = (values:any )=>{
|
||||||
|
|
||||||
|
const dataToSend = getDataToSend(values)
|
||||||
|
|
||||||
|
mutate(dataToSend)
|
||||||
|
// Submit Value
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<LayoutModal
|
||||||
|
|
||||||
|
isAddModal={true}
|
||||||
|
getInitialValues={getInitialValues()}
|
||||||
|
handleSubmit={handelSubmit}
|
||||||
|
status={status as QueryStatusEnum}
|
||||||
|
headerText={t('Add') +t('${(fileName)}')}
|
||||||
|
getValidationSchema={getValidationSchema()}>
|
||||||
|
|
||||||
|
<Form${capitalizeFirstLetter(fileName)} />
|
||||||
|
</LayoutModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Add${capitalizeFirstLetter(fileName)}Modal
|
||||||
|
|
||||||
|
`
|
||||||
|
fs.writeFileSync('src/Pages/'+fileName+"/"+"Add"+ capitalizeFirstLetter(fileName)+"Modal.tsx",
|
||||||
|
FileContiner
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`File "${fileName}" generated successfully.`);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function capitalizeFirstLetter(word) {
|
||||||
|
return (word).charAt(0).toUpperCase() + (word).slice(1);
|
||||||
|
}
|
||||||
67
src/Extensions/FileGenerator/generatePage.js
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// Get the file name from the command line arguments
|
||||||
|
const fileName = process.argv[2];
|
||||||
|
|
||||||
|
// Check if a file name is provided
|
||||||
|
if (!fileName) {
|
||||||
|
console.error('Please provide a file name.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const folderPath = 'src/Pages/'+fileName;
|
||||||
|
|
||||||
|
|
||||||
|
if (!fs.existsSync(folderPath)) {
|
||||||
|
fs.mkdirSync(folderPath, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
let FileContiner = `
|
||||||
|
import React from 'react'
|
||||||
|
import DashBody from '../../Layout/Dashboard/DashBody'
|
||||||
|
import DashHeader from '../../Layout/Dashboard/DashHeader'
|
||||||
|
import LyTable from '../../Layout/Dashboard/LyTable'
|
||||||
|
import useTableColumns from './useTableColumns'
|
||||||
|
import { useGet${capitalizeFirstLetter(fileName)}} from '../../api/${fileName}'
|
||||||
|
import { QueryStatusEnum } from '../../config/QueryStatus'
|
||||||
|
import Edit${capitalizeFirstLetter(fileName)}Modal from './Edit${capitalizeFirstLetter(fileName)}Modal'
|
||||||
|
import Add${capitalizeFirstLetter(fileName)}Modal from './Add${capitalizeFirstLetter(fileName)}Modal'
|
||||||
|
|
||||||
|
function ${capitalizeFirstLetter(fileName)}Page() {
|
||||||
|
|
||||||
|
const column =useTableColumns()
|
||||||
|
const {data ,status } = useGet${capitalizeFirstLetter(fileName)}()
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
// Pass Status to Layout
|
||||||
|
<DashBody status={status as QueryStatusEnum} >
|
||||||
|
<DashHeader title={'${capitalizeFirstLetter(fileName)}'}></DashHeader>
|
||||||
|
|
||||||
|
<LyTable
|
||||||
|
data={data}
|
||||||
|
isLoading={false}
|
||||||
|
columns={column}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Edit${capitalizeFirstLetter(fileName)}Modal />
|
||||||
|
<Add${capitalizeFirstLetter(fileName)}Modal />
|
||||||
|
|
||||||
|
</DashBody>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ${capitalizeFirstLetter(fileName)}Page
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
fs.writeFileSync('src/Pages/'+fileName+"/"+capitalizeFirstLetter(fileName)+"Page.tsx",
|
||||||
|
FileContiner
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`File "${fileName}" generated successfully.`);
|
||||||
|
|
||||||
|
|
||||||
|
function capitalizeFirstLetter(word) {
|
||||||
|
return (word).charAt(0).toUpperCase() + (word).slice(1);
|
||||||
|
}
|
||||||
73
src/Extensions/FileGenerator/generateformUtils.js
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// Get the file name from the command line arguments
|
||||||
|
const fileName = process.argv[2];
|
||||||
|
|
||||||
|
// Check if a file name is provided
|
||||||
|
if (!fileName) {
|
||||||
|
console.error('Please provide a file name.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let FileContiner = `
|
||||||
|
import * as Yup from "yup";
|
||||||
|
import { buildFormData } from "../../api/helper/buildFormData";
|
||||||
|
|
||||||
|
interface formUtilCommon {
|
||||||
|
name:string,
|
||||||
|
email:string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ObjectToEdit extends formUtilCommon {
|
||||||
|
|
||||||
|
id?:number,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InitialValues extends ObjectToEdit {
|
||||||
|
|
||||||
|
}
|
||||||
|
interface ValidateSchema extends formUtilCommon{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getInitialValues = (objectToEdit: ObjectToEdit | null = null): InitialValues => {
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
id:objectToEdit?.id?? 0 ,
|
||||||
|
name:objectToEdit?.name ?? "",
|
||||||
|
email:objectToEdit?.email?? ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getValidationSchema = (editMode: boolean = false): Yup.Schema<ValidateSchema> => {
|
||||||
|
// validate input
|
||||||
|
return Yup.object().shape({
|
||||||
|
name:Yup.string().required('required'),
|
||||||
|
email:Yup.string().required("required")
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDataToSend = (values: any): FormData => {
|
||||||
|
const data = { ...values };
|
||||||
|
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
buildFormData(formData, data);
|
||||||
|
return formData;
|
||||||
|
};
|
||||||
|
|
||||||
|
`
|
||||||
|
fs.writeFileSync('src/Pages/'+fileName+"/"+"formUtil.ts",
|
||||||
|
FileContiner
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`File "${fileName}" generated successfully.`);
|
||||||
|
|
||||||
|
|
||||||
|
function capitalizeFirstLetter(word) {
|
||||||
|
return (word).charAt(0).toUpperCase() + (word).slice(1);
|
||||||
|
}
|
||||||
45
src/Hooks/WithDrawer.tsx
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import React, { useState, ReactNode } from 'react';
|
||||||
|
import type { DrawerProps } from 'antd';
|
||||||
|
import { Drawer, Space } from 'antd';
|
||||||
|
|
||||||
|
interface WithDrawerProps {
|
||||||
|
button: React.ReactNode;
|
||||||
|
children: ReactNode;
|
||||||
|
title:string;
|
||||||
|
className?:string,
|
||||||
|
iopen?:boolean,
|
||||||
|
setOpen:any
|
||||||
|
}
|
||||||
|
|
||||||
|
const WithDrawer: React.FC<WithDrawerProps> = ({ children,title ="Basic Drawer",className ,setOpen, iopen }) => {
|
||||||
|
const [placement, setPlacement] = useState<DrawerProps['placement']>('right');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Drawer
|
||||||
|
title={title}
|
||||||
|
placement={placement}
|
||||||
|
closable={false}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
open={iopen}
|
||||||
|
key={placement}
|
||||||
|
>
|
||||||
|
<div className={className}>
|
||||||
|
{children}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</Drawer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WithDrawer;
|
||||||
|
|
||||||
|
|
||||||
|
// <WithDrawer
|
||||||
|
// button={<Button type="primary">Open</Button>}
|
||||||
|
// >
|
||||||
|
// {/* Your content goes here */}
|
||||||
|
// </WithDrawer>
|
||||||
16
src/Hooks/useEventListener.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
export const useEventListener = (eventType:any, callback:any, element = window) => {
|
||||||
|
const callbackRef = useRef(callback);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
callbackRef.current = callback;
|
||||||
|
}, [callback]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handler = (e:any) => callbackRef.current(e);
|
||||||
|
element.addEventListener(eventType, handler);
|
||||||
|
|
||||||
|
return () => element.removeEventListener(eventType, handler);
|
||||||
|
}, [eventType, element]);
|
||||||
|
};
|
||||||
16
src/Hooks/useFormField.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { useField, useFormikContext } from 'formik';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Field } from 'formik';
|
||||||
|
|
||||||
|
const useFormField = (name: string, props?: any) => {
|
||||||
|
const [field, meta] = useField({ name, ...props });
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const formik = useFormikContext<any>();
|
||||||
|
const isError = meta.touched && meta.error;
|
||||||
|
|
||||||
|
const errorMsg = meta.error ? t(meta.error.toString()) : '';
|
||||||
|
|
||||||
|
return { Field, field, meta, formik, isError, errorMsg, t };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useFormField;
|
||||||
35
src/Hooks/useHitRequests.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
|
||||||
|
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import useTriggerStatus from './useTriggerStatus'
|
||||||
|
import useAxios from '../api/helper/useAxios'
|
||||||
|
import { IRequestSend } from '../interfaces/axiosInterface'
|
||||||
|
|
||||||
|
|
||||||
|
function useHitRequests() {
|
||||||
|
|
||||||
|
const axios = useAxios()
|
||||||
|
const isOnline = useTriggerStatus()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOnline) {
|
||||||
|
|
||||||
|
const data = JSON.parse(localStorage.getItem("offlineApi") || "[]");
|
||||||
|
// check if offlineApi is empty.
|
||||||
|
if(data.length < 0) {
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
// mapping on offline requests
|
||||||
|
data.map((item: IRequestSend) => {
|
||||||
|
axios[item.method](item.endPoint, item.data,{headers:item?.headers})
|
||||||
|
})
|
||||||
|
// set response array to an empty array to clear response inside it
|
||||||
|
|
||||||
|
localStorage.setItem("offlineApi", "[]");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}, [isOnline])
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useHitRequests
|
||||||
21
src/Hooks/useLoadingState.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
function useLoadingState(initialValue: boolean, duration: number): [boolean, () => void] {
|
||||||
|
const [loading, setLoading] = useState<boolean>(initialValue);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
setLoading(false);
|
||||||
|
}, duration);
|
||||||
|
|
||||||
|
return () => clearTimeout(timeoutId);
|
||||||
|
}, [duration]);
|
||||||
|
|
||||||
|
const resetLoading = () => {
|
||||||
|
setLoading(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return [loading, resetLoading];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useLoadingState;
|
||||||
20
src/Hooks/useNavigateOnSuccess.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import React, { useEffect } from 'react'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
|
|
||||||
|
function useNavigateOnSuccess(isSuccess :boolean , to_path:string , callbackAfterSuccess?:any) {
|
||||||
|
|
||||||
|
const navigate = useNavigate()
|
||||||
|
useEffect(()=>{
|
||||||
|
|
||||||
|
if(isSuccess){
|
||||||
|
if (typeof callbackAfterSuccess === 'function') {
|
||||||
|
callbackAfterSuccess()
|
||||||
|
}
|
||||||
|
navigate(to_path , {replace:true})
|
||||||
|
}
|
||||||
|
},[isSuccess])
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useNavigateOnSuccess
|
||||||
36
src/Hooks/usePagination.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { Pagination } from "antd";
|
||||||
|
import { useLocation, useNavigate, useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
|
export const PaginationBody = ({ data }: any) => {
|
||||||
|
console.log(data , "ibrahimmmmmmmmm");
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
const pagination = location?.search || '';
|
||||||
|
const currentPage = parseInt(new URLSearchParams(location.search).get("page") || "1", 10);
|
||||||
|
const pageSize = parseInt(new URLSearchParams(location.search).get("per_page") || "8", 10);
|
||||||
|
|
||||||
|
const [searchParams] = useSearchParams()
|
||||||
|
const onChange = (page: number, pageSize?: number) => {
|
||||||
|
navigate(`?page=${page}&per_page=${pageSize || data?.per_page}&search=${searchParams.get('search')}`, { replace: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onShowSizeChange = (current: number, pageSize: number) => {
|
||||||
|
navigate(`?page=${current}&per_page=${pageSize}&search=${searchParams.get('search')}`, { replace: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Pagination
|
||||||
|
className='text-center mt-3 paginateStyle'
|
||||||
|
total={data}
|
||||||
|
showTotal={(total: any) => `Total ${total} items`}
|
||||||
|
pageSize={pageSize}
|
||||||
|
pageSizeOptions={[8, 16, 24, 32, 40]}
|
||||||
|
defaultCurrent={currentPage}
|
||||||
|
current={currentPage} // Adding this line will set the current page correctly
|
||||||
|
onChange={onChange}
|
||||||
|
onShowSizeChange={onShowSizeChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
21
src/Hooks/useTriggerStatus.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
const useTriggerStatus = () => {
|
||||||
|
// initialized with the current online status of the browser (navigator.onLine).
|
||||||
|
const [isOnline, setIsOnline] = useState<boolean>(navigator.onLine);
|
||||||
|
|
||||||
|
const updateOnlineStatus = () => {
|
||||||
|
setIsOnline(navigator.onLine);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Adds an event listener that calls updateOnlineStatus when the browser goes online or offline
|
||||||
|
window.addEventListener('online', updateOnlineStatus);
|
||||||
|
window.addEventListener('offline', updateOnlineStatus);
|
||||||
|
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return isOnline;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useTriggerStatus;
|
||||||
18
src/Hooks/useWindowSize.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useEventListener } from "./useEventListener";
|
||||||
|
|
||||||
|
export const useWindowSize = () => {
|
||||||
|
const [windowSize, setWindowSize] = useState({
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEventListener("resize", () => {
|
||||||
|
setWindowSize({
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return windowSize;
|
||||||
|
};
|
||||||
33
src/Layout/Dashboard/AddButton/AddButton.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import React from 'react'
|
||||||
|
import './Add_Button.scss'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { usePageState } from '../../../lib/state mangment/LayoutPagestate'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const AddButton = (props :any) => {
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='Add_Button' {...props} >
|
||||||
|
<button >
|
||||||
|
<span >
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
>
|
||||||
|
<path fill="none" d="M0 0h24v24H0z" />
|
||||||
|
<path fill="currentColor" d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z" />
|
||||||
|
</svg>{" "}
|
||||||
|
{t("Add")}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddButton
|
||||||
40
src/Layout/Dashboard/AddButton/AddButtonLayout.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import React from 'react'
|
||||||
|
import './Add_Button.scss'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { usePageState } from '../../../lib/state mangment/LayoutPagestate'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const AddButtonLayout = ({haveAddModal}:any) => {
|
||||||
|
const { setIsOpenAddModel , setObjectToEdit } = usePageState()
|
||||||
|
const [t] = useTranslation();
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='Add_Button' onClick={()=>{
|
||||||
|
if(haveAddModal){
|
||||||
|
setIsOpenAddModel()
|
||||||
|
}
|
||||||
|
setObjectToEdit(null)
|
||||||
|
|
||||||
|
}}>
|
||||||
|
<button>
|
||||||
|
<span >
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={24}
|
||||||
|
height={24}
|
||||||
|
>
|
||||||
|
<path fill="none" d="M0 0h24v24H0z" />
|
||||||
|
<path fill="currentColor" d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z" />
|
||||||
|
</svg>{" "}
|
||||||
|
{t("Add")}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddButtonLayout
|
||||||
36
src/Layout/Dashboard/AddButton/Add_Button.scss
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
.Add_Button{
|
||||||
|
button {
|
||||||
|
border: 2px solid var(--primary);
|
||||||
|
background-color: var(--primary);
|
||||||
|
border-radius: 0.5vw;
|
||||||
|
padding:.5vw 1vw;
|
||||||
|
font-size: 1vw ;
|
||||||
|
display: flex; justify-content: center; align-items: center;
|
||||||
|
box-shadow: 2px 2px 7px 0 var(--primary);
|
||||||
|
}
|
||||||
|
button span {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--bg);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color:var(--primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 700px) {
|
||||||
|
.Add_Button{
|
||||||
|
button{
|
||||||
|
font-size: 2vw !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 470px) {
|
||||||
|
.Add_Button{
|
||||||
|
button{
|
||||||
|
font-size: 3vw !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/Layout/Dashboard/DashBody.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { Spinner } from "reactstrap"
|
||||||
|
import { QueryStatusEnum } from "../../config/QueryStatus"
|
||||||
|
import LoadingPage from "../app/LoadingPage"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { BsEmojiFrown } from "react-icons/bs";
|
||||||
|
|
||||||
|
|
||||||
|
const DashBody = ({ children , status }: { children: React.ReactNode ,status?:QueryStatusEnum }) => {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
|
||||||
|
// Add You Custom Loadaing Page
|
||||||
|
if(status === QueryStatusEnum.LOADING){
|
||||||
|
|
||||||
|
return <LoadingPage />
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Your Custom Error Page
|
||||||
|
if(status === QueryStatusEnum.ERROR){
|
||||||
|
return (
|
||||||
|
<div className="error_show">
|
||||||
|
<span className="error_icon"><BsEmojiFrown/></span>
|
||||||
|
<span className="error_text">
|
||||||
|
{t("Ops")}...<br/>
|
||||||
|
{t("An Error According")} <br/>
|
||||||
|
{t("Please Try Again Later")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='Page' >
|
||||||
|
{ children }
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DashBody
|
||||||
27
src/Layout/Dashboard/DashHeader.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import React from "react";
|
||||||
|
import AddButtonLayout from "./AddButton/AddButtonLayout";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
interface DashHeaderProp {
|
||||||
|
title: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
showAddButton?: boolean;
|
||||||
|
haveAddModal?:boolean
|
||||||
|
}
|
||||||
|
const DashHeader = ({
|
||||||
|
children,
|
||||||
|
title,
|
||||||
|
haveAddModal= true ,
|
||||||
|
showAddButton = true,
|
||||||
|
}: DashHeaderProp) => {
|
||||||
|
const [t] = useTranslation();
|
||||||
|
return (
|
||||||
|
<div className="Page_Header">
|
||||||
|
<h3>{t(`${title}`)}</h3>
|
||||||
|
{children}
|
||||||
|
{showAddButton && <AddButtonLayout haveAddModal={haveAddModal}/>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DashHeader;
|
||||||
77
src/Layout/Dashboard/LayoutModal.tsx
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { Form, Formik } from 'formik'
|
||||||
|
import React, { useEffect } from 'react'
|
||||||
|
import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap'
|
||||||
|
import { usePageState } from '../../lib/state mangment/LayoutPagestate'
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { LoadingButton } from '../../Components/Ui/LoadingButton';
|
||||||
|
import { QueryStatusEnum } from '../../config/QueryStatus';
|
||||||
|
import useTriggerStatus from '../../Hooks/useTriggerStatus';
|
||||||
|
|
||||||
|
interface LayoutModalProps {
|
||||||
|
isAddModal: boolean;
|
||||||
|
headerText: string;
|
||||||
|
handleSubmit: (values: any) => void;
|
||||||
|
getInitialValues: any;
|
||||||
|
getValidationSchema: any;
|
||||||
|
children: React.ReactNode;
|
||||||
|
status?: QueryStatusEnum
|
||||||
|
}
|
||||||
|
function LayoutModal({ isAddModal, headerText, handleSubmit = () => { }, getInitialValues, getValidationSchema, status, children }: LayoutModalProps) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const { isOpenAddModel, setIsOpenAddModel, setIsOpenEditModel, isOpenEditModel, objectToEdit, CloseAllModal } = usePageState(state => state)
|
||||||
|
const isOnline = useTriggerStatus()
|
||||||
|
const [t] = useTranslation()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (status === QueryStatusEnum.SUCCESS) {
|
||||||
|
CloseAllModal()
|
||||||
|
}
|
||||||
|
}, [status, CloseAllModal])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal centered isOpen={isAddModal ? isOpenAddModel : isOpenEditModel} size="lg" >
|
||||||
|
<ModalHeader className='ModalHeader' toggle={() => isAddModal ? setIsOpenAddModel() : setIsOpenEditModel()} >
|
||||||
|
{t(headerText)}
|
||||||
|
</ModalHeader>
|
||||||
|
{
|
||||||
|
|
||||||
|
((objectToEdit != null && isOpenEditModel) || isOpenAddModel) &&
|
||||||
|
<Formik
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
initialValues={getInitialValues}
|
||||||
|
validationSchema={getValidationSchema}
|
||||||
|
>
|
||||||
|
|
||||||
|
{(formik) => (
|
||||||
|
<Form>
|
||||||
|
<ModalBody>
|
||||||
|
{children}
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
disabled={status === QueryStatusEnum.LOADING}
|
||||||
|
onClick={() => isAddModal ? setIsOpenAddModel() : setIsOpenEditModel()}
|
||||||
|
color="danger"
|
||||||
|
>
|
||||||
|
{t("cancel")}
|
||||||
|
</Button>
|
||||||
|
<LoadingButton
|
||||||
|
type="submit"
|
||||||
|
color="primary"
|
||||||
|
isLoading={status === QueryStatusEnum.LOADING}
|
||||||
|
>
|
||||||
|
{t(isAddModal ? "Add" :"edit")}
|
||||||
|
</LoadingButton>
|
||||||
|
</ModalFooter>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
}
|
||||||
|
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LayoutModal
|
||||||
40
src/Layout/Dashboard/LyTable.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
|
||||||
|
import DataTable from 'react-data-table-component';
|
||||||
|
import { Card, CardBody, Spinner } from 'reactstrap';
|
||||||
|
import { PaginationBody } from '../../Hooks/usePagination';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
const LyTable = (props?: any) => {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='LayoutBody'>
|
||||||
|
<Card>
|
||||||
|
<CardBody>
|
||||||
|
<DataTable
|
||||||
|
columns={props?.column}
|
||||||
|
data={props?.data}
|
||||||
|
progressPending={props?.isLoading}
|
||||||
|
noDataComponent={<h6 className="my-4">{t("no_records")}</h6>}
|
||||||
|
noHeader
|
||||||
|
pagination
|
||||||
|
progressComponent={<Spinner />}
|
||||||
|
|
||||||
|
{...(props.is_pagination && {
|
||||||
|
paginationServer: true,
|
||||||
|
paginationComponent: () => <PaginationBody data={props?.total} />
|
||||||
|
})}
|
||||||
|
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default LyTable
|
||||||
36
src/Layout/Dashboard/PageStructure.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import React, { FC } from "react";
|
||||||
|
import StatusCard from "../../Extensions/Editor/StatusCard";
|
||||||
|
import { Card, CardHeader, CardBody, CardTitle } from "reactstrap";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
interface PageStructureProps {
|
||||||
|
title?: string;
|
||||||
|
isLoading: boolean;
|
||||||
|
isError: boolean;
|
||||||
|
data?: any;
|
||||||
|
children:any
|
||||||
|
}
|
||||||
|
|
||||||
|
const PageStructure: FC<PageStructureProps> = ({
|
||||||
|
title,
|
||||||
|
isLoading,
|
||||||
|
isError,
|
||||||
|
data,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const {t} = useTranslation();
|
||||||
|
|
||||||
|
if (!data) return <StatusCard isLoading={isLoading} isError={isError} />;
|
||||||
|
return (
|
||||||
|
<Card className="Information_title">
|
||||||
|
{title && (
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>{t(title)}</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
)}
|
||||||
|
<CardBody>{children}</CardBody>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PageStructure;
|
||||||
76
src/Layout/Dashboard/ViewPage.tsx
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Card, CardHeader, CardTitle, CardBody, Button } from "reactstrap";
|
||||||
|
import { Formik, Form } from "formik";
|
||||||
|
import { LoadingButton } from "../../Components/Ui/LoadingButton";
|
||||||
|
import ProgressBar from "../../Components/Ui/ProgressBar";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { usePageState } from "../../lib/state mangment/LayoutPagestate";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
type TViewPage ={
|
||||||
|
children: React.ReactNode,
|
||||||
|
getInitialValues:any,
|
||||||
|
getValidationSchema:any,
|
||||||
|
getDataToSend:any,
|
||||||
|
handleSubmit:any,
|
||||||
|
BarStatus:any,
|
||||||
|
showProgressBar?:boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ViewPage: React.FC<TViewPage>= ({children,getInitialValues, getValidationSchema,handleSubmit,BarStatus,showProgressBar = true})=> {
|
||||||
|
|
||||||
|
const {objectToEdit} = usePageState()
|
||||||
|
const {t} = useTranslation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
// console.log(BarStatus);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="ViewTapPage">
|
||||||
|
<CardHeader className="CardHeader" >
|
||||||
|
<CardTitle className="View_information">
|
||||||
|
{t("View_information")}
|
||||||
|
</CardTitle>
|
||||||
|
<Button onClick={() => { navigate(-1);}}> {t("back")} </Button>
|
||||||
|
</CardHeader>
|
||||||
|
<CardBody>
|
||||||
|
{
|
||||||
|
<Formik
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
initialValues={getInitialValues(objectToEdit)}
|
||||||
|
validationSchema={getValidationSchema()}
|
||||||
|
>
|
||||||
|
{(formik) => (
|
||||||
|
<Form>
|
||||||
|
{/* <HeadTabs tabs={tabs} /> */}
|
||||||
|
{children}
|
||||||
|
{showProgressBar &&
|
||||||
|
<>
|
||||||
|
<ProgressBar
|
||||||
|
value={BarStatus?.value}
|
||||||
|
isLoading={BarStatus?.isLoading}
|
||||||
|
isError={BarStatus?.isError}
|
||||||
|
isSuccess={BarStatus?.isSuccess}
|
||||||
|
/>
|
||||||
|
<div className="d-flex mt-4 justify-content-center align-items-center">
|
||||||
|
<LoadingButton
|
||||||
|
type="submit"
|
||||||
|
color="primary"
|
||||||
|
isLoading={BarStatus?.isLoading}
|
||||||
|
>
|
||||||
|
{t("save")}
|
||||||
|
</LoadingButton>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
}
|
||||||
|
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default ViewPage;
|
||||||
18
src/Layout/Dashboard/useCloseModal.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import React, { useEffect } from 'react'
|
||||||
|
import { usePageState } from '../../lib/state mangment/LayoutPagestate'
|
||||||
|
|
||||||
|
function useCloseModal(statusClose:any) {
|
||||||
|
|
||||||
|
const {CloseAllModal} = usePageState()
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
|
||||||
|
if(statusClose){
|
||||||
|
CloseAllModal()
|
||||||
|
}
|
||||||
|
},[statusClose , CloseAllModal])
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useCloseModal
|
||||||
48
src/Layout/app/Const.tsx
Normal file
41
src/Layout/app/Etaxi.tsx
Normal file
11
src/Layout/app/Export.tsx
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import DashHeader from '../../Layout/Dashboard/DashHeader';
|
||||||
|
import LyTable from '../Dashboard/LyTable';
|
||||||
|
import LayoutModal from '../Dashboard/LayoutModal'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export {
|
||||||
|
DashHeader,
|
||||||
|
LyTable,
|
||||||
|
LayoutModal
|
||||||
|
}
|
||||||
76
src/Layout/app/Header.tsx
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { UserImageURL } from './Const'
|
||||||
|
import Translate from '../../Components/Utils/Translate'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Menu, MenuItem, MenuButton } from '@szhsin/react-menu';
|
||||||
|
import '@szhsin/react-menu/dist/index.css';
|
||||||
|
import '@szhsin/react-menu/dist/transitions/slide.css';
|
||||||
|
import Theme from '../../Components/Utils/Theme';
|
||||||
|
import { TOKEN_KEY } from '../../config/AppKey';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import useAuthState from '../../lib/state mangment/AuthState';
|
||||||
|
import { GiHamburgerMenu } from 'react-icons/gi';
|
||||||
|
import WithDrawer from './WithDrawer';
|
||||||
|
import SideBar from './SideBar';
|
||||||
|
|
||||||
|
type TUserData =
|
||||||
|
{
|
||||||
|
username: string | null,
|
||||||
|
role: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const Header = () => {
|
||||||
|
|
||||||
|
|
||||||
|
const [t] = useTranslation();
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
const { logout , user} = useAuthState()
|
||||||
|
const handelClick = () => {
|
||||||
|
logout()
|
||||||
|
navigate('/auth', { replace: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='Header'>
|
||||||
|
<div className='Header_Left'>
|
||||||
|
<WithDrawer
|
||||||
|
title="Cart Item"
|
||||||
|
button={
|
||||||
|
<div className="Cart_Icon">
|
||||||
|
<GiHamburgerMenu />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SideBar/>
|
||||||
|
</WithDrawer>
|
||||||
|
</div>
|
||||||
|
<div className='Header_Right'>
|
||||||
|
<Theme />
|
||||||
|
<Translate />
|
||||||
|
<Menu menuButton={<MenuButton>
|
||||||
|
|
||||||
|
<div className='User_Pro'>
|
||||||
|
<div className='User_info'>
|
||||||
|
<h6>{user?.name}</h6>
|
||||||
|
<p> {user?.role_type} </p>
|
||||||
|
</div>
|
||||||
|
<img className='UNK_User' src={UserImageURL} alt='' width={40} height={40} />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</MenuButton>} transition>
|
||||||
|
<MenuItem onClick={handelClick}>{t("Log Out")}</MenuItem>
|
||||||
|
|
||||||
|
</Menu>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Header
|
||||||
39
src/Layout/app/Layout.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import React, { useEffect } from 'react'
|
||||||
|
import SideBar from './SideBar'
|
||||||
|
import Header from './Header'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
import useAuthState from '../../lib/state mangment/AuthState'
|
||||||
|
|
||||||
|
const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
|
||||||
|
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const { isAuthenticated } = useAuthState()
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
|
||||||
|
navigate('/auth', { replace: true })
|
||||||
|
}
|
||||||
|
}, [navigate])
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="DashboardLayout">
|
||||||
|
<div className='DashboardLayout_Cover' >
|
||||||
|
<div className='out_Sidebar'>
|
||||||
|
<SideBar />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className={`DashboardLayout_Body`} id='DashboardLayout_Body'>
|
||||||
|
<Header />
|
||||||
|
<div className='Layout_Children'>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Layout
|
||||||
12
src/Layout/app/LoadingPage.tsx
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import React from 'react'
|
||||||
|
import LoadingSpinner from '../../Components/Ui/LoadingSpinner'
|
||||||
|
|
||||||
|
function LoadingPage() {
|
||||||
|
return (
|
||||||
|
<div style={{height:"80vh", width:"100%" , display:"flex" , justifyContent:"center" , alignItems:"center"}}>
|
||||||
|
<LoadingSpinner/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LoadingPage
|
||||||
17
src/Layout/app/NotFoundPage.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
|
function NotFoundPage() {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
return (
|
||||||
|
<div className='not_foound_page'>
|
||||||
|
<div className='container-not-found'>
|
||||||
|
|
||||||
|
<p>404 <h6>|</h6>This page could not be found</p>
|
||||||
|
<div> <button onClick={()=>navigate('/' , {replace:true})}>Home</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NotFoundPage
|
||||||
103
src/Layout/app/SideBar.tsx
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { FaAngleDown, FaAngleRight } from 'react-icons/fa';
|
||||||
|
import { GiHamburgerMenu } from 'react-icons/gi';
|
||||||
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
|
import { RoutesLinks } from '../../Routes';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useWindowSize } from '../../Hooks/useWindowSize';
|
||||||
|
import Etaxi from './Etaxi';
|
||||||
|
|
||||||
|
interface SidebarProps {}
|
||||||
|
|
||||||
|
const SideBar: React.FC<SidebarProps> = () => {
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
|
const [isOpenSide, setIsOpenSide] = useState<boolean>(false);
|
||||||
|
const [openDropdown, setOpenDropdown] = useState<number | null>(null);
|
||||||
|
const [t] = useTranslation();
|
||||||
|
const windowSize = useWindowSize()
|
||||||
|
|
||||||
|
const handleHamburgerMenu = () => {
|
||||||
|
setIsOpenSide(true);
|
||||||
|
document.getElementById('DashboardLayout_Body')?.classList.add('DashboardLayout_Body_Open');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleImg = () => {
|
||||||
|
setIsOpenSide(false);
|
||||||
|
document.getElementById('DashboardLayout_Body')?.classList.remove('DashboardLayout_Body_Open');
|
||||||
|
setOpenDropdown(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDropdown = (index: number) => {
|
||||||
|
setOpenDropdown((prev) => (prev === index ? null : index));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={isOpenSide ? 'SideBar SideBar_Open' : 'SideBar noOpen'}>
|
||||||
|
<div className='SideBar_Top' style={{display:"flex" , justifyContent:"space-around"}}>
|
||||||
|
<div onClick={handleImg} >
|
||||||
|
{/* Just put your src down there */}
|
||||||
|
<img src="/Layout/MisbarLogo.png" width={isOpenSide ? 50 :80} alt="" className='' />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className='HamburgerMenu' onClick={handleHamburgerMenu}>
|
||||||
|
<GiHamburgerMenu />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='RoutesLinks'>
|
||||||
|
{RoutesLinks?.map((item: any, index: number) => {
|
||||||
|
const isActive = pathname === item?.href;
|
||||||
|
const isDropdownOpen = openDropdown === index;
|
||||||
|
|
||||||
|
if (item?.hidden) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item?.href ) {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to={item?.href}
|
||||||
|
className={isActive ? 'SideBar_Link Active_SideBar_Link' : 'SideBar_Link'}
|
||||||
|
key={index}
|
||||||
|
>
|
||||||
|
{React.cloneElement(item.icon, { size: 30 })}
|
||||||
|
<div className='Link_Text'>{t(`${item?.name}`)}</div>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<React.Fragment key={index}>
|
||||||
|
<div
|
||||||
|
onClick={() => handleDropdown(index)}
|
||||||
|
className={
|
||||||
|
isDropdownOpen
|
||||||
|
? 'SideBar_Link DropDown DropDown_SideBar_Link Open'
|
||||||
|
: 'SideBar_Link DropDown'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{React.cloneElement(item.icon, { size: 30 })}
|
||||||
|
<div className='DropDown_Text'>{t(`${item?.name}`)}</div>
|
||||||
|
<div className='DropDown_Svg'>{isDropdownOpen ? <FaAngleDown /> : <FaAngleRight />}</div>
|
||||||
|
</div>
|
||||||
|
{isDropdownOpen &&
|
||||||
|
item?.children?.map((child: any, childIndex: number) => {
|
||||||
|
if (child?.href) {
|
||||||
|
return (
|
||||||
|
<Link to={child?.href} className={'SideBar_Link'} key={childIndex}>
|
||||||
|
{React.cloneElement(child.icon, { size: 30 })}
|
||||||
|
<div className='Link_Text'>{t(`${child?.name}`)}</div>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SideBar;
|
||||||