diff --git a/src/main/webapp/package-lock.json b/src/main/webapp/package-lock.json
index 6366724..e9606cb 100644
--- a/src/main/webapp/package-lock.json
+++ b/src/main/webapp/package-lock.json
@@ -22,6 +22,7 @@
"i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
"jspdf": "^4.1.0",
+ "jspdf-autotable": "^5.0.7",
"jszip": "^3.10.1",
"leaflet": "^1.9.4",
"obs-websocket-js": "^5.0.7",
@@ -4060,6 +4061,15 @@
"html2canvas": "^1.0.0-rc.5"
}
},
+ "node_modules/jspdf-autotable": {
+ "version": "5.0.7",
+ "resolved": "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-5.0.7.tgz",
+ "integrity": "sha512-2wr7H6liNDBYNwt25hMQwXkEWFOEopgKIvR1Eukuw6Zmprm/ZcnmLTQEjW7Xx3FCbD3v7pflLcnMAv/h1jFDQw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "jspdf": "^2 || ^3 || ^4"
+ }
+ },
"node_modules/jspdf/node_modules/fflate": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
diff --git a/src/main/webapp/package.json b/src/main/webapp/package.json
index 1fde546..fd64a9f 100644
--- a/src/main/webapp/package.json
+++ b/src/main/webapp/package.json
@@ -24,6 +24,7 @@
"i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
"jspdf": "^4.1.0",
+ "jspdf-autotable": "^5.0.7",
"jszip": "^3.10.1",
"leaflet": "^1.9.4",
"obs-websocket-js": "^5.0.7",
diff --git a/src/main/webapp/public/locales/en/cm.json b/src/main/webapp/public/locales/en/cm.json
index 8a640d9..1c2f09c 100644
--- a/src/main/webapp/public/locales/en/cm.json
+++ b/src/main/webapp/public/locales/en/cm.json
@@ -25,6 +25,7 @@
"cartonRouge": "Red card",
"catégorie": "Category",
"catégorieDâgeMoyenne": "Middle-aged category",
+ "catégorieSélectionnée": "Selected 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.",
@@ -86,6 +87,7 @@
"etatDesTablesDeMarque": "State of marque tables",
"exporter": "Export",
"fermer": "Close",
+ "feuilleVierge": "Blank sheet",
"finalesUniquement": "Finals only",
"genre": "Gender",
"genre.f": "F",
@@ -121,6 +123,7 @@
"poule": "Pool",
"poulePour": "Pool for: ",
"préparation...": "Preparing...",
+ "quoiImprimer?": "What print?",
"remplacer": "Replace",
"rouge": "Red",
"réinitialiser": "Reset",
@@ -164,6 +167,9 @@
"toast.matchs.create.error": "Error while creating matches.",
"toast.matchs.create.pending": "Creating matches in progress...",
"toast.matchs.create.success": "Matches created successfully.",
+ "toast.print.error": "Error while preparing print",
+ "toast.print.pending": "Preparing print...",
+ "toast.print.success": "Print ready!",
"toast.team.update.error": "Error while updating team",
"toast.team.update.pending": "Updating team...",
"toast.team.update.success": "Team updated!",
@@ -183,6 +189,8 @@
"tournois": "Tournaments",
"tousLesMatchs": "All matches",
"toutConserver": "Keep all",
+ "touteLaCatégorie": "The entire category",
+ "toutesLesCatégories": "All categories",
"ttm.admin.obs": "Short click: Download resources. Long click: Create OBS configuration",
"ttm.admin.scripte": "Copy integration script",
"ttm.table.inverserLaPosition": "Reverse fighter positions on this screen",
diff --git a/src/main/webapp/public/locales/fr/cm.json b/src/main/webapp/public/locales/fr/cm.json
index 16356a5..95d4100 100644
--- a/src/main/webapp/public/locales/fr/cm.json
+++ b/src/main/webapp/public/locales/fr/cm.json
@@ -25,6 +25,7 @@
"cartonRouge": "Carton rouge",
"catégorie": "Catégorie",
"catégorieDâgeMoyenne": "Catégorie d'âge moyenne",
+ "catégorieSélectionnée": "Catégorie sélectionnée",
"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",
@@ -86,6 +87,7 @@
"etatDesTablesDeMarque": "Etat des tables de marque",
"exporter": "Exporter",
"fermer": "Fermer",
+ "feuilleVierge": "Feuille vierge",
"finalesUniquement": "Finales uniquement",
"genre": "Genre",
"genre.f": "F",
@@ -121,6 +123,7 @@
"poule": "Poule",
"poulePour": "Poule pour: ",
"préparation...": "Préparation...",
+ "quoiImprimer?": "Quoi imprimer ?",
"remplacer": "Remplacer",
"rouge": "Rouge",
"réinitialiser": "Réinitialiser",
@@ -164,6 +167,9 @@
"toast.matchs.create.error": "Erreur lors de la création des matchs.",
"toast.matchs.create.pending": "Création des matchs en cours...",
"toast.matchs.create.success": "Matchs créés avec succès.",
+ "toast.print.error": "Erreur lors de la génération du PDF",
+ "toast.print.pending": "Génération du PDF en cours...",
+ "toast.print.success": "PDF généré !",
"toast.team.update.error": "Erreur lors de la mise à jour de l'équipe",
"toast.team.update.pending": "Mise à jour de l'équipe...",
"toast.team.update.success": "Équipe mise à jour !",
@@ -183,6 +189,8 @@
"tournois": "Tournois",
"tousLesMatchs": "Tous les matchs",
"toutConserver": "Tout conserver",
+ "touteLaCatégorie": "Toute la catégorie",
+ "toutesLesCatégories": "Toutes les catégories",
"ttm.admin.obs": "Clique court : Télécharger les ressources. Clique long : Créer la configuration obs",
"ttm.admin.scripte": "Copier le scripte d'intégration",
"ttm.table.inverserLaPosition": "Inverser la position des combattants sur cette écran",
diff --git a/src/main/webapp/src/components/cm/ListPresetSelect.jsx b/src/main/webapp/src/components/cm/ListPresetSelect.jsx
index 6212e53..7877f0d 100644
--- a/src/main/webapp/src/components/cm/ListPresetSelect.jsx
+++ b/src/main/webapp/src/components/cm/ListPresetSelect.jsx
@@ -15,7 +15,7 @@ export function ListPresetSelect({disabled, value, onChange, returnId = true}) {
value={returnId ? value : (value ? value.id : -1)}
onChange={e => {
if (returnId) {
- onChange(e.target.value)
+ onChange(Number(e.target.value))
} else {
onChange(data.find(c => c.id === Number(e.target.value)))
}
diff --git a/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx b/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx
index ee21e74..e8c92ed 100644
--- a/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx
+++ b/src/main/webapp/src/pages/competition/editor/CMAdmin.jsx
@@ -2,7 +2,7 @@ import React, {useEffect, useId, useRef, useState} from "react";
import {useRequestWS, useWS} from "../../../hooks/useWS.jsx";
import {LoadingProvider, useLoadingSwitcher} from "../../../hooks/useLoading.jsx";
import {toast} from "react-toastify";
-import {build_tree, resize_tree} from "../../../utils/TreeUtils.js"
+import {build_tree, from_sendTree, resize_tree} from "../../../utils/TreeUtils.js"
import {ConfirmDialog} from "../../../components/ConfirmDialog.jsx";
import {CategoryContent} from "./CategoryAdminContent.jsx";
import {exportOBSConfiguration} from "../../../hooks/useOBS.jsx";
@@ -14,14 +14,14 @@ import {detectOptimalBackground} from "../../../components/SmartLogoBackground.j
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";
+import {getToastMessage, toDataURL, win_end} from "../../../utils/Tools.js";
import {copyStyles} from "../../../utils/copyStyles.js";
import {StateWindow} from "./StateWindow.jsx";
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";
+import {makePDF} from "../../../utils/cmPdf.js";
const vite_url = import.meta.env.VITE_URL;
@@ -52,7 +52,7 @@ export function CMAdmin({compUuid}) {
return <>
-
+
@@ -399,46 +399,102 @@ function Menu({menuActions, compUuid}) {
}
function PrintModal({menuActions}) {
- const [categorie, setCategorie] = useState(true);
+ const [categorie, setCategorie] = useState(false);
const [categorieEmpty, setCategorieEmpty] = useState(false);
+ const [preset, setPreset] = useState(false);
+ const [presetEmpty, setPresetEmpty] = useState(false);
+ const [allCat, setAllCat] = useState(false);
+ const [allCatEmpty, setAllCatEmpty] = useState(false);
+
+ const [presetSelect, setPresetSelect] = useState(-1)
const {welcomeData} = useWS();
const {getComb} = useCombs();
const {t} = useTranslation("cm");
const print = (action) => {
- const pages = [];
- const names = [];
+ const pagesPromise = [];
- if (categorie) {
- if (menuActions.printCategorie) {
- const [name, page] = menuActions.printCategorie(categorieEmpty);
- pages.push(...page);
- names.push(name);
- }
- }
+ if (categorie && menuActions.printCategorie)
+ pagesPromise.push(menuActions.printCategorie(categorieEmpty))
- if (pages.length !== 0) {
- makePDF(action, pages, names.join(" - "), welcomeData?.name, getComb, t)
- }
+ if (preset && menuActions.printCategoriePreset)
+ pagesPromise.push(menuActions.printCategoriePreset(presetEmpty, presetSelect))
+
+ if (allCat && menuActions.printAllCategorie)
+ pagesPromise.push(menuActions.printAllCategorie(categorieEmpty, welcomeData?.name + " - " + t('toutesLesCatégories')))
+
+ toast.promise(
+ toDataURL("/Logo-FFSAF-2023.png").then(logo => {
+ return Promise.allSettled(pagesPromise).then(results => {
+ const pages = [];
+ const names = [];
+ let errors = 0;
+
+ for (const result of results) {
+ if (result.status === "fulfilled") {
+ const [name, page, error] = result.value;
+ pages.push(...page);
+ names.push(name);
+ errors += error
+ } else if (result.status === "rejected") {
+ errors += 1;
+ }
+ }
+
+ if (errors > 0) {
+ toast.error(t('erreurGénérationPages', {count: errors}));
+ }
+
+ if (pages.length !== 0) {
+ makePDF(action, pages, names.join(" - "), welcomeData?.name, getComb, t, logo)
+ }
+ })
+ }), getToastMessage("toast.print", "cm"))
}
return <>
-
Quoi imprimer ?
+ {t('quoiImprimer?')}
@@ -574,9 +630,7 @@ function TeamCardModal() {
>
}
-function CategoryHeader({
- cat, setCatId
- }) {
+function CategoryHeader({cat, setCatId, menuActions}) {
const setLoading = useLoadingSwitcher()
const bthRef = useRef(null);
const newBthRef = useRef(null);
@@ -722,9 +776,101 @@ function CategoryHeader({
+
}
+function PrintCats({menuActions, cats}) {
+ const {cards_v} = useCards();
+ const {sendRequest} = useWS();
+
+ function readAndConvertMatch(matches, data) {
+ matches.push({
+ ...data,
+ c1: data.c1?.id,
+ c2: data.c2?.id,
+ c1_cacheName: data.c1?.fname + " " + data.c1?.lname,
+ c2_cacheName: data.c2?.fname + " " + data.c2?.lname
+ })
+ }
+
+ const run = (categorieEmpty, cats2, name = "") => {
+ const pagesPromise = cats2.sort((a, b) => a.name.localeCompare(b.name)).map(cat_ => {
+ return sendRequest('getFullCategory', cat_.id)
+ .then((data) => {
+ const cat = {
+ id: data.id,
+ name: data.name,
+ liceName: data.liceName,
+ type: data.type,
+ trees: data.trees.sort((a, b) => a.level - b.level).map(d => from_sendTree(d, true)),
+ raw_trees: data.trees.sort((a, b) => a.level - b.level),
+ treeAreClassement: data.treeAreClassement,
+ fullClassement: data.fullClassement,
+ preset: data.preset,
+ }
+ if (name === "") {
+ name = data.preset.name;
+ }
+
+ const newCards = {};
+ for (const o of data.cards)
+ newCards[o.id] = o
+
+ let matches2 = [];
+ data.trees.flatMap(d => from_sendTree(d, false).flat()).forEach((data_) => readAndConvertMatch(matches2, data_));
+ data.matches.forEach((data_) => readAndConvertMatch(matches2, data_));
+
+ const activeMatches = matches2.filter(m => m.poule !== '-')
+ const groups = matches2.flatMap(d => [d.c1, d.c2]).filter((v, i, a) => v != null && a.indexOf(v) === i)
+ .map(d => {
+ let poule = activeMatches.find(m => (m.c1 === d || m.c2 === d) && m.categorie_ord !== -42)?.poule
+ if (!poule)
+ poule = '-'
+ return {id: d, poule: poule}
+ })
+
+ matches2 = matches2.filter(m => m.categorie === cat.id)
+ .map(m => ({...m, ...win_end(m, cards_v)}))
+ matches2.forEach(m => {
+ if (m.end && (!m.scores || m.scores.length === 0))
+ m.scores = [{n_round: 0, s1: 0, s2: 0}];
+ })
+
+ return {
+ type: "categorie",
+ params: ({cat, matches: matches2, groups, cards_v: Object.values({...cards_v, ...newCards}), categorieEmpty})
+ }
+ })
+ })
+
+ return Promise.allSettled(pagesPromise)
+ .then((results) => {
+ const pages = [];
+ let error = 0;
+
+ for (const result of results) {
+ if (result.status === "fulfilled") {
+ pages.push(result.value);
+ } else {
+ console.error(result.error);
+ error++;
+ }
+ }
+
+ return [name, pages, error];
+ })
+ }
+
+ menuActions.printCategoriePreset = (categorieEmpty, preset) => {
+ return run(categorieEmpty, cats.filter(cat => cat.preset?.id === preset))
+ }
+
+ menuActions.printAllCategorie = (categorieEmpty, name) => {
+ return run(categorieEmpty, cats, name)
+ }
+}
+
function ModalContent({state, setCatId, setConfirm, confirmRef}) {
const id = useId()
const [name, setName] = useState("")
diff --git a/src/main/webapp/src/utils/Tools.js b/src/main/webapp/src/utils/Tools.js
index 7f4f78e..9eec7de 100644
--- a/src/main/webapp/src/utils/Tools.js
+++ b/src/main/webapp/src/utils/Tools.js
@@ -418,3 +418,29 @@ export function hex2rgb(hex) {
// return {r, g, b}
return {r, g, b};
}
+
+export function toDataURL(src, outputFormat) {
+ return new Promise((resolve, reject) => {
+ const img = new Image();
+ img.crossOrigin = 'Anonymous';
+ img.onload = function() {
+ const canvas = document.createElement('CANVAS');
+ const ctx = canvas.getContext('2d');
+ let dataURL;
+ canvas.height = this.naturalHeight;
+ canvas.width = this.naturalWidth;
+ ctx.drawImage(this, 0, 0);
+ dataURL = canvas.toDataURL(outputFormat);
+ resolve(dataURL);
+ };
+ img.onerror = function() {
+ reject(new Error('Could not load image at ' + src));
+ }
+
+ img.src = src;
+ if (img.complete || img.complete === undefined) {
+ img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
+ img.src = src;
+ }
+ });
+}
diff --git a/src/main/webapp/src/utils/cmPdf.js b/src/main/webapp/src/utils/cmPdf.js
new file mode 100644
index 0000000..af0a0b9
--- /dev/null
+++ b/src/main/webapp/src/utils/cmPdf.js
@@ -0,0 +1,358 @@
+import {jsPDF} from 'jspdf'
+import {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";
+import autoTable from 'jspdf-autotable'
+
+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}]`
+ }
+}
+
+export function makePDF(action, pagesList, name, c_name, getComb, t, logo) {
+ //https://github.com/parallax/jsPDF/blob/ddbfc0f0250ca908f8061a72fa057116b7613e78/jspdf.js#L59
+ const doc = new jsPDF('p', 'pt', 'a4');
+ doc.setProperties({title: name, author: "FFSAF - Intranet", subject: c_name + " - " + name, creator: "FFSAF - Intranet"});
+
+ for (let i = 0; i < pagesList.length; i++) {
+ const context = {pdf_doc: doc, pdf_name: name, c_name, getComb, t, ...pagesList[i].params, logo}
+ switch (pagesList[i].type) {
+ case "categorie":
+ generateCategoriePDF(context);
+ break;
+ default:
+ break
+ }
+ if (i !== pagesList.length - 1)
+ doc.addPage();
+ }
+
+ 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;
+ }
+}
+
+function matchList(pdf_doc, matches, cat, cards_v, classement, getComb, t) {
+ const {getHeightCardForCombInMatch} = useCardsStatic(cards_v);
+ const liceName = (cat.liceName || "N/A").split(";");
+
+ const getBG = (combId, match) => {
+ 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 {fillColor: bg, textColor: text}
+ }
+
+ const head = [
+ ...(classement ? [
+ {content: t('place', {ns: "result"}), styles: {halign: "center"}},
+ ] : [
+ {content: t('no'), styles: {halign: "center"}},
+ {content: t('poule'), styles: {halign: "center"}},
+ {content: t('zone'), styles: {halign: "center"}},
+ ]),
+ {content: "", styles: {halign: "center"}},
+ {content: t('rouge'), styles: {halign: "center"}},
+ {content: t('résultat'), styles: {halign: "center"}},
+ {content: t('blue'), styles: {halign: "center"}},
+ {content: "", styles: {halign: "center"}},
+ ]
+
+ const body = matches.map((m, index) => ([
+ ...(classement ? [
+ {content: `${m.categorie_ord + 1}-${m.categorie_ord + 2}`, styles: {halign: "center", padding: "2pt 0"}}
+ ] : [
+ {content: index + 1, styles: {halign: "center", padding: "2pt 0"}},
+ {content: m.poule, styles: {halign: "center"}},
+ {content: liceName[index % liceName.length], styles: {halign: "center"}},
+ ]),
+ {content: m.end ? (m.win > 0 ? "X" : (m.win === 0 ? "-" : "")) : " ", styles: {halign: "center", ...getBG(m.c1, m)}},
+ {content: combName(getComb, m.c1), styles: {halign: "center", minWidth: "11em", paddingLeft: "0.2em"}},
+ {content: scoreToString2(m, cards_v), styles: {halign: "center"}},
+ {content: combName(getComb, m.c2), styles: {halign: "center", minWidth: "11em", paddingRight: "0.2em"}},
+ {content: m.end ? (m.win < 0 ? "X" : (m.win === 0 ? "-" : "")) : " ", styles: {halign: "center", ...getBG(m.c2, m)}},
+ ]))
+
+ autoTable(pdf_doc, {
+ startY: pdf_doc.lastAutoTable.finalY + 7,
+ styles: {fontSize: 10, cellPadding: 3},
+ columnStyles: classement ? {
+ 0: {cellWidth: 35},
+ 1: {cellWidth: 15},
+ 2: {cellWidth: "auto"},
+ 3: {cellWidth: 110},
+ 4: {cellWidth: "auto"},
+ 5: {cellWidth: 15},
+ } : {
+ 0: {cellWidth: 20},
+ 1: {cellWidth: 35},
+ 2: {cellWidth: 30},
+ 3: {cellWidth: 15},
+ 4: {cellWidth: "auto"},
+ 5: {cellWidth: 110},
+ 6: {cellWidth: "auto"},
+ 7: {cellWidth: 15},
+ },
+ head: [head],
+ body: body,
+ theme: 'grid',
+ })
+}
+
+function buildTree(pdf_doc, treeData, treeRaw, matches, cat, cards_v, getComb, t, categorieEmpty, comb_count = 0) {
+ 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
+ }
+
+
+ let out = []
+ for (let i = 0; i < treeRaw.length; i++) {
+ if (treeRaw.at(i).level > -10) {
+ out.push(parseTree(treeData.at(i)))
+ }
+ }
+
+ const canvas = drawGraphForPdf(out, 24, cards_v)
+ const imgData = canvas.toDataURL('image/png');
+ const height = canvas.height * (pdf_doc.internal.pageSize.getWidth() - 80) / canvas.width;
+ pdf_doc.addImage(imgData, 'PNG', 40, pdf_doc.lastAutoTable.finalY, pdf_doc.internal.pageSize.getWidth() - 80, height);
+
+ pdf_doc.lastAutoTable.finalY += height;
+
+ if (cat.fullClassement) {
+ let size = 0;
+ if (out.length > 0)
+ size = out[0].getMaxChildrenAtDepth(out[0].death() - 1)
+
+ const matches2 = cat.raw_trees?.filter(n => n.level <= -10).reverse().map(d => categorieEmpty ? ({}) : matches.find(m => m.id === d.match?.id))
+ if (matches2.length === 0) {
+ while (Math.ceil(comb_count / 2) - size > matches2.length)
+ matches2.push({})
+ }
+
+ matches2.forEach((v, i) => {
+ v.categorie_ord = (i + size) * 2
+ })
+
+ matchList(pdf_doc, matches2, cat, cards_v, true, getComb, t)
+ }
+}
+
+function pouleList(pdf_doc, 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;
+ }, {});
+
+ autoTable(pdf_doc, {
+ startY: pdf_doc.lastAutoTable.finalY + 7,
+ styles: {fontSize: 10, cellPadding: 3},
+ head: [Object.keys(groups2).map((poule) => ({content: `${t('poule')} ${poule}`, styles: {halign: "center"}}))],
+ body: [Object.keys(groups2).map((poule) => ({
+ content: groups2[poule].map(o => o.name).join(", "),
+ styles: {halign: "center", valign: "middle"}
+ }))],
+ theme: 'grid',
+ })
+}
+
+function makeHeader(pdf_doc, c_name, p_name, logo) {
+ autoTable(pdf_doc, {
+ startY: 20,
+ body: [[
+ {content: "", src: `${logo}`, rowSpan: 2, styles: {halign: 'center', valign: 'middle'}},
+ {content: c_name, colSpan: 3, styles: {halign: "center", valign: 'middle', fontSize: 20}},
+ ], [
+ {content: p_name, colSpan: 3, styles: {halign: "center", valign: 'middle', fontSize: 20}}
+ ]],
+ theme: 'plain',
+ columnStyles: {
+ 0: {cellWidth: 60, minCellHeight: 60},
+ },
+ didDrawCell: function (data) {
+ if (data.column.index === 0 && data.row.index === 0) {
+ const dim = data.cell.height - data.cell.padding('vertical');
+ const textPos = data.cell;
+ pdf_doc.addImage(data.cell.raw.src, textPos.x, textPos.y + data.cell.padding('vertical') / 2, dim, dim);
+ }
+ }
+ })
+}
+
+function generateCategoriePDF({pdf_doc, cat, matches, groups, getComb, cards_v, c_name, categorieEmpty, t, logo}) {
+ 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}
+ }
+ }
+
+ makeHeader(pdf_doc, c_name, cat.name, logo)
+
+ autoTable(pdf_doc, {
+ startY: pdf_doc.lastAutoTable.finalY,
+ styles: {fontSize: 10, cellPadding: 3},
+ body: [[
+ {content: `${t('catégorieDâgeMoyenne')} : ${getCatName(catAverage)}`, styles: {halign: "left"}},
+ {
+ content: `${t('arme', {ns: 'common'})} : ${getSwordTypeName(cat.preset?.sword)} - ${t('taille')} ${getSwordSize(cat.preset?.sword, catAverage, genreAverage)}`,
+ styles: {halign: "left"}
+ },
+ {
+ content: `${t('bouclier', {ns: 'common'})} : ${getShieldTypeName(cat.preset?.shield)} - ${t('taille')} ${getShieldSize(cat.preset?.shield, catAverage)}`,
+ styles: {halign: "left"}
+ },
+ ], [
+ {content: `${t('duréeRound')} : ${timePrint(time.round)}`, styles: {halign: "left"}},
+ {content: `${t('duréePause')} : ${timePrint(time.pause)}`, styles: {halign: "left"}},
+ {content: `${t('nombreDeCombattants')} : ${nbComb}`, styles: {halign: "left"}},
+ ], [
+ {
+ content: `${t('protectionObligatoire', {ns: 'common'})} ${getMandatoryProtectionsList(CatList.indexOf(catAverage) <= CatList.indexOf("JUNIOR") ?
+ cat.preset?.mandatoryProtection1 : cat.preset?.mandatoryProtection2, cat.preset?.shield, t).join(", ") || "---"}`,
+ colSpan: 3,
+ styles: {halign: "left"}
+ },
+ ]],
+ theme: 'grid',
+ })
+
+ pouleList(pdf_doc, groups, getComb, t)
+
+ if ((cat.type & 1) === 1)
+ matchList(pdf_doc, marches2.filter(m => m.categorie_ord !== -42).sort((a, b) => a.categorie_ord - b.categorie_ord),
+ cat, categorieEmpty ? [] : cards_v, false, getComb, t)
+
+ if ((cat.type & 2) === 2) {
+ pdf_doc.setFontSize(12);
+ pdf_doc.text((cat.treeAreClassement ? t('classement') : t('tournois')) + ':', 40, pdf_doc.lastAutoTable.finalY + 16)
+ pdf_doc.lastAutoTable.finalY += 16;
+ buildTree(pdf_doc, cat.trees, cat.raw_trees, marches2, cat, categorieEmpty ? [] : cards_v, getComb, t, categorieEmpty, nbComb)
+ }
+}
diff --git a/src/main/webapp/src/utils/cmPdf.jsx b/src/main/webapp/src/utils/cmPdf.jsx
deleted file mode 100644
index 4c8f4fe..0000000
--- a/src/main/webapp/src/utils/cmPdf.jsx
+++ /dev/null
@@ -1,378 +0,0 @@
-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
-
-
- {!classement && | {t('no')} | }
- {!classement && {t('poule')} | }
- {!classement && {t('zone')} | }
- |
- {t('rouge')} |
- {t('résultat')} |
- {t('blue')} |
- |
-
-
-
- {matches.map((m, index) => (
-
- {!classement && | {index + 1} | }
- {!classement && {m.poule} | }
- {!classement && {liceName[index % liceName.length]} | }
- {m.end && ((m.win > 0 && "X") || (m.win === 0 && "-"))} |
- |
- {scoreToString2(m, cards_v)} |
- |
- {m.end && ((m.win < 0 && "X") || (m.win === 0 && "-"))} |
-
- ))}
-
-
-}
-
-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
-

- {cat.fullClassement &&
-
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}/>}
-
-}
-
-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
-
-
- {Object.keys(groups2).map((poule) => | {t('poule')} {poule} | )}
-
-
-
-
- {Object.keys(groups2).map((poule) => | {groups2[poule].map(o => o.name).join(", ")} | )}
-
-
-
-
-}
-
-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(
-
- {processPage(pages[0], ({pdf_name: name, c_name, getComb, t}))}
-
);
-
- 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
- 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 <>
-
-
-
-
-
-
-
-
-
- |
-
- {c_name}
- {cat.name}
- |
-
-
-
- |
-
-
-
-
-
-
-
-
- | {t('catégorieDâgeMoyenne')} : {getCatName(catAverage)} |
-
- {t('arme', {ns: 'common'})} : {getSwordTypeName(cat.preset?.sword)} - {t('taille')} {getSwordSize(cat.preset?.sword, catAverage, genreAverage)}
- |
-
- {t('bouclier', {ns: 'common'})} : {getShieldTypeName(cat.preset?.shield)} - {t('taille')} {getShieldSize(cat.preset?.shield, catAverage)}
- |
-
-
- | {t('duréeRound')} : {timePrint(time.round)} |
- {t('duréePause')} : {timePrint(time.pause)} |
- {t('nombreDeCombattants')} : {nbComb} |
-
-
-
- |
-
-
-
-
- |
- {t('protectionObligatoire', {ns: 'common'})} {getMandatoryProtectionsList(CatList.indexOf(catAverage) <= CatList.indexOf("JUNIOR") ?
- cat.preset?.mandatoryProtection1 : cat.preset?.mandatoryProtection2, cat.preset?.shield, t).join(", ") || "---"}
- |
-
-
-
-
-
- {(cat.type & 1) === 1 &&
-
- 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}/>
-
}
-
- {(cat.type & 2) === 2 &&
-
- {cat.treeAreClassement ? t('classement') : t('tournois')} :
-
-
}
- >
-}