Merge branch 'qrcode' into master
This commit is contained in:
337
client/package-lock.json
generated
337
client/package-lock.json
generated
@@ -17,6 +17,7 @@
|
||||
"@refinedev/simple-rest": "^4.5.0",
|
||||
"@tanstack/react-query": "^4.29.19",
|
||||
"@types/loadable__component": "^5.13.4",
|
||||
"@yudiel/react-qr-scanner": "^1.1.10",
|
||||
"antd": "^5.6.4",
|
||||
"flag-icons": "^6.7.0",
|
||||
"i18next": "^23.2.8",
|
||||
@@ -26,6 +27,7 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^13.0.1",
|
||||
"react-router-dom": "^6.14.1",
|
||||
"react-to-print": "^2.14.13",
|
||||
"vite-plugin-svgr": "^3.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -40,7 +42,8 @@
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.1",
|
||||
"typescript": "^5.1.6",
|
||||
"vite": "^4.4.1"
|
||||
"vite": "^4.4.1",
|
||||
"vite-plugin-mkcert": "^1.16.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "16.x"
|
||||
@@ -1553,6 +1556,195 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-token": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.4.tgz",
|
||||
"integrity": "sha512-TWFX7cZF2LXoCvdmJWY7XVPi74aSY0+FfBZNSXEXFkMpjcqsQwDSYVv5FhRFaI0V1ECnwbz4j59T/G+rXNWaIQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/core": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-4.2.4.tgz",
|
||||
"integrity": "sha512-rYKilwgzQ7/imScn3M9/pFfUf4I1AZEH3KhyJmtPdE2zfaXAn2mFfUy4FbKewzc2We5y/LlKLj36fWJLKC2SIQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^3.0.0",
|
||||
"@octokit/graphql": "^5.0.0",
|
||||
"@octokit/request": "^6.0.0",
|
||||
"@octokit/request-error": "^3.0.0",
|
||||
"@octokit/types": "^9.0.0",
|
||||
"before-after-hook": "^2.2.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/endpoint": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.6.tgz",
|
||||
"integrity": "sha512-5L4fseVRUsDFGR00tMWD/Trdeeihn999rTMGRMC1G/Ldi1uWlWJzI98H4Iak5DB/RVvQuyMYKqSK/R6mbSOQyg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@octokit/types": "^9.0.0",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/endpoint/node_modules/is-plain-object": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
|
||||
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/graphql": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.6.tgz",
|
||||
"integrity": "sha512-Fxyxdy/JH0MnIB5h+UQ3yCoh1FG4kWXfFKkpWqjZHw/p+Kc8Y44Hu/kCgNBT6nU1shNumEchmW/sUO1JuQnPcw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@octokit/request": "^6.0.0",
|
||||
"@octokit/types": "^9.0.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/openapi-types": {
|
||||
"version": "18.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.0.0.tgz",
|
||||
"integrity": "sha512-V8GImKs3TeQRxRtXFpG2wl19V7444NIOTDF24AWuIbmNaNYOQMWRbjcGDXV5B+0n887fgDcuMNOmlul+k+oJtw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@octokit/plugin-paginate-rest": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-6.1.2.tgz",
|
||||
"integrity": "sha512-qhrmtQeHU/IivxucOV1bbI/xZyC/iOBhclokv7Sut5vnejAIAEXVcGQeRpQlU39E0WwK9lNvJHphHri/DB6lbQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@octokit/tsconfig": "^1.0.2",
|
||||
"@octokit/types": "^9.2.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/plugin-request-log": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz",
|
||||
"integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=3"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/plugin-rest-endpoint-methods": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-7.2.3.tgz",
|
||||
"integrity": "sha512-I5Gml6kTAkzVlN7KCtjOM+Ruwe/rQppp0QU372K1GP7kNOYEKe8Xn5BW4sE62JAHdwpq95OQK/qGNyKQMUzVgA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@octokit/types": "^10.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=3"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-10.0.0.tgz",
|
||||
"integrity": "sha512-Vm8IddVmhCgU1fxC1eyinpwqzXPEYu0NrYzD3YZjlGjyftdLBTeqNblRC0jmJmgxbJIsQlyogVeGnrNaaMVzIg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request": {
|
||||
"version": "6.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.8.tgz",
|
||||
"integrity": "sha512-ow4+pkVQ+6XVVsekSYBzJC0VTVvh/FCTUUgTsboGq+DTeWdyIFV8WSCdo0RIxk6wSkBTHqIK1mYuY7nOBXOchw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@octokit/endpoint": "^7.0.0",
|
||||
"@octokit/request-error": "^3.0.0",
|
||||
"@octokit/types": "^9.0.0",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request-error": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.3.tgz",
|
||||
"integrity": "sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@octokit/types": "^9.0.0",
|
||||
"deprecation": "^2.0.0",
|
||||
"once": "^1.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request/node_modules/is-plain-object": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
|
||||
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest": {
|
||||
"version": "19.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-19.0.13.tgz",
|
||||
"integrity": "sha512-/EzVox5V9gYGdbAI+ovYj3nXQT1TtTHRT+0eZPcuC05UFSWO3mdO9UY1C0i2eLF9Un1ONJkAk+IEtYGAC+TahA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@octokit/core": "^4.2.1",
|
||||
"@octokit/plugin-paginate-rest": "^6.1.2",
|
||||
"@octokit/plugin-request-log": "^1.0.4",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^7.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/tsconfig": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/tsconfig/-/tsconfig-1.0.2.tgz",
|
||||
"integrity": "sha512-I0vDR0rdtP8p2lGMzvsJzbhdOWy405HcGovrspJ8RRibHnyRgggUSNO5AIox5LmqiwmatHKYsvj6VGFHkqS7lA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@octokit/types": {
|
||||
"version": "9.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.3.2.tgz",
|
||||
"integrity": "sha512-D4iHGTdAnEEVsB8fl95m1hiz7D5YiRdQ9b/OEb3BYRVwbLsGHcRVPz+u+BgRLNk0Q0/4iZCBqDN96j2XNxfXrA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-compose-refs": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz",
|
||||
@@ -2538,6 +2730,38 @@
|
||||
"vite": "^4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@yudiel/react-qr-scanner": {
|
||||
"version": "1.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@yudiel/react-qr-scanner/-/react-qr-scanner-1.1.10.tgz",
|
||||
"integrity": "sha512-+YIHbnocynkkGgyMK8MMkn1NBnJRwJntdm4shQ+linm4NdGdSAavLYxJYwnk2PixZyVu/GZ3Uc2aM0XWlBq+Zw==",
|
||||
"dependencies": {
|
||||
"@zxing/library": "^0.20.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@zxing/library": {
|
||||
"version": "0.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.20.0.tgz",
|
||||
"integrity": "sha512-6Ev6rcqVjMakZFIDvbUf0dtpPGeZMTfyxYg4HkVWioWeN7cRcnUWT3bU6sdohc82O1nPXcjq6WiGfXX2Pnit6A==",
|
||||
"dependencies": {
|
||||
"ts-custom-error": "^3.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.4.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@zxing/text-encoding": "~0.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@zxing/text-encoding": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz",
|
||||
"integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
@@ -2830,6 +3054,12 @@
|
||||
"resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz",
|
||||
"integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg=="
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/atob": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||
@@ -2945,6 +3175,12 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/before-after-hook": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
|
||||
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
@@ -3522,6 +3758,18 @@
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/comma-separated-tokens": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz",
|
||||
@@ -3790,6 +4038,15 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
@@ -3799,6 +4056,12 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/deprecation": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
|
||||
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/destroy": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||
@@ -4926,6 +5189,20 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@@ -7640,6 +7917,12 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
|
||||
@@ -8487,6 +8770,15 @@
|
||||
"react-dom": "^16.3.0 || ^17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-to-print": {
|
||||
"version": "2.14.13",
|
||||
"resolved": "https://registry.npmjs.org/react-to-print/-/react-to-print-2.14.13.tgz",
|
||||
"integrity": "sha512-PqUGgTRZvkyBzWgaZHVBECWPX2nGEc3HOUy6WNXof6HT4yTFI92wtIkqQtr4jfvHbadqjwTgzgh6vgN8KXlWWw==",
|
||||
"peerDependencies": {
|
||||
"react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-virtual": {
|
||||
"version": "2.10.4",
|
||||
"resolved": "https://registry.npmjs.org/react-virtual/-/react-virtual-2.10.4.tgz",
|
||||
@@ -9781,6 +10073,14 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-custom-error": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.3.1.tgz",
|
||||
"integrity": "sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz",
|
||||
@@ -9994,6 +10294,12 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/universal-user-agent": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
|
||||
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
@@ -10245,6 +10551,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-mkcert": {
|
||||
"version": "1.16.0",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-mkcert/-/vite-plugin-mkcert-1.16.0.tgz",
|
||||
"integrity": "sha512-5r+g8SB9wZzLNUFekGwZo3e0P6QlS6rbxK5p9z/itxNAimsYohgjK/YfVPVxM9EuglP9hjridq0lUejo9v1nVg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@octokit/rest": "^19.0.5",
|
||||
"axios": "^1.2.2",
|
||||
"debug": "^4.3.4",
|
||||
"picocolors": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v16.7.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": ">=3"
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-mkcert/node_modules/axios": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
|
||||
"integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-svgr": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-3.2.0.tgz",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"@refinedev/simple-rest": "^4.5.0",
|
||||
"@tanstack/react-query": "^4.29.19",
|
||||
"@types/loadable__component": "^5.13.4",
|
||||
"@yudiel/react-qr-scanner": "^1.1.10",
|
||||
"antd": "^5.6.4",
|
||||
"flag-icons": "^6.7.0",
|
||||
"i18next": "^23.2.8",
|
||||
@@ -25,6 +26,7 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^13.0.1",
|
||||
"react-router-dom": "^6.14.1",
|
||||
"react-to-print": "^2.14.13",
|
||||
"vite-plugin-svgr": "^3.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -39,7 +41,8 @@
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.1",
|
||||
"typescript": "^5.1.6",
|
||||
"vite": "^4.4.1"
|
||||
"vite": "^4.4.1",
|
||||
"vite-plugin-mkcert": "^1.16.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "refine dev",
|
||||
|
||||
@@ -53,6 +53,58 @@
|
||||
"dashboard": {
|
||||
"title": "Dashboard"
|
||||
},
|
||||
"printing": {
|
||||
"generic": {
|
||||
"title": "Printing",
|
||||
"description": "Tune in the settings below to get the desired print layout. Keep in mind that printers and your OS may apply it's own margins and scaling, so you may need to adjust the settings accordingly. Test it on a piece of regular paper before printing on the actual labels.",
|
||||
"print": "Print",
|
||||
"columns": "Columns",
|
||||
"rows": "Rows",
|
||||
"paperSize": "Paper Size",
|
||||
"showBorder": "Show Border",
|
||||
"previewScale": "Preview Scale",
|
||||
"marginLeft": "Left Margin",
|
||||
"marginRight": "Right Margin",
|
||||
"marginTop": "Top Margin",
|
||||
"marginBottom": "Bottom Margin",
|
||||
"blockRowsTop": "Block Rows Top",
|
||||
"blockRowsBottom": "Block Rows Bottom",
|
||||
"blockColumnsLeft": "Block Columns Left",
|
||||
"blockColumnsRight": "Block Columns Right",
|
||||
"borders": {
|
||||
"none": "None",
|
||||
"border": "Border",
|
||||
"grid": "Grid"
|
||||
}
|
||||
},
|
||||
"qrcode": {
|
||||
"button": "Print QR Codes",
|
||||
"title": "QR Code Printing",
|
||||
"textSize": "Content Text Size",
|
||||
"showSpoolmanIcon": "Show Spoolman Icon",
|
||||
"showContent": "Show QR Code Content"
|
||||
},
|
||||
"spoolSelect": {
|
||||
"title": "Select Spools",
|
||||
"description": "Select spools to print QR codes for.",
|
||||
"showArchived": "Show Archived",
|
||||
"noSpoolsSelected": "You have not selected any spools.",
|
||||
"selectAll": "Select/Unselect All",
|
||||
"selectedTotal_one": "{{count}} spool selected",
|
||||
"selectedTotal_other": "{{count}} spools selected"
|
||||
}
|
||||
},
|
||||
"scanner": {
|
||||
"title": "QR Code Scanner",
|
||||
"description": "Scan a Spoolman QR code to view details about the spool.",
|
||||
"error": {
|
||||
"notAllowed": "You have not allowed access to the camera.",
|
||||
"insecureContext": "The page is not served over HTTPS.",
|
||||
"streamApiNotSupported": "The browser does not support the MediaStream API.",
|
||||
"notReadable": "The camera is not readable.",
|
||||
"unknown": "An unknown error occurred. ({{error}})"
|
||||
}
|
||||
},
|
||||
"spool": {
|
||||
"spool": "Spools",
|
||||
"fields": {
|
||||
|
||||
@@ -53,6 +53,58 @@
|
||||
"dashboard": {
|
||||
"title": "Hem"
|
||||
},
|
||||
"printing": {
|
||||
"generic": {
|
||||
"title": "Utskrift",
|
||||
"description": "Justera inställningarna nedan för att få önskat utskriftslayout. Kom ihåg att skrivare och ditt operativsystem kan tillämpa sina egna marginaler och skalning, så du kan behöva justera inställningarna därefter. Testa på ett vanligt papper innan du skriver ut på de faktiska etiketterna.",
|
||||
"print": "Skriv ut",
|
||||
"columns": "Kolumner",
|
||||
"rows": "Rader",
|
||||
"paperSize": "Papperstyp",
|
||||
"showBorder": "Visa ram",
|
||||
"previewScale": "Skala förhandsgranskning",
|
||||
"marginLeft": "Marginal - vänster",
|
||||
"marginRight": "Marginal - höger",
|
||||
"marginTop": "Marginal - topp",
|
||||
"marginBottom": "Marginal - botten",
|
||||
"blockRowsTop": "Blockera rader - topp",
|
||||
"blockRowsBottom": "Blockera rader - botten",
|
||||
"blockColumnsLeft": "Blockera kolumner - vänster",
|
||||
"blockColumnsRight": "Blockera kolumner - höger",
|
||||
"borders": {
|
||||
"none": "Ingen",
|
||||
"border": "Ram",
|
||||
"grid": "Rutnät"
|
||||
}
|
||||
},
|
||||
"qrcode": {
|
||||
"button": "Skriv ut QR-koder",
|
||||
"title": "QR-kod utskrift",
|
||||
"textSize": "Textstorlek",
|
||||
"showSpoolmanIcon": "Visa Spoolman-ikon",
|
||||
"showContent": "Visa QR-kodsinnehåll"
|
||||
},
|
||||
"spoolSelect": {
|
||||
"title": "Välj spolar",
|
||||
"description": "Välj spolar att skriva ut QR-koder för.",
|
||||
"showArchived": "Tillåt arkiverade",
|
||||
"noSpoolsSelected": "Du har inte valt några spolar.",
|
||||
"selectAll": "Markera/avmarkera alla",
|
||||
"selectedTotal_one": "{{count}} spole vald",
|
||||
"selectedTotal_other": "{{count}} spolar valda"
|
||||
}
|
||||
},
|
||||
"scanner": {
|
||||
"title": "Skanna QR-kod",
|
||||
"description": "Skanna en QR-kod för att visa information om en spole.",
|
||||
"error": {
|
||||
"notAllowed": "Du har inte tillåtit åtkomst till kameran.",
|
||||
"insecureContext": "Sidan måste använda sig av HTTPS.",
|
||||
"streamApiNotSupported": "Webbläsaren stödjer inte MediaStream API.",
|
||||
"notReadable": "Kameran är inte läsbar.",
|
||||
"unknown": "Ett okänt fel inträffade. ({{error}})"
|
||||
}
|
||||
},
|
||||
"spool": {
|
||||
"spool": "Spolar",
|
||||
"fields": {
|
||||
|
||||
@@ -15,6 +15,7 @@ import { ColorModeContext } from "../../contexts/color-mode";
|
||||
|
||||
import "/node_modules/flag-icons/css/flag-icons.min.css";
|
||||
import { languages } from "../../i18n";
|
||||
import QRCodeScannerModal from "../qrCodeScanner";
|
||||
|
||||
const { useToken } = theme;
|
||||
|
||||
@@ -84,6 +85,7 @@ export const Header: React.FC<RefineThemedLayoutV2HeaderProps> = ({
|
||||
onChange={() => setMode(mode === "light" ? "dark" : "light")}
|
||||
defaultChecked={mode === "dark"}
|
||||
/>
|
||||
<QRCodeScannerModal />
|
||||
</Space>
|
||||
</AntdLayout.Header>
|
||||
);
|
||||
|
||||
640
client/src/components/printing/printingDialog.tsx
Normal file
640
client/src/components/printing/printingDialog.tsx
Normal file
@@ -0,0 +1,640 @@
|
||||
import React, { useRef } from "react";
|
||||
import {
|
||||
Modal,
|
||||
Slider,
|
||||
Button,
|
||||
Select,
|
||||
Row,
|
||||
Col,
|
||||
Form,
|
||||
Divider,
|
||||
RadioChangeEvent,
|
||||
Radio,
|
||||
InputNumber,
|
||||
} from "antd";
|
||||
import ReactToPrint from "react-to-print";
|
||||
import { useSavedState } from "../../utils/saveload";
|
||||
import { useTranslate } from "@refinedev/core";
|
||||
|
||||
interface PrintingDialogProps {
|
||||
items: JSX.Element[];
|
||||
style?: string;
|
||||
extraSettings?: JSX.Element;
|
||||
visible: boolean;
|
||||
onCancel: () => void;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
interface PaperDimensions {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
const paperDimensions: { [key: string]: PaperDimensions } = {
|
||||
A3: {
|
||||
width: 297,
|
||||
height: 420,
|
||||
},
|
||||
A4: {
|
||||
width: 210,
|
||||
height: 297,
|
||||
},
|
||||
A5: {
|
||||
width: 148,
|
||||
height: 210,
|
||||
},
|
||||
Letter: {
|
||||
width: 216,
|
||||
height: 279,
|
||||
},
|
||||
Legal: {
|
||||
width: 216,
|
||||
height: 356,
|
||||
},
|
||||
Tabloid: {
|
||||
width: 279,
|
||||
height: 432,
|
||||
},
|
||||
};
|
||||
|
||||
const PrintingDialog: React.FC<PrintingDialogProps> = ({
|
||||
items,
|
||||
style,
|
||||
extraSettings,
|
||||
visible,
|
||||
onCancel,
|
||||
title,
|
||||
}) => {
|
||||
const t = useTranslate();
|
||||
|
||||
const [marginLeft, setMarginLeft] = useSavedState("print-marginLeft", 10);
|
||||
const [marginTop, setMarginTop] = useSavedState("print-marginTop", 10);
|
||||
const [marginRight, setMarginRight] = useSavedState("print-marginRight", 10);
|
||||
const [marginBottom, setMarginBottom] = useSavedState(
|
||||
"print-marginBottom",
|
||||
10
|
||||
);
|
||||
const [blockRowsTop, setBlockRowsTop] = useSavedState(
|
||||
"print-blockRowsTop",
|
||||
0
|
||||
);
|
||||
const [blockRowsBottom, setBlockRowsBottom] = useSavedState(
|
||||
"print-blockRowsBottom",
|
||||
0
|
||||
);
|
||||
const [blockColumnsLeft, setBlockColumnsLeft] = useSavedState(
|
||||
"print-blockColumnsLeft",
|
||||
0
|
||||
);
|
||||
const [blockColumnsRight, setBlockColumnsRight] = useSavedState(
|
||||
"print-blockColumnsRight",
|
||||
0
|
||||
);
|
||||
const [paperColumns, setPaperColumns] = useSavedState("print-itemsPerRow", 3);
|
||||
const [paperRows, setPaperRows] = useSavedState("print-rowsPerPage", 8);
|
||||
const [paperSize, setPaperSize] = useSavedState("print-paperSize", "A4");
|
||||
const [previewScale, setPreviewScale] = useSavedState(
|
||||
"print-previewScale",
|
||||
0.6
|
||||
);
|
||||
const [borderShowMode, setBorderShowMode] = useSavedState<
|
||||
"none" | "border" | "grid"
|
||||
>("print-borderShowMode", "grid");
|
||||
|
||||
const paperWidth = paperDimensions[paperSize].width;
|
||||
const paperHeight = paperDimensions[paperSize].height;
|
||||
|
||||
const printRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const calculatedRowHeight =
|
||||
(paperHeight - marginTop - marginBottom) / paperRows;
|
||||
|
||||
const itemsPerRow = paperColumns - blockColumnsLeft - blockColumnsRight;
|
||||
const rowsPerPage = paperRows - blockRowsTop - blockRowsBottom;
|
||||
|
||||
const rowsOfItems = [];
|
||||
for (let row_idx = 0; row_idx <= items.length / itemsPerRow; row_idx += 1) {
|
||||
rowsOfItems.push(
|
||||
items.slice(row_idx * itemsPerRow, (row_idx + 1) * itemsPerRow)
|
||||
);
|
||||
}
|
||||
|
||||
const pageBlocks = [];
|
||||
for (
|
||||
let page_idx = 0;
|
||||
page_idx <= rowsOfItems.length / rowsPerPage;
|
||||
page_idx += 1
|
||||
) {
|
||||
pageBlocks.push(
|
||||
rowsOfItems.slice(page_idx * rowsPerPage, (page_idx + 1) * rowsPerPage)
|
||||
);
|
||||
}
|
||||
|
||||
const pages = pageBlocks.map(function (rows, idx) {
|
||||
const itemRowsBlockTop = [...Array(blockRowsTop).keys()].map((row) => (
|
||||
<tr key={row}>
|
||||
<td>
|
||||
<div style={{ height: `${calculatedRowHeight}mm` }}></div>
|
||||
</td>
|
||||
</tr>
|
||||
));
|
||||
const itemRows = rows.map((row, idx) => {
|
||||
const itemColumnsBlockLeft = [...Array(blockColumnsLeft).keys()].map(
|
||||
(col) => (
|
||||
<td key={col}>
|
||||
<div
|
||||
style={{
|
||||
width: `${
|
||||
(paperWidth - marginLeft - marginRight) / paperColumns
|
||||
}mm`,
|
||||
}}
|
||||
></div>
|
||||
</td>
|
||||
)
|
||||
);
|
||||
return (
|
||||
<tr key={idx}>
|
||||
{itemColumnsBlockLeft}
|
||||
{row.map((item, index) => (
|
||||
<td>
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: `${
|
||||
(paperWidth - marginLeft - marginRight) / paperColumns
|
||||
}mm`,
|
||||
height: `${calculatedRowHeight}mm`,
|
||||
flexDirection: "column",
|
||||
border: borderShowMode === "grid" ? "1px solid #000" : "none",
|
||||
}}
|
||||
>
|
||||
{item}
|
||||
</div>
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className="print-page"
|
||||
key={idx}
|
||||
style={{
|
||||
width: `${paperWidth}mm`,
|
||||
height: `${paperHeight}mm`,
|
||||
backgroundColor: "#FFF",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="print-page-area"
|
||||
style={{
|
||||
border: borderShowMode !== "none" ? "1px solid #000" : "none",
|
||||
height: `${paperHeight - marginTop - marginBottom}mm`,
|
||||
width: `${paperWidth - marginLeft - marginRight}mm`,
|
||||
marginTop: `calc(${marginTop}mm - ${
|
||||
borderShowMode !== "none" ? "1px" : "0px"
|
||||
})`,
|
||||
marginLeft: `calc(${marginLeft}mm - ${
|
||||
borderShowMode !== "none" ? "1px" : "0px"
|
||||
})`,
|
||||
marginRight: `calc(${marginRight}mm - ${
|
||||
borderShowMode !== "none" ? "1px" : "0px"
|
||||
})`,
|
||||
marginBottom: `calc(${marginBottom}mm - ${
|
||||
borderShowMode !== "none" ? "1px" : "0px"
|
||||
})`,
|
||||
}}
|
||||
>
|
||||
<table
|
||||
style={{
|
||||
alignContent: "flex-start",
|
||||
}}
|
||||
>
|
||||
{itemRowsBlockTop}
|
||||
{itemRows}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={visible}
|
||||
title={title ?? t("printing.generic.title")}
|
||||
onCancel={onCancel}
|
||||
footer={[
|
||||
<ReactToPrint
|
||||
key="print-button"
|
||||
trigger={() => (
|
||||
<Button type="primary">{t("printing.generic.print")}</Button>
|
||||
)}
|
||||
content={() => printRef.current}
|
||||
/>,
|
||||
]}
|
||||
width={1200} // Set the modal width to accommodate the preview
|
||||
>
|
||||
<Row gutter={16}>
|
||||
<Col span={24}>{t("printing.generic.description")}</Col>
|
||||
<Col span={14}>
|
||||
<div
|
||||
style={{
|
||||
transform: "translateZ(0)",
|
||||
overflow: "hidden scroll",
|
||||
height: "70vh",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="print-container"
|
||||
ref={printRef}
|
||||
style={{
|
||||
transform: `scale(${previewScale})`,
|
||||
transformOrigin: "top left",
|
||||
display: "block",
|
||||
}}
|
||||
>
|
||||
<style>
|
||||
{`
|
||||
@media print {
|
||||
@page {
|
||||
size: auto;
|
||||
margin: 0;
|
||||
}
|
||||
.print-container {
|
||||
transform: scale(1) !important;
|
||||
}
|
||||
.print-page {
|
||||
page-break-before: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen {
|
||||
.print-page {
|
||||
margin-top: 10mm;
|
||||
}
|
||||
}
|
||||
|
||||
.print-page td {
|
||||
padding: 0;
|
||||
}
|
||||
${style ?? ""}
|
||||
`}
|
||||
</style>
|
||||
{pages}
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={10}>
|
||||
<Form
|
||||
labelAlign="left"
|
||||
colon={false}
|
||||
labelWrap={true}
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 16 }}
|
||||
>
|
||||
<Form.Item label={t("printing.generic.marginLeft")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={-20}
|
||||
max={50}
|
||||
step={0.1}
|
||||
tooltip={{ formatter: (value) => `${value} mm` }}
|
||||
value={marginLeft}
|
||||
onChange={(value) => {
|
||||
setMarginLeft(value);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
step={0.1}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={marginLeft}
|
||||
addonAfter="mm"
|
||||
onChange={(value) => {
|
||||
setMarginLeft(value ?? 0);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.marginTop")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={-20}
|
||||
max={50}
|
||||
step={0.1}
|
||||
tooltip={{ formatter: (value) => `${value} mm` }}
|
||||
value={marginTop}
|
||||
onChange={(value) => {
|
||||
setMarginTop(value);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
step={0.1}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={marginTop}
|
||||
addonAfter="mm"
|
||||
onChange={(value) => {
|
||||
setMarginTop(value ?? 0);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.marginRight")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={-20}
|
||||
max={50}
|
||||
step={0.1}
|
||||
tooltip={{ formatter: (value) => `${value} mm` }}
|
||||
value={marginRight}
|
||||
onChange={(value) => {
|
||||
setMarginRight(value);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
step={0.1}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={marginRight}
|
||||
addonAfter="mm"
|
||||
onChange={(value) => {
|
||||
setMarginRight(value ?? 0);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.marginBottom")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={-20}
|
||||
max={50}
|
||||
step={0.1}
|
||||
tooltip={{ formatter: (value) => `${value} mm` }}
|
||||
value={marginBottom}
|
||||
onChange={(value) => {
|
||||
setMarginBottom(value);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
step={0.1}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={marginBottom}
|
||||
addonAfter="mm"
|
||||
onChange={(value) => {
|
||||
setMarginBottom(value ?? 0);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.columns")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={1}
|
||||
max={5}
|
||||
value={paperColumns}
|
||||
onChange={(value) => {
|
||||
if (value - blockColumnsLeft - blockColumnsRight < 1)
|
||||
return;
|
||||
setPaperColumns(value);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
min={1}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={paperColumns}
|
||||
onChange={(value) => {
|
||||
if (
|
||||
(value ?? 1) - blockColumnsLeft - blockColumnsRight <
|
||||
1
|
||||
)
|
||||
return;
|
||||
setPaperColumns(value ?? 1);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.rows")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={1}
|
||||
max={15}
|
||||
value={paperRows}
|
||||
onChange={(value) => {
|
||||
if (value - blockRowsTop - blockRowsBottom < 1) return;
|
||||
setPaperRows(value);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
min={1}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={paperRows}
|
||||
onChange={(value) => {
|
||||
if ((value ?? 1) - blockRowsTop - blockRowsBottom < 1)
|
||||
return;
|
||||
setPaperRows(value ?? 1);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.paperSize")}>
|
||||
<Select
|
||||
value={paperSize}
|
||||
onChange={(value) => setPaperSize(value)}
|
||||
>
|
||||
{Object.keys(paperDimensions).map((key) => (
|
||||
<Select.Option key={key} value={key}>
|
||||
{key}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.showBorder")}>
|
||||
<Radio.Group
|
||||
options={[
|
||||
{ label: t("printing.generic.borders.none"), value: "none" },
|
||||
{
|
||||
label: t("printing.generic.borders.border"),
|
||||
value: "border",
|
||||
},
|
||||
{ label: t("printing.generic.borders.grid"), value: "grid" },
|
||||
]}
|
||||
onChange={(e: RadioChangeEvent) => {
|
||||
setBorderShowMode(e.target.value);
|
||||
}}
|
||||
value={borderShowMode}
|
||||
optionType="button"
|
||||
buttonStyle="solid"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.blockRowsTop")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={0}
|
||||
max={3}
|
||||
value={blockRowsTop}
|
||||
onChange={(value) => {
|
||||
if (paperRows - value - blockRowsBottom < 1) return;
|
||||
setBlockRowsTop(value);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
min={0}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={blockRowsTop}
|
||||
onChange={(value) => {
|
||||
if (paperRows - (value ?? 0) - blockRowsBottom < 1)
|
||||
return;
|
||||
setBlockRowsTop(value ?? 0);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.blockRowsBottom")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={0}
|
||||
max={3}
|
||||
value={blockRowsBottom}
|
||||
onChange={(value) => {
|
||||
if (paperRows - value - blockRowsTop < 1) return;
|
||||
setBlockRowsBottom(value);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
min={0}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={blockRowsBottom}
|
||||
onChange={(value) => {
|
||||
if (paperRows - (value ?? 0) - blockRowsTop < 1) return;
|
||||
setBlockRowsBottom(value ?? 0);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.blockColumnsLeft")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={0}
|
||||
max={3}
|
||||
value={blockColumnsLeft}
|
||||
onChange={(value) => {
|
||||
if (paperColumns - value - blockColumnsRight < 1) return;
|
||||
setBlockColumnsLeft(value);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
min={0}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={blockColumnsLeft}
|
||||
onChange={(value) => {
|
||||
if (paperColumns - (value ?? 0) - blockColumnsRight < 1)
|
||||
return;
|
||||
setBlockColumnsLeft(value ?? 0);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.generic.blockColumnsRight")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={0}
|
||||
max={3}
|
||||
value={blockColumnsRight}
|
||||
onChange={(value) => {
|
||||
if (paperColumns - value - blockColumnsLeft < 1) return;
|
||||
setBlockColumnsRight(value);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
min={0}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={blockColumnsRight}
|
||||
onChange={(value) => {
|
||||
if (paperColumns - (value ?? 0) - blockColumnsLeft < 1)
|
||||
return;
|
||||
setBlockColumnsRight(value ?? 0);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
{extraSettings && <Divider />}
|
||||
{extraSettings}
|
||||
<Divider />
|
||||
<Form.Item label={t("printing.generic.previewScale")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
min={0.1}
|
||||
max={1}
|
||||
step={0.01}
|
||||
value={previewScale}
|
||||
onChange={(value) => {
|
||||
setPreviewScale(value);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
min={0.1}
|
||||
max={1}
|
||||
step={0.01}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={previewScale}
|
||||
onChange={(value) => {
|
||||
setPreviewScale(value ?? 0.1);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Col>
|
||||
</Row>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default PrintingDialog;
|
||||
130
client/src/components/printing/qrCodePrintingDialog.tsx
Normal file
130
client/src/components/printing/qrCodePrintingDialog.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
import { Col, Form, InputNumber, QRCode, Row, Slider, Switch } from "antd";
|
||||
import { useSavedState } from "../../utils/saveload";
|
||||
import PrintingDialog from "./printingDialog";
|
||||
import { useTranslate } from "@refinedev/core";
|
||||
|
||||
interface QRCodeData {
|
||||
value: string;
|
||||
label?: string;
|
||||
errorLevel?: "L" | "M" | "Q" | "H";
|
||||
}
|
||||
|
||||
interface QRCodePrintingDialogProps {
|
||||
visible: boolean;
|
||||
items: QRCodeData[];
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
const QRCodePrintingDialog: React.FC<QRCodePrintingDialogProps> = ({
|
||||
visible,
|
||||
items,
|
||||
onCancel,
|
||||
}) => {
|
||||
const t = useTranslate();
|
||||
|
||||
const [showContent, setShowContent] = useSavedState(
|
||||
"print-showContent",
|
||||
true
|
||||
);
|
||||
const [textSize, setTextSize] = useSavedState("print-textSize", 5);
|
||||
const [showSpoolmanIcon, setShowSpoolmanIcon] = useSavedState(
|
||||
"print-showSpoolmanIcon",
|
||||
true
|
||||
);
|
||||
|
||||
const elements = items.map((item) => {
|
||||
return (
|
||||
<>
|
||||
<QRCode
|
||||
className="print-qrcode"
|
||||
icon={showSpoolmanIcon ? "/favicon.ico" : undefined}
|
||||
value={item.value}
|
||||
errorLevel={item.errorLevel}
|
||||
type="svg"
|
||||
color="#000"
|
||||
/>
|
||||
{showContent && (
|
||||
<div
|
||||
className="print-qrcode-title"
|
||||
style={{ textAlign: "center", color: "#000" }}
|
||||
>
|
||||
{item.label ?? item.value}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<PrintingDialog
|
||||
visible={visible}
|
||||
title={t("printing.qrcode.title")}
|
||||
items={elements}
|
||||
extraSettings={
|
||||
<>
|
||||
<Form.Item label={t("printing.qrcode.showContent")}>
|
||||
<Switch
|
||||
checked={showContent}
|
||||
onChange={(checked) => setShowContent(checked)}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.qrcode.textSize")}>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Slider
|
||||
disabled={!showContent}
|
||||
tooltip={{ formatter: (value) => `${value} mm` }}
|
||||
min={3}
|
||||
max={15}
|
||||
value={textSize}
|
||||
step={0.1}
|
||||
onChange={(value) => {
|
||||
setTextSize(value);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<InputNumber
|
||||
disabled={!showContent}
|
||||
min={0.01}
|
||||
step={0.1}
|
||||
style={{ margin: "0 16px" }}
|
||||
value={textSize}
|
||||
addonAfter="mm"
|
||||
onChange={(value) => {
|
||||
setTextSize(value ?? 5);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("printing.qrcode.showSpoolmanIcon")}>
|
||||
<Switch
|
||||
checked={showSpoolmanIcon}
|
||||
onChange={(checked) => setShowSpoolmanIcon(checked)}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
}
|
||||
style={`
|
||||
.print-page .print-qrcode {
|
||||
height: 100% !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.print-page .print-qrcode-title {
|
||||
font-size: ${textSize}mm;
|
||||
}
|
||||
|
||||
.print-page svg {
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: auto;
|
||||
}
|
||||
`}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default QRCodePrintingDialog;
|
||||
82
client/src/components/qrCodeScanner.tsx
Normal file
82
client/src/components/qrCodeScanner.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import React, { useState } from "react";
|
||||
import { FloatButton, Modal, Space } from "antd";
|
||||
import { QrScanner } from "@yudiel/react-qr-scanner";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { CameraOutlined } from "@ant-design/icons";
|
||||
import { useTranslate } from "@refinedev/core";
|
||||
|
||||
const QRCodeScannerModal: React.FC = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [lastError, setLastError] = useState<string | null>(null);
|
||||
const t = useTranslate();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const onScan = (result: string) => {
|
||||
// Check for the spoolman ID format
|
||||
const match = result.match(/^web\+spoolman:s-(?<id>[0-9]+)$/);
|
||||
if (match && match.groups) {
|
||||
setVisible(false);
|
||||
navigate(`/spool/show/${match.groups.id}`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<FloatButton
|
||||
type="primary"
|
||||
onClick={() => setVisible(true)}
|
||||
icon={<CameraOutlined />}
|
||||
shape="circle"
|
||||
/>
|
||||
<Modal
|
||||
open={visible}
|
||||
onCancel={() => setVisible(false)}
|
||||
footer={null}
|
||||
title={t("scanner.title")}
|
||||
>
|
||||
<Space direction="vertical" style={{ width: "100%" }}>
|
||||
<p>{t("scanner.description")}</p>
|
||||
<QrScanner
|
||||
viewFinder={
|
||||
lastError
|
||||
? () => (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
textAlign: "center",
|
||||
width: "100%",
|
||||
top: "50%",
|
||||
}}
|
||||
>
|
||||
<p>{lastError}</p>
|
||||
</div>
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
onDecode={onScan}
|
||||
onError={(error: Error) => {
|
||||
console.error(error);
|
||||
if (error.name === "NotAllowedError") {
|
||||
setLastError(t("scanner.error.notAllowed"));
|
||||
} else if (
|
||||
error.name === "InsecureContextError" ||
|
||||
(location.protocol !== "https:" &&
|
||||
navigator.mediaDevices === undefined)
|
||||
) {
|
||||
setLastError(t("scanner.error.insecureContext"));
|
||||
} else if (error.name === "StreamApiNotSupportedError") {
|
||||
setLastError(t("scanner.error.streamApiNotSupported"));
|
||||
} else if (error.name === "NotReadableError") {
|
||||
setLastError(t("scanner.error.notReadable"));
|
||||
} else {
|
||||
setLastError(t("scanner.error.unknown", { error: error.name }));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default QRCodeScannerModal;
|
||||
53
client/src/components/selectAndPrintDialog.tsx
Normal file
53
client/src/components/selectAndPrintDialog.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
// React FC that combines the functionality of first selecting what spools to print QR code for, and then opens up the QR code printing dialog.
|
||||
|
||||
import React from "react";
|
||||
import SpoolSelectModal from "./spoolSelectModal";
|
||||
import QRCodePrintingDialog from "./printing/qrCodePrintingDialog";
|
||||
import { Button } from "antd";
|
||||
import { ISpool } from "../pages/spools/model";
|
||||
import { PrinterOutlined } from "@ant-design/icons";
|
||||
import { useTranslate } from "@refinedev/core";
|
||||
|
||||
const SelectAndPrint: React.FC = () => {
|
||||
const t = useTranslate();
|
||||
|
||||
const [step, setStep] = React.useState(0);
|
||||
const [selectedSpools, setSelectedSpools] = React.useState<ISpool[]>([]);
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PrinterOutlined />}
|
||||
onClick={() => {
|
||||
setStep(1);
|
||||
}}
|
||||
>
|
||||
{t("printing.qrcode.button")}
|
||||
</Button>
|
||||
<SpoolSelectModal
|
||||
visible={step === 1}
|
||||
description={t("printing.spoolSelect.description")}
|
||||
onCancel={() => {
|
||||
setStep(0);
|
||||
}}
|
||||
onContinue={(spools) => {
|
||||
setSelectedSpools(spools);
|
||||
setStep(2);
|
||||
}}
|
||||
/>
|
||||
<QRCodePrintingDialog
|
||||
visible={step === 2}
|
||||
onCancel={() => {
|
||||
setStep(1);
|
||||
}}
|
||||
items={selectedSpools.map((spool) => ({
|
||||
value: `web+spoolman:s-${spool.id}`,
|
||||
label: `s-${spool.id}`,
|
||||
errorLevel: "H",
|
||||
}))}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectAndPrint;
|
||||
217
client/src/components/spoolSelectModal.tsx
Normal file
217
client/src/components/spoolSelectModal.tsx
Normal file
@@ -0,0 +1,217 @@
|
||||
import React, { useState } from "react";
|
||||
import { Modal, Table, Checkbox, Space, Row, Col, message } from "antd";
|
||||
import { ISpool } from "../pages/spools/model";
|
||||
import { FilteredColumn, SortedColumn, SpoolIconColumn } from "./column";
|
||||
import { TableState } from "../utils/saveload";
|
||||
import { useTable } from "@refinedev/antd";
|
||||
import { genericSorter, typeSorters } from "../utils/sorting";
|
||||
import { genericFilterer, typeFilters } from "../utils/filtering";
|
||||
import { t } from "i18next";
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
description?: string;
|
||||
onCancel: () => void;
|
||||
onContinue: (selectedSpools: ISpool[]) => void;
|
||||
}
|
||||
|
||||
interface ISpoolCollapsed extends ISpool {
|
||||
filament_name: string;
|
||||
material?: string;
|
||||
}
|
||||
|
||||
const SpoolSelectModal: React.FC<Props> = ({
|
||||
visible,
|
||||
description,
|
||||
onCancel,
|
||||
onContinue,
|
||||
}) => {
|
||||
const [selectedItems, setSelectedItems] = useState<number[]>([]);
|
||||
const [showArchived, setShowArchived] = useState(false);
|
||||
const [messageApi, contextHolder] = message.useMessage();
|
||||
|
||||
const { tableProps, sorters, filters, current, pageSize } = useTable<ISpool>({
|
||||
meta: {
|
||||
queryParams: {
|
||||
["allow_archived"]: showArchived,
|
||||
},
|
||||
},
|
||||
syncWithLocation: false,
|
||||
pagination: {
|
||||
mode: "off",
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
sorters: {
|
||||
mode: "off",
|
||||
},
|
||||
filters: {
|
||||
mode: "off",
|
||||
},
|
||||
});
|
||||
|
||||
// Store state in local storage
|
||||
const tableState: TableState = {
|
||||
sorters,
|
||||
filters,
|
||||
pagination: { current, pageSize },
|
||||
};
|
||||
|
||||
// Collapse the dataSource to a mutable list and add a filament_name field
|
||||
const dataSource: ISpoolCollapsed[] = React.useMemo(
|
||||
() =>
|
||||
(tableProps.dataSource ?? []).map((element) => {
|
||||
let filament_name: string;
|
||||
if (element.filament.vendor && "name" in element.filament.vendor) {
|
||||
filament_name = `${element.filament.vendor.name} - ${element.filament.name}`;
|
||||
} else {
|
||||
filament_name =
|
||||
element.filament.name ?? element.filament.id.toString();
|
||||
}
|
||||
return {
|
||||
...element,
|
||||
filament_name,
|
||||
material: element.filament.material,
|
||||
};
|
||||
}),
|
||||
[tableProps.dataSource]
|
||||
);
|
||||
|
||||
// Type the sorters and filters
|
||||
const typedSorters = typeSorters<ISpoolCollapsed>(sorters);
|
||||
const typedFilters = typeFilters<ISpoolCollapsed>(filters);
|
||||
|
||||
// Filter and sort the dataSource
|
||||
const filteredDataSource = React.useMemo(() => {
|
||||
const filtered = dataSource.filter(genericFilterer(typedFilters));
|
||||
filtered.sort(genericSorter(typedSorters));
|
||||
return filtered;
|
||||
}, [dataSource, typedFilters, typedSorters]);
|
||||
|
||||
// Function to add/remove all filtered items from selected items
|
||||
const selectUnselectFiltered = (select: boolean) => {
|
||||
setSelectedItems((prevSelected) => {
|
||||
const filtered = filteredDataSource
|
||||
.map((spool) => spool.id)
|
||||
.filter((spool) => !prevSelected.includes(spool));
|
||||
return select ? [...prevSelected, ...filtered] : filtered;
|
||||
});
|
||||
};
|
||||
|
||||
// Handler for selecting/unselecting individual items
|
||||
const handleSelectItem = (item: number) => {
|
||||
setSelectedItems((prevSelected) =>
|
||||
prevSelected.includes(item)
|
||||
? prevSelected.filter((selected) => selected !== item)
|
||||
: [...prevSelected, item]
|
||||
);
|
||||
};
|
||||
|
||||
// State for the select/unselect all checkbox
|
||||
const isAllFilteredSelected = filteredDataSource.every((spool) =>
|
||||
selectedItems.includes(spool.id)
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t("printing.spoolSelect.title")}
|
||||
open={visible}
|
||||
onCancel={onCancel}
|
||||
onOk={() => {
|
||||
if (selectedItems.length === 0) {
|
||||
messageApi.open({
|
||||
type: "error",
|
||||
content: t("printing.spoolSelect.noSpoolsSelected"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
onContinue(
|
||||
dataSource.filter((spool) => selectedItems.includes(spool.id))
|
||||
);
|
||||
}}
|
||||
width={600}
|
||||
>
|
||||
{contextHolder}
|
||||
<Space direction="vertical" style={{ width: "100%" }}>
|
||||
{description && <div>{description}</div>}
|
||||
<Table
|
||||
{...tableProps}
|
||||
rowKey="id"
|
||||
dataSource={filteredDataSource}
|
||||
pagination={false}
|
||||
scroll={{ x: "max-content", y: 200 }}
|
||||
>
|
||||
<Table.Column
|
||||
width={50}
|
||||
render={(_, item: ISpool) => (
|
||||
<Checkbox
|
||||
checked={selectedItems.includes(item.id)}
|
||||
onChange={() => handleSelectItem(item.id)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{SortedColumn({
|
||||
id: "id",
|
||||
i18ncat: "spool",
|
||||
dataSource,
|
||||
tableState,
|
||||
})}
|
||||
{SpoolIconColumn({
|
||||
id: "filament_name",
|
||||
i18ncat: "spool",
|
||||
color: (record: ISpoolCollapsed) => record.filament.color_hex,
|
||||
dataSource,
|
||||
tableState,
|
||||
})}
|
||||
{FilteredColumn({
|
||||
id: "material",
|
||||
i18ncat: "spool",
|
||||
dataSource,
|
||||
tableState,
|
||||
})}
|
||||
</Table>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Checkbox
|
||||
checked={isAllFilteredSelected}
|
||||
onChange={(e) => {
|
||||
selectUnselectFiltered(e.target.checked);
|
||||
}}
|
||||
>
|
||||
{t("printing.spoolSelect.selectAll")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<div style={{ float: "right" }}>
|
||||
{t("printing.spoolSelect.selectedTotal", {
|
||||
count: selectedItems.length,
|
||||
})}
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Checkbox
|
||||
checked={showArchived}
|
||||
onChange={(e) => {
|
||||
setShowArchived(e.target.checked);
|
||||
if (!e.target.checked) {
|
||||
// Remove archived spools from selected items
|
||||
setSelectedItems((prevSelected) =>
|
||||
prevSelected.filter(
|
||||
(selected) =>
|
||||
dataSource.find((spool) => spool.id === selected)
|
||||
?.archived !== true
|
||||
)
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t("printing.spoolSelect.showArchived")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
</Row>
|
||||
</Space>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpoolSelectModal;
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
SpoolIconColumn,
|
||||
} from "../../components/column";
|
||||
import { setSpoolArchived } from "./functions";
|
||||
import SelectAndPrint from "../../components/selectAndPrintDialog";
|
||||
|
||||
dayjs.extend(utc);
|
||||
|
||||
@@ -191,6 +192,7 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
|
||||
<List
|
||||
headerButtons={({ defaultButtons }) => (
|
||||
<>
|
||||
<SelectAndPrint />
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<InboxOutlined />}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { defineConfig } from "vite";
|
||||
import svgr from "vite-plugin-svgr";
|
||||
import mkcert from 'vite-plugin-mkcert'
|
||||
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react(), svgr()],
|
||||
server: { https: true },
|
||||
plugins: [react(), svgr(), mkcert()],
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user