diff --git a/src/pages/home/Home.ts b/src/pages/home/Home.ts index 00a08bf..7ed066a 100644 --- a/src/pages/home/Home.ts +++ b/src/pages/home/Home.ts @@ -1,14 +1,19 @@ // src/pages/process/Home.ts -import Services from '../../services/service'; -import globalCss from '../../assets/styles/style.css?inline'; -import homeHtml from './home.html?raw'; -import { displayEmojis, generateCreateBtn, prepareAndSendPairingTx, addressToEmoji } from '../../utils/sp-address.utils'; -import { Router } from '../../router/index'; +import Services from "../../services/service"; +import globalCss from "../../assets/styles/style.css?inline"; +import homeHtml from "./home.html?raw"; +import { + displayEmojis, + generateCreateBtn, + prepareAndSendPairingTx, + addressToEmoji, +} from "../../utils/sp-address.utils"; +import { Router } from "../../router/index"; export class HomePage extends HTMLElement { constructor() { super(); - this.attachShadow({ mode: 'open' }); + this.attachShadow({ mode: "open" }); } connectedCallback() { @@ -120,26 +125,36 @@ export class HomePage extends HTMLElement { const container = this.shadowRoot; if (!container) return; - (window as any).__PAIRING_READY = false; - const loaderDiv = container.querySelector('#iframe-loader') as HTMLDivElement; - const mainContentDiv = container.querySelector('#main-content') as HTMLDivElement; - const tabs = container.querySelectorAll('.tab'); + const loaderDiv = container.querySelector( + "#iframe-loader" + ) as HTMLDivElement; + const mainContentDiv = container.querySelector( + "#main-content" + ) as HTMLDivElement; + const tabs = container.querySelectorAll(".tab"); tabs.forEach((tab) => { - tab.addEventListener('click', () => { + tab.addEventListener("click", () => { // Remplacement de addSubscription pour simplifier ici - container.querySelectorAll('.tab').forEach((t) => t.classList.remove('active')); - tab.classList.add('active'); - container.querySelectorAll('.tab-content').forEach((content) => content.classList.remove('active')); - container.querySelector(`#${tab.getAttribute('data-tab') as string}`)?.classList.add('active'); + container + .querySelectorAll(".tab") + .forEach((t) => t.classList.remove("active")); + tab.classList.add("active"); + container + .querySelectorAll(".tab-content") + .forEach((content) => content.classList.remove("active")); + container + .querySelector(`#${tab.getAttribute("data-tab") as string}`) + ?.classList.add("active"); }); }); - const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + const delay = (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms)); try { await delay(500); - this.addLoaderStep('Initialisation des services...'); + this.addLoaderStep("Initialisation des services..."); const service = await Services.getInstance(); await delay(700); @@ -149,42 +164,51 @@ export class HomePage extends HTMLElement { if (pairingId) { await delay(300); - this.addLoaderStep('Appairage existant trouvé.'); + this.addLoaderStep("Appairage existant trouvé."); service.setProcessId(pairingId); } else { await delay(300); this.addLoaderStep("Création d'un appairage sécurisé..."); await prepareAndSendPairingTx(); - this.addLoaderStep('Appairage créé avec succès.'); + this.addLoaderStep("Appairage créé avec succès."); } // --- SUCCÈS --- - (window as any).__PAIRING_READY = true; - console.log('[Home] Auto-pairing terminé avec succès.'); + console.log("[Home] Auto-pairing terminé avec succès."); + document.dispatchEvent( + new CustomEvent("app:pairing-ready", { + detail: { success: true }, + }) + ); if (window.self !== window.top) { // CAS IFRAME : On ne bouge pas ! // On affiche juste un état "Prêt" dans le loader pour le debug visuel this.addLoaderStep("Prêt. En attente de l'application parente..."); - console.log('[Home] 📡 Mode Iframe : Pas de redirection. Attente des messages API.'); - + console.log( + "[Home] 📡 Mode Iframe : Pas de redirection. Attente des messages API." + ); } else { // CAS STANDALONE : On redirige - console.log('[Home] 🚀 Mode Standalone : Redirection vers /process...'); + console.log("[Home] 🚀 Mode Standalone : Redirection vers /process..."); await delay(500); // On nettoie l'UI avant de partir - if (loaderDiv) loaderDiv.style.display = 'none'; - if (mainContentDiv) mainContentDiv.style.display = 'block'; + if (loaderDiv) loaderDiv.style.display = "none"; + if (mainContentDiv) mainContentDiv.style.display = "block"; // Hop, on navigue - Router.navigate('process'); + Router.navigate("process"); } - container.querySelectorAll('.tab').forEach((t) => t.classList.remove('active')); - container.querySelector('[data-tab="tab2"]')?.classList.add('active'); - container.querySelectorAll('.tab-content').forEach((content) => content.classList.remove('active')); - container.querySelector('#tab2')?.classList.add('active'); + container + .querySelectorAll(".tab") + .forEach((t) => t.classList.remove("active")); + container.querySelector('[data-tab="tab2"]')?.classList.add("active"); + container + .querySelectorAll(".tab-content") + .forEach((content) => content.classList.remove("active")); + container.querySelector("#tab2")?.classList.add("active"); const spAddress = await service.getDeviceAddress(); generateCreateBtn(); @@ -193,28 +217,35 @@ export class HomePage extends HTMLElement { await delay(1000); - if (loaderDiv) loaderDiv.style.display = 'none'; - if (mainContentDiv) mainContentDiv.style.display = 'block'; + if (loaderDiv) loaderDiv.style.display = "none"; + if (mainContentDiv) mainContentDiv.style.display = "block"; - console.log('[Home] Init terminée.'); - (window as any).__PAIRING_READY = true; + console.log("[Home] Init terminée."); } catch (e: any) { - console.error('[Home] Erreur:', e); + console.error("[Home] Erreur:", e); this.addLoaderStep(`Erreur: ${e.message}`); - (window as any).__PAIRING_READY = 'error'; + document.dispatchEvent( + new CustomEvent("app:pairing-ready", { + detail: { success: false, error: e.message }, + }) + ); } } addLoaderStep(text: string) { const container = this.shadowRoot; if (!container) return; - const currentStep = container.querySelector('.loader-step.active') as HTMLParagraphElement; - if (currentStep) currentStep.classList.remove('active'); + const currentStep = container.querySelector( + ".loader-step.active" + ) as HTMLParagraphElement; + if (currentStep) currentStep.classList.remove("active"); - const stepsContainer = container.querySelector('#loader-steps-container') as HTMLDivElement; + const stepsContainer = container.querySelector( + "#loader-steps-container" + ) as HTMLDivElement; if (stepsContainer) { - const newStep = document.createElement('p'); - newStep.className = 'loader-step active'; + const newStep = document.createElement("p"); + newStep.className = "loader-step active"; newStep.textContent = text; stepsContainer.appendChild(newStep); } @@ -223,7 +254,9 @@ export class HomePage extends HTMLElement { async populateMemberSelect() { const container = this.shadowRoot; if (!container) return; - const memberSelect = container.querySelector('#memberSelect') as HTMLSelectElement; + const memberSelect = container.querySelector( + "#memberSelect" + ) as HTMLSelectElement; if (!memberSelect) return; const service = await Services.getInstance(); @@ -231,7 +264,7 @@ export class HomePage extends HTMLElement { for (const [processId, member] of Object.entries(members)) { const emojis = await addressToEmoji(processId); - const option = document.createElement('option'); + const option = document.createElement("option"); option.value = processId; option.textContent = `Member (${emojis})`; memberSelect.appendChild(option); @@ -239,5 +272,4 @@ export class HomePage extends HTMLElement { } } -customElements.define('home-page', HomePage); - +customElements.define("home-page", HomePage); diff --git a/src/pages/process/ProcessList.ts b/src/pages/process/ProcessList.ts index e02a8ee..30b3c96 100755 --- a/src/pages/process/ProcessList.ts +++ b/src/pages/process/ProcessList.ts @@ -1,7 +1,8 @@ -import processHtml from './process.html?raw'; -import globalCss from '../../assets/styles/style.css?inline'; -import Services from '../../services/service'; -import { Router } from '../../router'; +// src/pages/process/ProcessList.ts + +import processHtml from "./process.html?raw"; +import globalCss from "../../assets/styles/style.css?inline"; +import Services from "../../services/service"; export class ProcessListPage extends HTMLElement { private services!: Services; @@ -16,187 +17,54 @@ export class ProcessListPage extends HTMLElement { constructor() { super(); - this.attachShadow({ mode: 'open' }); + this.attachShadow({ mode: "open" }); } async connectedCallback() { this.services = await Services.getInstance(); this.render(); - // Petit délai pour s'assurer que le DOM est prêt setTimeout(() => this.initLogic(), 0); } render() { if (this.shadowRoot) { + // Le CSS et le HTML de base sont statiques, donc innerHTML est OK ici. this.shadowRoot.innerHTML = ` ${processHtml} `; @@ -207,47 +75,50 @@ export class ProcessListPage extends HTMLElement { const root = this.shadowRoot; if (!root) return; - // Récupération des éléments - this.wrapper = root.querySelector('#autocomplete-wrapper') as HTMLElement; - this.inputInput = root.querySelector('#process-input') as HTMLInputElement; - this.autocompleteList = root.querySelector('#autocomplete-list') as HTMLUListElement; - this.tagsContainer = root.querySelector('#selected-tags-container') as HTMLElement; - this.detailsContainer = root.querySelector('#process-details') as HTMLElement; - this.okButton = root.querySelector('#go-to-process-btn') as HTMLButtonElement; + this.wrapper = root.querySelector("#autocomplete-wrapper") as HTMLElement; + this.inputInput = root.querySelector("#process-input") as HTMLInputElement; + this.autocompleteList = root.querySelector( + "#autocomplete-list" + ) as HTMLUListElement; + this.tagsContainer = root.querySelector( + "#selected-tags-container" + ) as HTMLElement; + this.detailsContainer = root.querySelector( + "#process-details" + ) as HTMLElement; + this.okButton = root.querySelector( + "#go-to-process-btn" + ) as HTMLButtonElement; - // Listeners - this.inputInput.addEventListener('keyup', () => this.handleInput()); - this.inputInput.addEventListener('click', () => this.openDropdown()); + this.inputInput.addEventListener("keyup", () => this.handleInput()); + this.inputInput.addEventListener("click", () => this.openDropdown()); - // Fermeture du dropdown au clic extérieur - document.addEventListener('click', (e) => { + document.addEventListener("click", (e) => { const path = e.composedPath(); if (!path.includes(this.wrapper)) { this.closeDropdown(); } }); - this.okButton.addEventListener('click', () => this.goToProcess()); + this.okButton.addEventListener("click", () => this.goToProcess()); - // Écoute des mises à jour du service - document.addEventListener('processes-updated', async () => { + document.addEventListener("processes-updated", async () => { await this.populateList(this.inputInput.value); }); - // Chargement initial - await this.populateList(''); + await this.populateList(""); } - // --- Logique Autocomplete --- + // --- Logique Autocomplete Sécurisée --- async populateList(query: string) { - this.autocompleteList.innerHTML = ''; + this.autocompleteList.innerHTML = ""; // Vide la liste proprement const mineArray = (await this.services.getMyProcesses()) ?? []; const allProcesses = await this.services.getProcesses(); - - // On combine tout, en mettant les miens d'abord - const otherProcesses = Object.keys(allProcesses).filter((id) => !mineArray.includes(id)); + const otherProcesses = Object.keys(allProcesses).filter( + (id) => !mineArray.includes(id) + ); const listToShow = [...mineArray, ...otherProcesses]; let count = 0; @@ -258,22 +129,33 @@ export class ProcessListPage extends HTMLElement { const name = this.services.getProcessName(process) || pid; - // Filtre - if (query && !name.toLowerCase().includes(query.toLowerCase()) && !pid.includes(query)) { + if ( + query && + !name.toLowerCase().includes(query.toLowerCase()) && + !pid.includes(query) + ) { continue; } count++; - const li = document.createElement('li'); - li.textContent = name; + const li = document.createElement("li"); + + const nameSpan = document.createElement("span"); + nameSpan.textContent = name; + li.appendChild(nameSpan); + if (mineArray.includes(pid)) { - li.classList.add('my-process'); - li.innerHTML += ' (Mien)'; + li.classList.add("my-process"); + const small = document.createElement("small"); + small.style.opacity = "0.6"; + small.style.marginLeft = "8px"; + small.textContent = "(Mien)"; // Texte statique sûr + li.appendChild(small); } - li.addEventListener('click', () => { + li.addEventListener("click", () => { this.addTag(pid, name); - this.inputInput.value = ''; + this.inputInput.value = ""; this.showProcessDetails(pid); this.closeDropdown(); }); @@ -282,10 +164,10 @@ export class ProcessListPage extends HTMLElement { } if (count === 0) { - const empty = document.createElement('li'); - empty.textContent = 'Aucun résultat'; - empty.style.cursor = 'default'; - empty.style.opacity = '0.5'; + const empty = document.createElement("li"); + empty.textContent = "Aucun résultat"; + empty.style.cursor = "default"; + empty.style.opacity = "0.5"; this.autocompleteList.appendChild(empty); } } @@ -296,84 +178,121 @@ export class ProcessListPage extends HTMLElement { } openDropdown() { - this.autocompleteList.style.display = 'block'; + this.autocompleteList.style.display = "block"; } closeDropdown() { - this.autocompleteList.style.display = 'none'; + this.autocompleteList.style.display = "none"; } - // --- Gestion des Tags --- + // --- Gestion des Tags Sécurisée --- addTag(pid: string, name: string) { - // On nettoie les anciens tags (mode single select pour l'instant, ou multi si tu veux) - this.tagsContainer.innerHTML = ''; + this.tagsContainer.innerHTML = ""; - const tag = document.createElement('div'); - tag.className = 'tag'; - tag.innerHTML = ` - ${name} - × - `; + const tag = document.createElement("div"); + tag.className = "tag"; - tag.querySelector('.tag-close')?.addEventListener('click', (e) => { + const spanName = document.createElement("span"); + spanName.textContent = name; + tag.appendChild(spanName); + + const closeBtn = document.createElement("span"); + closeBtn.className = "tag-close"; + closeBtn.innerHTML = "×"; + + closeBtn.addEventListener("click", (e) => { e.stopPropagation(); this.removeTag(); }); + tag.appendChild(closeBtn); this.tagsContainer.appendChild(tag); } removeTag() { - this.tagsContainer.innerHTML = ''; - this.detailsContainer.innerHTML = '

