LFFE-10 merging from develop on feature
This commit is contained in:
81
.gitlab-ci.yml
Normal file
81
.gitlab-ci.yml
Normal file
@@ -0,0 +1,81 @@
|
||||
image: node:latest
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- node_modules
|
||||
|
||||
stages:
|
||||
- lint
|
||||
- test
|
||||
- test:build
|
||||
- deploy
|
||||
|
||||
merge:test:lint:
|
||||
stage: lint
|
||||
script:
|
||||
- npm install
|
||||
- cp example.env.local .env.local
|
||||
- cp example.env.local .env.test
|
||||
- npm run lint
|
||||
only:
|
||||
- merge_requests
|
||||
|
||||
merge:test:jest:
|
||||
stage: test
|
||||
script:
|
||||
- npm install
|
||||
- cp example.env.local .env.local
|
||||
- cp example.env.local .env.test
|
||||
- npm run test
|
||||
only:
|
||||
- merge_requests
|
||||
|
||||
#merge:test:build:
|
||||
# stage: test:build
|
||||
# script:
|
||||
# - npm install
|
||||
# - cp example.env.local .env.local
|
||||
# - cp example.env.local .env.test
|
||||
# - npm run build
|
||||
# only:
|
||||
# - merge_requests
|
||||
|
||||
test:lint:
|
||||
stage: lint
|
||||
script:
|
||||
- npm install
|
||||
- cp example.env.local .env.local
|
||||
- cp example.env.local .env.test
|
||||
- npm run lint
|
||||
only:
|
||||
- develop
|
||||
- main
|
||||
|
||||
test:jest:
|
||||
stage: test
|
||||
script:
|
||||
- npm install
|
||||
- cp example.env.local .env.local
|
||||
- cp example.env.local .env.test
|
||||
- npm run test
|
||||
only:
|
||||
- develop
|
||||
- main
|
||||
|
||||
#test:build:
|
||||
# stage: test:build
|
||||
# script:
|
||||
# - npm install
|
||||
# - cp example.env.local .env.local
|
||||
# - cp example.env.local .env.test
|
||||
# - npm run build
|
||||
# only:
|
||||
# - develop
|
||||
# - main
|
||||
|
||||
deploy:
|
||||
stage: deploy
|
||||
script:
|
||||
- echo "Run deploy project"
|
||||
only:
|
||||
- main
|
||||
16
cypress.config.js
Normal file
16
cypress.config.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const { defineConfig } = require("cypress");
|
||||
|
||||
module.exports = defineConfig({
|
||||
e2e: {
|
||||
setupNodeEvents(on, config) {
|
||||
// implement node event listeners here
|
||||
},
|
||||
},
|
||||
|
||||
component: {
|
||||
devServer: {
|
||||
framework: "next",
|
||||
bundler: "webpack",
|
||||
},
|
||||
},
|
||||
});
|
||||
25
cypress/support/commands.js
Normal file
25
cypress/support/commands.js
Normal file
@@ -0,0 +1,25 @@
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
14
cypress/support/component-index.html
Normal file
14
cypress/support/component-index.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Components App</title>
|
||||
<!-- Used by Next.js to inject CSS. -->
|
||||
<div id="__next_css__DO_NOT_USE__"></div>
|
||||
</head>
|
||||
<body>
|
||||
<div data-cy-root></div>
|
||||
</body>
|
||||
</html>
|
||||
27
cypress/support/component.js
Normal file
27
cypress/support/component.js
Normal file
@@ -0,0 +1,27 @@
|
||||
// ***********************************************************
|
||||
// This example support/component.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
|
||||
import { mount } from 'cypress/react18'
|
||||
|
||||
Cypress.Commands.add('mount', mount)
|
||||
|
||||
// Example use:
|
||||
// cy.mount(<MyComponent />)
|
||||
20
cypress/support/e2e.js
Normal file
20
cypress/support/e2e.js
Normal file
@@ -0,0 +1,20 @@
|
||||
// ***********************************************************
|
||||
// This example support/e2e.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
@@ -1,5 +1,5 @@
|
||||
NEXT_PUBLIC_API_NAME = "Loan Facilities Dashboard"
|
||||
NEXT_PUBLIC_API_VERSION = "1.8.10"
|
||||
NEXT_PUBLIC_API_VERSION = "1.10.2"
|
||||
NEXT_PUBLIC_DEFAULT_LANGUAGE = "fa"
|
||||
NEXT_PUBLIC_DEFAULT_DIRECTION = "rtl"
|
||||
|
||||
@@ -7,6 +7,7 @@ NEXT_PUBLIC_PRIMARY_MAIN = "#084070"
|
||||
NEXT_PUBLIC_SECONDARY_MAIN = "#FF4E00"
|
||||
|
||||
NEXT_PUBLIC_BASE_URL = "https://loan.witel.ir"
|
||||
NEXT_PUBLIC_POWERED_BY_URL = "https://witel.ir"
|
||||
|
||||
#["NEXT_PUBLIC_HAS_WIDGET" , "NEXT_PUBLIC_HAS_NOTIFICATION"]
|
||||
NEXT_PUBLIC_HAS_VALUE=["NEXT_PUBLIC_HAS_NOTIFICATION"]
|
||||
|
||||
17
jest.config.mjs
Normal file
17
jest.config.mjs
Normal file
@@ -0,0 +1,17 @@
|
||||
import nextJest from 'next/jest.js'
|
||||
|
||||
const createJestConfig = nextJest({
|
||||
// Provide the path to your Next.js app to load next.config.js and .env files in your test environment
|
||||
dir: './',
|
||||
})
|
||||
|
||||
// Add any custom config to be passed to Jest
|
||||
/** @type {import('jest').Config} */
|
||||
const config = {
|
||||
// Add more setup options before each test is run
|
||||
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
|
||||
testEnvironment: 'jest-environment-jsdom',
|
||||
}
|
||||
|
||||
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
|
||||
export default createJestConfig(config)
|
||||
22
jest.setup.js
Normal file
22
jest.setup.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import '@testing-library/jest-dom'
|
||||
import {server} from "./mocks/server";
|
||||
import mockRouter from 'next-router-mock'
|
||||
|
||||
jest.mock('next/router', () => jest.requireActual('next-router-mock'))
|
||||
|
||||
beforeAll(() => {
|
||||
server.listen()
|
||||
})
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
mockRouter.query = {}
|
||||
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
server.resetHandlers()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
server.close()
|
||||
})
|
||||
13
mocks/AppWithProvider.jsx
Normal file
13
mocks/AppWithProvider.jsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import App from "@/pages/_app";
|
||||
import fa from "&/locales/fa/app.json"
|
||||
|
||||
const translations = {fa};
|
||||
const MockAppWithProviders = ({children, locale, title, isBot}) => {
|
||||
const pageProps = {
|
||||
title: title || '', isBot: isBot || true, locale: locale || 'fa', messages: translations[locale || 'fa']
|
||||
}
|
||||
|
||||
return (<App Component={() => (<>{children}</>)} pageProps={pageProps}/>)
|
||||
}
|
||||
|
||||
export default MockAppWithProviders
|
||||
5
mocks/handler.js
Normal file
5
mocks/handler.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import {userHandler} from "./handlers/user";
|
||||
|
||||
export const handler = [
|
||||
...userHandler
|
||||
];
|
||||
19
mocks/handlers/user.js
Normal file
19
mocks/handlers/user.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import {rest} from "msw";
|
||||
import {GET_USER_ROUTE} from "@/core/data/apiRoutes";
|
||||
|
||||
export const userHandler = [
|
||||
rest.get(GET_USER_ROUTE, (req, res, ctx) => {
|
||||
return res(ctx.json({
|
||||
data: {
|
||||
id: 10,
|
||||
full_name: "Witel Company",
|
||||
position: "Software Engineer",
|
||||
permissions: [
|
||||
"manage_users",
|
||||
"manage_roles",
|
||||
"manage_boss"
|
||||
],
|
||||
}
|
||||
}))
|
||||
}),
|
||||
]
|
||||
4
mocks/server.js
Normal file
4
mocks/server.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import {setupServer} from "msw/node";
|
||||
import {handler} from "./handler";
|
||||
|
||||
export const server = setupServer(...handler)
|
||||
18
package.json
18
package.json
@@ -10,7 +10,11 @@
|
||||
"lint": "next lint",
|
||||
"update": "run-script-os",
|
||||
"update:win32": "echo 'ok'",
|
||||
"update:linux": "rm -rf .next/ && npm i && npm run build && sudo systemctl restart loan_facilities_develop_expert.service"
|
||||
"update:linux": "rm -rf .next/ && npm i && npm run build && sudo systemctl restart loan_facilities_develop_expert.service",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watchAll",
|
||||
"cypress:open": "cypress open",
|
||||
"cypress:run": "cypress run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.10.6",
|
||||
@@ -51,7 +55,17 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^7.6.0",
|
||||
"eslint-config-next": "^13.3.0",
|
||||
"@testing-library/jest-dom": "^6.1.4",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^14.5.1",
|
||||
"cypress": "^13.4.0",
|
||||
"eslint-config-next": "^13.5.6",
|
||||
"eslint-plugin-jest-dom": "^5.1.0",
|
||||
"eslint-plugin-testing-library": "^6.1.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"msw": "^1.3.1",
|
||||
"next-router-mock": "^0.9.10",
|
||||
"run-script-os": "^1.1.6"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,3 +167,10 @@ This set of fonts are used in this project under the license: (.....)
|
||||
url('./fonts/woff/Parastoo-Bold.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
|
||||
url('./fonts/ttf/Parastoo-Bold.ttf') format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: Bnazanin;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: url('./fonts/ttf/BNazanin.ttf') format('truetype');
|
||||
}
|
||||
|
||||
BIN
public/fonts/ttf/BNazanin.ttf
Normal file
BIN
public/fonts/ttf/BNazanin.ttf
Normal file
Binary file not shown.
BIN
public/images/logo.png
Normal file
BIN
public/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -1,19 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="611.781" height="634.738" viewBox="0 0 611.781 634.738">
|
||||
<g id="Group_67" data-name="Group 67" transform="translate(17717.998 5548)">
|
||||
<g id="Group_35" data-name="Group 35" transform="translate(-18337.291 -5635.72)">
|
||||
<path id="Path_5" data-name="Path 5"
|
||||
d="M126.892,461.042c-1.676-6.24-3.057-12-4.776-17.668-6.5-21.426-17.146-40.936-27.846-60.456-10.362-18.9-20.22-38.081-30.31-57.132-.389-.734-.9-1.406-1.791-2.8C36.884,364.062,18.983,407.918,1.474,451.962l-1.21-.256a8.878,8.878,0,0,1-.214-2.524c2.581-12.372,4.479-24.951,8.05-37.034a334.761,334.761,0,0,1,13.91-37.364Q35.923,342.5,51.02,310.733c3.525-7.389,1.922-13.976.051-20.991a185.716,185.716,0,0,1-6.18-54.9,183.572,183.572,0,0,1,21.089-78.7A248.249,248.249,0,0,1,132.8,75.26a320,320,0,0,1,59.11-36.67c23.209-11.317,47.033-21.1,72.336-26.513A487.808,487.808,0,0,1,314.282,3.7C333.731,1.624,353.352,1.16,372.9.018a22,22,0,0,1,3.3.145c.4.037.775.291,1.236,1.13L360.926,4.642c-26.4,5.318-52.988,9.831-79.147,16.136a295.335,295.335,0,0,0-105.32,48.21c-24.648,17.9-45.935,39.123-61.823,65.3-11.419,18.813-17.813,39.38-20.064,61.275-1.445,14.062-1.024,28.1.166,42.133a44.216,44.216,0,0,0,1,4.8c1.418-1.165,2.427-1.875,3.3-2.728,10.2-10.038,20.041-20.48,30.661-30.055,14.1-12.713,29.419-23.882,46.9-31.678,21.42-9.55,43.69-13.568,66.553-6.9,10.553,3.079,20.531,8.393,30.44,13.353,20.075,10.051,39.841,20.721,59.892,30.821a182.512,182.512,0,0,0,68.478,19.421c33.07,2.549,64.407-3.894,93.662-19.748,25.04-13.571,46.544-31.674,66.252-52.041,15.44-15.956,28.916-33.487,41.494-51.746,2.217-3.219,4.962-6.073,7.463-9.1l.983.382a15.039,15.039,0,0,1-.828,3.67c-3.9,7.643-7.67,15.361-11.911,22.812-17.8,31.272-39.019,60-64.85,85.13-26.732,26.009-56.556,47.619-90.953,62.526a162.5,162.5,0,0,1-74.241,14.106c-24.876-.775-48.015,5.5-69.742,17.063-14.861,7.91-29.333,16.559-43.879,25.049-13.773,8.039-28.189,13.431-44.484,11.949-2.667-.242-2.49,1.854-2.988,3.479-2.151,7.019-3.92,14.193-6.627,20.993-3.8,9.547-10.327,17.4-17.557,24.572-15.526,15.4-28.738,32.778-42.59,49.608-4.68,5.687-9.284,11.436-14.28,17.6M284.4,266.987c-.334-.819-.375-1.206-.591-1.393a13.8,13.8,0,0,0-1.854-1.356c-22.793-13.885-46.277-26.286-71.807-34.532-20.915-6.755-41.455-6.841-61.848,1.941-15.741,6.779-28.925,17.112-41,29.01-5.591,5.508-5.981,8.93-2.421,15.921q12.355,24.272,24.657,48.572c10.087,19.93,20.125,39.886,30.279,59.783.543,1.062,1.967,1.674,2.98,2.5a12.859,12.859,0,0,0,1.933-3.081,49.022,49.022,0,0,0-2.4-33.292c-6.476-14.911-14.075-29.337-21.275-43.93-4.967-10.065-10.144-20.026-15.07-30.112-1.63-3.338-2.558-6.917.574-10.1,10.226-10.371,22.1-18.1,36.115-22.335,3.993-1.206,5.77-.416,7.975,3.16,3.927,6.365,7.687,12.846,11.888,19.028,9.727,14.31,23.437,21.051,40.7,20.974,16.93-.075,32.491-5.033,47.273-12.854,4.715-2.495,9.282-5.27,13.894-7.9"
|
||||
transform="translate(619.257 87.713)" fill="#231955"/>
|
||||
<path id="Path_6" data-name="Path 6"
|
||||
d="M104.479,767.41c2.322-11.049,4.27-22.193,7.046-33.127,5.72-22.532,15.277-43.572,26.263-63.957,16.423-30.474,34.631-59.844,55.63-87.405,20.939-27.481,43.511-53.464,70.53-75.261,24.13-19.467,50.887-33.842,81.027-41.582,4.676-1.2,9.437-2.118,14.2-2.939a15,15,0,0,0,11.744-9.6c4.286-10.252,11.9-14.085,22.732-13.761a202.3,202.3,0,0,0,89.232-17.644A257.328,257.328,0,0,0,528.458,395.6c1.4-1.009,2.885-1.912,5.222-3.451-.953,3.026-1.265,5.258-2.293,7.089-14.936,26.6-31.633,51.891-54.057,72.956-18.879,17.734-40.265,31.046-65.744,36.862-15.1,3.446-30.348,6.253-45.529,9.349-18.622,3.8-37.406,6.945-55.825,11.553-27.566,6.9-51.782,20.673-73.654,38.685-23.608,19.441-42.473,43.054-59.8,68-24.96,35.939-44.814,74.717-63.24,114.278q-3.9,8.368-7.793,16.738l-1.263-.245"
|
||||
transform="translate(583.845 -45.198)" fill="#231955"/>
|
||||
<path id="Path_7" data-name="Path 7"
|
||||
d="M405.392,690.855l-53.546,54.494-55.389-51.215,55.1-53.062L405.4,690.854"
|
||||
transform="translate(518.777 -129.57)" fill="#ff9100"/>
|
||||
</g>
|
||||
<text id="MARHABA" transform="translate(-17322 -4931)" fill="#bcbcbc" font-size="50"
|
||||
font-family="Montserrat-ExtraBold, Montserrat" font-weight="800">
|
||||
<tspan x="-281.35" y="0">MARHABA</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -2,6 +2,7 @@
|
||||
"app_name": "سامانه جامع تسهیلات",
|
||||
"app_short_name": "سامانه تسهیلات",
|
||||
"dashboard": "داشبورد",
|
||||
"powered_by_witel": "توسعه یافته توسط وایتل",
|
||||
"first_page": "خوش آمدید",
|
||||
"login": "ورود",
|
||||
"login_expert": "ورود کارشناس",
|
||||
@@ -15,6 +16,11 @@
|
||||
"between": "میان",
|
||||
"online_message": "شما به اینترنت وصل هستید",
|
||||
"offline_message": "اتصال شما به اینترنت قطع شده است",
|
||||
"routing_to": "در حال انتقال به صفحه",
|
||||
"data_not_found": "اطلاعاتی یافت نشد!",
|
||||
"btn_print": "چاپ",
|
||||
"print_loading": "درحال پردازش اطلاعات ...",
|
||||
"button_back_to_previous": "بازگشت به صفحه قبل",
|
||||
"header": {
|
||||
"open_profile": "پروفایل",
|
||||
"edit_profile": " پروفایل",
|
||||
@@ -161,7 +167,10 @@
|
||||
"updated_at": "تاریخ بروزرسانی",
|
||||
"navgan_id": "کد ناوگان",
|
||||
"vehicle_type": "نوع ماشین",
|
||||
"state_name": "وضعیت درخواست"
|
||||
"state_name": "وضعیت درخواست",
|
||||
"confirm": "ارجاع به کارشناس ماشین آلات",
|
||||
"upload_file_unit": "فایل بارگذاری شده باید حداکثر 2Mb باشد",
|
||||
"upload_file_format": "فرمت قابل قبول : png,jpg,pdf"
|
||||
},
|
||||
"CommercialChief": {
|
||||
"name": "نام",
|
||||
@@ -183,7 +192,10 @@
|
||||
"updated_at": "تاریخ بروزرسانی",
|
||||
"navgan_id": "کد ناوگان",
|
||||
"vehicle_type": "نوع ماشین",
|
||||
"state_name": "وضعیت درخواست"
|
||||
"state_name": "وضعیت درخواست",
|
||||
"confirm": "تایید نهایی و ارجاع به بانک",
|
||||
"upload_file_unit": "فایل بارگذاری شده باید حداکثر 2Mb باشد",
|
||||
"upload_file_format": "فرمت قابل قبول : png,jpg,pdf"
|
||||
},
|
||||
"PassengerBoss": {
|
||||
"name": "نام",
|
||||
@@ -194,7 +206,14 @@
|
||||
"updated_at": "تاریخ بروزرسانی",
|
||||
"navgan_id": "کد ناوگان",
|
||||
"vehicle_type": "نوع ماشین",
|
||||
"state_name": "وضعیت درخواست"
|
||||
"state_name": "وضعیت درخواست",
|
||||
"confirm": "ارجاع به معاون حمل و نقل",
|
||||
"upload_file": "صورت جلسه کارگروه استانی را بارگذاری کنید",
|
||||
"upload_file_required": "وارد کردن صورت جلسه کارگروه استانی الزامیست",
|
||||
"upload_file_unit": "فایل بارگذاری شده باید حداکثر 2Mb باشد",
|
||||
"upload_file_format": "فرمت قابل قبول : png,jpg,pdf",
|
||||
"print_final_credit_amount": "چاپ صورت جلسه تعیین مبلغ نهایی",
|
||||
"max_amount": "حداکثر مبلغ تصویب شده {value} میلیون ریال می باشد"
|
||||
},
|
||||
"RefahiProvinceManager": {
|
||||
"name": "نام",
|
||||
@@ -262,7 +281,10 @@
|
||||
"updated_at": "تاریخ بروزرسانی",
|
||||
"navgan_id": "کد ناوگان",
|
||||
"vehicle_type": "نوع ماشین",
|
||||
"state_name": "وضعیت درخواست"
|
||||
"state_name": "وضعیت درخواست",
|
||||
"confirm": "ارجاع به مدیر کل استانی",
|
||||
"upload_file_unit": "فایل بارگذاری شده باید حداکثر 2Mb باشد",
|
||||
"upload_file_format": "فرمت قابل قبول : png,jpg,pdf"
|
||||
},
|
||||
"InspectorExpert": {
|
||||
"name": "نام",
|
||||
@@ -284,7 +306,14 @@
|
||||
"updated_at": "تاریخ بروزرسانی",
|
||||
"navgan_id": "کد ناوگان",
|
||||
"vehicle_type": "نوع ماشین",
|
||||
"state_name": "وضعیت درخواست"
|
||||
"state_name": "وضعیت درخواست",
|
||||
"confirm": "ارجاع به کارگروه استانی",
|
||||
"upload_file": "گزارش کارشناسی را بارگذاری کنید",
|
||||
"upload_file_required": "وارد کردن گزارش کارشناسی الزامیست",
|
||||
"upload_file_unit": "فایل بارگذاری شده باید حداکثر 2Mb باشد",
|
||||
"upload_file_format": "فرمت قابل قبول : png,jpg,pdf",
|
||||
"print_report": "چاپ گزارش کارشناسی ماشین آلات",
|
||||
"max_amount": "حداکثر مبلغ پیشنهادی {value} میلیون ریال می باشد"
|
||||
},
|
||||
"UserManagement": {
|
||||
"name": "نام",
|
||||
@@ -306,23 +335,24 @@
|
||||
"updated_at": "تاریخ بروزرسانی"
|
||||
},
|
||||
"ConfirmDialog": {
|
||||
"confirm": "تایید",
|
||||
"confirm": "ارجاع",
|
||||
"context": "آیا از تایید این آیتم اطمینان دارید؟",
|
||||
"button-cancel": "بستن",
|
||||
"button-confirm": "تایید",
|
||||
"button-confirm": "ارجاع",
|
||||
"description_error": "وارد کردن توضیحات الزامی است!",
|
||||
"optional": " (اختیاری)",
|
||||
"unit": " (ریال)",
|
||||
"unit": " (میلیون ریال)",
|
||||
"description": "توضیحات خود را وارد نمائید",
|
||||
"choose_file": "انتخاب فایل",
|
||||
"approved_amount": "مبلغ تصویب شده",
|
||||
"approved_amount_error": "وارد کردن مبلغ تصویب شده الزامیست",
|
||||
"approved_amount_positive": "مبلغ تصویب شده باید مثبت باشد",
|
||||
"approved_amount_number": "مبلغ تصویب شده باید عدد باشد",
|
||||
"toman": "تومان",
|
||||
"toman": "میلیون تومان",
|
||||
"proposed_amount_error": "وارد کردن مقدار پیشنهادی الزامیست",
|
||||
"proposed_amount": "مقدار پیشنهادی",
|
||||
"proposed_amount_positive": "مقدار پیشنهادی باید مثبت باشد"
|
||||
"proposed_amount_positive": "مقدار پیشنهادی باید مثبت باشد",
|
||||
"proposed_amount_number": "مقدار پیشنهادی باید عدد باشد"
|
||||
},
|
||||
"ReviseDialog": {
|
||||
"revise": " نیاز به ویرایش",
|
||||
@@ -361,7 +391,8 @@
|
||||
"permission_min_error": "حداقل باید یک دسترسی انتخاب شود",
|
||||
"type_id": "نوع کاربر",
|
||||
"navgan_id": "کد ناوگان",
|
||||
"button-update": "ویرایش"
|
||||
"button-update": "ویرایش",
|
||||
"loading_permissions_list": "درحال دریافت لیست دسترسی ها"
|
||||
},
|
||||
"UploadSystem": {
|
||||
"upload_file": "فایل خود را بارگذاری کنید",
|
||||
@@ -468,7 +499,8 @@
|
||||
"type_id": "نوع کاربر",
|
||||
"navgan_id": "کد ناوگان",
|
||||
"button-cancel": "انصراف",
|
||||
"button-add": "ثبت"
|
||||
"button-add": "ثبت",
|
||||
"loading_permissions_list": "درحال دریافت لیست دسترسی ها"
|
||||
},
|
||||
"reports": {
|
||||
"loan_progress": "درصد پیشرفت تسویه وام",
|
||||
|
||||
9
public/print.scss
Normal file
9
public/print.scss
Normal file
@@ -0,0 +1,9 @@
|
||||
@media print {
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
@page {
|
||||
size: A4;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import PrintIcon from "@mui/icons-material/Print";
|
||||
import {Button, CircularProgress} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {useRouter} from "next/router";
|
||||
import {useState} from "react";
|
||||
import useRequest from "@/lib/app/hooks/useRequest";
|
||||
import {GET_MACHINARY_OFFICE} from "@/core/data/apiRoutes";
|
||||
import usePrint from "@/lib/app/hooks/usePrint";
|
||||
|
||||
const PrintReport = () => {
|
||||
const t = useTranslations();
|
||||
const router = useRouter()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const requestServer = useRequest({auth: true, pending: false, success: {notification: {show: false}}})
|
||||
const {setPrintPage, setPrintTitle} = usePrint()
|
||||
const clickHandler = () => {
|
||||
setLoading(true)
|
||||
const params = new URLSearchParams();
|
||||
params.set("start", '0');
|
||||
params.set("filters", '[]');
|
||||
params.set("sorting", '[]');
|
||||
requestServer(`${GET_MACHINARY_OFFICE}?${params}`, 'get').then((response) => {
|
||||
const _data = response.data.data
|
||||
setPrintPage(_data.length)
|
||||
setPrintTitle(t('MachinaryOffice.print_report'))
|
||||
sessionStorage.setItem('report-print', JSON.stringify(_data))
|
||||
router.push('/dashboard/machinery-expert/prints/report')
|
||||
}).catch(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
size="small"
|
||||
disabled={loading}
|
||||
sx={{textTransform: "unset", alignSelf: "center"}}
|
||||
startIcon={loading ? <CircularProgress size={18} color="inherit"/> : <PrintIcon/>}
|
||||
onClick={clickHandler}
|
||||
>
|
||||
{t("MachinaryOffice.print_report")}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default PrintReport
|
||||
@@ -1,16 +1,19 @@
|
||||
import UploadSystem from "@/core/components/UploadSystem";
|
||||
import useNotification from "@/lib/app/hooks/useNotification";
|
||||
import useRequest from "@/lib/app/hooks/useRequest";
|
||||
import {Button, DialogActions, DialogContent, Stack, TextField, Typography} from "@mui/material"
|
||||
import {Button, DialogActions, DialogContent, FormHelperText, Stack, TextField, Typography} from "@mui/material"
|
||||
import {useFormik} from "formik";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {CONFIRM_MACHINARY_OFFICE} from "@/core/data/apiRoutes";
|
||||
import UploadFileNotification from "@/core/components/notifications/UploadFileNotification";
|
||||
import {useState} from "react";
|
||||
import PriceField from "@/core/components/PriceField";
|
||||
import * as Yup from "yup";
|
||||
|
||||
const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
const vehicle_amount = {
|
||||
"اتوبوس": 7000,
|
||||
"مینی بوس": 2000,
|
||||
}
|
||||
const ConfirmContent = ({mutate, setOpenConfirmDialog, rowId, vehicle_type}) => {
|
||||
const t = useTranslations();
|
||||
const [selectedImage, setSelectedImage] = useState("");
|
||||
const [fileType, setfileType] = useState(null);
|
||||
@@ -18,14 +21,24 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
const [showAddIcon, setShowAddIcon] = useState(true);
|
||||
const requestServer = useRequest({auth: true})
|
||||
const {update_notification} = useNotification()
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
proposed_amount: Yup.mixed().test(
|
||||
"is-number",
|
||||
`${t("ConfirmDialog.proposed_amount_number")}`,
|
||||
(value) => !isNaN(value)
|
||||
).test("positive", `${t("ConfirmDialog.proposed_amount_positive")}`, (value) => value >= 0)
|
||||
.required(t("ConfirmDialog.proposed_amount_error"))
|
||||
).test("max-amount", `${t("MachinaryOffice.max_amount", {value: parseInt(vehicle_amount[vehicle_type]).toLocaleString('en')})}`,
|
||||
(value) => value <= vehicle_amount[vehicle_type])
|
||||
.test("positive", `${t("ConfirmDialog.proposed_amount_positive")}`, (value) => value >= 0)
|
||||
.required(t("ConfirmDialog.proposed_amount_error")),
|
||||
confirm_img: Yup.mixed()
|
||||
.required(t("MachinaryOffice.upload_file_required"))
|
||||
.test('fileSize', `${t("MachinaryOffice.upload_file_unit")}`, (value) => {
|
||||
return value.size <= 2 * 1024 * 1024;
|
||||
})
|
||||
.test('fileType', `${t("MachinaryOffice.upload_file_format")}`, (value) => {
|
||||
const allowedTypes = ['image/jpg', 'image/jpeg', 'image/png', 'application/pdf'];
|
||||
return allowedTypes.includes(value.type);
|
||||
})
|
||||
});
|
||||
|
||||
const formik = useFormik({
|
||||
@@ -37,9 +50,8 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
onSubmit: (values, {setSubmitting}) => {
|
||||
const formData = new FormData();
|
||||
formData.append("proposed_amount", values.proposed_amount);
|
||||
if (values.description != "") formData.append("description", values.description);
|
||||
if (values.description != "") formData.append("expert_description", values.description);
|
||||
if (values.confirm_img != null) formData.append("attachment", values.confirm_img);
|
||||
|
||||
requestServer(`${CONFIRM_MACHINARY_OFFICE}/${rowId}`, 'post', {
|
||||
data: formData,
|
||||
}).then((response) => {
|
||||
@@ -60,19 +72,14 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
const handleUploadChange = (event) => {
|
||||
const uploadedFile = event.target?.files?.[0];
|
||||
if (uploadedFile) {
|
||||
const maxFileSize = 2 * 1024 * 1024;
|
||||
if (uploadedFile.size > maxFileSize) {
|
||||
UploadFileNotification(t);
|
||||
event.target.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const fileType = event.target?.files?.[0].type;
|
||||
const fileName = event.target?.files?.[0].name;
|
||||
setSelectedImage(URL.createObjectURL(uploadedFile));
|
||||
setfileType(fileType);
|
||||
setfileName(fileName);
|
||||
formik.setFieldValue("confirm_img", uploadedFile);
|
||||
formik.setFieldValue("confirm_img", uploadedFile).then(() => {
|
||||
formik.setFieldTouched("confirm_img", true);
|
||||
})
|
||||
setShowAddIcon(false);
|
||||
}
|
||||
};
|
||||
@@ -129,7 +136,7 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Typography>{t("UploadSystem.upload_file")}{t("UploadSystem.optional")}</Typography>
|
||||
<Typography>{t("MachinaryOffice.upload_file")}</Typography>
|
||||
<UploadSystem
|
||||
selectedImage={selectedImage}
|
||||
handleUploadChange={handleUploadChange} // Pass the updated function directly
|
||||
@@ -140,9 +147,19 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
fileName={fileName}
|
||||
imageAlt={t("app_name")}
|
||||
imageSize={[250, 150]}
|
||||
onBlur={formik.handleBlur("confirm_img")}
|
||||
error={
|
||||
formik.touched.confirm_img &&
|
||||
Boolean(formik.errors.confirm_img)
|
||||
}
|
||||
setShowAddIcon={setShowAddIcon}
|
||||
showAddIcon={showAddIcon}
|
||||
/>
|
||||
<FormHelperText
|
||||
error={Boolean(formik.errors.confirm_img)}
|
||||
>
|
||||
{formik.touched.confirm_img && formik.errors.confirm_img}
|
||||
</FormHelperText>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {Dialog, DialogTitle, IconButton, Tooltip} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import ThumbUpAltIcon from "@mui/icons-material/ThumbUpAlt";
|
||||
import {useState} from "react";
|
||||
import ConfirmContent from "./ConfirmContent";
|
||||
import CallMadeIcon from '@mui/icons-material/CallMade';
|
||||
|
||||
const Confirm = ({rowId, mutate}) => {
|
||||
const Confirm = ({rowId, mutate, vehicle_type}) => {
|
||||
const t = useTranslations();
|
||||
const [openConfirmDialog, setOpenConfirmDialog] = useState(false);
|
||||
return (
|
||||
@@ -16,13 +16,14 @@ const Confirm = ({rowId, mutate}) => {
|
||||
setOpenConfirmDialog(true)
|
||||
}}
|
||||
>
|
||||
<ThumbUpAltIcon/>
|
||||
<CallMadeIcon/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog fullWidth open={openConfirmDialog}
|
||||
PaperProps={{sx: {boxShadow: 'rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px'}}}>
|
||||
<DialogTitle>{t("ConfirmDialog.confirm")}</DialogTitle>
|
||||
<ConfirmContent mutate={mutate} rowId={rowId} setOpenConfirmDialog={setOpenConfirmDialog}/>
|
||||
<DialogTitle>{t("MachinaryOffice.confirm")}</DialogTitle>
|
||||
<ConfirmContent mutate={mutate} rowId={rowId} setOpenConfirmDialog={setOpenConfirmDialog}
|
||||
vehicle_type={vehicle_type}/>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ import {useFormik} from "formik";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {useState} from "react";
|
||||
import * as Yup from "yup";
|
||||
import {REJECT_INSPECTOR_EXPERT} from "@/core/data/apiRoutes";
|
||||
import {REJECT_MACHINARY_OFFICE} from "@/core/data/apiRoutes";
|
||||
|
||||
const RejectContent = ({rowId, mutate, setOpenRejectDialog}) => {
|
||||
const [selectedImage, setSelectedImage] = useState("");
|
||||
@@ -29,10 +29,10 @@ const RejectContent = ({rowId, mutate, setOpenRejectDialog}) => {
|
||||
validationSchema,
|
||||
onSubmit: (values, {setSubmitting}) => {
|
||||
const formData = new FormData();
|
||||
formData.append("description", values.description);
|
||||
formData.append("expert_description", values.description);
|
||||
if (values.reject_img != null) formData.append("attachment", values.reject_img);
|
||||
|
||||
requestServer(`${REJECT_INSPECTOR_EXPERT}/${rowId}`, 'post', {
|
||||
requestServer(`${REJECT_MACHINARY_OFFICE}/${rowId}`, 'post', {
|
||||
data: formData,
|
||||
}).then((response) => {
|
||||
setOpenRejectDialog(false)
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import PrintablePage from "@/core/components/PrintablePage";
|
||||
import {Box, Stack, Typography} from "@mui/material";
|
||||
|
||||
const Content = ({data}) => {
|
||||
return (
|
||||
<>
|
||||
<PrintablePage key={data.id} header={true} footer={true}>
|
||||
<Box sx={{mt: 6}}>
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: 'Bnazanin',
|
||||
fontWeight: 'bold',
|
||||
fontSize: '1.2rem',
|
||||
textAlign: 'center'
|
||||
}}> {'گزارش کارشناسی ماشین آلات'} </Typography>
|
||||
</Box>
|
||||
<Box textAlign={'justify'} sx={{lineHeight: '1.8', mt: 2}}>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', pl: 4}}> {'با توجه به نامه شماره'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>35482</Typography>
|
||||
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'مورخ'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>1402/08/20</Typography>
|
||||
<Typography
|
||||
sx={{fontFamily: 'Bnazanin'}}
|
||||
component={'span'}> {'معاون محترم برنامه ریزی سازمان راهداری و حمل و نقل جاده ای در خصوص تسهیلات بند (الف) تبصره (18) قانون بودجه سال'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>1402</Typography>
|
||||
<Typography
|
||||
sx={{fontFamily: 'Bnazanin'}}
|
||||
component={'span'}> {'موضوع بازسازی ناوگان اتوبوس و مینی بوس و نیز تصویب آن درشورای برنامه ریزی و توسعه استان'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>تهران</Typography>
|
||||
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'و نامه شماره'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>35482</Typography>
|
||||
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'مورخ'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>1402/08/20</Typography>
|
||||
<Typography
|
||||
sx={{fontFamily: 'Bnazanin'}}
|
||||
component={'span'}> {'اداره کل راهداری و حمل و نقل جاده ای'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>تهران</Typography>
|
||||
<Typography
|
||||
sx={{fontFamily: 'Bnazanin'}}
|
||||
component={'span'}> {'در خصوص استفاده از ظرفیت انجمن های صنفی رانندگان در تاریخ'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>1402/08/20</Typography>
|
||||
<Typography
|
||||
sx={{fontFamily: 'Bnazanin'}}
|
||||
component={'span'}> {'از ناوگان اتوبوس/مینی بوس به شماره پلاک انتظامی'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>25پ4582</Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin'}}> {'و شماره هوشمند ناوگان'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>5875698725</Typography>
|
||||
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'به مالکیت آقای'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>امیرحسین محمودی</Typography>
|
||||
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'با کد ملی'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>0311318897</Typography>
|
||||
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'و شماره تماس'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>09120630655</Typography>
|
||||
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'ساکن شهرستان'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>تهران</Typography>
|
||||
<Typography
|
||||
sx={{fontFamily: 'Bnazanin'}}
|
||||
component={'span'}> {'در جلسه مطرح شد که نتیجه آن به شرح ذیل مورد تایید می باشد.'} </Typography>
|
||||
</Box>
|
||||
<Box sx={{border: 1, borderRadius: 1, mt: 3, p: 2}}>
|
||||
<Typography
|
||||
sx={{fontFamily: 'Bnazanin'}}> {'مبلغ پیشنهادی به عدد .............................................. ریال و به حروف ...................................................................... ریال می باشد.'} </Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
lineBreak: 'anywhere',
|
||||
fontFamily: 'Bnazanin',
|
||||
}}> {'توضیحات : ................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................'} </Typography>
|
||||
<Stack direction={'row'} justifyContent={'space-between'}>
|
||||
<Typography
|
||||
sx={{fontFamily: 'Bnazanin'}}> {'نام و نام خانوادگی کارشناس:'} </Typography>
|
||||
<Typography
|
||||
sx={{fontFamily: 'Bnazanin'}}> {'تاریخ و امضاء'} </Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
</PrintablePage>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Content
|
||||
@@ -0,0 +1,60 @@
|
||||
import {useEffect, useState} from "react";
|
||||
import CenterLayout from "@/layouts/CenterLayout";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {Stack, Typography} from "@mui/material";
|
||||
import Content from "@/components/dashboard/machinary-office/Prints/report/Content";
|
||||
|
||||
const ReportComponent = () => {
|
||||
const t = useTranslations()
|
||||
const [data, setData] = useState(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [isNotData, setIsNotData] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
if (sessionStorage.getItem('report-print') === null) {
|
||||
setLoading(false)
|
||||
setIsNotData(true)
|
||||
sessionStorage.setItem('report-print', 'seen')
|
||||
} else if (sessionStorage.getItem('report-print') === 'seen') {
|
||||
setLoading(false)
|
||||
setData(null)
|
||||
setIsNotData(true)
|
||||
} else if (data) {
|
||||
setLoading(false)
|
||||
sessionStorage.setItem('report-print', 'seen')
|
||||
setIsNotData(false)
|
||||
} else {
|
||||
setLoading(true)
|
||||
setData(JSON.parse(sessionStorage.getItem('report-print')))
|
||||
setIsNotData(true)
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<Stack sx={{bgcolor: '#efefef', width: isNotData ? '100%' : 'auto', height: isNotData ? '100%' : 'auto'}}>
|
||||
{loading ? (
|
||||
<CenterLayout>
|
||||
<Stack>
|
||||
<Typography variant={'h5'}>
|
||||
{t('print_loading')}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</CenterLayout>
|
||||
) : !data ? (
|
||||
<CenterLayout>
|
||||
<Typography variant={'h5'}>
|
||||
{t('data_not_found')}
|
||||
</Typography>
|
||||
</CenterLayout>
|
||||
) : !data.length ? (
|
||||
<CenterLayout>
|
||||
<Typography variant={'h5'}>
|
||||
{t('data_not_found')}
|
||||
</Typography>
|
||||
</CenterLayout>
|
||||
) : data.map(item => <Content key={item.id} data={item}/>)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
export default ReportComponent
|
||||
@@ -3,11 +3,11 @@ import Confirm from "./Form/ConfirmForm";
|
||||
import Reject from "./Form/RejectForm";
|
||||
|
||||
const TableRow = ({row, mutate}) => {
|
||||
|
||||
return (
|
||||
<Box sx={{display: "flex", flexWrap: "nowrap", gap: "8px"}}>
|
||||
<Confirm
|
||||
rowId={row.getValue("id")}
|
||||
vehicle_type={row.original.vehicle_type}
|
||||
mutate={mutate}
|
||||
/>
|
||||
<Reject
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import {Stack, Tooltip} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
|
||||
function TableToolbar() {
|
||||
const t = useTranslations();
|
||||
return (
|
||||
<Stack direction={"row"} spacing={2}>
|
||||
<Tooltip title={t("add")} arrow placement="right"></Tooltip>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default TableToolbar;
|
||||
12
src/components/dashboard/machinary-office/TableToolbar.jsx
Normal file
12
src/components/dashboard/machinary-office/TableToolbar.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import {Stack} from "@mui/material";
|
||||
import PrintReport from "@/components/dashboard/machinary-office/Buttons/printReport";
|
||||
|
||||
const TableToolbar = () => {
|
||||
return (
|
||||
<Stack direction={"row"} spacing={2}>
|
||||
<PrintReport/>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
export default TableToolbar
|
||||
@@ -6,6 +6,7 @@ import TableRowActions from "./TableRowActions";
|
||||
import moment from "jalali-moment";
|
||||
import DataTable from "@/core/components/DataTable";
|
||||
import MuiDatePicker from "@/core/components/MuiDatePicker";
|
||||
import TableToolbar from "@/components/dashboard/machinary-office/TableToolbar";
|
||||
|
||||
function DashboardMachinaryOfficeComponent() {
|
||||
const t = useTranslations();
|
||||
@@ -160,7 +161,8 @@ function DashboardMachinaryOfficeComponent() {
|
||||
tableUrl={GET_MACHINARY_OFFICE}
|
||||
columns={columns}
|
||||
selectableRow={false}
|
||||
enableCustomToolbar={false}
|
||||
enableCustomToolbar={true}
|
||||
CustomToolbar={TableToolbar}
|
||||
enableLastUpdate={true}
|
||||
sorting={[{
|
||||
id: 'id', desc: false
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import UploadSystem from "@/core/components/UploadSystem";
|
||||
import useNotification from "@/lib/app/hooks/useNotification";
|
||||
import useRequest from "@/lib/app/hooks/useRequest";
|
||||
import {Button, DialogActions, DialogContent, Stack, TextField, Typography} from "@mui/material"
|
||||
import {Button, DialogActions, DialogContent, FormHelperText, Stack, TextField, Typography} from "@mui/material"
|
||||
import {useFormik} from "formik";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {CONFIRM_NAVGAN_PROVINCE_MANAGER} from "@/core/data/apiRoutes";
|
||||
import UploadFileNotification from "@/core/components/notifications/UploadFileNotification";
|
||||
import {useState} from "react";
|
||||
import * as Yup from "yup";
|
||||
|
||||
const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
const t = useTranslations();
|
||||
@@ -17,14 +17,27 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
const requestServer = useRequest({auth: true})
|
||||
const {update_notification} = useNotification()
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
confirm_img: Yup.mixed().notRequired(t("ConfirmDialog.approved_amount_error"))
|
||||
.test('fileSize', `${t("NavganProvinceManager.upload_file_unit")}`, (value) => {
|
||||
if (!value) return true
|
||||
return value.size <= 2 * 1024 * 1024;
|
||||
})
|
||||
.test('fileType', `${t("NavganProvinceManager.upload_file_format")}`, (value) => {
|
||||
if (!value) return true
|
||||
const allowedTypes = ['image/jpg', 'image/jpeg', 'image/png', 'application/pdf']; // Define your allowed file types
|
||||
return allowedTypes.includes(value.type);
|
||||
})
|
||||
})
|
||||
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
description: "",
|
||||
confirm_img: null
|
||||
},
|
||||
}, validationSchema,
|
||||
onSubmit: (values, {setSubmitting}) => {
|
||||
const formData = new FormData();
|
||||
if (values.description != "") formData.append("description", values.description);
|
||||
if (values.description != "") formData.append("expert_description", values.description);
|
||||
if (values.confirm_img != null) formData.append("attachment", values.confirm_img);
|
||||
|
||||
requestServer(`${CONFIRM_NAVGAN_PROVINCE_MANAGER}/${rowId}`, 'post', {
|
||||
@@ -47,23 +60,17 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
const handleUploadChange = (event) => {
|
||||
const uploadedFile = event.target?.files?.[0];
|
||||
if (uploadedFile) {
|
||||
const maxFileSize = 2 * 1024 * 1024;
|
||||
if (uploadedFile.size > maxFileSize) {
|
||||
UploadFileNotification(t);
|
||||
event.target.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const fileType = event.target?.files?.[0].type;
|
||||
const fileName = event.target?.files?.[0].name;
|
||||
setSelectedImage(URL.createObjectURL(uploadedFile));
|
||||
setfileType(fileType);
|
||||
setfileName(fileName);
|
||||
formik.setFieldValue("confirm_img", uploadedFile);
|
||||
formik.setFieldValue("confirm_img", uploadedFile).then(() => {
|
||||
formik.setFieldTouched("confirm_img", true);
|
||||
})
|
||||
setShowAddIcon(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogContent>
|
||||
@@ -96,7 +103,17 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
imageSize={[250, 150]}
|
||||
setShowAddIcon={setShowAddIcon}
|
||||
showAddIcon={showAddIcon}
|
||||
onBlur={() => formik.handleBlur("confirm_img")}
|
||||
error={
|
||||
formik.touched.confirm_img &&
|
||||
Boolean(formik.errors.confirm_img)
|
||||
}
|
||||
/>
|
||||
<FormHelperText
|
||||
error={Boolean(formik.errors.confirm_img)}
|
||||
>
|
||||
{formik.touched.confirm_img && formik.errors.confirm_img}
|
||||
</FormHelperText>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
@@ -106,7 +123,7 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
{t("ConfirmDialog.button-cancel")}
|
||||
</Button>
|
||||
<Button onClick={formik.handleSubmit} variant="contained" color="primary"
|
||||
disabled={formik.isSubmitting}>
|
||||
disabled={formik.isSubmitting || !formik.isValid}>
|
||||
{t("ConfirmDialog.button-confirm")}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import {Dialog, DialogTitle, IconButton, Tooltip} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import ThumbUpAltIcon from "@mui/icons-material/ThumbUpAlt";
|
||||
import {useState} from "react";
|
||||
import ConfirmContent from "./ConfirmContent";
|
||||
|
||||
import CallMadeIcon from '@mui/icons-material/CallMade';
|
||||
const Confirm = ({rowId, mutate}) => {
|
||||
const t = useTranslations();
|
||||
const [openConfirmDialog, setOpenConfirmDialog] = useState(false);
|
||||
@@ -16,12 +15,12 @@ const Confirm = ({rowId, mutate}) => {
|
||||
setOpenConfirmDialog(true)
|
||||
}}
|
||||
>
|
||||
<ThumbUpAltIcon/>
|
||||
<CallMadeIcon/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog fullWidth open={openConfirmDialog}
|
||||
PaperProps={{sx: {boxShadow: 'rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px'}}}>
|
||||
<DialogTitle>{t("ConfirmDialog.confirm")}</DialogTitle>
|
||||
<DialogTitle>{t("NavganProvinceManager.confirm")}</DialogTitle>
|
||||
<ConfirmContent mutate={mutate} rowId={rowId} setOpenConfirmDialog={setOpenConfirmDialog}/>
|
||||
</Dialog>
|
||||
</>
|
||||
|
||||
@@ -29,7 +29,7 @@ const RejectContent = ({rowId, mutate, setOpenRejectDialog}) => {
|
||||
validationSchema,
|
||||
onSubmit: (values, {setSubmitting}) => {
|
||||
const formData = new FormData();
|
||||
formData.append("description", values.description);
|
||||
formData.append("expert_description", values.description);
|
||||
if (values.reject_img != null) formData.append("attachment", values.reject_img);
|
||||
|
||||
requestServer(`${REJECT_NAVGAN_PROVINCE_MANAGER}/${rowId}`, 'post', {
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import {Stack, Tooltip} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
|
||||
function TableToolbar() {
|
||||
const t = useTranslations();
|
||||
return (
|
||||
<Stack direction={"row"} spacing={2}>
|
||||
<Tooltip title={t("add")} arrow placement="right"></Tooltip>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default TableToolbar;
|
||||
@@ -0,0 +1,48 @@
|
||||
import PrintIcon from "@mui/icons-material/Print";
|
||||
import {Button, CircularProgress} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {useRouter} from "next/router";
|
||||
import {useState} from "react";
|
||||
import useRequest from "@/lib/app/hooks/useRequest";
|
||||
import {GET_PASSENGER_BOSS} from "@/core/data/apiRoutes";
|
||||
import usePrint from "@/lib/app/hooks/usePrint";
|
||||
|
||||
const PrintFinalCreditAmount = () => {
|
||||
const t = useTranslations();
|
||||
const router = useRouter()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const requestServer = useRequest({auth: true, pending: false, success: {notification: {show: false}}})
|
||||
const {setPrintPage, setPrintTitle} = usePrint()
|
||||
const clickHandler = () => {
|
||||
setLoading(true)
|
||||
const params = new URLSearchParams();
|
||||
params.set("start", '0');
|
||||
params.set("filters", '[]');
|
||||
params.set("sorting", '[]');
|
||||
requestServer(`${GET_PASSENGER_BOSS}?${params}`, 'get').then((response) => {
|
||||
const _data = response.data.data
|
||||
setPrintPage(_data.length)
|
||||
setPrintTitle(t('PassengerBoss.print_final_credit_amount'))
|
||||
sessionStorage.setItem('final-credit-amount-print', JSON.stringify(_data))
|
||||
router.push('/dashboard/passenger-boss/prints/final-credit-amount')
|
||||
}).catch(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
size="small"
|
||||
disabled={loading}
|
||||
sx={{textTransform: "unset", alignSelf: "center"}}
|
||||
startIcon={loading ? <CircularProgress size={18} color="inherit"/> : <PrintIcon/>}
|
||||
onClick={clickHandler}
|
||||
>
|
||||
{t("PassengerBoss.print_final_credit_amount")}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default PrintFinalCreditAmount
|
||||
@@ -1,16 +1,19 @@
|
||||
import UploadSystem from "@/core/components/UploadSystem";
|
||||
import useNotification from "@/lib/app/hooks/useNotification";
|
||||
import useRequest from "@/lib/app/hooks/useRequest";
|
||||
import {Button, DialogActions, DialogContent, Stack, TextField, Typography} from "@mui/material"
|
||||
import {Button, DialogActions, DialogContent, FormHelperText, Stack, TextField, Typography} from "@mui/material"
|
||||
import {useFormik} from "formik";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {CONFIRM_PASSENGER_BOSS} from "@/core/data/apiRoutes";
|
||||
import UploadFileNotification from "@/core/components/notifications/UploadFileNotification";
|
||||
import {useState} from "react";
|
||||
import PriceField from "@/core/components/PriceField";
|
||||
import * as Yup from "yup";
|
||||
|
||||
const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
const vehicle_amount = {
|
||||
"اتوبوس": 7000,
|
||||
"مینی بوس": 2000,
|
||||
}
|
||||
const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog, vehicle_type}) => {
|
||||
const t = useTranslations();
|
||||
const [selectedImage, setSelectedImage] = useState("");
|
||||
const [fileType, setfileType] = useState(null);
|
||||
@@ -24,8 +27,19 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
"is-number",
|
||||
`${t("ConfirmDialog.approved_amount_number")}`,
|
||||
(value) => !isNaN(value)
|
||||
).test("positive", `${t("ConfirmDialog.approved_amount_positive")}`, (value) => value >= 0)
|
||||
.required(t("ConfirmDialog.approved_amount_error"))
|
||||
).test("max-amount", `${t("PassengerBoss.max_amount", {value: parseInt(vehicle_amount[vehicle_type]).toLocaleString('en')})}`,
|
||||
(value) => value <= vehicle_amount[vehicle_type])
|
||||
.test("positive", `${t("ConfirmDialog.approved_amount_positive")}`, (value) => value >= 0)
|
||||
.required(t("ConfirmDialog.approved_amount_error")),
|
||||
confirm_img: Yup.mixed()
|
||||
.required(t("PassengerBoss.upload_file_required"))
|
||||
.test('fileSize', `${t("PassengerBoss.upload_file_unit")}`, (value) => {
|
||||
return value.size <= 2 * 1024 * 1024;
|
||||
})
|
||||
.test('fileType', `${t("PassengerBoss.upload_file_format")}`, (value) => {
|
||||
const allowedTypes = ['image/jpg', 'image/jpeg', 'image/png', 'application/pdf']; // Define your allowed file types
|
||||
return allowedTypes.includes(value.type);
|
||||
})
|
||||
});
|
||||
|
||||
const formik = useFormik({
|
||||
@@ -37,8 +51,8 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
onSubmit: (values, {setSubmitting}) => {
|
||||
const formData = new FormData();
|
||||
formData.append("approved_amount", values.approved_amount);
|
||||
if (values.description != "") formData.append("description", values.description);
|
||||
if (values.confirm_img != null) formData.append("attachment", values.confirm_img);
|
||||
if (values.description != "") formData.append("expert_description", values.description);
|
||||
formData.append("attachment", values.confirm_img);
|
||||
|
||||
requestServer(`${CONFIRM_PASSENGER_BOSS}/${rowId}`, 'post', {
|
||||
data: formData,
|
||||
@@ -60,19 +74,14 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
const handleUploadChange = (event) => {
|
||||
const uploadedFile = event.target?.files?.[0];
|
||||
if (uploadedFile) {
|
||||
const maxFileSize = 2 * 1024 * 1024;
|
||||
if (uploadedFile.size > maxFileSize) {
|
||||
UploadFileNotification(t);
|
||||
event.target.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const fileType = event.target?.files?.[0].type;
|
||||
const fileName = event.target?.files?.[0].name;
|
||||
setSelectedImage(URL.createObjectURL(uploadedFile));
|
||||
setfileType(fileType);
|
||||
setfileName(fileName);
|
||||
formik.setFieldValue("confirm_img", uploadedFile);
|
||||
formik.setFieldValue("confirm_img", uploadedFile).then(() => {
|
||||
formik.setFieldTouched("confirm_img", true);
|
||||
})
|
||||
setShowAddIcon(false);
|
||||
}
|
||||
};
|
||||
@@ -83,7 +92,6 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
formik.setFieldValue("approved_amount", formik.values.approved_amount)
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogContent>
|
||||
@@ -129,7 +137,7 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
/>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Typography>{t("UploadSystem.upload_file")}{t("UploadSystem.optional")}</Typography>
|
||||
<Typography>{t("PassengerBoss.upload_file")}</Typography>
|
||||
<UploadSystem
|
||||
selectedImage={selectedImage}
|
||||
handleUploadChange={handleUploadChange} // Pass the updated function directly
|
||||
@@ -142,7 +150,17 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
imageSize={[250, 150]}
|
||||
setShowAddIcon={setShowAddIcon}
|
||||
showAddIcon={showAddIcon}
|
||||
onBlur={() => formik.handleBlur("confirm_img")}
|
||||
error={
|
||||
formik.touched.confirm_img &&
|
||||
Boolean(formik.errors.confirm_img)
|
||||
}
|
||||
/>
|
||||
<FormHelperText
|
||||
error={Boolean(formik.errors.confirm_img)}
|
||||
>
|
||||
{formik.touched.confirm_img && formik.errors.confirm_img}
|
||||
</FormHelperText>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {Dialog, DialogTitle, IconButton, Tooltip} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import ThumbUpAltIcon from "@mui/icons-material/ThumbUpAlt";
|
||||
import {useState} from "react";
|
||||
import ConfirmContent from "./ConfirmContent";
|
||||
import CallMadeIcon from '@mui/icons-material/CallMade';
|
||||
|
||||
const Confirm = ({rowId, mutate}) => {
|
||||
const Confirm = ({rowId, mutate, vehicle_type}) => {
|
||||
const t = useTranslations();
|
||||
const [openConfirmDialog, setOpenConfirmDialog] = useState(false);
|
||||
return (
|
||||
@@ -16,13 +16,14 @@ const Confirm = ({rowId, mutate}) => {
|
||||
setOpenConfirmDialog(true)
|
||||
}}
|
||||
>
|
||||
<ThumbUpAltIcon/>
|
||||
<CallMadeIcon/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog fullWidth open={openConfirmDialog}
|
||||
PaperProps={{sx: {boxShadow: 'rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px'}}}>
|
||||
<DialogTitle>{t("ConfirmDialog.confirm")}</DialogTitle>
|
||||
<ConfirmContent mutate={mutate} rowId={rowId} setOpenConfirmDialog={setOpenConfirmDialog}/>
|
||||
<DialogTitle>{t("PassengerBoss.confirm")}</DialogTitle>
|
||||
<ConfirmContent mutate={mutate} rowId={rowId} setOpenConfirmDialog={setOpenConfirmDialog}
|
||||
vehicle_type={vehicle_type}/>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -29,7 +29,7 @@ const RejectContent = ({rowId, mutate, setOpenRejectDialog}) => {
|
||||
validationSchema,
|
||||
onSubmit: (values, {setSubmitting}) => {
|
||||
const formData = new FormData();
|
||||
formData.append("description", values.description);
|
||||
formData.append("expert_description", values.description);
|
||||
if (values.reject_img != null) formData.append("attachment", values.reject_img);
|
||||
|
||||
requestServer(`${REJECT_PASSENGER_BOSS}/${rowId}`, 'post', {
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
import PrintablePage from "@/core/components/PrintablePage";
|
||||
import {Box, Grid, Stack, Typography} from "@mui/material";
|
||||
|
||||
const Content = ({data}) => {
|
||||
return (
|
||||
<>
|
||||
<PrintablePage key={data.id} header={true} footer={true}>
|
||||
<Box sx={{mt: 6}}>
|
||||
<Typography
|
||||
sx={{
|
||||
fontFamily: 'Bnazanin',
|
||||
fontWeight: 'bold',
|
||||
fontSize: '1.2rem',
|
||||
textAlign: 'center'
|
||||
}}> {'فرم صورتجلسه تعیین مبلغ نهایی اعتبار'} </Typography>
|
||||
</Box>
|
||||
<Box textAlign={'justify'} sx={{lineHeight: '1.8', mt: 2}}>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', pl: 4}}> {'با توجه به نامه شماره'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>35482</Typography>
|
||||
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'مورخ'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>1402/08/20</Typography>
|
||||
<Typography
|
||||
sx={{fontFamily: 'Bnazanin'}}
|
||||
component={'span'}> {'معاون محترم برنامه ریزی سازمان راهداری و حمل و نقل جاده ای در خصوص تسهیلات بند (الف) تبصره (18) قانون بودجه سال'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>1402</Typography>
|
||||
<Typography
|
||||
sx={{fontFamily: 'Bnazanin'}}
|
||||
component={'span'}> {'موضوع بازسازی ناوگان اتوبوس و مینی بوس و نیز تصویب آن درشورای برنامه ریزی و توسعه استان'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>تهران</Typography>
|
||||
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'و نامه شماره'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>35482</Typography>
|
||||
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'مورخ'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>1402/08/20</Typography>
|
||||
<Typography
|
||||
sx={{fontFamily: 'Bnazanin'}}
|
||||
component={'span'}> {'اداره کل راهداری و حمل و نقل جاده ای'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>تهران</Typography>
|
||||
<Typography
|
||||
sx={{fontFamily: 'Bnazanin'}}
|
||||
component={'span'}> {'در خصوص استفاده از ظرفیت انجمن های صنفی رانندگان در تاریخ'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>1402/08/20</Typography>
|
||||
<Typography
|
||||
sx={{fontFamily: 'Bnazanin'}}
|
||||
component={'span'}> {'از ناوگان اتوبوس/مینی بوس به شماره پلاک انتظامی'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>25پ4582</Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin'}}> {'و شماره هوشمند ناوگان'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>5875698725</Typography>
|
||||
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'به مالکیت آقای'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>امیرحسین محمودی</Typography>
|
||||
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'با کد ملی'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>0311318897</Typography>
|
||||
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'و شماره تماس'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>09120630655</Typography>
|
||||
<Typography component={'span'} sx={{fontFamily: 'Bnazanin'}}> {'ساکن شهرستان'} </Typography>
|
||||
<Typography component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>تهران</Typography>
|
||||
<Typography
|
||||
sx={{fontFamily: 'Bnazanin'}}
|
||||
component={'span'}> {'در جلسه مطرح شد که نتیجه آن به شرح ذیل مورد تایید می باشد.'} </Typography>
|
||||
</Box>
|
||||
<Box sx={{border: 1, borderRadius: 1, mt: 3, p: 2}}>
|
||||
<Typography textAlign={'justify'}
|
||||
sx={{fontFamily: 'Bnazanin'}}> {'با توجه به گزارش واصله از اداره ماشین آلات (نامه در پیوست موجود می باشد) جهت بازسازی ناوگان مذکور مبلغ .............................................. ریال معادل .............................................. تومان مورد تایید می باشد.'} </Typography>
|
||||
</Box>
|
||||
<Grid container columns={4} sx={{border: 1, borderRadius: 1, mt: 3}}>
|
||||
<Grid item xs={1} sx={{borderRight: 1, aspectRatio: '3 / 1'}}>
|
||||
<Stack alignItems={'center'}>
|
||||
<Typography variant={'caption'} sx={{fontFamily: 'Bnazanin'}}>رئیس انجمن</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={1} sx={{borderRight: 1, aspectRatio: '3 / 1'}}>
|
||||
<Stack alignItems={'center'}>
|
||||
<Typography variant={'caption'} sx={{fontFamily: 'Bnazanin'}}>رئیس اداره مسافر</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={1} sx={{borderRight: 1, aspectRatio: '3 / 1'}}>
|
||||
|
||||
</Grid>
|
||||
<Grid item xs={1} sx={{aspectRatio: '3 / 1'}}>
|
||||
|
||||
</Grid>
|
||||
<Grid item xs={1} sx={{borderRight: 1, borderTop: 1, aspectRatio: '2 / 1'}}>
|
||||
<Stack alignItems={'center'} justifyContent={'center'} sx={{height: '100%'}}>
|
||||
<Typography variant={'caption'} sx={{fontFamily: 'Bnazanin'}}>امضاء</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={1} sx={{borderRight: 1, borderTop: 1, aspectRatio: '2 / 1'}}>
|
||||
<Stack alignItems={'center'} justifyContent={'center'} sx={{height: '100%'}}>
|
||||
<Typography variant={'caption'} sx={{fontFamily: 'Bnazanin'}}>امضاء</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={1} sx={{borderRight: 1, borderTop: 1, aspectRatio: '2 / 1'}}>
|
||||
<Stack alignItems={'center'} justifyContent={'center'} sx={{height: '100%'}}>
|
||||
<Typography variant={'caption'} sx={{fontFamily: 'Bnazanin'}}>امضاء</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={1} sx={{borderTop: 1, aspectRatio: '2 / 1'}}>
|
||||
<Stack alignItems={'center'} justifyContent={'center'} sx={{height: '100%'}}>
|
||||
<Typography variant={'caption'} sx={{fontFamily: 'Bnazanin'}}>امضاء</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</PrintablePage>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Content
|
||||
@@ -0,0 +1,60 @@
|
||||
import {useEffect, useState} from "react";
|
||||
import CenterLayout from "@/layouts/CenterLayout";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {Stack, Typography} from "@mui/material";
|
||||
import Content from "@/components/dashboard/passenger-boss/Prints/final-credit-amount/Content";
|
||||
|
||||
const FinalCreditAmountComponent = () => {
|
||||
const t = useTranslations()
|
||||
const [data, setData] = useState(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [isNotData, setIsNotData] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
if (sessionStorage.getItem('final-credit-amount-print') === null) {
|
||||
setLoading(false)
|
||||
setIsNotData(true)
|
||||
sessionStorage.setItem('final-credit-amount-print', 'seen')
|
||||
} else if (sessionStorage.getItem('final-credit-amount-print') === 'seen') {
|
||||
setLoading(false)
|
||||
setData(null)
|
||||
setIsNotData(true)
|
||||
} else if (data) {
|
||||
setLoading(false)
|
||||
sessionStorage.setItem('final-credit-amount-print', 'seen')
|
||||
setIsNotData(false)
|
||||
} else {
|
||||
setLoading(true)
|
||||
setData(JSON.parse(sessionStorage.getItem('final-credit-amount-print')))
|
||||
setIsNotData(true)
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<Stack sx={{bgcolor: '#efefef', width: isNotData ? '100%' : 'auto', height: isNotData ? '100%' : 'auto'}}>
|
||||
{loading ? (
|
||||
<CenterLayout>
|
||||
<Stack>
|
||||
<Typography variant={'h5'}>
|
||||
{t('print_loading')}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</CenterLayout>
|
||||
) : !data ? (
|
||||
<CenterLayout>
|
||||
<Typography variant={'h5'}>
|
||||
{t('data_not_found')}
|
||||
</Typography>
|
||||
</CenterLayout>
|
||||
) : !data.length ? (
|
||||
<CenterLayout>
|
||||
<Typography variant={'h5'}>
|
||||
{t('data_not_found')}
|
||||
</Typography>
|
||||
</CenterLayout>
|
||||
) : data.map(item => <Content key={item.id} data={item}/>)}
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
export default FinalCreditAmountComponent
|
||||
@@ -8,6 +8,7 @@ const TableRow = ({row, mutate}) => {
|
||||
<Box sx={{display: "flex", flexWrap: "nowrap", gap: "8px"}}>
|
||||
<Confirm
|
||||
rowId={row.getValue("id")}
|
||||
vehicle_type={row.original.vehicle_type}
|
||||
mutate={mutate}
|
||||
/>
|
||||
<Reject
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import {Stack, Tooltip} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
|
||||
function TableToolbar() {
|
||||
const t = useTranslations();
|
||||
return (
|
||||
<Stack direction={"row"} spacing={2}>
|
||||
<Tooltip title={t("add")} arrow placement="right"></Tooltip>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default TableToolbar;
|
||||
12
src/components/dashboard/passenger-boss/TableToolbar.jsx
Normal file
12
src/components/dashboard/passenger-boss/TableToolbar.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import {Stack} from "@mui/material";
|
||||
import PrintFinalCreditAmount from "@/components/dashboard/passenger-boss/Buttons/printFinalCreditAmount";
|
||||
|
||||
const TableToolbar = () => {
|
||||
return (
|
||||
<Stack direction={"row"} spacing={2}>
|
||||
<PrintFinalCreditAmount/>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
export default TableToolbar
|
||||
@@ -6,6 +6,7 @@ import TableRowActions from "./TableRowActions";
|
||||
import moment from "jalali-moment";
|
||||
import DataTable from "@/core/components/DataTable";
|
||||
import MuiDatePicker from "@/core/components/MuiDatePicker";
|
||||
import TableToolbar from "@/components/dashboard/passenger-boss/TableToolbar";
|
||||
|
||||
function DashboardPassengerOfficeComponent() {
|
||||
const t = useTranslations();
|
||||
@@ -161,7 +162,8 @@ function DashboardPassengerOfficeComponent() {
|
||||
tableUrl={GET_PASSENGER_BOSS}
|
||||
columns={columns}
|
||||
selectableRow={false}
|
||||
enableCustomToolbar={false}
|
||||
enableCustomToolbar={true}
|
||||
CustomToolbar={TableToolbar}
|
||||
enableLastUpdate={true}
|
||||
sorting={[{
|
||||
id: 'id', desc: false
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import UploadSystem from "@/core/components/UploadSystem";
|
||||
import useNotification from "@/lib/app/hooks/useNotification";
|
||||
import useRequest from "@/lib/app/hooks/useRequest";
|
||||
import {Button, DialogActions, DialogContent, Stack, TextField, Typography} from "@mui/material"
|
||||
import {Button, DialogActions, DialogContent, FormHelperText, Stack, TextField, Typography} from "@mui/material"
|
||||
import {useFormik} from "formik";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {CONFIRM_PASSENGER_OFFICE} from "@/core/data/apiRoutes";
|
||||
import UploadFileNotification from "@/core/components/notifications/UploadFileNotification";
|
||||
import {useState} from "react";
|
||||
import * as Yup from "yup";
|
||||
|
||||
const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
const t = useTranslations();
|
||||
@@ -17,11 +17,24 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
const requestServer = useRequest({auth: true})
|
||||
const {update_notification} = useNotification()
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
confirm_img: Yup.mixed().notRequired(t("ConfirmDialog.approved_amount_error"))
|
||||
.test('fileSize', `${t("PassengerOffice.upload_file_unit")}`, (value) => {
|
||||
if (!value) return true
|
||||
return value.size <= 2 * 1024 * 1024;
|
||||
})
|
||||
.test('fileType', `${t("PassengerOffice.upload_file_format")}`, (value) => {
|
||||
if (!value) return true
|
||||
const allowedTypes = ['image/jpg', 'image/jpeg', 'image/png', 'application/pdf']; // Define your allowed file types
|
||||
return allowedTypes.includes(value.type);
|
||||
})
|
||||
})
|
||||
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
description: "",
|
||||
confirm_img: null
|
||||
},
|
||||
}, validationSchema,
|
||||
onSubmit: (values, {setSubmitting}) => {
|
||||
const formData = new FormData();
|
||||
if (values.description != "") formData.append("description", values.description);
|
||||
@@ -47,19 +60,14 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
const handleUploadChange = (event) => {
|
||||
const uploadedFile = event.target?.files?.[0];
|
||||
if (uploadedFile) {
|
||||
const maxFileSize = 2 * 1024 * 1024;
|
||||
if (uploadedFile.size > maxFileSize) {
|
||||
UploadFileNotification(t);
|
||||
event.target.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const fileType = event.target?.files?.[0].type;
|
||||
const fileName = event.target?.files?.[0].name;
|
||||
setSelectedImage(URL.createObjectURL(uploadedFile));
|
||||
setfileType(fileType);
|
||||
setfileName(fileName);
|
||||
formik.setFieldValue("confirm_img", uploadedFile);
|
||||
formik.setFieldValue("confirm_img", uploadedFile).then(() => {
|
||||
formik.setFieldTouched("confirm_img", true);
|
||||
})
|
||||
setShowAddIcon(false);
|
||||
}
|
||||
};
|
||||
@@ -96,7 +104,17 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
imageSize={[250, 150]}
|
||||
setShowAddIcon={setShowAddIcon}
|
||||
showAddIcon={showAddIcon}
|
||||
onBlur={() => formik.handleBlur("confirm_img")}
|
||||
error={
|
||||
formik.touched.confirm_img &&
|
||||
Boolean(formik.errors.confirm_img)
|
||||
}
|
||||
/>
|
||||
<FormHelperText
|
||||
error={Boolean(formik.errors.confirm_img)}
|
||||
>
|
||||
{formik.touched.confirm_img && formik.errors.confirm_img}
|
||||
</FormHelperText>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
@@ -106,7 +124,7 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
{t("ConfirmDialog.button-cancel")}
|
||||
</Button>
|
||||
<Button onClick={formik.handleSubmit} variant="contained" color="primary"
|
||||
disabled={formik.isSubmitting}>
|
||||
disabled={formik.isSubmitting || !formik.isValid}>
|
||||
{t("ConfirmDialog.button-confirm")}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import {Dialog, DialogTitle, IconButton, Tooltip} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import ThumbUpAltIcon from "@mui/icons-material/ThumbUpAlt";
|
||||
import {useState} from "react";
|
||||
import ConfirmContent from "./ConfirmContent";
|
||||
|
||||
import CallMadeIcon from '@mui/icons-material/CallMade';
|
||||
const Confirm = ({rowId, mutate}) => {
|
||||
const t = useTranslations();
|
||||
const [openConfirmDialog, setOpenConfirmDialog] = useState(false);
|
||||
@@ -16,12 +15,12 @@ const Confirm = ({rowId, mutate}) => {
|
||||
setOpenConfirmDialog(true)
|
||||
}}
|
||||
>
|
||||
<ThumbUpAltIcon/>
|
||||
<CallMadeIcon/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog fullWidth open={openConfirmDialog}
|
||||
PaperProps={{sx: {boxShadow: 'rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px'}}}>
|
||||
<DialogTitle>{t("ConfirmDialog.confirm")}</DialogTitle>
|
||||
<DialogTitle>{t("PassengerOffice.confirm")}</DialogTitle>
|
||||
<ConfirmContent mutate={mutate} rowId={rowId} setOpenConfirmDialog={setOpenConfirmDialog}/>
|
||||
</Dialog>
|
||||
</>
|
||||
|
||||
@@ -29,7 +29,7 @@ const RejectContent = ({rowId, mutate, setOpenRejectDialog}) => {
|
||||
validationSchema,
|
||||
onSubmit: (values, {setSubmitting}) => {
|
||||
const formData = new FormData();
|
||||
formData.append("description", values.description);
|
||||
formData.append("expert_description", values.description);
|
||||
if (values.reject_img != null) formData.append("attachment", values.reject_img);
|
||||
|
||||
requestServer(`${REJECT_PASSENGER_OFFICE}/${rowId}`, 'post', {
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import {Stack, Tooltip} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
|
||||
function TableToolbar() {
|
||||
const t = useTranslations();
|
||||
return (
|
||||
<Stack direction={"row"} spacing={2}>
|
||||
<Tooltip title={t("add")} arrow placement="right"></Tooltip>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default TableToolbar;
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Checkbox, CircularProgress,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
FormControl,
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
FormLabel,
|
||||
Grid,
|
||||
Stack,
|
||||
TextField
|
||||
TextField, Typography
|
||||
} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import useRequest from "@/lib/app/hooks/useRequest";
|
||||
@@ -21,7 +21,7 @@ import usePermissions from "@/lib/app/hooks/usePermissions";
|
||||
const CreateContent = ({mutate, setOpenConfirmDialog}) => {
|
||||
const t = useTranslations();
|
||||
const requestServer = useRequest({auth: true})
|
||||
const {permissions_list} = usePermissions()
|
||||
const {permissions_list, isLoading} = usePermissions()
|
||||
const validationSchema = Yup.object().shape({
|
||||
name: Yup.string().required(t("AddDialog.name_error")),
|
||||
name_fa: Yup.string().required(t("AddDialog.name_fa_error")),
|
||||
@@ -93,13 +93,22 @@ const CreateContent = ({mutate, setOpenConfirmDialog}) => {
|
||||
sx={{mt: 2}}
|
||||
>
|
||||
<FormHelperText>{formik.touched.permissions && formik.errors.permissions}</FormHelperText>
|
||||
{isLoading ?
|
||||
<Stack direction={'row'} alignItems={'center'} spacing={2}
|
||||
justifyContent={'center'}>
|
||||
<CircularProgress size={20}/>
|
||||
<Typography
|
||||
variant={'caption'}>{t("AddDialog.loading_permissions_list")}</Typography>
|
||||
</Stack>
|
||||
: (
|
||||
<Grid container spacing={2}>
|
||||
{permissions_list ? (
|
||||
permissions_list.map((permission) => (
|
||||
<Grid key={permission.id} item xs={6}>
|
||||
<>
|
||||
{permissions_list.map((permission, index) => (
|
||||
<Grid key={permission.id} item xs={6} data-testid= "PermissionList-checkbox">
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
data-testid= {`PermissionList-checkbox-${index}`}
|
||||
checked={formik.values.permissions.includes(permission.id)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
@@ -113,9 +122,11 @@ const CreateContent = ({mutate, setOpenConfirmDialog}) => {
|
||||
label={permission.name_fa}
|
||||
/>
|
||||
</Grid>
|
||||
))
|
||||
) : null}
|
||||
))}
|
||||
</>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
</FormControl>
|
||||
</FormLabel>
|
||||
</Stack>
|
||||
|
||||
@@ -24,6 +24,7 @@ const DeleteForm = ({rowId, mutate}) => {
|
||||
const handleSubmit = () => {
|
||||
setIsSubmitting(true)
|
||||
requestServer(`${DELETE_ROLE_MANAGEMENT}/${rowId}`, 'delete').then((response) => {
|
||||
setOpenConfirmDialog(false)
|
||||
mutate()
|
||||
update_notification()
|
||||
}).catch(() => {
|
||||
|
||||
@@ -4,7 +4,7 @@ import usePermissions from "@/lib/app/hooks/usePermissions";
|
||||
import useRequest from "@/lib/app/hooks/useRequest";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Checkbox, CircularProgress,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
FormControl,
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
FormLabel,
|
||||
Grid,
|
||||
Stack,
|
||||
TextField
|
||||
TextField, Typography
|
||||
} from "@mui/material"
|
||||
import {useFormik} from "formik";
|
||||
import {useTranslations} from "next-intl";
|
||||
@@ -23,7 +23,7 @@ const UpdateContent = ({row, mutate, setOpenConfirmDialog}) => {
|
||||
const t = useTranslations();
|
||||
const requestServer = useRequest({auth: true})
|
||||
const {update_notification} = useNotification()
|
||||
const {permissions_list} = usePermissions()
|
||||
const {permissions_list, isLoading} = usePermissions()
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
name: Yup.string().required(t("UpdateDialog.name_error")),
|
||||
@@ -93,13 +93,22 @@ const UpdateContent = ({row, mutate, setOpenConfirmDialog}) => {
|
||||
sx={{mt: 2}}
|
||||
>
|
||||
<FormHelperText>{formik.touched.permissions && formik.errors.permissions}</FormHelperText>
|
||||
{isLoading ?
|
||||
<Stack direction={'row'} alignItems={'center'} spacing={2}
|
||||
justifyContent={'center'}>
|
||||
<CircularProgress size={20}/>
|
||||
<Typography
|
||||
variant={'caption'}>{t("UpdateDialog.loading_permissions_list")}</Typography>
|
||||
</Stack>
|
||||
: (
|
||||
<Grid container spacing={2}>
|
||||
{permissions_list ? (
|
||||
permissions_list.map((permission) => (
|
||||
<>
|
||||
{permissions_list.map((permission) => (
|
||||
<Grid key={permission.id} item xs={6}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
data-testid="PermissionList-checkbox"
|
||||
checked={formik.values.permissions.includes(permission.id)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
@@ -113,9 +122,11 @@ const UpdateContent = ({row, mutate, setOpenConfirmDialog}) => {
|
||||
label={permission.name_fa}
|
||||
/>
|
||||
</Grid>
|
||||
))
|
||||
) : null}
|
||||
))}
|
||||
</>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
</FormControl>
|
||||
</FormLabel>
|
||||
</Stack>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import UploadSystem from "@/core/components/UploadSystem";
|
||||
import useNotification from "@/lib/app/hooks/useNotification";
|
||||
import useRequest from "@/lib/app/hooks/useRequest";
|
||||
import {Button, DialogActions, DialogContent, Stack, TextField, Typography} from "@mui/material"
|
||||
import {Button, DialogActions, DialogContent, FormHelperText, Stack, TextField, Typography} from "@mui/material"
|
||||
import {useFormik} from "formik";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {CONFIRM_TRANSPORTATION_ASSISTANCE} from "@/core/data/apiRoutes";
|
||||
import UploadFileNotification from "@/core/components/notifications/UploadFileNotification";
|
||||
import {useState} from "react";
|
||||
import * as Yup from "yup";
|
||||
|
||||
const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
const t = useTranslations();
|
||||
@@ -17,14 +17,27 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
const requestServer = useRequest({auth: true})
|
||||
const {update_notification} = useNotification()
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
confirm_img: Yup.mixed().notRequired(t("ConfirmDialog.approved_amount_error"))
|
||||
.test('fileSize', `${t("TransportationAssistance.upload_file_unit")}`, (value) => {
|
||||
if (!value) return true
|
||||
return value.size <= 2 * 1024 * 1024;
|
||||
})
|
||||
.test('fileType', `${t("TransportationAssistance.upload_file_format")}`, (value) => {
|
||||
if (!value) return true
|
||||
const allowedTypes = ['image/jpg', 'image/jpeg', 'image/png', 'application/pdf']; // Define your allowed file types
|
||||
return allowedTypes.includes(value.type);
|
||||
})
|
||||
})
|
||||
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
description: "",
|
||||
confirm_img: null
|
||||
},
|
||||
}, validationSchema,
|
||||
onSubmit: (values, {setSubmitting}) => {
|
||||
const formData = new FormData();
|
||||
if (values.description != "") formData.append("description", values.description);
|
||||
if (values.description != "") formData.append("expert_description", values.description);
|
||||
if (values.confirm_img != null) formData.append("attachment", values.confirm_img);
|
||||
|
||||
requestServer(`${CONFIRM_TRANSPORTATION_ASSISTANCE}/${rowId}`, 'post', {
|
||||
@@ -47,19 +60,14 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
const handleUploadChange = (event) => {
|
||||
const uploadedFile = event.target?.files?.[0];
|
||||
if (uploadedFile) {
|
||||
const maxFileSize = 2 * 1024 * 1024;
|
||||
if (uploadedFile.size > maxFileSize) {
|
||||
UploadFileNotification(t);
|
||||
event.target.value = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const fileType = event.target?.files?.[0].type;
|
||||
const fileName = event.target?.files?.[0].name;
|
||||
setSelectedImage(URL.createObjectURL(uploadedFile));
|
||||
setfileType(fileType);
|
||||
setfileName(fileName);
|
||||
formik.setFieldValue("confirm_img", uploadedFile);
|
||||
formik.setFieldValue("confirm_img", uploadedFile).then(() => {
|
||||
formik.setFieldTouched("confirm_img", true);
|
||||
})
|
||||
setShowAddIcon(false);
|
||||
}
|
||||
};
|
||||
@@ -96,7 +104,17 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
imageSize={[250, 150]}
|
||||
setShowAddIcon={setShowAddIcon}
|
||||
showAddIcon={showAddIcon}
|
||||
onBlur={() => formik.handleBlur("confirm_img")}
|
||||
error={
|
||||
formik.touched.confirm_img &&
|
||||
Boolean(formik.errors.confirm_img)
|
||||
}
|
||||
/>
|
||||
<FormHelperText
|
||||
error={Boolean(formik.errors.confirm_img)}
|
||||
>
|
||||
{formik.touched.confirm_img && formik.errors.confirm_img}
|
||||
</FormHelperText>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
@@ -106,7 +124,7 @@ const ConfirmContent = ({rowId, mutate, setOpenConfirmDialog}) => {
|
||||
{t("ConfirmDialog.button-cancel")}
|
||||
</Button>
|
||||
<Button onClick={formik.handleSubmit} variant="contained" color="primary"
|
||||
disabled={formik.isSubmitting || !formik.dirty || !formik.isValid}>
|
||||
disabled={formik.isSubmitting || !formik.isValid}>
|
||||
{t("ConfirmDialog.button-confirm")}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import {Dialog, DialogTitle, IconButton, Tooltip} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import ThumbUpAltIcon from "@mui/icons-material/ThumbUpAlt";
|
||||
import {useState} from "react";
|
||||
import ConfirmContent from "./ConfirmContent";
|
||||
|
||||
import CallMadeIcon from '@mui/icons-material/CallMade';
|
||||
const Confirm = ({rowId, mutate}) => {
|
||||
const t = useTranslations();
|
||||
const [openConfirmDialog, setOpenConfirmDialog] = useState(false);
|
||||
@@ -16,12 +15,12 @@ const Confirm = ({rowId, mutate}) => {
|
||||
setOpenConfirmDialog(true)
|
||||
}}
|
||||
>
|
||||
<ThumbUpAltIcon/>
|
||||
<CallMadeIcon/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Dialog fullWidth open={openConfirmDialog}
|
||||
PaperProps={{sx: {boxShadow: 'rgba(60, 64, 67, 0.3) 0px 1px 16px 0px, rgba(60, 64, 67, 0.15) 0px 1px 10px 0px'}}}>
|
||||
<DialogTitle>{t("ConfirmDialog.confirm")}</DialogTitle>
|
||||
<DialogTitle>{t("TransportationAssistance.confirm")}</DialogTitle>
|
||||
<ConfirmContent mutate={mutate} rowId={rowId} setOpenConfirmDialog={setOpenConfirmDialog}/>
|
||||
</Dialog>
|
||||
</>
|
||||
|
||||
@@ -29,7 +29,7 @@ const RejectContent = ({rowId, mutate, setOpenRejectDialog}) => {
|
||||
validationSchema,
|
||||
onSubmit: (values, {setSubmitting}) => {
|
||||
const formData = new FormData();
|
||||
formData.append("description", values.description);
|
||||
formData.append("expert_description", values.description);
|
||||
if (values.reject_img != null) formData.append("attachment", values.reject_img);
|
||||
|
||||
requestServer(`${REJECT_TRANSPORTATION_ASSISTANCE}/${rowId}`, 'post', {
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import {Stack, Tooltip} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
|
||||
function TableToolbar() {
|
||||
const t = useTranslations();
|
||||
return (
|
||||
<Stack direction={"row"} spacing={2}>
|
||||
<Tooltip title={t("add")} arrow placement="right"></Tooltip>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export default TableToolbar;
|
||||
61
src/components/first/__tests__/index.test.js
Normal file
61
src/components/first/__tests__/index.test.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import {render, screen, waitFor} from "@testing-library/react";
|
||||
import FirstComponent from "@/components/first";
|
||||
import MockAppWithProviders from "../../../../mocks/AppWithProvider";
|
||||
import {server} from "../../../../mocks/server";
|
||||
import {rest} from "msw";
|
||||
import {GET_USER_ROUTE} from "@/core/data/apiRoutes";
|
||||
|
||||
describe("First Component From First Page", () => {
|
||||
describe("Rendering", () => {
|
||||
it("App Name Text Rendered", () => {
|
||||
render(<MockAppWithProviders><FirstComponent/></MockAppWithProviders>);
|
||||
const appNameElement = screen.queryByText(/سامانه جامع تسهیلات/i);
|
||||
expect(appNameElement).toBeInTheDocument()
|
||||
});
|
||||
it("App version Text Rendered", () => {
|
||||
render(<MockAppWithProviders><FirstComponent/></MockAppWithProviders>);
|
||||
const versionControler = screen.queryByText(process.env.NEXT_PUBLIC_API_VERSION, {exact: false});
|
||||
expect(versionControler).toBeInTheDocument()
|
||||
});
|
||||
it("Powered By Rendered With Currect URL", () => {
|
||||
render(<MockAppWithProviders><FirstComponent/></MockAppWithProviders>);
|
||||
const linkElement = screen.queryByText('توسعه یافته توسط وایتل');
|
||||
expect(linkElement).toBeInTheDocument()
|
||||
expect(linkElement).toHaveAttribute('href', process.env.NEXT_PUBLIC_POWERED_BY_URL);
|
||||
});
|
||||
});
|
||||
describe("Behavioral", () => {
|
||||
it("Show Login Button And Do Not Show Dashboard Button When User Is Not Authenticated", async () => {
|
||||
render(<MockAppWithProviders><FirstComponent/></MockAppWithProviders>);
|
||||
await waitFor(() => {
|
||||
const authenticationButtonLogin = screen.queryByText(/ورود کارشناس/i)
|
||||
const authenticationButtonDashboard = screen.queryByText(/داشبورد/i)
|
||||
expect(authenticationButtonLogin).toBeInTheDocument()
|
||||
expect(authenticationButtonDashboard).not.toBeInTheDocument()
|
||||
})
|
||||
});
|
||||
it("Show Dashboard Button And Do Not Show Login Button When User Is Authenticated", async () => {
|
||||
localStorage.setItem("_token", 'token');
|
||||
render(<MockAppWithProviders><FirstComponent/></MockAppWithProviders>);
|
||||
await waitFor(() => {
|
||||
const authenticationButtonLogin = screen.queryByText(/ورود کارشناس/i)
|
||||
const authenticationButtonDashboard = screen.queryByText(/داشبورد/i)
|
||||
expect(authenticationButtonLogin).not.toBeInTheDocument()
|
||||
expect(authenticationButtonDashboard).toBeInTheDocument()
|
||||
})
|
||||
});
|
||||
it("Show Login Button And Do Not Show Dashboard Button When User Authentication Is Expired", async () => {
|
||||
localStorage.setItem("_token", 'token');
|
||||
server.use(rest.get(GET_USER_ROUTE, (req, res, ctx) => {
|
||||
return res(ctx.status(401))
|
||||
}))
|
||||
render(<MockAppWithProviders><FirstComponent/></MockAppWithProviders>);
|
||||
await waitFor(() => {
|
||||
const authenticationButtonLogin = screen.queryByText(/ورود کارشناس/i)
|
||||
const authenticationButtonDashboard = screen.queryByText(/داشبورد/i)
|
||||
expect(authenticationButtonLogin).toBeInTheDocument()
|
||||
expect(authenticationButtonDashboard).not.toBeInTheDocument()
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,10 @@
|
||||
import {NextLinkComposed} from "@/core/components/LinkRouting";
|
||||
import LinkRouting, {NextLinkComposed} from "@/core/components/LinkRouting";
|
||||
import CenterLayout from "@/layouts/CenterLayout";
|
||||
import FullPageLayout from "@/layouts/FullPageLayout";
|
||||
import useUser from "@/lib/app/hooks/useUser";
|
||||
import {Button, Stack, Typography} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import SvgDashboard from "@/core/components/svgs/SvgDashboard";
|
||||
import process from "next/dist/build/webpack/loaders/resolve-url-loader/lib/postcss";
|
||||
|
||||
const FirstComponent = () => {
|
||||
const t = useTranslations();
|
||||
@@ -18,28 +17,16 @@ const FirstComponent = () => {
|
||||
<Typography variant="h5" sx={{textAlign: "center"}}>
|
||||
{t("app_name")}
|
||||
</Typography>
|
||||
{isAuth ? (
|
||||
<Button
|
||||
variant="outlined"
|
||||
data-testid="button-login-or-dashboard"
|
||||
variant={isAuth ? "outlined" : "contained"}
|
||||
component={NextLinkComposed}
|
||||
to={{
|
||||
pathname: "/dashboard",
|
||||
pathname: isAuth ? "/dashboard" : "/login-expert",
|
||||
}}
|
||||
>
|
||||
{t("dashboard")}
|
||||
{isAuth ? t("dashboard") : t("login_expert")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
sx={{mx: 2}}
|
||||
variant="contained"
|
||||
component={NextLinkComposed}
|
||||
to={{
|
||||
pathname: "/login-expert",
|
||||
}}
|
||||
>
|
||||
{t("login_expert")}
|
||||
</Button>
|
||||
)}
|
||||
</CenterLayout>
|
||||
<Stack direction="row" alignItems="center" justifyContent="center">
|
||||
<Typography variant={"caption"}
|
||||
@@ -47,7 +34,19 @@ const FirstComponent = () => {
|
||||
color: 'primary.main',
|
||||
fontFamily: 'Arial',
|
||||
fontWeight: 'bold'
|
||||
}}>v{process.env.NEXT_PUBLIC_API_VERSION}</Typography>
|
||||
}}
|
||||
>
|
||||
v{process.env.NEXT_PUBLIC_API_VERSION}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack direction="row" alignItems="center" justifyContent="center">
|
||||
<LinkRouting
|
||||
sx={{margin: 0.5, fontSize: "14px"}}
|
||||
href={process.env.NEXT_PUBLIC_POWERED_BY_URL}
|
||||
target="_blank"
|
||||
>
|
||||
{t("powered_by_witel")}
|
||||
</LinkRouting>
|
||||
</Stack>
|
||||
</FullPageLayout>
|
||||
);
|
||||
|
||||
65
src/components/layouts/Dashboard/Content/index.jsx
Normal file
65
src/components/layouts/Dashboard/Content/index.jsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import LoadingHardPage from "@/core/components/LoadingHardPage";
|
||||
import RolePermissionMiddleware from "@/middlewares/RolePermission";
|
||||
import BreadCrumbs from "@/components/layouts/Dashboard/Breadcrumbs";
|
||||
import FullPageLayout from "@/layouts/FullPageLayout";
|
||||
import {useEffect, useMemo, useState} from "react";
|
||||
import sidebarMenu from "@/core/data/sidebarMenu";
|
||||
import {useRouter} from "next/router";
|
||||
import {useTranslations} from "next-intl";
|
||||
|
||||
const Content = (props) => {
|
||||
const t = useTranslations()
|
||||
const router = useRouter()
|
||||
const [routing, setRouting] = useState(false)
|
||||
const [routingItem, setRoutingItem] = useState({})
|
||||
const pageList = useMemo(() => {
|
||||
const list = []
|
||||
for (const menu of sidebarMenu) {
|
||||
for (const item of menu) {
|
||||
if (item.type === 'menu') {
|
||||
for (const subItem of item.subItem) {
|
||||
list.push(subItem)
|
||||
}
|
||||
} else {
|
||||
list.push(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const handlerStartRoute = (url) => {
|
||||
setRoutingItem(pageList.find(page => page.route === url))
|
||||
setRouting(true)
|
||||
}
|
||||
const handlerCompleteRoute = () => {
|
||||
setRouting(false)
|
||||
}
|
||||
|
||||
router.events.on('routeChangeStart', handlerStartRoute)
|
||||
router.events.on('routeChangeComplete', handlerCompleteRoute)
|
||||
|
||||
return () => {
|
||||
router.events.off('routeChangeStart', handlerStartRoute)
|
||||
router.events.off('routeChangeComplete', handlerCompleteRoute)
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
return (
|
||||
<FullPageLayout sx={{mt: 3, position: 'relative'}}>
|
||||
<LoadingHardPage icon={routingItem?.icon}
|
||||
label={routingItem?.key ? `${t('routing_to')} ${t(routingItem?.key)}` : ''}
|
||||
loading={routing} width={100} height={100}
|
||||
sx={{position: "absolute", bgcolor: "#fffc"}}>
|
||||
<RolePermissionMiddleware requiredPermissions={props.permissions}>
|
||||
<BreadCrumbs isVisible={true}/>
|
||||
{props.children}
|
||||
</RolePermissionMiddleware>
|
||||
</LoadingHardPage>
|
||||
</FullPageLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default Content
|
||||
@@ -1,5 +1,5 @@
|
||||
import MenuIcon from "@mui/icons-material/Menu";
|
||||
import {AppBar, Box, Container, CssBaseline, IconButton, Stack, Toolbar, useTheme} from "@mui/material";
|
||||
import {AppBar, Box, Container, IconButton, Stack, Toolbar, useTheme} from "@mui/material";
|
||||
import ProfileMenu from "./ProfileMenu";
|
||||
|
||||
function Header({drawerWidth, handleDrawerToggle}) {
|
||||
@@ -7,7 +7,6 @@ function Header({drawerWidth, handleDrawerToggle}) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<CssBaseline/>
|
||||
<AppBar
|
||||
position="fixed"
|
||||
sx={{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {Divider, List} from "@mui/material";
|
||||
import {useEffect, useReducer} from "react";
|
||||
import {Fragment, useEffect, useReducer, useState} from "react";
|
||||
import SidebarListItem from "./SidebarListItem";
|
||||
import sidebarMenu from "@/core/data/sidebarMenu";
|
||||
import {useRouter} from "next/router";
|
||||
@@ -10,18 +10,48 @@ function reducer(state, action) {
|
||||
case "COLLAPSE_MENU":
|
||||
return state.map((itemsArr) =>
|
||||
itemsArr.map((item) =>
|
||||
action.key == item.key
|
||||
? {...item, showSubItem: !item.showSubItem}
|
||||
: item
|
||||
action.key === item.key ? {...item, showSubItem: !item.showSubItem} : item
|
||||
)
|
||||
);
|
||||
case "SELECTED":
|
||||
return state.map((itemsArr) =>
|
||||
itemsArr.map((item) =>
|
||||
action.route == item.route
|
||||
? {...item, selected: true}
|
||||
: {...item, selected: false}
|
||||
)
|
||||
itemsArr.map((item) => {
|
||||
return item.type === "page"
|
||||
? {...item, selected: action.route === item.route, routing: false}
|
||||
: item.subItem && Array.isArray(item.subItem)
|
||||
? {
|
||||
...item,
|
||||
subItem: item.subItem.map((subitem) => ({
|
||||
...subitem,
|
||||
selected: subitem.route === action.route,
|
||||
routing: false
|
||||
})),
|
||||
showSubItem: item.subItem.some(
|
||||
(subitem) => subitem.selected
|
||||
),
|
||||
}
|
||||
: item;
|
||||
})
|
||||
);
|
||||
case "SELECTING":
|
||||
return state.map((itemsArr) =>
|
||||
itemsArr.map((item) => {
|
||||
return item.type === "page"
|
||||
? {...item, selected: action.route === item.route, routing: action.route === item.route}
|
||||
: item.subItem && Array.isArray(item.subItem)
|
||||
? {
|
||||
...item,
|
||||
subItem: item.subItem.map((subitem) => ({
|
||||
...subitem,
|
||||
selected: subitem.route === action.route,
|
||||
routing: subitem.route === action.route
|
||||
})),
|
||||
showSubItem: item.subItem.some(
|
||||
(subitem) => subitem.selected
|
||||
),
|
||||
}
|
||||
: item;
|
||||
})
|
||||
);
|
||||
default:
|
||||
throw new Error();
|
||||
@@ -32,26 +62,48 @@ export default function SidebarList({handleDrawerToggle}) {
|
||||
const [itemMenu, dispatch] = useReducer(reducer, sidebarMenu);
|
||||
const {user} = useUser();
|
||||
const router = useRouter();
|
||||
const [selectedKey, setSelectedKey] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({type: "SELECTED", route: router.pathname});
|
||||
}, [router.pathname]);
|
||||
setSelectedKey(router.pathname);
|
||||
const handlerStartRoute = (url) => {
|
||||
dispatch({type: "SELECTING", route: url});
|
||||
}
|
||||
|
||||
const filteredItemMenu = itemMenu[0].filter(
|
||||
(item) => user.permissions.includes(item.permission) || item.permission === "all"
|
||||
);
|
||||
router.events.on('routeChangeStart', handlerStartRoute)
|
||||
|
||||
return () => {
|
||||
router.events.off('routeChangeStart', handlerStartRoute)
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
selectedKey && document.querySelector(`[data-route="${selectedKey}"]`)?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "center"
|
||||
});
|
||||
}, [selectedKey]);
|
||||
|
||||
return (
|
||||
<List>
|
||||
{filteredItemMenu.map((item, index) => (
|
||||
<List dense={true} sx={{overflow: "scroll"}}>
|
||||
{itemMenu.map((itemArr, index) => (
|
||||
<Fragment key={index}>
|
||||
{itemArr.map((item) =>
|
||||
<Fragment key={item.key}>
|
||||
{(user?.permissions?.includes(item.permission) || item.permission === "all") &&
|
||||
<SidebarListItem
|
||||
item={item}
|
||||
dispatch={dispatch}
|
||||
key={item.key}
|
||||
handleDrawerToggle={handleDrawerToggle}
|
||||
/>
|
||||
/>}
|
||||
</Fragment>
|
||||
)}
|
||||
<Divider/>
|
||||
</Fragment>
|
||||
))}
|
||||
{filteredItemMenu.length > 0 && <Divider/>}
|
||||
</List>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import {NextLinkComposed} from "@/core/components/LinkRouting";
|
||||
import ExpandLess from "@mui/icons-material/ExpandLess";
|
||||
import ExpandMore from "@mui/icons-material/ExpandMore";
|
||||
import {Badge, IconButton, ListItem, ListItemButton, ListItemIcon, ListItemText, Typography,} from "@mui/material";
|
||||
import {
|
||||
Badge,
|
||||
CircularProgress,
|
||||
IconButton,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {Fragment} from "react";
|
||||
import SidebarListSubItem from "./SidebarListSubItem";
|
||||
@@ -9,11 +18,10 @@ import useNotification from "@/lib/app/hooks/useNotification";
|
||||
|
||||
const SidebarListItem = ({item, dispatch, handleDrawerToggle}) => {
|
||||
const t = useTranslations();
|
||||
const {notification_count} = useNotification()
|
||||
|
||||
return (
|
||||
<Fragment key={item.key}>
|
||||
<ListItem disablePadding secondaryAction={
|
||||
const {notification_count} = useNotification();
|
||||
const hasSubItems = item.type === "menu" && item.subItem && item.subItem.length > 0;
|
||||
const renderBadge = () => {
|
||||
return !hasSubItems ? (
|
||||
<IconButton>
|
||||
<Badge
|
||||
badgeContent={notification_count ? notification_count[item.name] : 0}
|
||||
@@ -25,7 +33,12 @@ const SidebarListItem = ({item, dispatch, handleDrawerToggle}) => {
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
}>
|
||||
) : null;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListItem data-route={item.route} disablePadding secondaryAction={renderBadge()}>
|
||||
<ListItemButton
|
||||
selected={item.selected}
|
||||
{...(item.type == "page" && {
|
||||
@@ -35,10 +48,9 @@ const SidebarListItem = ({item, dispatch, handleDrawerToggle}) => {
|
||||
},
|
||||
})}
|
||||
onClick={() => {
|
||||
if (item.type == "menu") {
|
||||
if (hasSubItems) {
|
||||
dispatch({type: "COLLAPSE_MENU", key: item.key});
|
||||
}
|
||||
handleDrawerToggle();
|
||||
}}
|
||||
sx={{
|
||||
minHeight: 48,
|
||||
@@ -50,10 +62,13 @@ const SidebarListItem = ({item, dispatch, handleDrawerToggle}) => {
|
||||
minWidth: 0,
|
||||
justifyContent: "center",
|
||||
color: "primary.main",
|
||||
width: 40,
|
||||
height: 24,
|
||||
pr: 2,
|
||||
}}
|
||||
>
|
||||
{item.icon}
|
||||
{item.routing ?
|
||||
<CircularProgress size={24} color="inherit"/> : item.icon}
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={t(item.key)}
|
||||
@@ -66,9 +81,7 @@ const SidebarListItem = ({item, dispatch, handleDrawerToggle}) => {
|
||||
}
|
||||
/>
|
||||
|
||||
|
||||
{item.type == "menu" &&
|
||||
(item.showSubItem ? <ExpandLess/> : <ExpandMore/>)}
|
||||
{hasSubItems && (item.showSubItem ? <ExpandLess/> : <ExpandMore/>)}
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
{item.subItem && (
|
||||
@@ -77,7 +90,7 @@ const SidebarListItem = ({item, dispatch, handleDrawerToggle}) => {
|
||||
handleDrawerToggle={handleDrawerToggle}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,18 +1,44 @@
|
||||
import {NextLinkComposed} from "@/core/components/LinkRouting";
|
||||
import InboxIcon from "@mui/icons-material/MoveToInbox";
|
||||
import {Collapse, List, ListItemButton, ListItemIcon, ListItemText,} from "@mui/material";
|
||||
import {
|
||||
Badge,
|
||||
CircularProgress,
|
||||
Collapse,
|
||||
IconButton,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {Fragment} from "react";
|
||||
import useNotification from "@/lib/app/hooks/useNotification";
|
||||
|
||||
const SidebarListSubItem = ({item, handleDrawerToggle}) => {
|
||||
const t = useTranslations();
|
||||
const {notification_count} = useNotification();
|
||||
const renderBadge = (subitem) => (
|
||||
<IconButton>
|
||||
<Badge
|
||||
badgeContent={notification_count ? notification_count[subitem.name] : 0}
|
||||
color="error"
|
||||
variant="standard"
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
);
|
||||
|
||||
return (
|
||||
<Collapse in={item.showSubItem} timeout="auto" unmountOnExit>
|
||||
<List component="div" disablePadding sx={{bgcolor: "#f6f6f6"}}>
|
||||
{item.subItem.map((subitem, index) => (
|
||||
<Fragment key={subitem.key}>
|
||||
|
||||
<Collapse in={item.showSubItem} timeout="auto" mountOnEnter={true} unmountOnExit={true}>
|
||||
<List component="div" disablePadding sx={{bgcolor: "#f6f6f6", pr: 0}}>
|
||||
{item.subItem.map((subitem) => (
|
||||
<ListItem key={subitem.key} disablePadding secondaryAction={renderBadge(subitem)}>
|
||||
<ListItemButton
|
||||
selected={subitem.selected}
|
||||
component={NextLinkComposed}
|
||||
to={{
|
||||
pathname: subitem.route,
|
||||
@@ -20,28 +46,32 @@ const SidebarListSubItem = ({item, handleDrawerToggle}) => {
|
||||
sx={{
|
||||
minHeight: 48,
|
||||
}}
|
||||
onClick={(event) => {
|
||||
if (item.type == "menu") {
|
||||
dispatch({type: "COLLAPSE_MENU", key: item.key});
|
||||
}
|
||||
handleDrawerToggle();
|
||||
}}
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: 0,
|
||||
justifyContent: "center",
|
||||
width: 40,
|
||||
height: 24,
|
||||
pr: 2,
|
||||
}}
|
||||
>
|
||||
<InboxIcon/>
|
||||
{subitem.routing ?
|
||||
<CircularProgress size={24} color="inherit"/> : subitem.icon}
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t(subitem.key)}/>
|
||||
<ListItemText primary={t(subitem.key)} secondary={
|
||||
subitem.secondary !== undefined ? (
|
||||
<Typography variant="caption" color="textSecondary">
|
||||
{t(subitem.secondary)}
|
||||
</Typography>
|
||||
) : null
|
||||
}/>
|
||||
</ListItemButton>
|
||||
</Fragment>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Collapse>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,13 +1,31 @@
|
||||
import {Box, Drawer} from "@mui/material";
|
||||
import {Box, Drawer, useMediaQuery} from "@mui/material";
|
||||
import SidebarDrawer from "./SidebarDrawer";
|
||||
import {useTheme} from "@mui/material/styles";
|
||||
|
||||
const Sidebar = (props) => {
|
||||
const theme = useTheme();
|
||||
const isUpSm = useMediaQuery((theme.breakpoints.up('sm')))
|
||||
return (
|
||||
<Box
|
||||
component="nav"
|
||||
sx={{width: {md: props.drawerWidth}, flexShrink: {sm: 0}}}
|
||||
aria-label="mailbox folders"
|
||||
> {isUpSm ? (
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
sx={{
|
||||
display: {xs: "none", md: "block"},
|
||||
"& .MuiDrawer-paper": {
|
||||
boxSizing: "border-box",
|
||||
width: props.drawerWidth,
|
||||
overflow: "hidden",
|
||||
},
|
||||
}}
|
||||
open
|
||||
>
|
||||
<SidebarDrawer handleDrawerToggle={props.handleDrawerToggle}/>
|
||||
</Drawer>
|
||||
) : (
|
||||
<Drawer
|
||||
container={props.container}
|
||||
variant="temporary"
|
||||
@@ -21,24 +39,13 @@ const Sidebar = (props) => {
|
||||
"& .MuiDrawer-paper": {
|
||||
boxSizing: "border-box",
|
||||
width: props.drawerWidth,
|
||||
overflow: "hidden",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SidebarDrawer handleDrawerToggle={props.handleDrawerToggle}/>
|
||||
</Drawer>
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
sx={{
|
||||
display: {xs: "none", md: "block"},
|
||||
"& .MuiDrawer-paper": {
|
||||
boxSizing: "border-box",
|
||||
width: props.drawerWidth,
|
||||
},
|
||||
}}
|
||||
open
|
||||
>
|
||||
<SidebarDrawer handleDrawerToggle={props.handleDrawerToggle}/>
|
||||
</Drawer>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import {useState} from "react";
|
||||
import Header from "./header";
|
||||
import Sidebar from "./sidebar";
|
||||
import {Toolbar} from "@mui/material";
|
||||
import BreadCrumbs from "./breadcrumbs";
|
||||
import FullPageLayout from "@/layouts/FullPageLayout";
|
||||
import RolePermissionMiddleware from "@/middlewares/RolePermission";
|
||||
import Header from "@/components/layouts/Dashboard/Header";
|
||||
import Sidebar from "@/components/layouts/Dashboard/Sidebar";
|
||||
import Content from "@/components/layouts/Dashboard/Content";
|
||||
|
||||
const drawerWidth = 240;
|
||||
|
||||
@@ -17,6 +16,7 @@ const DashboardLayouts = (props) => {
|
||||
const handleDrawerToggle = () => {
|
||||
setMobileOpen(!mobileOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<FullPageLayout direction="row">
|
||||
<Header
|
||||
@@ -34,12 +34,7 @@ const DashboardLayouts = (props) => {
|
||||
sx={{flexGrow: 1, width: {sm: `calc(100% - ${drawerWidth}px)`}}}
|
||||
>
|
||||
<Toolbar/>
|
||||
<FullPageLayout sx={{mt: 3}}>
|
||||
<RolePermissionMiddleware requiredPermissions={props.permissions}>
|
||||
<BreadCrumbs isVisible={true}/>
|
||||
{props.children}
|
||||
</RolePermissionMiddleware>
|
||||
</FullPageLayout>
|
||||
<Content {...props}/>
|
||||
</FullPageLayout>
|
||||
</FullPageLayout>
|
||||
);
|
||||
|
||||
18
src/components/layouts/Print/Header/ProfileData.jsx
Normal file
18
src/components/layouts/Print/Header/ProfileData.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import {Avatar, Stack, Typography} from "@mui/material";
|
||||
import useUser from "@/lib/app/hooks/useUser";
|
||||
|
||||
export default function ProfileData() {
|
||||
const {user} = useUser();
|
||||
return (
|
||||
<Stack alignItems="center" spacing={2} sx={{p: 3}}>
|
||||
<Avatar
|
||||
sx={{width: "80px", height: "80px"}}
|
||||
alt="User Image"
|
||||
src={user.avatar}
|
||||
/>
|
||||
<Typography sx={{fontSize: 15, fontWeight: 600}} textAlign="center">
|
||||
{user.username}
|
||||
</Typography>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
62
src/components/layouts/Print/Header/ProfileMenu.jsx
Normal file
62
src/components/layouts/Print/Header/ProfileMenu.jsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import {Avatar, IconButton, Menu, Tooltip} from "@mui/material";
|
||||
import {useState} from "react";
|
||||
import ProfileData from "./ProfileData";
|
||||
import ProfileOptions from "./ProfileOptions";
|
||||
import {useTranslations} from "next-intl";
|
||||
import useUser from "@/lib/app/hooks/useUser";
|
||||
|
||||
function ProfileMenu() {
|
||||
const t = useTranslations();
|
||||
const [anchorElUser, setAnchorElUser] = useState(null);
|
||||
const {user} = useUser();
|
||||
|
||||
const handleOpenUserMenu = (event) => {
|
||||
setAnchorElUser(event.currentTarget);
|
||||
};
|
||||
const handleCloseUserMenu = () => {
|
||||
setAnchorElUser(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip title={t("header.open_profile")} arrow>
|
||||
<IconButton onClick={handleOpenUserMenu} sx={{p: 0}}>
|
||||
<Avatar
|
||||
sx={{
|
||||
width: 24,
|
||||
height: 24,
|
||||
backgroundColor: "#fff",
|
||||
color: "primary.main",
|
||||
}}
|
||||
alt="User Image"
|
||||
src={user.avatar}
|
||||
/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Menu
|
||||
MenuListProps={{sx: {py: 0}}}
|
||||
sx={{
|
||||
mt: 6,
|
||||
}}
|
||||
id="menu-appbar"
|
||||
anchorEl={anchorElUser}
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right",
|
||||
}}
|
||||
keepMounted
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right",
|
||||
}}
|
||||
open={Boolean(anchorElUser)}
|
||||
onClose={handleCloseUserMenu}
|
||||
>
|
||||
<ProfileData/>
|
||||
<ProfileOptions handleCloseUserMenu={handleCloseUserMenu}/>
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProfileMenu;
|
||||
49
src/components/layouts/Print/Header/ProfileOptionLogOut.jsx
Normal file
49
src/components/layouts/Print/Header/ProfileOptionLogOut.jsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import {Box, Button, MenuItem, Typography} from "@mui/material";
|
||||
import MeetingRoomIcon from "@mui/icons-material/MeetingRoom";
|
||||
import useUser from "@/lib/app/hooks/useUser";
|
||||
import {useTranslations} from "next-intl";
|
||||
|
||||
export default function ProfileOptionLogOut({handleCloseUserMenu}) {
|
||||
const t = useTranslations();
|
||||
const {clearToken} = useUser();
|
||||
const handleClickLogOut = () => {
|
||||
handleCloseUserMenu();
|
||||
clearToken();
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<MenuItem
|
||||
component={Button}
|
||||
to={{
|
||||
pathname: "/dashboard/logout",
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
borderTop: 1,
|
||||
px: 3,
|
||||
py: 1.5,
|
||||
borderColor: "#e1e1e1",
|
||||
textTransform: "unset",
|
||||
}}
|
||||
onClick={handleClickLogOut}
|
||||
>
|
||||
<Box sx={{display: "flex", alignItems: "center", flex: 1}}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
color: "primary.main",
|
||||
pr: 2,
|
||||
}}
|
||||
>
|
||||
<MeetingRoomIcon/>
|
||||
</Box>
|
||||
<Typography sx={{flex: 1}} textAlign="start">
|
||||
{t("header.logout")}
|
||||
</Typography>
|
||||
</Box>
|
||||
</MenuItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
49
src/components/layouts/Print/Header/ProfileOptions.jsx
Normal file
49
src/components/layouts/Print/Header/ProfileOptions.jsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import {Box, MenuItem, Typography} from "@mui/material";
|
||||
import {NextLinkComposed} from "@/core/components/LinkRouting";
|
||||
import {useTranslations} from "next-intl";
|
||||
import headerProfileItems from "@/core/data/headerProfileItems";
|
||||
import ProfileOptionLogOut from "./ProfileOptionLogOut";
|
||||
|
||||
export default function ProfileOptions({handleCloseUserMenu}) {
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<>
|
||||
{headerProfileItems.map((profile_item) => (
|
||||
<MenuItem
|
||||
component={NextLinkComposed}
|
||||
to={{
|
||||
pathname: profile_item.route,
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
borderTop: 1,
|
||||
px: 3,
|
||||
py: 1.5,
|
||||
borderColor: "#e1e1e1",
|
||||
}}
|
||||
key={profile_item.key}
|
||||
onClick={handleCloseUserMenu}
|
||||
>
|
||||
<Box sx={{display: "flex", alignItems: "center", flex: 1}}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
color: "primary.main",
|
||||
pr: 2,
|
||||
}}
|
||||
>
|
||||
{profile_item.icon}
|
||||
</Box>
|
||||
<Typography sx={{flex: 1}} textAlign="start">
|
||||
{t(profile_item.name)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</MenuItem>
|
||||
))}
|
||||
<ProfileOptionLogOut handleCloseUserMenu={handleCloseUserMenu}/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
72
src/components/layouts/Print/Header/index.jsx
Normal file
72
src/components/layouts/Print/Header/index.jsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import {AppBar, Button, Container, Stack, Toolbar, Typography, useTheme} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import {useRouter} from "next/router";
|
||||
import {useState} from "react";
|
||||
import usePrint from "@/lib/app/hooks/usePrint";
|
||||
|
||||
function Header({drawerWidth, handleDrawerToggle}) {
|
||||
const theme = useTheme();
|
||||
const t = useTranslations()
|
||||
const router = useRouter()
|
||||
const {printDetails} = usePrint()
|
||||
const [disabled, setDisabled] = useState(false)
|
||||
|
||||
return (
|
||||
<AppBar
|
||||
position="fixed"
|
||||
sx={{
|
||||
width: '100%',
|
||||
displayPrint: 'none'
|
||||
}}
|
||||
>
|
||||
<Container maxWidth="xxl">
|
||||
<Toolbar
|
||||
disableGutters
|
||||
sx={{
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
sx={{
|
||||
flex: 1,
|
||||
position: "relative",
|
||||
...theme.mixins.toolbar,
|
||||
}}
|
||||
>
|
||||
<Button onClick={() => {
|
||||
setDisabled(true);
|
||||
if (disabled) return
|
||||
router.back()
|
||||
}}
|
||||
size={'large'} sx={{
|
||||
color: "inherit",
|
||||
}}>{t('button_back_to_previous')}</Button>
|
||||
</Stack>
|
||||
<Stack
|
||||
alignItems={'center'}
|
||||
justifyContent={'center'}
|
||||
sx={{
|
||||
flex: 1,
|
||||
position: "relative",
|
||||
...theme.mixins.toolbar,
|
||||
}}
|
||||
spacing={1}
|
||||
>
|
||||
<Typography>{printDetails.title}</Typography>
|
||||
<Typography variant={'body2'}>{'تعداد صفحه:'} {printDetails.count}</Typography>
|
||||
</Stack>
|
||||
<Stack direction="row" spacing={4} justifyContent="flex-end" sx={{flex: 1}}>
|
||||
<Button onClick={() => {
|
||||
if (disabled) return;
|
||||
print()
|
||||
}} size={'large'} variant={'contained'}
|
||||
color={'secondary'}>{t('btn_print')}</Button>
|
||||
</Stack>
|
||||
</Toolbar>
|
||||
</Container>
|
||||
</AppBar>
|
||||
);
|
||||
}
|
||||
|
||||
export default Header;
|
||||
14
src/components/layouts/Print/index.jsx
Normal file
14
src/components/layouts/Print/index.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import Header from "@/components/layouts/Print/Header";
|
||||
import {Toolbar} from "@mui/material";
|
||||
|
||||
const Print = (props) => {
|
||||
return (
|
||||
<>
|
||||
<Header/>
|
||||
<Toolbar sx={{displayPrint: 'none'}}/>
|
||||
{props.children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Print
|
||||
@@ -1,5 +1,4 @@
|
||||
import LinkRouting from "@/core/components/LinkRouting";
|
||||
import PasswordField from "@/core/components/PasswordField";
|
||||
import StyledForm from "@/core/components/StyledForm";
|
||||
import {GET_USER_TOKEN} from "@/core/data/apiRoutes";
|
||||
import CenterLayout from "@/layouts/CenterLayout";
|
||||
@@ -14,6 +13,7 @@ import * as Yup from "yup";
|
||||
import useDirection from "@/lib/app/hooks/useDirection";
|
||||
import SvgLogin from "@/core/components/svgs/SvgLogin";
|
||||
import useRequest from "@/lib/app/hooks/useRequest";
|
||||
import PasswordField from "@/core/components/PasswordField";
|
||||
|
||||
const LoginComponent = () => {
|
||||
const t = useTranslations();
|
||||
|
||||
@@ -14,7 +14,6 @@ function DataTable(props) {
|
||||
const [columnFilters, setColumnFilters] = useState([]);
|
||||
const [sorting, setSorting] = useState(props.sorting || []);
|
||||
const [pagination, setPagination] = useState({pageIndex: 0, pageSize: 10});
|
||||
const [rowCount, setRowCount] = useState(0);
|
||||
const [columnFilterFns, setColumnFilterFns] = useState(() => {
|
||||
let output = {};
|
||||
const list = props.columns.map((item) => item.enableColumnFilter ? {[item.id]: item.filterFn} : {[item.id]: ""});
|
||||
@@ -34,8 +33,11 @@ function DataTable(props) {
|
||||
const tableLocalization = useMemo(() => languageList.find((item) => item.key == languageApp).tableLocalization, [languageApp, languageList]);
|
||||
|
||||
const fetchUrl = useMemo(() => {
|
||||
const url = new URL(props.tableUrl);
|
||||
url.searchParams.set("start", `${pagination.pageIndex * pagination.pageSize}`);
|
||||
const params = new URLSearchParams();
|
||||
params.set(
|
||||
"start",
|
||||
`${pagination.pageIndex * pagination.pageSize}`
|
||||
);
|
||||
const filters = columnFilters.map((filter) => {
|
||||
let datatype;
|
||||
for (const i in props.columns) {
|
||||
@@ -47,10 +49,10 @@ function DataTable(props) {
|
||||
...filter, fn: columnFilterFns[filter.id], datatype: datatype,
|
||||
};
|
||||
});
|
||||
url.searchParams.set("size", pagination.pageSize);
|
||||
url.searchParams.set("filters", JSON.stringify(filters ?? []));
|
||||
url.searchParams.set("sorting", JSON.stringify(sorting ?? []));
|
||||
return url;
|
||||
params.set("size", pagination.pageSize);
|
||||
params.set("filters", JSON.stringify(filters ?? []));
|
||||
params.set("sorting", JSON.stringify(sorting ?? []));
|
||||
return `${props.tableUrl}?${params}`;
|
||||
}, [props.tableUrl, columnFilters, columnFilterFns, pagination, sorting, props.columns,]);
|
||||
|
||||
const {data, isValidating, mutate} = useSWR(fetchUrl, (...args) =>
|
||||
@@ -60,7 +62,7 @@ function DataTable(props) {
|
||||
}).then((response) => response.data).catch(() => {
|
||||
})
|
||||
, {
|
||||
revalidateIfStale: false,
|
||||
revalidateIfStale: true,
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: true,
|
||||
keepPreviousData: true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Backdrop, Box, styled} from "@mui/material";
|
||||
import {Backdrop, Box, Stack, styled, Typography} from "@mui/material";
|
||||
import SvgLoading from "@/core/components/svgs/SvgLoading";
|
||||
|
||||
const LoadingImage = styled(Box)({
|
||||
@@ -9,7 +9,7 @@ const LoadingImage = styled(Box)({
|
||||
},
|
||||
"50%": {
|
||||
// opacity: 1,
|
||||
transform: "scale(2)",
|
||||
transform: "scale(.5)",
|
||||
},
|
||||
"100%": {
|
||||
// opacity: 0,
|
||||
@@ -19,20 +19,29 @@ const LoadingImage = styled(Box)({
|
||||
animation: "load 2s infinite",
|
||||
});
|
||||
|
||||
const LoadingHardPage = ({children, loading}) => {
|
||||
const LoadingHardPage = ({children, loading, sx = {}, icon = null, width = 200, height = 200, label = ''}) => {
|
||||
return (
|
||||
<>
|
||||
<Backdrop
|
||||
sx={{bgcolor: "#fff", zIndex: (theme) => theme.zIndex.drawer + 1}}
|
||||
sx={{bgcolor: "#fff", zIndex: (theme) => theme.zIndex.drawer + 1, ...sx}}
|
||||
open={loading}
|
||||
>
|
||||
<Stack alignItems={'center'} spacing={2}>
|
||||
<LoadingImage
|
||||
width={100}
|
||||
height={100}
|
||||
width={width}
|
||||
height={height}
|
||||
>
|
||||
<SvgLoading width={100}
|
||||
height={100}/>
|
||||
{icon ? (
|
||||
<Box sx={{color: "primary.main", width: width, height: height}}>
|
||||
{icon}
|
||||
</Box>
|
||||
) : (
|
||||
<SvgLoading width={width}
|
||||
height={height}/>
|
||||
)}
|
||||
</LoadingImage>
|
||||
<Typography variant={'body2'} sx={{color: "primary.main"}}>{label}</Typography>
|
||||
</Stack>
|
||||
</Backdrop>
|
||||
{children}
|
||||
</>
|
||||
|
||||
11
src/core/components/LtrTextField.jsx
Normal file
11
src/core/components/LtrTextField.jsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import {styled, TextField} from "@mui/material";
|
||||
|
||||
const LtrTextField = styled(TextField)`
|
||||
.MuiInputBase-input {
|
||||
/* @noflip */
|
||||
direction: ltr;
|
||||
text-align: left;
|
||||
}
|
||||
`;
|
||||
|
||||
export default LtrTextField
|
||||
@@ -6,13 +6,13 @@ import WifiOffIcon from '@mui/icons-material/WifiOff';
|
||||
import {useTranslations} from "next-intl";
|
||||
|
||||
const NetworkComponent = () => {
|
||||
const toastId = useRef(null);
|
||||
const networkToastId = useRef(null);
|
||||
const network = useNetwork()
|
||||
const t = useTranslations()
|
||||
|
||||
useEffect(() => {
|
||||
if (network.online) {
|
||||
toast.update(toastId.current, {
|
||||
toast.update(networkToastId.current, {
|
||||
type: toast.TYPE.SUCCESS,
|
||||
render: t('online_message'),
|
||||
autoClose: 2000,
|
||||
@@ -22,8 +22,9 @@ const NetworkComponent = () => {
|
||||
});
|
||||
return
|
||||
}
|
||||
toast.dismiss()
|
||||
toastId.current = toast.warn(t('offline_message'), {
|
||||
networkToastId.current = toast.warn(t('offline_message'), {
|
||||
containerId: 'connection',
|
||||
draggable: false,
|
||||
autoClose: false, closeButton: false, closeOnClick: false, icon: <WifiOffIcon/>
|
||||
})
|
||||
}, [network.online]);
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import {Stack, TextField, Typography} from "@mui/material";
|
||||
import {Stack, Typography} from "@mui/material";
|
||||
import {useTranslations} from "next-intl";
|
||||
import LtrTextField from "@/core/components/LtrTextField";
|
||||
|
||||
const PriceField = (props) => {
|
||||
const t = useTranslations();
|
||||
return (
|
||||
<Stack spacing={1}>
|
||||
<Stack>
|
||||
<TextField
|
||||
<LtrTextField
|
||||
{...props}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
82
src/core/components/PrintablePage.jsx
Normal file
82
src/core/components/PrintablePage.jsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import {Box, Container, Divider, Grid, Paper, Stack, Typography} from '@mui/material';
|
||||
|
||||
const PrintablePage = ({children, header, footer}) => (
|
||||
<>
|
||||
<Box sx={{width: "100%", height: 8, displayPrint: 'none'}}/>
|
||||
<Container
|
||||
sx={{
|
||||
width: '21cm',
|
||||
minHeight: '29.7cm',
|
||||
margin: '0 auto',
|
||||
backgroundColor: 'white',
|
||||
p: '16px !important',
|
||||
pb: footer ? '84px!important' : '',
|
||||
position: 'relative'
|
||||
}}
|
||||
component="article" maxWidth="false">
|
||||
<Grid container columns={5} sx={{pt: 2, px: 4, display: header ? '' : 'none'}}>
|
||||
<Grid item xs={1}>
|
||||
<Box sx={{width: 70}}>
|
||||
<Box component={'img'} sx={{width: '100%', height: '100%', objectFit: 'contain'}}
|
||||
src={'/images/logo.png'}/>
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item xs={3} sx={{textAlign: 'center'}}>
|
||||
<Typography sx={{fontFamily: 'Bnazanin'}}>باسمه تعالی</Typography>
|
||||
<Typography sx={{fontFamily: 'Bnazanin'}}>جمهوری اسلامی ایران</Typography>
|
||||
<Typography sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>وزارت راه و
|
||||
شهرسازی</Typography>
|
||||
<Typography sx={{fontFamily: 'Bnazanin', fontWeight: 'bold'}}>سازمان
|
||||
راهداری و حمل و نقل جاده
|
||||
ای</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={1}>
|
||||
<Stack>
|
||||
<Typography sx={{fontFamily: 'Bnazanin'}}>شماره:</Typography>
|
||||
<Typography sx={{fontFamily: 'Bnazanin'}}>تاریخ:</Typography>
|
||||
<Typography sx={{fontFamily: 'Bnazanin'}}>پیوست:</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Paper elevation={0} sx={{px: 8}}>
|
||||
{children}
|
||||
</Paper>
|
||||
<Box sx={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
px: 4,
|
||||
pb: 2,
|
||||
display: footer ? '' : 'none'
|
||||
}}>
|
||||
<Divider sx={{borderStyle: 'double', borderBottomWidth: 3, borderColor: '#000'}}/>
|
||||
<Stack sx={{my: .5}}>
|
||||
<Box sx={{textAlign: 'center'}}>
|
||||
<Typography variant={'caption'} fontWeight={'bold'} component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', px: 1}}> آدرس: تهران-بلوار
|
||||
کشاورز-خیابان فلسطین جنوبی-خیابان
|
||||
دمشق-پلاک17 </Typography>
|
||||
<Typography variant={'caption'} fontWeight={'bold'} component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', px: 1}}> تلفن: 88-88804379 </Typography>
|
||||
<Typography variant={'caption'} fontWeight={'bold'} component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', px: 1}}> تلفن گویا: 88804400 </Typography>
|
||||
<Typography variant={'caption'} fontWeight={'bold'} component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', px: 1}}> کدپستی: 1416753941 </Typography>
|
||||
</Box>
|
||||
<Box sx={{textAlign: 'center'}}>
|
||||
<Typography variant={'caption'} fontWeight={'bold'} component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', px: 2}}>صندوق پستی: 3773-14155</Typography>
|
||||
<Typography variant={'caption'} fontWeight={'bold'} component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', px: 2}}>پست الکترونیک: info@rmto.ir</Typography>
|
||||
<Typography variant={'caption'} fontWeight={'bold'} component={'span'}
|
||||
sx={{fontFamily: 'Bnazanin', px: 2}}>سایت الکترونیک: www.rmto.ir</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Container>
|
||||
<Box sx={{width: "100%", height: 8, displayPrint: 'none'}}/>
|
||||
</>
|
||||
);
|
||||
|
||||
export default PrintablePage;
|
||||
@@ -10,7 +10,6 @@ const UploadSystem = ({
|
||||
handleUploadChange,
|
||||
fieldname,
|
||||
setFieldValue,
|
||||
imageAlt,
|
||||
imageSize,
|
||||
fileType,
|
||||
fileName,
|
||||
@@ -19,7 +18,6 @@ const UploadSystem = ({
|
||||
}) => {
|
||||
const t = useTranslations();
|
||||
const fileInputRef = useRef(null);
|
||||
|
||||
const handleClick = () => {
|
||||
fileInputRef.current.click();
|
||||
};
|
||||
@@ -69,13 +67,14 @@ const UploadSystem = ({
|
||||
variant="subtitle2"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
fontSize: "1rem",
|
||||
fontSize: "0.8rem",
|
||||
color: "#a19d9d",
|
||||
mt: 1,
|
||||
}}
|
||||
textAlign="center"
|
||||
>
|
||||
{t("UploadSystem.upload_file")}
|
||||
{t("UploadSystem.upload_file_format")}<br/>
|
||||
{t("UploadSystem.upload_file_unit")}
|
||||
</Typography>
|
||||
</Box>
|
||||
</>
|
||||
|
||||
@@ -2,8 +2,8 @@ import DangerousIcon from "@mui/icons-material/Dangerous";
|
||||
import {Box, Typography} from "@mui/material";
|
||||
import {toast} from "react-toastify";
|
||||
|
||||
const ErrorNotification = (t, status, message) => {
|
||||
toast(
|
||||
const ErrorNotification = (pushToastList, notificationType, t, status, message) => {
|
||||
const toastId = toast(
|
||||
() => (
|
||||
<>
|
||||
<Box
|
||||
@@ -29,11 +29,13 @@ const ErrorNotification = (t, status, message) => {
|
||||
</>
|
||||
),
|
||||
{
|
||||
containerId: 'validation',
|
||||
autoClose: false,
|
||||
closeOnClick: false,
|
||||
draggable: false,
|
||||
}
|
||||
);
|
||||
pushToastList(notificationType, toastId);
|
||||
};
|
||||
|
||||
export default ErrorNotification;
|
||||
@@ -1,12 +1,14 @@
|
||||
import {toast} from "react-toastify";
|
||||
|
||||
const PendingNotification = (t) => {
|
||||
toast(t("notifications.pending"), {
|
||||
const PendingNotification = (pushToastList, notificationType, t) => {
|
||||
const toastId = toast(t("notifications.pending"), {
|
||||
containerId: 'validation',
|
||||
autoClose: false,
|
||||
closeButton: false,
|
||||
closeOnClick: false,
|
||||
draggable: false,
|
||||
});
|
||||
pushToastList(notificationType, toastId);
|
||||
};
|
||||
|
||||
export default PendingNotification;
|
||||
@@ -2,8 +2,8 @@ import BeenhereIcon from "@mui/icons-material/Beenhere";
|
||||
import {Box, Typography} from "@mui/material";
|
||||
import {toast} from "react-toastify";
|
||||
|
||||
const SuccessNotification = (t, status) => {
|
||||
toast(
|
||||
const SuccessNotification = (pushToastList, notificationType, t, status) => {
|
||||
const toastId = toast(
|
||||
() => (
|
||||
<>
|
||||
<Box
|
||||
@@ -30,6 +30,7 @@ const SuccessNotification = (t, status) => {
|
||||
</>
|
||||
),
|
||||
{
|
||||
containerId: 'validation',
|
||||
autoClose: 3000,
|
||||
hideProgressBar: true,
|
||||
pauseOnHover: true,
|
||||
@@ -37,6 +38,7 @@ const SuccessNotification = (t, status) => {
|
||||
draggable: true,
|
||||
}
|
||||
);
|
||||
pushToastList(notificationType, toastId);
|
||||
};
|
||||
|
||||
export default SuccessNotification;
|
||||
@@ -26,6 +26,8 @@ const UploadFileNotification = (t) => {
|
||||
</>
|
||||
),
|
||||
{
|
||||
containerId: 'validation',
|
||||
toastId: 'upload',
|
||||
autoClose: 3000,
|
||||
hideProgressBar: true,
|
||||
pauseOnHover: true,
|
||||
|
||||
@@ -2,8 +2,8 @@ import ReportIcon from "@mui/icons-material/Report";
|
||||
import {Box, Typography} from "@mui/material";
|
||||
import {toast} from "react-toastify";
|
||||
|
||||
const WarningNotification = (t, status) => {
|
||||
toast(
|
||||
const WarningNotification = (pushToastList, notificationType, t, status) => {
|
||||
const toastId = toast(
|
||||
() => (
|
||||
<>
|
||||
<Box
|
||||
@@ -30,11 +30,13 @@ const WarningNotification = (t, status) => {
|
||||
</>
|
||||
),
|
||||
{
|
||||
containerId: 'validation',
|
||||
autoClose: false,
|
||||
closeOnClick: false,
|
||||
draggable: false,
|
||||
}
|
||||
);
|
||||
pushToastList(notificationType, toastId);
|
||||
};
|
||||
|
||||
export default WarningNotification;
|
||||
@@ -1,54 +1,27 @@
|
||||
import {toast} from "react-toastify";
|
||||
import ErrorNotification from "./ErrorNotification";
|
||||
import WarningNotification from "./WarningNotification";
|
||||
import SuccessNotification from "./SuccessNotification";
|
||||
import pendingNotification from "@/core/components/notifications/PendingNotification";
|
||||
import WarningNotification from "@/core/components/notifications/WarningNotification";
|
||||
import ErrorNotification from "@/core/components/notifications/ErrorNotification";
|
||||
import SuccessNotification from "@/core/components/notifications/SuccessNotification";
|
||||
|
||||
const Notifications = async (t, response) => {
|
||||
const {status, data} = response != undefined ? response : ""
|
||||
toast.dismiss();
|
||||
switch (status) {
|
||||
case 200:
|
||||
SuccessNotification(t, status);
|
||||
const Notifications = (pushToastList, notificationType, t, status, message) => {
|
||||
switch (notificationType) {
|
||||
case "pending":
|
||||
pendingNotification(pushToastList, notificationType, t);
|
||||
break;
|
||||
case 400:
|
||||
ErrorNotification(t, status);
|
||||
case "warning":
|
||||
WarningNotification(pushToastList, notificationType, t, status);
|
||||
break;
|
||||
case 401:
|
||||
ErrorNotification(t, status);
|
||||
case "error":
|
||||
if (message) {
|
||||
ErrorNotification(pushToastList, notificationType, t, status, message)
|
||||
} else {
|
||||
ErrorNotification(pushToastList, notificationType, t, status)
|
||||
}
|
||||
break;
|
||||
case 403:
|
||||
ErrorNotification(t, status);
|
||||
break;
|
||||
case 422:
|
||||
ErrorNotification(t, status, data.message);
|
||||
break;
|
||||
case 500:
|
||||
WarningNotification(t, status);
|
||||
break;
|
||||
case 503:
|
||||
WarningNotification(t, status);
|
||||
break;
|
||||
case 504:
|
||||
WarningNotification(t, status);
|
||||
break;
|
||||
default:
|
||||
toast(t("notifications.pending"), {
|
||||
autoClose: false,
|
||||
closeOnClick: false,
|
||||
draggable: false,
|
||||
});
|
||||
case "success":
|
||||
SuccessNotification(pushToastList, notificationType, t, status);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
export default Notifications;
|
||||
|
||||
/*
|
||||
usage document
|
||||
|
||||
** for pending use ( Notifications( t, undefined) ) this before your request.
|
||||
** for success use ( Notifications( t, response) ) this inside .then() of your request.
|
||||
** for Error and Warning use ( Notifications( t, error.response) ) this inside .catche() of your request.
|
||||
|
||||
end usage document
|
||||
*/
|
||||
|
||||
@@ -21,7 +21,7 @@ const sidebarMenu = [
|
||||
key: "sidebar.dashboard",
|
||||
type: "page",
|
||||
route: "/dashboard",
|
||||
icon: <SpaceDashboardIcon/>,
|
||||
icon: <SpaceDashboardIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||
selected: false,
|
||||
permission: "all",
|
||||
},
|
||||
@@ -40,7 +40,7 @@ const sidebarMenu = [
|
||||
name: "passenger_office_chief",
|
||||
type: "page",
|
||||
route: "/dashboard/passenger-office-chief",
|
||||
icon: <AirlineSeatReclineNormalIcon/>,
|
||||
icon: <AirlineSeatReclineNormalIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||
selected: false,
|
||||
permission: "manage_passenger_office_navgan",
|
||||
},
|
||||
@@ -49,7 +49,7 @@ const sidebarMenu = [
|
||||
name: "machinery_expert",
|
||||
type: "page",
|
||||
route: "/dashboard/machinery-expert",
|
||||
icon: <DirectionsCarFilledIcon/>,
|
||||
icon: <DirectionsCarFilledIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||
selected: false,
|
||||
permission: "manage_machinery_navgan",
|
||||
},
|
||||
@@ -59,7 +59,7 @@ const sidebarMenu = [
|
||||
name: "province_working_group",
|
||||
type: "page",
|
||||
route: "/dashboard/passenger-boss",
|
||||
icon: <AssignmentIndIcon/>,
|
||||
icon: <AssignmentIndIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||
selected: false,
|
||||
permission: "manage_province_working_group_navgan",
|
||||
},
|
||||
@@ -69,7 +69,7 @@ const sidebarMenu = [
|
||||
name: "transportation_assistant",
|
||||
type: "page",
|
||||
route: "/dashboard/transportation-assistant",
|
||||
icon: <DirectionsRailwayIcon/>,
|
||||
icon: <DirectionsRailwayIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||
selected: false,
|
||||
permission: "manage_transportation_navgan",
|
||||
},
|
||||
@@ -79,7 +79,7 @@ const sidebarMenu = [
|
||||
name: "province_manager_navgan",
|
||||
type: "page",
|
||||
route: "/dashboard/navgan-province-manager",
|
||||
icon: <DesktopWindowsIcon/>,
|
||||
icon: <DesktopWindowsIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||
selected: false,
|
||||
permission: "manage_province_affairs_navgan",
|
||||
},
|
||||
@@ -88,7 +88,7 @@ const sidebarMenu = [
|
||||
name: "province_head_expert",
|
||||
type: "page",
|
||||
route: "/dashboard/province-head-expert",
|
||||
icon: <GavelIcon/>,
|
||||
icon: <GavelIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||
selected: false,
|
||||
permission: "manage_province_headquarter_refahi",
|
||||
},
|
||||
@@ -97,7 +97,7 @@ const sidebarMenu = [
|
||||
name: "inspector_expert",
|
||||
type: "page",
|
||||
route: "/dashboard/inspector-expert",
|
||||
icon: <GradingIcon/>,
|
||||
icon: <GradingIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||
selected: false,
|
||||
permission: "manage_inspector_refahi",
|
||||
},
|
||||
@@ -106,7 +106,7 @@ const sidebarMenu = [
|
||||
name: "commercial_chief",
|
||||
type: "page",
|
||||
route: "/dashboard/commercial-chief",
|
||||
icon: <BusinessCenterIcon/>,
|
||||
icon: <BusinessCenterIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||
selected: false,
|
||||
permission: "manage_commercial_refahi",
|
||||
},
|
||||
@@ -115,7 +115,7 @@ const sidebarMenu = [
|
||||
name: "development_assistant",
|
||||
type: "page",
|
||||
route: "/dashboard/development-assistant",
|
||||
icon: <Diversity3Icon/>,
|
||||
icon: <Diversity3Icon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||
selected: false,
|
||||
permission: "manage_development_refahi",
|
||||
},
|
||||
@@ -125,7 +125,7 @@ const sidebarMenu = [
|
||||
name: "province_manager_refahi",
|
||||
type: "page",
|
||||
route: "/dashboard/refahi-province-manager",
|
||||
icon: <SupervisedUserCircleIcon/>,
|
||||
icon: <SupervisedUserCircleIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||
selected: false,
|
||||
permission: "manage_province_affairs_refahi",
|
||||
},
|
||||
@@ -135,7 +135,7 @@ const sidebarMenu = [
|
||||
name: "refahi_loan_management",
|
||||
type: "page",
|
||||
route: "/dashboard/refahi-loan-management",
|
||||
icon: <PaidIcon/>,
|
||||
icon: <PaidIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||
selected: false,
|
||||
permission: "manage_refahi_loan",
|
||||
},
|
||||
@@ -145,7 +145,7 @@ const sidebarMenu = [
|
||||
name: "navgan_loan_management",
|
||||
type: "page",
|
||||
route: "/dashboard/navgan-loan-management",
|
||||
icon: <PaidIcon/>,
|
||||
icon: <PaidIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||
selected: false,
|
||||
permission: "manage_navgan_loan",
|
||||
},
|
||||
@@ -154,7 +154,7 @@ const sidebarMenu = [
|
||||
name: "expert_management",
|
||||
type: "page",
|
||||
route: "/dashboard/expert-management",
|
||||
icon: <ManageAccountsIcon/>,
|
||||
icon: <ManageAccountsIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||
selected: false,
|
||||
permission: "manage_experts",
|
||||
},
|
||||
@@ -163,7 +163,7 @@ const sidebarMenu = [
|
||||
name: "user_management",
|
||||
type: "page",
|
||||
route: "/dashboard/user-management",
|
||||
icon: <PersonIcon/>,
|
||||
icon: <PersonIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||
selected: false,
|
||||
permission: "manage_users",
|
||||
},
|
||||
@@ -172,7 +172,7 @@ const sidebarMenu = [
|
||||
name: "role_management",
|
||||
type: "page",
|
||||
route: "/dashboard/role-management",
|
||||
icon: <AccessibilityIcon/>,
|
||||
icon: <AccessibilityIcon sx={{width: 'inherit', height: 'inherit'}}/>,
|
||||
selected: false,
|
||||
permission: "manage_roles",
|
||||
},
|
||||
|
||||
@@ -1,45 +1,48 @@
|
||||
import ErrorNotification from "@/core/components/notifications/ErrorNotification";
|
||||
import WarningNotification from "@/core/components/notifications/WarningNotification";
|
||||
import {toast} from "react-toastify";
|
||||
import Notifications from "@/core/components/notifications";
|
||||
|
||||
export const errorSetting = (t, notification) => {
|
||||
if (notification) toast.dismiss();
|
||||
export const errorSetting = (dismissToastList, t, notification) => {
|
||||
if (notification) {
|
||||
dismissToastList(["pending", "warning", "error", "success"])
|
||||
}
|
||||
}
|
||||
|
||||
export const errorRequest = (t, notification) => {
|
||||
if (notification) toast.dismiss();
|
||||
}
|
||||
|
||||
export const errorResponse = (response, clearToken, t, notification) => {
|
||||
if (notification) toast.dismiss();
|
||||
if (isServerError(response.status)) {
|
||||
errorServer(response, t, notification)
|
||||
} else if (isClientError(response.status)) {
|
||||
errorClient(response, clearToken, t, notification)
|
||||
export const errorRequest = (dismissToastList, t, notification) => {
|
||||
if (notification) {
|
||||
dismissToastList(["pending", "warning", "error", "success"])
|
||||
}
|
||||
}
|
||||
|
||||
const errorServer = (response, t, notification) => {
|
||||
if (notification) WarningNotification(t, response.status);
|
||||
export const errorResponse = (pushToastList, dismissToastList, response, clearToken, t, notification) => {
|
||||
if (notification) {
|
||||
dismissToastList(["pending", "warning", "error", "success"])
|
||||
}
|
||||
if (isServerError(response.status)) {
|
||||
errorServer(pushToastList, response, t, notification)
|
||||
} else if (isClientError(response.status)) {
|
||||
errorClient(pushToastList, response, clearToken, t, notification)
|
||||
}
|
||||
}
|
||||
const errorClient = (response, clearToken, t, notification) => {
|
||||
|
||||
const errorServer = (pushToastList, response, t, notification) => {
|
||||
if (notification) Notifications(pushToastList, "warning", t, response.status);
|
||||
}
|
||||
const errorClient = (pushToastList, response, clearToken, t, notification) => {
|
||||
switch (response.status) {
|
||||
case 401:
|
||||
clearToken()
|
||||
if (notification) ErrorNotification(t, response.status)
|
||||
if (notification) Notifications(pushToastList, "error", t, response.status);
|
||||
break;
|
||||
case 422:
|
||||
if ('type' in response.data) {
|
||||
errorLogic(response, t, notification)
|
||||
errorLogic(pushToastList, response, t, notification)
|
||||
break;
|
||||
}
|
||||
errorValidation(response, t, notification)
|
||||
errorValidation(pushToastList, response, t, notification)
|
||||
break;
|
||||
case 429:
|
||||
if (notification) ErrorNotification(t, response.status)
|
||||
if (notification) Notifications(pushToastList, "error", t, response.status);
|
||||
break
|
||||
default:
|
||||
if (notification) ErrorNotification(t, response.status)
|
||||
if (notification) Notifications(pushToastList, "error", t, response.status);
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -47,16 +50,16 @@ const errorClient = (response, clearToken, t, notification) => {
|
||||
const isServerError = status => status >= 500 && status <= 599;
|
||||
const isClientError = status => status >= 400 && status <= 499;
|
||||
|
||||
const errorLogic = (response, t, notification) => {
|
||||
if (notification) ErrorNotification(t, response.status, response.data.message)
|
||||
const errorLogic = (pushToastList, response, t, notification) => {
|
||||
if (notification) Notifications(pushToastList, "error", t, response.status, response.data.message);
|
||||
}
|
||||
const errorValidation = (response, t, notification) => {
|
||||
const errorValidation = (pushToastList, response, t, notification) => {
|
||||
if (notification) {
|
||||
const errorsMap = Object.keys(response.data.errors)
|
||||
const errorsArray = response.data.errors
|
||||
|
||||
errorsMap.map((item, index) => {
|
||||
ErrorNotification(t, response.status, errorsArray[item][0]);
|
||||
Notifications(pushToastList, "error", t, response.status, errorsArray[item][0]);
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
import SuccessNotification from "@/core/components/notifications/SuccessNotification";
|
||||
import {toast} from "react-toastify";
|
||||
import Notifications from "@/core/components/notifications";
|
||||
|
||||
export const successRequest = (response, t, options) => {
|
||||
export const successRequest = (pushToastList, dismissToastList, response, t, options) => {
|
||||
if (options.notification && options.success.notification.show) {
|
||||
toast.dismiss();
|
||||
SuccessNotification(t, response.status)
|
||||
dismissToastList(["pending", "warning", "error", "success"])
|
||||
Notifications(pushToastList, "success", t, response.status);
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,16 @@ function AppLayout({children, isBot}) {
|
||||
color={theme.palette.secondary.dark}
|
||||
options={{showSpinner: false}}
|
||||
/>
|
||||
<ToastContainer position={directionApp === "ltr" ? "top-left" : "top-right"} rtl={directionApp === 'rtl'}/>
|
||||
<ToastContainer
|
||||
enableMultiContainer
|
||||
containerId="validation"
|
||||
position={directionApp === "ltr" ? "top-left" : "top-right"}
|
||||
rtl={directionApp === 'rtl'}/>
|
||||
<ToastContainer
|
||||
enableMultiContainer
|
||||
containerId={'connection'}
|
||||
position={directionApp === "ltr" ? "top-right" : "top-left"}
|
||||
rtl={directionApp === 'rtl'}/>
|
||||
<NetworkComponent/>
|
||||
{children}
|
||||
</>);
|
||||
|
||||
12
src/layouts/PrintLayout.jsx
Normal file
12
src/layouts/PrintLayout.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import WithAuthMiddleware from "@/middlewares/WithAuth";
|
||||
import Print from "@/components/layouts/Print";
|
||||
|
||||
const PrintLayout = (props) => {
|
||||
return (
|
||||
<WithAuthMiddleware>
|
||||
<Print {...props}/>
|
||||
</WithAuthMiddleware>
|
||||
)
|
||||
}
|
||||
|
||||
export default PrintLayout
|
||||
@@ -1,14 +1,16 @@
|
||||
import DashboardLayout from "@/layouts/DashboardLayout";
|
||||
import {Fragment} from "react";
|
||||
import PrintLayout from "@/layouts/PrintLayout";
|
||||
|
||||
const layoutList = {
|
||||
DashboardLayout
|
||||
DashboardLayout,
|
||||
PrintLayout
|
||||
}
|
||||
|
||||
const Layout = ({layout = {}, children}) => {
|
||||
|
||||
const Component = layoutList[layout?.name] || Fragment
|
||||
const props = layout.props || {}
|
||||
const props = layout?.props || {}
|
||||
|
||||
return (
|
||||
<Component {...props}>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {FA_DATATABLE_LOCALIZATION} from "&/locales/fa/datatable";
|
||||
import {FA_CHART_LOCALIZATION} from "&/locales/fa/chart";
|
||||
import {useRouter} from "next/router";
|
||||
import {createContext, useEffect, useState} from "react";
|
||||
import useUser from "../hooks/useUser";
|
||||
import {FA_CHART_LOCALIZATION} from "&/locales/fa/chart";
|
||||
|
||||
export const LanguageContext = createContext();
|
||||
|
||||
@@ -20,7 +20,7 @@ export const LanguageProvider = ({children}) => {
|
||||
];
|
||||
const {user, userChangedLanguage, changeLanguageState} = useUser();
|
||||
const [languageIsReady, setLanguageIsReady] = useState(false);
|
||||
const [languageApp, setLanguageApp] = useState();
|
||||
const [languageApp, setLanguageApp] = useState(process.env.NEXT_PUBLIC_DEFAULT_LANGUAGE);
|
||||
const [directionApp, setDirectionApp] = useState(
|
||||
process.env.NEXT_PUBLIC_DEFAULT_DIRECTION
|
||||
);
|
||||
@@ -28,9 +28,7 @@ export const LanguageProvider = ({children}) => {
|
||||
useEffect(() => {
|
||||
const lang = localStorage.getItem("_language");
|
||||
|
||||
if (!lang && !languageApp) {
|
||||
setLanguageApp(process.env.NEXT_PUBLIC_DEFAULT_LANGUAGE);
|
||||
} else if (lang) {
|
||||
if (lang) {
|
||||
setLanguageApp(lang);
|
||||
}
|
||||
}, []);
|
||||
|
||||
24
src/lib/app/contexts/print.jsx
Normal file
24
src/lib/app/contexts/print.jsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import {createContext, useReducer} from "react";
|
||||
|
||||
export const PrintContext = createContext();
|
||||
|
||||
const reducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case "SET_PRINT_PAGE":
|
||||
return {
|
||||
...state, count: action.count
|
||||
};
|
||||
case "SET_PRINT_TITLE":
|
||||
return {
|
||||
...state, title: action.title
|
||||
};
|
||||
}
|
||||
};
|
||||
export const PrintProvider = ({children}) => {
|
||||
const [printDetails, printDispatch] = useReducer(reducer, {title: '', count: 0});
|
||||
return (
|
||||
<PrintContext.Provider value={{printDetails, printDispatch}}>
|
||||
{children}
|
||||
</PrintContext.Provider>
|
||||
);
|
||||
};
|
||||
37
src/lib/app/contexts/toast.jsx
Normal file
37
src/lib/app/contexts/toast.jsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import {createContext, useReducer} from "react";
|
||||
import {toast} from "react-toastify";
|
||||
|
||||
export const ToastContext = createContext()
|
||||
|
||||
const reducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case "PUSH":
|
||||
return {
|
||||
...state,
|
||||
[action.toast_type]: [...state[action.toast_type], action.toast_id]
|
||||
};
|
||||
case "DISMISS":
|
||||
action.toast_type.map((item) => {
|
||||
state[item].map((id) => {
|
||||
toast.dismiss(id);
|
||||
})
|
||||
state[item] = []
|
||||
});
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export const ToastProvider = ({children}) => {
|
||||
const [state, dispatch] = useReducer(reducer, {
|
||||
pending: [],
|
||||
error: [],
|
||||
warning: [],
|
||||
success: []
|
||||
});
|
||||
|
||||
return (
|
||||
<ToastContext.Provider value={{state, dispatch}}>
|
||||
{children}
|
||||
</ToastContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,6 @@
|
||||
import {GET_USER_ROUTE} from "@/core/data/apiRoutes";
|
||||
import axios from "axios";
|
||||
import {createContext, useCallback, useEffect, useReducer} from "react";
|
||||
import {errorRequest, errorResponse, errorSetting} from "@/core/utils/errorHandler";
|
||||
|
||||
const initialUser = {
|
||||
isAuth: false,
|
||||
@@ -78,13 +77,7 @@ export const UserProvider = ({children}) => {
|
||||
if (typeof callback === "function") callback(data);
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.response) {
|
||||
errorResponse(error.response, clearToken, null, false)
|
||||
} else if (error.request) {
|
||||
errorRequest(null, false)
|
||||
} else {
|
||||
errorSetting(null, false)
|
||||
}
|
||||
if (error.response.status === 401) clearToken()
|
||||
})
|
||||
},
|
||||
[state.token]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user