feat: PDF for categories
This commit is contained in:
parent
d857fce71f
commit
752f03cba5
217
src/main/webapp/package-lock.json
generated
217
src/main/webapp/package-lock.json
generated
@ -21,6 +21,7 @@
|
||||
"i18next": "^25.8.0",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"jspdf": "^4.1.0",
|
||||
"jszip": "^3.10.1",
|
||||
"leaflet": "^1.9.4",
|
||||
"obs-websocket-js": "^5.0.7",
|
||||
@ -1605,6 +1606,19 @@
|
||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/pako": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz",
|
||||
"integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/raf": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
|
||||
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.2.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz",
|
||||
@ -1631,6 +1645,13 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.7.tgz",
|
||||
"integrity": "sha512-VgDNokpBoKF+wrdvhAAfS55OMQpL6QRglwTwNC3kIgBrzZxA4WsFj+2eLfEA/uMUDzBcEhYmjSbwQakn/i3ajA=="
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@types/use-sync-external-store": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
||||
@ -1884,6 +1905,16 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/base64-arraybuffer": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
|
||||
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.9.15",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.15.tgz",
|
||||
@ -2028,6 +2059,26 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/canvg": {
|
||||
"version": "3.0.11",
|
||||
"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz",
|
||||
"integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@types/raf": "^3.4.0",
|
||||
"core-js": "^3.8.3",
|
||||
"raf": "^3.4.1",
|
||||
"regenerator-runtime": "^0.13.7",
|
||||
"rgbcolor": "^1.0.1",
|
||||
"stackblur-canvas": "^2.0.0",
|
||||
"svg-pathdata": "^6.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cfb": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
|
||||
@ -2088,6 +2139,18 @@
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/core-js": {
|
||||
"version": "3.48.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz",
|
||||
"integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/core-js"
|
||||
}
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
@ -2139,6 +2202,16 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/css-line-break": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
|
||||
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/css-to-react-native": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
|
||||
@ -2384,6 +2457,16 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz",
|
||||
"integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||
"optional": true,
|
||||
"optionalDependencies": {
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
@ -2952,6 +3035,23 @@
|
||||
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-png": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz",
|
||||
"integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/pako": "^2.0.3",
|
||||
"iobuffer": "^5.3.2",
|
||||
"pako": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-png/node_modules/pako": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
|
||||
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
|
||||
"license": "(MIT AND Zlib)"
|
||||
},
|
||||
"node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
@ -3348,6 +3448,20 @@
|
||||
"void-elements": "3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html2canvas": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
|
||||
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"css-line-break": "^2.1.0",
|
||||
"text-segmentation": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "25.8.0",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.8.0.tgz",
|
||||
@ -3471,6 +3585,12 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/iobuffer": {
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz",
|
||||
"integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-array-buffer": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
|
||||
@ -3923,6 +4043,29 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jspdf": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-4.1.0.tgz",
|
||||
"integrity": "sha512-xd1d/XRkwqnsq6FP3zH1Q+Ejqn2ULIJeDZ+FTKpaabVpZREjsJKRJwuokTNgdqOU+fl55KgbvgZ1pRTSWCP2kQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"fast-png": "^6.2.0",
|
||||
"fflate": "^0.8.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"canvg": "^3.0.11",
|
||||
"core-js": "^3.6.0",
|
||||
"dompurify": "^3.3.1",
|
||||
"html2canvas": "^1.0.0-rc.5"
|
||||
}
|
||||
},
|
||||
"node_modules/jspdf/node_modules/fflate": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
|
||||
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jsx-ast-utils": {
|
||||
"version": "3.3.5",
|
||||
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
|
||||
@ -4358,6 +4501,13 @@
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/performance-now": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@ -4485,6 +4635,16 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/raf": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
|
||||
"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"performance-now": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.2.3",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
||||
@ -4732,6 +4892,13 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/regexp.prototype.flags": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
|
||||
@ -4783,6 +4950,16 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/rgbcolor": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
|
||||
"integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
|
||||
"license": "MIT OR SEE LICENSE IN FEEL-FREE.md",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 0.8.15"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.55.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz",
|
||||
@ -5071,6 +5248,16 @@
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/stackblur-canvas": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
|
||||
"integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.1.14"
|
||||
}
|
||||
},
|
||||
"node_modules/stop-iteration-iterator": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
|
||||
@ -5246,6 +5433,26 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/svg-pathdata": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
|
||||
"integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/text-segmentation": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
|
||||
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"utrie": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-invariant": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||
@ -5438,6 +5645,16 @@
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||
},
|
||||
"node_modules/utrie": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
|
||||
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"base64-arraybuffer": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/uzip": {
|
||||
"version": "0.20201231.0",
|
||||
"resolved": "https://registry.npmjs.org/uzip/-/uzip-0.20201231.0.tgz",
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
"i18next": "^25.8.0",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"jspdf": "^4.1.0",
|
||||
"jszip": "^3.10.1",
|
||||
"leaflet": "^1.9.4",
|
||||
"obs-websocket-js": "^5.0.7",
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
"actuel": "Current",
|
||||
"administration": "Administration",
|
||||
"adresseDuServeur": "Server address",
|
||||
"afficher": "Show",
|
||||
"ajoutAutomatique": "Automatic addition",
|
||||
"ajouter": "Add",
|
||||
"ajouterDesCombattants": "Add fighters",
|
||||
@ -23,6 +24,7 @@
|
||||
"cartonNoir": "Black card",
|
||||
"cartonRouge": "Red card",
|
||||
"catégorie": "Category",
|
||||
"catégorieDâgeMoyenne": "Middle-aged category",
|
||||
"catégoriesVontêtreCréées": "weight categories will be created",
|
||||
"ceCartonEstIssuDunCartonDéquipe": "This card comes from a team card, do you really want to delete it?",
|
||||
"certainsCombattantsNontPasDePoidsRenseigné": "Some fighters do not have a weight listed; they will NOT be included in the categories.",
|
||||
@ -89,6 +91,7 @@
|
||||
"genre.f": "F",
|
||||
"genre.h": "M",
|
||||
"genre.na": "NA",
|
||||
"imprimer": "Print",
|
||||
"individuelle": "Individual",
|
||||
"informationCatégorie": "Category information",
|
||||
"inscrit": "Registered",
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
"actuel": "Actuel",
|
||||
"administration": "Administration",
|
||||
"adresseDuServeur": "Adresse du serveur",
|
||||
"afficher": "Afficher",
|
||||
"ajoutAutomatique": "Ajout automatique",
|
||||
"ajouter": "Ajouter",
|
||||
"ajouterDesCombattants": "Ajouter des combattants",
|
||||
@ -23,6 +24,7 @@
|
||||
"cartonNoir": "Carton noir",
|
||||
"cartonRouge": "Carton rouge",
|
||||
"catégorie": "Catégorie",
|
||||
"catégorieDâgeMoyenne": "Catégorie d'âge moyenne",
|
||||
"catégoriesVontêtreCréées": "catégories de poids vont être créées",
|
||||
"ceCartonEstIssuDunCartonDéquipe": "Ce carton est issu d'un carton d'équipe, voulez-vous vraiment le supprimer ?",
|
||||
"certainsCombattantsNontPasDePoidsRenseigné": "Certains combattants n'ont pas de poids renseigné, ils ne seront PAS insert dans les catégories",
|
||||
@ -89,6 +91,7 @@
|
||||
"genre.f": "F",
|
||||
"genre.h": "H",
|
||||
"genre.na": "NA",
|
||||
"imprimer": "Imprimer",
|
||||
"individuelle": "Individuelle",
|
||||
"informationCatégorie": "Information catégorie",
|
||||
"inscrit": "Inscrit",
|
||||
|
||||
@ -164,3 +164,27 @@ const ProtectionSelector = ({
|
||||
}
|
||||
|
||||
export default ProtectionSelector;
|
||||
|
||||
|
||||
export function getMandatoryProtectionsList(mandatoryProtection, shield, t) {
|
||||
const protections = [];
|
||||
const isOn = (bit) => (mandatoryProtection & (1 << (bit - 1))) !== 0;
|
||||
|
||||
if (isOn(1)) protections.push(t('casque', {ns: "common"}));
|
||||
if (isOn(2)) protections.push(t('gorgerin', {ns: "common"}));
|
||||
if (isOn(3)) protections.push(t('coquilleProtectionPelvienne', {ns: "common"}));
|
||||
if (isOn(4) && !shield) protections.push(t('gants', {ns: "common"}));
|
||||
if (isOn(4) && shield) protections.push(t('gantMainsArmées', {ns: "common"}));
|
||||
if (isOn(5) && shield) protections.push(t('gantMainBouclier', {ns: "common"}));
|
||||
if (isOn(6)) protections.push(t('plastron', {ns: "common"}));
|
||||
if (isOn(7) && !shield) protections.push(t('protectionDeBras', {ns: "common"}));
|
||||
if (isOn(7) && shield) protections.push(t('protectionDeBrasArmé', {ns: "common"}));
|
||||
if (isOn(8) && shield) protections.push(t('protectionDeBrasDeBouclier', {ns: "common"}));
|
||||
if (isOn(9)) protections.push(t('protectionDeJambes', {ns: "common"}));
|
||||
if (isOn(10)) protections.push(t('protectionDeGenoux', {ns: "common"}));
|
||||
if (isOn(11)) protections.push(t('protectionDeCoudes', {ns: "common"}));
|
||||
if (isOn(12)) protections.push(t('protectionDorsale', {ns: "common"}));
|
||||
if (isOn(13)) protections.push(t('protectionDePieds', {ns: "common"}));
|
||||
|
||||
return protections;
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {SimpleIconsOBS} from "../../../assets/SimpleIconsOBS.ts";
|
||||
import JSZip from "jszip";
|
||||
import {detectOptimalBackground} from "../../../components/SmartLogoBackground.jsx";
|
||||
import {faFile, faGlobe, faTableCellsLarge, faTrash} from "@fortawesome/free-solid-svg-icons";
|
||||
import {faFile, faGlobe, faPrint, faTableCellsLarge, faTrash} from "@fortawesome/free-solid-svg-icons";
|
||||
import {Trans, useTranslation} from "react-i18next";
|
||||
import i18n from "i18next";
|
||||
import {getToastMessage} from "../../../utils/Tools.js";
|
||||
@ -21,6 +21,7 @@ import {CombName, useCombs} from "../../../hooks/useComb.jsx";
|
||||
import {useCards, useCardsDispatch} from "../../../hooks/useCard.jsx";
|
||||
import {ListPresetSelect} from "../../../components/cm/ListPresetSelect.jsx";
|
||||
import {AutoNewCatModalContent, AutoNewCatSModalContent} from "../../../components/cm/AutoCatModalContent.jsx";
|
||||
import {makePDF} from "../../../utils/cmPdf.jsx";
|
||||
|
||||
const vite_url = import.meta.env.VITE_URL;
|
||||
|
||||
@ -177,6 +178,7 @@ function Menu({menuActions, compUuid}) {
|
||||
const longPress = useRef({time: null, timer: null, button: null});
|
||||
const obsModal = useRef(null);
|
||||
const teamCardModal = useRef(null);
|
||||
const printModal = useRef(null);
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
const [showStateWin, setShowStateWin] = useState(false)
|
||||
@ -265,7 +267,8 @@ function Menu({menuActions, compUuid}) {
|
||||
}
|
||||
|
||||
const copyScriptToClipboard = () => {
|
||||
navigator.clipboard.writeText(`<!--suppress ALL -->
|
||||
// noinspection JSFileReferences
|
||||
navigator.clipboard.writeText(`
|
||||
<div id='safca_api_data'></div>
|
||||
<script type="module">
|
||||
import {initCompetitionApi} from '${vite_url}/competition.js';
|
||||
@ -290,6 +293,11 @@ function Menu({menuActions, compUuid}) {
|
||||
onMouseUp={() => longPressUp("cards")}
|
||||
data-bs-toggle="tooltip2" data-bs-placement="top"
|
||||
data-bs-title={t("carton")}/>
|
||||
<FontAwesomeIcon icon={faPrint} size="xl"
|
||||
style={{color: "#6c757d", cursor: "pointer"}}
|
||||
onClick={() => printModal.current.click()}
|
||||
data-bs-toggle="tooltip2" data-bs-placement="top"
|
||||
data-bs-title={t('imprimer')}/>
|
||||
<FontAwesomeIcon icon={SimpleIconsOBS} size="xl"
|
||||
style={{color: "#6c757d", cursor: "pointer"}}
|
||||
onMouseDown={() => longPressDown("obs")}
|
||||
@ -375,6 +383,71 @@ function Menu({menuActions, compUuid}) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button ref={printModal} type="button" className="btn btn-link" data-bs-toggle="modal" data-bs-target="#PrintModal"
|
||||
style={{display: 'none'}}>
|
||||
Launch printModal
|
||||
</button>
|
||||
<div className="modal fade" id="PrintModal" tabIndex="-1" aria-labelledby="PrintModalLabel" aria-hidden="true">
|
||||
<div className="modal-dialog">
|
||||
<div className="modal-content">
|
||||
<PrintModal menuActions={menuActions}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
function PrintModal({menuActions}) {
|
||||
const [categorie, setCategorie] = useState(true);
|
||||
const [categorieEmpty, setCategorieEmpty] = useState(false);
|
||||
|
||||
const {welcomeData} = useWS();
|
||||
const {getComb} = useCombs();
|
||||
const {t} = useTranslation("cm");
|
||||
|
||||
const print = (action) => {
|
||||
const pages = [];
|
||||
const names = [];
|
||||
|
||||
if (categorie) {
|
||||
if (menuActions.printCategorie) {
|
||||
const [name, page] = menuActions.printCategorie(categorieEmpty);
|
||||
pages.push(...page);
|
||||
names.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
if (pages.length !== 0) {
|
||||
makePDF(action, pages, names.join(" - "), welcomeData?.name, getComb, t)
|
||||
}
|
||||
}
|
||||
|
||||
return <>
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">Quoi imprimer ?</h5>
|
||||
<button type="button" className="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<div className="form-check">
|
||||
<input className="form-check-input" type="checkbox" checked={categorie} id="checkPrint"
|
||||
onChange={e => setCategorie(e.target.checked)}/>
|
||||
<label className="form-check-label" htmlFor="checkPrint">Catégorie sélectionner</label>
|
||||
</div>
|
||||
{categorie &&
|
||||
<div className="form-check" style={{marginLeft: "1em"}}>
|
||||
<input className="form-check-input" type="checkbox" checked={categorieEmpty} id="checkPrint2"
|
||||
onChange={e => setCategorieEmpty(e.target.checked)}/>
|
||||
<label className="form-check-label" htmlFor="checkPrint2">Feuille vierge</label>
|
||||
</div>}
|
||||
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
<button type="submit" className="btn btn-primary" data-bs-dismiss="modal" onClick={() => print("show")}>{t('afficher')}</button>
|
||||
<button type="submit" className="btn btn-primary" data-bs-dismiss="modal" onClick={() => print("download")}>{t('enregistrer')}</button>
|
||||
<button type="submit" className="btn btn-primary" data-bs-dismiss="modal" onClick={() => print("print")}>{t('imprimer')}</button>
|
||||
<button type="button" className="btn btn-secondary" data-bs-dismiss="modal">{t('fermer')}</button>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
@ -22,6 +22,7 @@ import {hasEffectCard, useCards, useCardsDispatch} from "../../../hooks/useCard.
|
||||
import {ScorePanel} from "./ScoreAndCardPanel.jsx";
|
||||
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
|
||||
import {AutoCatModalContent} from "../../../components/cm/AutoCatModalContent.jsx";
|
||||
import {makePDF} from "../../../utils/cmPdf.jsx";
|
||||
|
||||
const vite_url = import.meta.env.VITE_URL;
|
||||
|
||||
@ -184,9 +185,28 @@ export function CategoryContent({cat, catId, setCat, menuActions}) {
|
||||
<div className="col-md-9">
|
||||
{cat && <ListMatch cat={cat} matches={matches} groups={groups} reducer={reducer}/>}
|
||||
</div>
|
||||
<PrintMatch menuActions={menuActions} matches={matches} groups={groups} cat={cat}/>
|
||||
</>
|
||||
}
|
||||
|
||||
function PrintMatch({menuActions, cat, groups, matches}) {
|
||||
const {cards_v} = useCards();
|
||||
const marches2 = matches.filter(m => m.categorie === cat.id)
|
||||
.map(m => ({...m, ...win_end(m, cards_v)}))
|
||||
|
||||
marches2.forEach(m => {
|
||||
if (m.end && (!m.scores || m.scores.length === 0))
|
||||
m.scores = [{n_round: 0, s1: 0, s2: 0}];
|
||||
})
|
||||
|
||||
menuActions.printCategorie = (categorieEmpty) => {
|
||||
return [cat.name, [
|
||||
{type: "categorie", params: ({cat, matches: marches2, groups, cards_v, categorieEmpty})}
|
||||
]]
|
||||
}
|
||||
return <></>
|
||||
}
|
||||
|
||||
function AddComb({groups, setGroups, removeGroup, menuActions, cat}) {
|
||||
const {data, setData} = useRequestWS("getRegister", null)
|
||||
const combDispatch = useCombsDispatch()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import {useEffect, useRef} from "react";
|
||||
import {scorePrint} from "../../utils/Tools.js";
|
||||
import {compareCardOrder, useCardsStatic} from "../../hooks/useCard.jsx";
|
||||
import {useCardsStatic} from "../../hooks/useCard.jsx";
|
||||
|
||||
const max_x = 500;
|
||||
|
||||
@ -451,3 +451,279 @@ export function DrawGraph({
|
||||
<canvas ref={canvasRef} style={{border: "1px solid grey", marginTop: "10px", position: "relative", opacity: 1}} id="myCanvas"></canvas>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
export function drawGraphForPdf(root = [], size = 14, cards = []) {
|
||||
const {getHeightCardForCombInMatch} = useCardsStatic(cards);
|
||||
const sizeY = size * 0.5;
|
||||
|
||||
function getBounds(root) {
|
||||
let px = max_x;
|
||||
let py;
|
||||
let maxx, minx, miny, maxy
|
||||
|
||||
function drawNode(tree, px, py) {
|
||||
let death = tree.death() - 1
|
||||
|
||||
if (death === 0) {
|
||||
if (miny > py - sizeY - ((sizeY * 1.5 / 2) | 0)) miny = py - sizeY - (sizeY * 1.5 / 2) | 0;
|
||||
if (maxy < py + sizeY + ((sizeY * 1.5 / 2) | 0)) maxy = py + sizeY + (sizeY * 1.5 / 2) | 0;
|
||||
} else {
|
||||
if (miny > py - sizeY * 2 * death - ((sizeY * 1.5 / 2) | 0))
|
||||
miny = py - sizeY * 2 * death - ((sizeY * 1.5 / 2) | 0);
|
||||
if (maxy < py + sizeY * 2 * death + ((sizeY * 1.5 / 2) | 0))
|
||||
maxy = py + sizeY * 2 * death + ((sizeY * 1.5 / 2) | 0);
|
||||
}
|
||||
if (minx > px - size * 2 - size * 8) minx = px - size * 2 - size * 8;
|
||||
|
||||
if (tree.left != null) drawNode(tree.left, px - size * 2 - size * 8, py - sizeY * 2 * death);
|
||||
if (tree.right != null) drawNode(tree.right, px - size * 2 - size * 8, py + sizeY * 2 * death);
|
||||
}
|
||||
|
||||
if (root != null) {
|
||||
py = (sizeY * 2 * root.at(0).death() + (((sizeY * 1.5 / 2) | 0) + sizeY) * root.at(0).death()) * 2;
|
||||
|
||||
maxx = px;
|
||||
minx = px;
|
||||
miny = py - (sizeY * 1.5 / 2) | 0;
|
||||
maxy = py + (sizeY * 1.5 / 2) | 0;
|
||||
|
||||
for (const node of root) {
|
||||
px = px - size * 2 - size * 8;
|
||||
if (minx > px) minx = px;
|
||||
|
||||
drawNode(node, px, py);
|
||||
//graphics2D.drawRect(minx, miny, maxx - minx, maxy - miny);
|
||||
py = maxy + ((sizeY * 2 * node.death() + ((sizeY * 1.5 / 2) | 0)));
|
||||
px = maxx;
|
||||
}
|
||||
} else {
|
||||
minx = 0;
|
||||
maxx = 0;
|
||||
miny = 0;
|
||||
maxy = 0;
|
||||
}
|
||||
|
||||
return [minx, maxx, miny, maxy];
|
||||
}
|
||||
|
||||
// Fonction pour dessiner du texte avec gestion de la taille
|
||||
const printText = (ctx, s, x, y, width, height, lineG, lineD) => {
|
||||
ctx.save();
|
||||
ctx.translate(x, y);
|
||||
let tSize = 17;
|
||||
let ratioX = height * 1.0 / 20.0;
|
||||
ctx.font = "100 " + tSize + "px Arial";
|
||||
|
||||
let mw = width - (ratioX * 2) | 0;
|
||||
if (ctx.measureText(s).width > mw) {
|
||||
do {
|
||||
tSize--;
|
||||
ctx.font = tSize + "px Arial";
|
||||
} while (ctx.measureText(s).width > mw && tSize > 10);
|
||||
|
||||
if (ctx.measureText(s).width > mw) {
|
||||
let truncated = "";
|
||||
const words = s.split(" ");
|
||||
for (const word of words) {
|
||||
if (ctx.measureText(truncated + word).width >= mw) {
|
||||
truncated += "...";
|
||||
break;
|
||||
} else {
|
||||
truncated += word + " ";
|
||||
}
|
||||
}
|
||||
s = truncated;
|
||||
}
|
||||
}
|
||||
|
||||
const text = ctx.measureText(s);
|
||||
let dx = (width - text.width) / 2;
|
||||
let dy = ((height - text.actualBoundingBoxDescent) / 2) + (text.actualBoundingBoxAscent / 2);
|
||||
ctx.fillText(s, dx, dy, width - dy);
|
||||
ctx.restore();
|
||||
|
||||
ctx.beginPath();
|
||||
if (lineD) {
|
||||
ctx.moveTo((ratioX * 2.5 + x + dx + text.width) | 0, y + height / 2);
|
||||
ctx.lineTo(x + width, y + height / 2);
|
||||
}
|
||||
if (lineG) {
|
||||
ctx.moveTo(x, y + height / 2);
|
||||
ctx.lineTo((dx + x - ratioX * 2.5) | 0, y + height / 2);
|
||||
}
|
||||
ctx.stroke();
|
||||
};
|
||||
|
||||
// Fonction pour afficher les scores
|
||||
const printScores = (ctx, scores, px, py, scale) => {
|
||||
ctx.save();
|
||||
ctx.translate(px - size * 2, py - size * scale);
|
||||
ctx.font = "100 14px Arial";
|
||||
ctx.textBaseline = 'top';
|
||||
|
||||
for (let i = 0; i < scores.length; i++) {
|
||||
const score = scorePrint(scores[i].s1) + "-" + scorePrint(scores[i].s2);
|
||||
const div = (scores.length <= 2) ? 2 : (scores.length >= 4) ? 4 : 3;
|
||||
const text = ctx.measureText(score);
|
||||
let dx = (size * 2 - text.width) / 2;
|
||||
let dy = ((size * 2 / div - text.actualBoundingBoxDescent) / 2) + (text.actualBoundingBoxAscent / 2);
|
||||
|
||||
ctx.fillStyle = '#ffffffdd';
|
||||
ctx.fillRect(dx, size * 2 * scale / div * i + dy, text.width, 14);
|
||||
ctx.fillStyle = "#000000";
|
||||
ctx.fillText(score, dx, size * 2 * scale / div * i + dy, size * 2);
|
||||
}
|
||||
ctx.restore();
|
||||
};
|
||||
|
||||
const printCard = (ctx, pos, combId, match) => {
|
||||
const cards2 = getHeightCardForCombInMatch(combId, match)
|
||||
if (cards2 != null) {
|
||||
let oldColor = ctx.fillStyle;
|
||||
switch (cards2.type) {
|
||||
case "BLUE":
|
||||
ctx.fillStyle = "#2e2efd";
|
||||
break;
|
||||
case "YELLOW":
|
||||
ctx.fillStyle = "#d8d800";
|
||||
break;
|
||||
case "RED":
|
||||
ctx.fillStyle = "#FF0000";
|
||||
break;
|
||||
case "BLACK":
|
||||
ctx.fillStyle = "#000000";
|
||||
break;
|
||||
default:
|
||||
ctx.fillStyle = "#FFFFFF00";
|
||||
}
|
||||
|
||||
if (cards2.match === match.id) {
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = ctx.fillStyle
|
||||
ctx.arc(pos.x + pos.width - 10, pos.y + 5, 5, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
ctx.strokeStyle = "#000000"
|
||||
} else
|
||||
ctx.fillRect(pos.x + pos.width - 18, pos.y - 5, 12, 12);
|
||||
ctx.fillStyle = oldColor;
|
||||
}
|
||||
}
|
||||
|
||||
// Fonction pour dessiner un nœud
|
||||
const drawNode = (ctx, tree, px, py, max_y) => {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(px, py);
|
||||
ctx.lineTo(px - size, py);
|
||||
ctx.stroke();
|
||||
|
||||
let death = tree.death() - 1;
|
||||
let match = tree.data;
|
||||
|
||||
if (death === 0) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(px - size, py + sizeY);
|
||||
ctx.lineTo(px - size, py - sizeY);
|
||||
ctx.moveTo(px - size, py + sizeY);
|
||||
ctx.lineTo(px - size * 2, py + sizeY);
|
||||
ctx.moveTo(px - size, py - sizeY);
|
||||
ctx.lineTo(px - size * 2, py - sizeY);
|
||||
ctx.stroke();
|
||||
|
||||
printScores(ctx, match.scores, px, py, 1);
|
||||
|
||||
|
||||
const pos = {x: px - size * 2 - size * 8, y: py - sizeY - (sizeY * 1.5 / 2 | 0), width: size * 8, height: (sizeY * 1.5 | 0)}
|
||||
printCard(ctx, pos, match.c1, match)
|
||||
ctx.fillStyle = "#FF0000"
|
||||
printText(ctx, (match.c1FullName == null) ? "" : match.c1FullName, pos.x, pos.y, pos.width, pos.height, false, true)
|
||||
|
||||
const pos2 = {x: px - size * 2 - size * 8, y: py + sizeY - (sizeY * 1.5 / 2 | 0), width: size * 8, height: (sizeY * 1.5 | 0)}
|
||||
printCard(ctx, pos2, match.c2, match)
|
||||
ctx.fillStyle = "#0000FF"
|
||||
printText(ctx, (match.c2FullName == null) ? "" : match.c2FullName, pos2.x, pos2.y, pos2.width, pos2.height, false, true)
|
||||
|
||||
if (max_y.current < py + sizeY + ((sizeY * 1.5 / 2) | 0)) {
|
||||
max_y.current = py + sizeY + (sizeY * 1.5 / 2 | 0);
|
||||
}
|
||||
} else {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(px - size, py);
|
||||
ctx.lineTo(px - size, py + sizeY * 2 * death);
|
||||
ctx.moveTo(px - size, py);
|
||||
ctx.lineTo(px - size, py - sizeY * 2 * death);
|
||||
ctx.moveTo(px - size, py + sizeY * 2 * death);
|
||||
ctx.lineTo(px - size * 2, py + sizeY * 2 * death);
|
||||
ctx.moveTo(px - size, py - sizeY * 2 * death);
|
||||
ctx.lineTo(px - size * 2, py - sizeY * 2 * death);
|
||||
ctx.stroke();
|
||||
|
||||
printScores(ctx, match.scores, px, py, 1.5);
|
||||
|
||||
const pos = {x: px - size * 2 - size * 8, y: py - sizeY * 2 * death - (sizeY * 1.5 / 2 | 0), width: size * 8, height: (sizeY * 1.5 | 0)}
|
||||
printCard(ctx, pos, match.c1, match)
|
||||
ctx.fillStyle = "#FF0000"
|
||||
printText(ctx, (match.c1FullName == null) ? "" : match.c1FullName, pos.x, pos.y, pos.width, pos.height, true, true)
|
||||
|
||||
const pos2 = {x: px - size * 2 - size * 8, y: py + sizeY * 2 * death - (sizeY * 1.5 / 2 | 0), width: size * 8, height: (sizeY * 1.5 | 0)}
|
||||
printCard(ctx, pos2, match.c2, match)
|
||||
ctx.fillStyle = "#0000FF"
|
||||
printText(ctx, (match.c2FullName == null) ? "" : match.c2FullName, pos2.x, pos2.y, pos2.width, pos2.height, true, true)
|
||||
|
||||
if (max_y.current < py + sizeY * 2 * death + ((sizeY * 1.5 / 2) | 0)) {
|
||||
max_y.current = py + sizeY * 2 * death + ((sizeY * 1.5 / 2 | 0));
|
||||
}
|
||||
}
|
||||
|
||||
if (tree.left != null) {
|
||||
drawNode(ctx, tree.left, px - size * 2 - size * 8, py - sizeY * 2 * death, max_y);
|
||||
}
|
||||
if (tree.right != null) {
|
||||
drawNode(ctx, tree.right, px - size * 2 - size * 8, py + sizeY * 2 * death, max_y);
|
||||
}
|
||||
};
|
||||
|
||||
// Dessiner sur le canvas principal
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.id = "myCanvas";
|
||||
canvas.style.border = "1px solid grey";
|
||||
canvas.style.marginTop = "10px";
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
const [minx, maxx, miny, maxy] = getBounds(root);
|
||||
canvas.width = maxx - minx;
|
||||
canvas.height = maxy - miny;
|
||||
ctx.translate(-minx, -miny);
|
||||
ctx.fillStyle = "#000000";
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeStyle = "#000000";
|
||||
|
||||
let px = maxx;
|
||||
let py;
|
||||
const max_y = {current: 0};
|
||||
|
||||
py = (sizeY * 2 * root[0].death() + (((sizeY * 1.5 / 2) | 0) + sizeY) * root[0].death()) * 2;
|
||||
max_y.current = py + (sizeY * 1.5 / 2 | 0);
|
||||
for (const node of root) {
|
||||
let win_name = "";
|
||||
if (node.data.end) {
|
||||
win_name = node.data.win > 0
|
||||
? (node.data.c1FullName === null ? "???" : node.data.c1FullName)
|
||||
: (node.data.c2FullName === null ? "???" : node.data.c2FullName);
|
||||
}
|
||||
|
||||
ctx.fillStyle = "#18A918";
|
||||
printText(ctx, win_name,
|
||||
px - size * 2 - size * 8, py - ((sizeY * 1.5 / 2) | 0),
|
||||
size * 8, (sizeY * 1.5 | 0), true, false);
|
||||
|
||||
px = px - size * 2 - size * 8;
|
||||
drawNode(ctx, node, px, py, max_y);
|
||||
py = max_y.current + ((sizeY * 2 * node.death() + ((sizeY * 1.5 / 2) | 0)));
|
||||
px = maxx;
|
||||
}
|
||||
|
||||
return canvas;
|
||||
}
|
||||
|
||||
378
src/main/webapp/src/utils/cmPdf.jsx
Normal file
378
src/main/webapp/src/utils/cmPdf.jsx
Normal file
@ -0,0 +1,378 @@
|
||||
import {jsPDF} from 'jspdf'
|
||||
import {renderToString} from "react-dom/server";
|
||||
import React from "react";
|
||||
import {hasEffectCard, useCardsStatic} from "../hooks/useCard.jsx";
|
||||
import {CatList, getCatName, getShieldSize, getShieldTypeName, getSwordSize, getSwordTypeName, timePrint, virtualScore, win_end} from "./Tools.js";
|
||||
import {getMandatoryProtectionsList} from "../components/ProtectionSelector.jsx";
|
||||
import {scoreToString2} from "./CompetitionTools.js";
|
||||
import {TreeNode} from "./TreeUtils.js";
|
||||
import {drawGraphForPdf} from "../pages/result/DrawGraph.jsx";
|
||||
|
||||
function CombName({getComb, combId}) {
|
||||
if (!combId)
|
||||
return <> </>
|
||||
const comb = getComb(combId, null);
|
||||
if (comb) {
|
||||
if (comb.lname === "__team")
|
||||
return <>{comb.fname}</>
|
||||
return <>{comb.fname} {comb.lname}</>
|
||||
} else {
|
||||
return <>[Comb #{combId}]</>
|
||||
}
|
||||
}
|
||||
|
||||
function MatchList({matches, cat, cards_v, classement, getComb, t}) {
|
||||
const {getHeightCardForCombInMatch} = useCardsStatic(cards_v);
|
||||
const liceName = (cat.liceName || "N/A").split(";");
|
||||
|
||||
const getBG = (combId, match, cat) => {
|
||||
const c = getHeightCardForCombInMatch(combId, match)
|
||||
if (!c)
|
||||
return ""
|
||||
let bg = "";
|
||||
let text = "#000";
|
||||
switch (c.type) {
|
||||
case "YELLOW":
|
||||
bg = "#ffc107";
|
||||
break;
|
||||
case "RED":
|
||||
bg = "#dc3545";
|
||||
text = "#FFF";
|
||||
break;
|
||||
case "BLACK":
|
||||
bg = "#000000";
|
||||
text = "#FFF";
|
||||
break;
|
||||
case "BLUE":
|
||||
bg = "#0d6efd";
|
||||
text = "#FFF";
|
||||
break;
|
||||
}
|
||||
return {
|
||||
backgroundColor: bg,
|
||||
color: text,
|
||||
borderRadius: c.match === match.id ? "50%" : "0",
|
||||
opacity: hasEffectCard(c, match.id, cat.id) ? 1 : 0.5
|
||||
}
|
||||
}
|
||||
|
||||
return <table style={{width: "100%", borderCollapse: "collapse"}} border={1}>
|
||||
<thead>
|
||||
<tr>
|
||||
{!classement && <th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}}>{t('no')}</th>}
|
||||
{!classement && <th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}}>{t('poule')}</th>}
|
||||
{!classement && <th style={{textAlign: "center", paddingLeft: "0.2em", paddingRight: "0.2em"}}>{t('zone')}</th>}
|
||||
<th></th>
|
||||
<th style={{textAlign: "center"}}>{t('rouge')}</th>
|
||||
<th style={{textAlign: "center"}}>{t('résultat')}</th>
|
||||
<th style={{textAlign: "center"}}>{t('blue')}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{matches.map((m, index) => (
|
||||
<tr key={m.id} id={m.id} style={{
|
||||
background: index % 2 ? "#ededed" : "white",
|
||||
borderLeft: "0.5px solid gray",
|
||||
borderTop: index === 0 ? "2px solid black" : ""
|
||||
}}>
|
||||
{!classement && <th style={{textAlign: "center", padding: "2pt 0"}} scope="row">{index + 1}</th>}
|
||||
{!classement && <td style={{textAlign: "center"}}>{m.poule}</td>}
|
||||
{!classement && <td style={{textAlign: "center"}}>{liceName[index % liceName.length]}</td>}
|
||||
<td style={{textAlign: "center", ...getBG(m.c1, m, cat)}}>{m.end && ((m.win > 0 && "X") || (m.win === 0 && "-"))}</td>
|
||||
<td style={{textAlign: "center", minWidth: "11em", paddingLeft: "0.2em"}}><CombName combId={m.c1} getComb={getComb}/></td>
|
||||
<td style={{textAlign: "center"}}>{scoreToString2(m, cards_v)}</td>
|
||||
<td style={{textAlign: "center", minWidth: "11em", paddingRight: "0.2em"}}><CombName combId={m.c2} getComb={getComb}/></td>
|
||||
<td style={{textAlign: "center", ...getBG(m.c2, m, cat)}}>{m.end && ((m.win < 0 && "X") || (m.win === 0 && "-"))}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
function BuildTree({treeData, treeRaw, matches, cat, cards_v, getComb, t, categorieEmpty}) {
|
||||
function parseTree(data_in) {
|
||||
if (data_in?.data == null)
|
||||
return null
|
||||
|
||||
const matchData = matches.find(m => m.id === data_in.data)
|
||||
const c1 = categorieEmpty ? null : getComb(matchData?.c1)
|
||||
const c2 = categorieEmpty ? null : getComb(matchData?.c2)
|
||||
|
||||
const scores2 = []
|
||||
for (const score of matchData?.scores) {
|
||||
scores2.push({
|
||||
...score,
|
||||
s1: virtualScore(matchData?.c1, score, matchData, cards_v),
|
||||
s2: virtualScore(matchData?.c2, score, matchData, cards_v)
|
||||
})
|
||||
}
|
||||
|
||||
let node = new TreeNode({
|
||||
...matchData,
|
||||
...win_end(matchData, cards_v),
|
||||
scores: scores2,
|
||||
c1FullName: c1 !== null ? c1.fname + " " + c1.lname : null,
|
||||
c2FullName: c2 !== null ? c2.fname + " " + c2.lname : null
|
||||
})
|
||||
node.left = parseTree(data_in?.left)
|
||||
node.right = parseTree(data_in?.right)
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
function initTree(data_in, data_raw) {
|
||||
let out = []
|
||||
for (let i = 0; i < data_raw.length; i++) {
|
||||
if (data_raw.at(i).level > -10) {
|
||||
out.push(parseTree(data_in.at(i)))
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
const imgData = drawGraphForPdf(initTree(treeData, treeRaw), 24, cards_v).toDataURL('image/png');
|
||||
return <div>
|
||||
<img src={imgData} alt="Arbre" style={{width: "100%", margin: "4pt 0", objectFit: "scale-down"}}/>
|
||||
{cat.fullClassement &&
|
||||
<MatchList
|
||||
matches={cat.raw_trees?.filter(n => n.level <= -10).reverse().map(d => categorieEmpty ? ({}) : matches.find(m => m.id === d.match?.id))}
|
||||
cat={cat}
|
||||
cards_v={cards_v} classement={true} t={t} getComb={getComb}/>}
|
||||
</div>
|
||||
}
|
||||
|
||||
function PouleList({groups, getComb, t}) {
|
||||
const groups2 = groups.map(g => {
|
||||
const comb = getComb(g.id);
|
||||
return {...g, name: comb ? comb.fname + " " + comb.lname : "", teamMembers: comb ? comb.teamMembers : []};
|
||||
}).sort((a, b) => {
|
||||
if (a.poule !== b.poule) {
|
||||
if (a.poule === '-') return 1;
|
||||
if (b.poule === '-') return -1;
|
||||
return a.poule.localeCompare(b.poule);
|
||||
}
|
||||
return a.name.localeCompare(b.name);
|
||||
}).reduce((acc, curr) => {
|
||||
const poule = curr.poule;
|
||||
if (!acc[poule]) {
|
||||
acc[poule] = [];
|
||||
}
|
||||
acc[poule].push(curr);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return <table style={{width: "100%"}}>
|
||||
<thead style={{borderCollapse: "collapse"}}>
|
||||
<tr style={{borderCollapse: "collapse"}}>
|
||||
{Object.keys(groups2).map((poule) => <th key={poule} style={{
|
||||
textAlign: "center",
|
||||
borderCollapse: "collapse",
|
||||
border: "1px solid black"
|
||||
}}>{t('poule')} {poule}</th>)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody style={{borderCollapse: "collapse"}}>
|
||||
<tr style={{borderCollapse: "collapse"}}>
|
||||
{Object.keys(groups2).map((poule) => <td key={poule} style={{
|
||||
textAlign: "center",
|
||||
borderCollapse: "collapse",
|
||||
border: "1px solid black"
|
||||
}}>{groups2[poule].map(o => o.name).join(", ")}</td>)}
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
}
|
||||
|
||||
export function makePDF(action, pagesList, name, c_name, getComb, t) {
|
||||
//https://github.com/parallax/jsPDF/blob/ddbfc0f0250ca908f8061a72fa057116b7613e78/jspdf.js#L59
|
||||
const doc = new jsPDF('p', 'pt', 'a4');
|
||||
let pageHeight = doc.internal.pageSize.getHeight();
|
||||
|
||||
htmlPage(doc, pagesList);
|
||||
|
||||
function htmlPage(doc2, pages, index = 0) {
|
||||
const hasPages = pages.length > 0;
|
||||
|
||||
if (!hasPages) {
|
||||
doc2.setProperties({title: name, author: "FFSAF - Intranet", subject: c_name + " - " + name, creator: "FFSAF - Intranet"});
|
||||
|
||||
switch (action) {
|
||||
case "show":
|
||||
window.open(doc.output('bloburl', {filename: name + '.pdf'}));
|
||||
break;
|
||||
case "print":
|
||||
const iframe = document.createElement('iframe'); //load content in an iframe to print later
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
iframe.style.display = 'none';
|
||||
iframe.src = doc.output('bloburl', {filename: name + '.pdf'});
|
||||
iframe.onload = function () {
|
||||
setTimeout(function () {
|
||||
iframe.focus();
|
||||
iframe.contentWindow.print();
|
||||
}, 1);
|
||||
};
|
||||
break;
|
||||
case "download":
|
||||
default:
|
||||
doc.save(name + '.pdf');
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let htmlElement = renderToString(
|
||||
<div style={{width: "835.82px", margin: "0 auto", letterSpacing: "0.065em"}}>
|
||||
{processPage(pages[0], ({pdf_name: name, c_name, getComb, t}))}
|
||||
</div>);
|
||||
|
||||
return doc2.html(htmlElement, {
|
||||
y: index * pageHeight,
|
||||
callback: function (pdf) {
|
||||
if (index > 0 && pages.length > 1) pdf.addPage("a4", "p");
|
||||
const restPages = pages.slice();
|
||||
restPages.splice(0, 1);
|
||||
htmlPage(pdf, restPages, index + 1);
|
||||
},
|
||||
html2canvas: {
|
||||
scale: 0.65,
|
||||
logging: false,
|
||||
letterRendering: 1,
|
||||
allowTaint: true,
|
||||
useCORS: true,
|
||||
},
|
||||
margin: [30, 30, 30, 30],
|
||||
width: 595.28,
|
||||
//windowWidth: 300
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function processPage(page, context) {
|
||||
switch (page.type) {
|
||||
case "categorie":
|
||||
return <GenerateCategoriePDF {...context} {...page.params} />
|
||||
default:
|
||||
return <></>
|
||||
}
|
||||
}
|
||||
|
||||
export function GenerateCategoriePDF({cat, matches, groups, getComb, cards_v, c_name, categorieEmpty, t}) {
|
||||
let catAverage = "---";
|
||||
let genreAverage = "H";
|
||||
let nbComb = 0;
|
||||
let time = 0;
|
||||
|
||||
const marches2 = matches.filter(m => m.categorie === cat.id)
|
||||
.map(m => categorieEmpty ? ({...m, end: false, scores: []}) : m)
|
||||
|
||||
if (marches2.length !== 0) {
|
||||
const genres = [];
|
||||
const cats = [];
|
||||
const combs_ = [];
|
||||
for (const m of marches2) {
|
||||
if (m.c1 && !combs_.includes(m.c1))
|
||||
combs_.push(m.c1);
|
||||
if (m.c2 && !combs_.includes(m.c2))
|
||||
combs_.push(m.c2);
|
||||
}
|
||||
|
||||
combs_.map(cId => getComb(cId, null)).filter(c => c && c.categorie)
|
||||
.forEach(c => {
|
||||
cats.push(Math.min(CatList.length, CatList.indexOf(c.categorie) + c.overCategory))
|
||||
genres.push(c.genre)
|
||||
});
|
||||
|
||||
const catAvg = Math.round(cats.reduce((a, b) => a + b, 0) / cats.length);
|
||||
|
||||
nbComb = combs_.length;
|
||||
catAverage = CatList.at(catAvg) || "---";
|
||||
genreAverage = Math.round(genres.reduce((a, b) => a + (b === "F" ? 1 : 0), 0) / genres.length) > 0.5 ? "F" : "H";
|
||||
|
||||
if (cat.preset && cat.preset.categories) {
|
||||
const catAvailable = cat.preset.categories.map(c => CatList.indexOf(c.categorie));
|
||||
let p;
|
||||
if (catAvailable.includes(catAvg)) {
|
||||
p = cat.preset.categories.find(c => CatList.indexOf(c.categorie) === catAvg);
|
||||
} else {
|
||||
p = cat.preset.categories.find(c => CatList.indexOf(c.categorie) ===
|
||||
catAvailable.reduce((a, b) => Math.abs(b - catAvg) < Math.abs(a - catAvg) ? b : a));
|
||||
}
|
||||
time = {round: p.roundDuration, pause: p.pauseDuration}
|
||||
}
|
||||
}
|
||||
|
||||
return <>
|
||||
<table style={{width: "100%", borderCollapse: "collapse", border: "1px solid black"}}>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>
|
||||
<table style={{width: "100%"}}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style={{width: "90pt"}}>
|
||||
<img src="/Logo-FFSAF-2023.png" alt="Logo" style={{height: "90pt", width: "90pt"}}/>
|
||||
</td>
|
||||
<td align={'center'}>
|
||||
<h2 style={{marginLeft: "10pt", textAlign: "center"}}>{c_name}</h2>
|
||||
<h2 style={{marginLeft: "10pt", textAlign: "center"}}>{cat.name}</h2>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="table-group-divider">
|
||||
<tr>
|
||||
<td>
|
||||
<table style={{width: "100%"}}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>{t('catégorieDâgeMoyenne')} :</strong> {getCatName(catAverage)}</td>
|
||||
<td>
|
||||
<strong>{t('arme', {ns: 'common'})} :</strong> {getSwordTypeName(cat.preset?.sword)} - {t('taille')} {getSwordSize(cat.preset?.sword, catAverage, genreAverage)}
|
||||
</td>
|
||||
<td>
|
||||
<strong>{t('bouclier', {ns: 'common'})} :</strong> {getShieldTypeName(cat.preset?.shield)} - {t('taille')} {getShieldSize(cat.preset?.shield, catAverage)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{t('duréeRound')} :</strong> {timePrint(time.round)}</td>
|
||||
<td><strong>{t('duréePause')} :</strong> {timePrint(time.pause)}</td>
|
||||
<td><strong>{t('nombreDeCombattants')} :</strong> {nbComb}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{t('protectionObligatoire', {ns: 'common'})}</strong> {getMandatoryProtectionsList(CatList.indexOf(catAverage) <= CatList.indexOf("JUNIOR") ?
|
||||
cat.preset?.mandatoryProtection1 : cat.preset?.mandatoryProtection2, cat.preset?.shield, t).join(", ") || "---"}
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
<div style={{marginTop: "10pt"}}>
|
||||
<PouleList groups={groups} t={t} getComb={getComb}/>
|
||||
</div>
|
||||
{(cat.type & 1) === 1 &&
|
||||
<div style={{marginTop: "7pt"}}>
|
||||
<MatchList matches={marches2.filter(m => m.categorie_ord !== -42).sort((a, b) => a.categorie_ord - b.categorie_ord)} cat={cat}
|
||||
cards_v={categorieEmpty ? [] : cards_v} classement={false} t={t} getComb={getComb}/>
|
||||
</div>}
|
||||
|
||||
{(cat.type & 2) === 2 &&
|
||||
<div style={{marginTop: "7pt"}}>
|
||||
<strong>{cat.treeAreClassement ? t('classement') : t('tournois')} :</strong>
|
||||
<BuildTree treeData={cat.trees} treeRaw={cat.raw_trees} cat={cat} matches={marches2} cards_v={categorieEmpty ? [] : cards_v}
|
||||
getComb={getComb} t={t} categorieEmpty={categorieEmpty}/>
|
||||
</div>}
|
||||
</>
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user