Aucun processus sélectionné.

'; + this.tagsContainer.innerHTML = ""; + + this.detailsContainer.innerHTML = ""; + const emptyState = document.createElement("div"); + emptyState.className = "empty-state"; + const p = document.createElement("p"); + p.textContent = "Aucun processus sélectionné."; + emptyState.appendChild(p); + this.detailsContainer.appendChild(emptyState); + this.okButton.disabled = true; - this.okButton.classList.add('disabled'); + this.okButton.classList.add("disabled"); } - // --- Détails du processus --- + // --- Détails du processus Sécurisés --- async showProcessDetails(pid: string) { - this.detailsContainer.innerHTML = '

Chargement...

'; + this.detailsContainer.textContent = "Chargement..."; // Safe loader const process = await this.services.getProcess(pid); if (!process) return; - this.detailsContainer.innerHTML = ''; // Clear + this.detailsContainer.innerHTML = ""; - const name = this.services.getProcessName(process) || 'Sans nom'; + const name = this.services.getProcessName(process) || "Sans nom"; // Description - let description = 'Pas de description'; + let description = "Pas de description"; const lastState = this.services.getLastCommitedState(process); - if (lastState?.pcd_commitment['description']) { - const diff = await this.services.getDiffByValue(lastState.pcd_commitment['description']); + if (lastState?.pcd_commitment["description"]) { + const diff = await this.services.getDiffByValue( + lastState.pcd_commitment["description"] + ); if (diff) description = diff.value_commitment; } - const containerDiv = document.createElement('div'); - containerDiv.className = 'process-item'; - containerDiv.innerHTML = ` -
${name}
-
${description}
-
ID: ${pid}
-
États en attente :
- `; + const containerDiv = document.createElement("div"); + containerDiv.className = "process-item"; + + // Titre + const titleDiv = document.createElement("div"); + titleDiv.className = "process-title-display"; + titleDiv.textContent = name; // Safe + containerDiv.appendChild(titleDiv); + + // Description + const descDiv = document.createElement("div"); + descDiv.style.fontSize = "0.9rem"; + descDiv.style.marginBottom = "10px"; + descDiv.textContent = description; // Safe + containerDiv.appendChild(descDiv); + + // ID + const idDiv = document.createElement("div"); + idDiv.style.fontSize = "0.8rem"; + idDiv.style.opacity = "0.7"; + idDiv.style.marginBottom = "10px"; + idDiv.textContent = `ID: ${pid}`; // Safe + containerDiv.appendChild(idDiv); + + // Label "États en attente" + const labelDiv = document.createElement("div"); + labelDiv.style.fontWeight = "bold"; + labelDiv.style.marginTop = "15px"; + labelDiv.textContent = "États en attente :"; + containerDiv.appendChild(labelDiv); const uncommitted = this.services.getUncommitedStates(process); if (uncommitted.length > 0) { uncommitted.forEach((state) => { - const el = document.createElement('div'); - el.className = 'state-element'; + const el = document.createElement("div"); + el.className = "state-element"; + // textContent ici aussi, même si state_id est technique el.textContent = `État: ${state.state_id.substring(0, 16)}...`; - el.addEventListener('click', () => { - // Gestion de la sélection - this.shadowRoot?.querySelectorAll('.state-element').forEach((x) => x.classList.remove('selected')); - el.classList.add('selected'); + el.addEventListener("click", () => { + this.shadowRoot + ?.querySelectorAll(".state-element") + .forEach((x) => x.classList.remove("selected")); + el.classList.add("selected"); - // Activation du bouton this.okButton.disabled = false; this.okButton.dataset.target = `${pid}/${state.state_id}`; }); @@ -381,10 +300,10 @@ export class ProcessListPage extends HTMLElement { containerDiv.appendChild(el); }); } else { - const empty = document.createElement('div'); - empty.style.padding = '10px'; - empty.style.opacity = '0.6'; - empty.textContent = 'Aucun état en attente de validation.'; + const empty = document.createElement("div"); + empty.style.padding = "10px"; + empty.style.opacity = "0.6"; + empty.textContent = "Aucun état en attente de validation."; containerDiv.appendChild(empty); } @@ -394,11 +313,10 @@ export class ProcessListPage extends HTMLElement { goToProcess() { const target = this.okButton.dataset.target; if (target) { - console.log('Navigation vers', target); - alert('Navigation vers la page de détail du processus (à implémenter): ' + target); - // Router.navigate(`process-detail/${target}`); + console.log("Navigation vers", target); + alert("Navigation vers : " + target); } } } -customElements.define('process-list-page', ProcessListPage); +customElements.define("process-list-page", ProcessListPage); diff --git a/src/utils/sp-address.utils.ts b/src/utils/sp-address.utils.ts index f6236ca..57774c2 100755 --- a/src/utils/sp-address.utils.ts +++ b/src/utils/sp-address.utils.ts @@ -116,14 +116,14 @@ export function initAddressInput() { if (address) { const emojis = await addressToEmoji(address); if (emojiDisplay) { - emojiDisplay.innerHTML = emojis; + emojiDisplay.textContent = emojis; } if (okButton) { okButton.style.display = 'inline-block'; } } else { if (emojiDisplay) { - emojiDisplay.innerHTML = ''; + emojiDisplay.textContent = ''; } if (okButton) { okButton.style.display = 'none';