diff --git a/public/style/account.css b/public/style/account.css index b0e35bd..bdaca7b 100755 --- a/public/style/account.css +++ b/public/style/account.css @@ -595,10 +595,11 @@ body { .container { display: flex; flex: 1; - height: calc(100% - 4vh); - margin-top: 4vh; + height: 90vh; + margin-top: 9vh; margin-left: -1%; text-align: left; + width: 209vh; } /* Liste des information sur l'account */ @@ -612,6 +613,8 @@ body { overflow-y: auto; border-right: 2px solid #2c3e50; flex-shrink: 0; + padding-right: 10px; + height: 91vh; } .parameter-list ul { @@ -640,9 +643,10 @@ body { background-color: #ffffff; border-radius: 10px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); - margin: 10px; + margin: 0px; margin-top: 20px; - margin-left: 0%; + margin-left: 1%; + margin-bottom: -7px; } /* En-tête du parametre */ @@ -1317,3 +1321,46 @@ body { .banner-image.clickable:hover { opacity: 0.8; } + +.parameter-list-ul.profile { + position: relative; + overflow: hidden; + max-height: 200px; + margin-bottom: 20px; +} + + +.profile-preview { + position: relative; + width: 100%; + height: 100%; +} + +.preview-banner { + position: relative; + width: 100%; + height: 120px; + overflow: hidden; +} + +.preview-banner-img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.preview-info { + position: relative; + display: flex; + align-items: center; + padding: 10px; + gap: 10px; + background: rgba(0, 0, 0, 0.3); +} + +.preview-avatar { + width: 45px; + height: 45px; + border-radius: 50%; + border: 2px solid white; +} diff --git a/public/style/chat.css b/public/style/chat.css index 8ab120b..1b54b14 100755 --- a/public/style/chat.css +++ b/public/style/chat.css @@ -145,10 +145,11 @@ body { .container { display: flex; flex: 1; - height: calc(100% - 4vh); - margin-top: 4vh; + height: 90vh; + margin-top: 9vh; margin-left: -1%; text-align: left; + width: 209vh; } @@ -164,8 +165,8 @@ body { border-right: 2px solid #2c3e50; flex-shrink: 0; padding-right: 10px; + height: 91vh; } - .group-list ul { cursor: pointer; list-style: none; @@ -194,13 +195,14 @@ body { display: flex; flex-direction: column; flex: 1; - min-width: 0; + min-width: 0; background-color: #ffffff; border-radius: 10px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); - margin: 10px; + margin: 0px; margin-top: 20px; - margin-left: 0%; + margin-left: 1%; + margin-bottom: -7px; } /* En-tête du chat */ diff --git a/public/style/signature.css b/public/style/signature.css index f2c6b83..115c049 100755 --- a/public/style/signature.css +++ b/public/style/signature.css @@ -18,6 +18,8 @@ body { flex-direction: column; } + + /* 4NK NAVBAR */ .brand-logo { @@ -144,10 +146,11 @@ body { .container { display: flex; flex: 1; - height: calc(100% - 4vh); - margin-top: 4vh; + height: 90vh; + margin-top: 9vh; margin-left: -1%; text-align: left; + width: 209vh; } @@ -163,6 +166,7 @@ body { border-right: 2px solid #2c3e50; flex-shrink: 0; padding-right: 10px; + height: 91vh; } .group-list ul { @@ -194,13 +198,14 @@ body { display: flex; flex-direction: column; flex: 1; - min-width: 0; + min-width: 0; background-color: #ffffff; border-radius: 10px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); - margin: 10px; + margin: 0px; margin-top: 20px; - margin-left: 0%; + margin-left: 1%; + margin-bottom: -7px; } /* En-tête du chat */ diff --git a/src/components/profile-header/profile-header.html b/src/components/profile-header/profile-header.html deleted file mode 100755 index c2800a3..0000000 --- a/src/components/profile-header/profile-header.html +++ /dev/null @@ -1,12 +0,0 @@ -
- - -
diff --git a/src/pages/account/account.html b/src/pages/account/account.html index e62e0e4..654be9a 100755 --- a/src/pages/account/account.html +++ b/src/pages/account/account.html @@ -1,99 +1,10 @@ - + - - - + Account - - - - - -
- - - - - -
- -
- - - - -
- - -
-
-
-
-
-
-
-
-
- - - - + + + + + diff --git a/src/pages/account/account.ts b/src/pages/account/account.ts index 4e384b3..0e5ebe6 100755 --- a/src/pages/account/account.ts +++ b/src/pages/account/account.ts @@ -1,1262 +1,1425 @@ -declare global { - interface Window { - initAccount: () => void; - showContractPopup: (contractId: string) => void; - showPairing: () => void; - showWallet: () => void; - showData: () => void; - addWalletRow: () => void; - confirmWalletRow: () => void; - cancelWalletRow: () => void; - openAvatarPopup: () => void; - closeAvatarPopup: () => void; - editDeviceName: (cell: HTMLTableCellElement) => void; - showNotifications: (processName: string) => void; - closeNotificationPopup: (event: Event) => void; - markAsRead: (processName: string, messageId: number, element: HTMLElement) => void; - exportRecovery: () => void; - confirmDeleteAccount: () => void; - deleteAccount: () => void; - updateNavbarBanner: (bannerUrl: string) => void; - saveBannerToLocalStorage: (bannerUrl: string) => void; - loadSavedBanner: () => void; - cancelAddRow: () => void; - saveName: (cell: HTMLElement, input: HTMLInputElement) => void; - showProcessNotifications: (processName: string) => void; - handleLogout: () => void; - initializeEventListeners: () => void; - showProcess: () => void; - updateNavbarName: (name: string) => void; - updateNavbarLastName: (lastName: string) => void; - showAlert: (title: string, text?: string, icon?: string) => void; - addRow: () => void; - confirmRow: () => void; - cancelRow: () => void; - deleteRow: (button: HTMLButtonElement) => void; - generateRecoveryWords: () => string[]; - exportUserData: () => void; - updateActionButtons: () => void; - } -} - -import Swal from 'sweetalert2'; -import { STORAGE_KEYS, defaultRows, mockProcessRows, mockNotifications, notificationMessages, mockDataRows, mockContracts, ALLOWED_ROLES } from '../../mocks/mock-account/constAccountMock'; -import { Row, WalletRow, DataRow, Notification, Contract, NotificationMessage } from '../../mocks/mock-account/interfacesAccountMock'; -import { addressToEmoji } from '../../utils/sp-address.utils'; -import { getCorrectDOM } from '../../utils/document.utils'; - -let isAddingRow = false; -let currentRow: HTMLTableRowElement | null = null; -let currentMode: keyof typeof STORAGE_KEYS = 'pairing'; - -export function showAlert(message: string): void { - // Créer la popup si elle n'existe pas - let alertPopup = document.querySelector('.alert-popup'); - if (!alertPopup) { - alertPopup = document.createElement('div'); - alertPopup.className = 'alert-popup'; - document.body.appendChild(alertPopup); - } - - // Définir le message et afficher la popup - alertPopup.textContent = message; - (alertPopup as HTMLElement).style.display = 'block'; - - // Cacher la popup après 3 secondes - setTimeout(() => { - (alertPopup as HTMLElement).style.display = 'none'; - }, 3000); -} - -class AccountElement extends HTMLElement { - private dom: Node; - - constructor() { - super(); - this.attachShadow({ mode: 'open' }); - this.dom = getCorrectDOM('account-element'); - - window.showPairing = () => this.showPairing(); - window.showWallet = () => this.showWallet(); - window.showProcess = () => this.showProcess(); - window.showData = () => this.showData(); - window.addWalletRow = () => this.addWalletRow(); - window.confirmWalletRow = () => this.confirmWalletRow(); - window.cancelWalletRow = () => this.cancelWalletRow(); - window.editDeviceName = (cell: HTMLTableCellElement) => this.editDeviceName(cell); - window.showProcessNotifications = (processName: string) => this.showProcessNotifications(processName); - window.handleLogout = () => this.handleLogout(); - window.confirmDeleteAccount = () => this.confirmDeleteAccount(); - window.showContractPopup = (contractId: string) => this.showContractPopup(contractId); - window.addRow = () => this.addRow(); - window.deleteRow = (button: HTMLButtonElement) => this.deleteRow(button); - window.confirmRow = () => this.confirmRow(); - window.cancelRow = () => this.cancelRow(); - window.updateNavbarBanner = (bannerUrl: string) => this.updateNavbarBanner(bannerUrl); - window.saveBannerToLocalStorage = (bannerUrl: string) => this.saveBannerToLocalStorage(bannerUrl); - window.loadSavedBanner = () => this.loadSavedBanner(); - window.closeNotificationPopup = (event: Event) => this.closeNotificationPopup(event); - window.markAsRead = (processName: string, messageId: number, element: HTMLElement) => this.markAsRead(processName, messageId, element); - window.exportRecovery = () => this.exportRecovery(); - window.generateRecoveryWords = () => this.generateRecoveryWords(); - window.exportUserData = () => this.exportUserData(); - window.updateActionButtons = () => this.updateActionButtons(); - window.openAvatarPopup = () => this.openAvatarPopup(); - window.closeAvatarPopup = () => this.closeAvatarPopup(); - - if (!localStorage.getItem('rows')) { - localStorage.setItem('rows', JSON.stringify(defaultRows)); - } - } - - // Fonctions de gestion des comptes et de l'interface utilisateur - private confirmDeleteAccount(): void { - const modal = document.createElement('div'); - modal.className = 'confirm-delete-modal'; - modal.innerHTML = ` -

Delete Account

-

Are you sure you want to delete your account? This action cannot be undone.

-
- - -
- `; - - document.body.appendChild(modal); - modal.style.display = 'block'; - - const cancelBtn = modal.querySelector('.cancel-btn'); - const confirmBtn = modal.querySelector('.confirm-btn'); - - cancelBtn?.addEventListener('click', () => { - modal.remove(); - }); - - confirmBtn?.addEventListener('click', () => { - this.deleteAccount(); - modal.remove(); - }); - } - - private deleteAccount(): void { - localStorage.clear(); - window.location.href = '/login.html'; - } - - private updateNavbarBanner(imageUrl: string): void { - const navbarSection = document.querySelector('.nav-wrapper .avatar-section'); - if (!navbarSection) return; - - let bannerImg = navbarSection.querySelector('.banner-image'); - - if (!bannerImg) { - bannerImg = document.createElement('img'); - bannerImg.className = 'banner-image'; - navbarSection.insertBefore(bannerImg, navbarSection.firstChild); - } - - bannerImg.src = imageUrl; - } - - private saveBannerToLocalStorage(dataUrl: string): void { - localStorage.setItem('userBanner', dataUrl); - } - - private loadSavedBanner(): void { - const savedBanner = localStorage.getItem('userBanner'); - if (savedBanner) { - const bannerImg = document.getElementById('popup-banner-img') as HTMLImageElement; - if (bannerImg) { - bannerImg.src = savedBanner; - } - this.updateNavbarBanner(savedBanner); - } - } - - private closeNotificationPopup(event: Event): void { - const target = event.target as HTMLElement; - const isOverlay = target.classList.contains('notification-popup-overlay'); - const isCloseButton = target.classList.contains('close-popup'); - if (!isOverlay && !isCloseButton) return; - - const popup = document.querySelector('.notification-popup-overlay'); - if (popup) popup.remove(); - } - - private markAsRead(processName: string, messageId: number, element: HTMLElement): void { - const process = mockProcessRows.find((p) => p.process === processName); - if (!process) return; - - const message = process.notification.messages.find((m) => m.id === messageId); - if (!message || message.read) return; - - message.read = true; - - element.classList.remove('unread'); - element.classList.add('read'); - const statusIcon = element.querySelector('.notification-status i'); - if (statusIcon) { - statusIcon.classList.remove('fa-circle'); - statusIcon.classList.add('fa-check'); - } - - const notifCount = this.calculateNotifications(process.notification.messages); - const countElement = document.querySelector(`.notification-count[data-process="${processName}"]`); - if (countElement) { - countElement.textContent = `${notifCount.unread}/${notifCount.total}`; - - const bellContainer = countElement.closest('.notification-container'); - const bell = bellContainer?.querySelector('.fa-bell'); - if (bell && bellContainer && notifCount.unread === 0) { - bellContainer.classList.remove('has-unread'); - (bell as HTMLElement).style.color = '#666'; - } - } - } - - // Fonctions de gestion des données et de l'interface - private calculateNotifications(messages: NotificationMessage[]): { unread: number; total: number } { - const total = messages.length; - const unread = messages.filter((msg) => !msg.read).length; - return { unread, total }; - } - - // Fonctions de récupération - private exportRecovery(): void { - Swal.fire({ - title: 'Recovery Words Export', - text: '4 words will be displayed. We strongly recommend writing them down on paper before exporting the account. Do you want to continue?', - icon: 'warning', - showCancelButton: true, - confirmButtonText: 'Confirm', - cancelButtonText: 'Cancel', - confirmButtonColor: '#C89666', - cancelButtonColor: '#6c757d', - }).then((result) => { - if (result.isConfirmed) { - const recoveryWords = this.generateRecoveryWords(); - localStorage.setItem('recoveryWords', JSON.stringify(recoveryWords)); - - Swal.fire({ - title: 'Your Recovery Words', - html: ` -
- ${recoveryWords - .map( - (word, index) => ` -
- ${index + 1}. - ${word} -
- `, - ) - .join('')} -
-
- Please write these words down carefully. They will be needed to recover your account. -
- `, - showCancelButton: false, - confirmButtonText: 'I confirm the export', - confirmButtonColor: '#C89666', - allowOutsideClick: false, - allowEscapeKey: false, - }).then((result) => { - if (result.isConfirmed) { - // Stocker l'état du bouton dans le localStorage - localStorage.setItem('recoveryExported', 'true'); - - const exportRecoveryBtn = document.querySelector('.recovery-btn') as HTMLButtonElement; - if (exportRecoveryBtn) { - exportRecoveryBtn.disabled = true; - exportRecoveryBtn.style.opacity = '0.5'; - exportRecoveryBtn.style.cursor = 'not-allowed'; - } - } - }); - } - }); - } - - private generateRecoveryWords(): string[] { - const wordsList = ['apple', 'banana', 'orange', 'grape', 'kiwi', 'mango', 'peach', 'plum', 'lemon', 'lime', 'cherry', 'melon', 'pear', 'fig', 'date', 'berry']; - const recoveryWords: string[] = []; - while (recoveryWords.length < 4) { - const randomWord = wordsList[Math.floor(Math.random() * wordsList.length)]; - if (!recoveryWords.includes(randomWord)) { - recoveryWords.push(randomWord); - } - } - return recoveryWords; - } - - private exportUserData(): void { - const data: { [key: string]: string | null } = {}; - for (let i = 0; i < localStorage.length; i++) { - const key = localStorage.key(i); - if (key) { - const value = localStorage.getItem(key); - data[key] = value; - } - } - - const jsonData = JSON.stringify(data, null, 2); - const blob = new Blob([jsonData], { type: 'application/json' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = 'user_data.json'; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - } - - private updateActionButtons(): void { - const buttonContainer = document.querySelector('.button-container'); - if (!buttonContainer) return; - - buttonContainer.innerHTML = ` -
- - -
- `; - } - - private getConfirmFunction(): string { - switch (currentMode) { - case 'wallet': - return 'window.confirmWalletRow()'; - case 'process': - return 'window.confirmProcessRow()'; - default: - return 'window.confirmRow()'; - } - } - - private getCancelFunction(): string { - switch (currentMode) { - case 'wallet': - return 'window.cancelWalletRow()'; - case 'process': - return 'window.cancelProcessRow()'; - default: - return 'window.cancelRow()'; - } - } - - // Fonctions de gestion des tableaux - private addRow(): void { - if (isAddingRow) return; - - isAddingRow = true; - const table = document.querySelector('#pairing-table tbody'); - if (!table) return; - - currentRow = table.insertRow(); - const cells = ['SP Address', 'Device Name', 'SP Emojis']; - - cells.forEach((_, index) => { - const cell = currentRow!.insertCell(); - const input = document.createElement('input'); - input.type = 'text'; - input.className = 'edit-input'; - - // Ajouter un événement pour mettre à jour automatiquement les emojis - if (index === 0) { - input.addEventListener('input', async (e) => { - const addressInput = e.target as HTMLInputElement; - const emojiCell = currentRow!.cells[2]; - const emojis = await addressToEmoji(addressInput.value); - if (emojiCell.querySelector('input')) { - (emojiCell.querySelector('input') as HTMLInputElement).value = emojis; - } - }); - } - - if (index === 2) { - input.readOnly = true; - } - - cell.appendChild(input); - }); - - const deleteCell = currentRow.insertCell(); - deleteCell.style.width = '40px'; - - this.updateActionButtons(); - } - - // Fonctions de mise à jour de l'interface - private updateTableContent(rows: Row[]): void { - const tbody = document.querySelector('#pairing-table tbody'); - if (!tbody) return; - - tbody.innerHTML = rows - .map( - (row) => ` - - ${row.column1} - ${row.column2} - ${row.column3} - - - - - `, - ) - .join(''); - } - - private confirmRow(): void { - if (!currentRow) return; - - const inputs = currentRow.getElementsByTagName('input'); - const values: string[] = Array.from(inputs).map((input) => input.value.trim()); - - // Vérification des champs vides - if (values.some((value) => value === '')) { - showAlert('Please fill in all fields'); - return; - } - - // Vérification de la longueur de l'adresse SP - if (values[0].length !== 118) { - showAlert('SP Address must be exactly 118 characters long'); - return; - } - - const newRow: Row = { - column1: values[0], - column2: values[1], - column3: values[2], - }; - - const storageKey = STORAGE_KEYS[currentMode]; - const rows: Row[] = JSON.parse(localStorage.getItem(storageKey) || '[]'); - rows.push(newRow); - localStorage.setItem(storageKey, JSON.stringify(rows)); - - isAddingRow = false; - currentRow = null; - - this.resetButtonContainer(); - this.updateTableContent(rows); - } - - private cancelRow(): void { - if (!currentRow) return; - - currentRow.remove(); - isAddingRow = false; - currentRow = null; - - this.resetButtonContainer(); - } - - private resetButtonContainer(): void { - const buttonContainer = document.querySelector('.button-container'); - if (!buttonContainer) return; - - buttonContainer.innerHTML = ` - - `; - } - - private deleteRow(button: HTMLButtonElement): void { - const row = button.closest('tr'); - if (!row) return; - - const table = row.closest('tbody'); - if (!table) return; - - // Vérifier le nombre de lignes restantes - const remainingRows = table.getElementsByTagName('tr').length; - if (remainingRows <= 2) { - showAlert('You must keep at least 2 devices paired'); - return; - } - - // Animation de suppression - row.style.transition = 'opacity 0.3s'; - row.style.opacity = '0'; - - setTimeout(() => { - // Obtenir l'index avant la suppression - const index = Array.from(table.children).indexOf(row); - - // Supprimer la ligne du DOM - row.remove(); - - // Mettre à jour le localStorage - const storageKey = STORAGE_KEYS[currentMode]; - const rows = JSON.parse(localStorage.getItem(storageKey) || '[]'); - rows.splice(index, 1); - localStorage.setItem(storageKey, JSON.stringify(rows)); - }, 300); - } - - private editDeviceName(cell: HTMLTableCellElement): void { - if (cell.classList.contains('editing')) return; - - const currentValue = cell.textContent || ''; - const input = document.createElement('input'); - input.type = 'text'; - input.value = currentValue; - input.className = 'edit-input'; - - input.addEventListener('blur', () => this.finishEditing(cell, input)); - input.addEventListener('keypress', (e: KeyboardEvent) => { - if (e.key === 'Enter') { - this.finishEditing(cell, input); - } - }); - - cell.textContent = ''; - cell.appendChild(input); - cell.classList.add('editing'); - input.focus(); - } - - private finishEditing(cell: HTMLTableCellElement, input: HTMLInputElement): void { - const newValue = input.value.trim(); - if (newValue === '') { - cell.textContent = cell.getAttribute('data-original-value') || ''; - cell.classList.remove('editing'); - return; - } - - const row = cell.closest('tr'); - if (!row) return; - - const table = row.closest('tbody'); - if (!table) return; - - const index = Array.from(table.children).indexOf(row); - const storageKey = STORAGE_KEYS[currentMode]; - const rows: Row[] = JSON.parse(localStorage.getItem(storageKey) || '[]'); - - if (rows[index]) { - rows[index].column2 = newValue; - localStorage.setItem(storageKey, JSON.stringify(rows)); - } - - cell.textContent = newValue; - cell.classList.remove('editing'); - } - // Fonction pour gérer le téléchargement de l'avatar - private handleAvatarUpload(event: Event): void { - const input = event.target as HTMLInputElement; - const file = input.files?.[0]; - - if (file) { - const reader = new FileReader(); - reader.onload = (e: ProgressEvent) => { - const result = e.target?.result as string; - const popupAvatar = document.getElementById('popup-avatar-img') as HTMLImageElement; - const navAvatar = document.querySelector('.nav-wrapper .avatar') as HTMLImageElement; - - if (popupAvatar) popupAvatar.src = result; - if (navAvatar) navAvatar.src = result; - - localStorage.setItem('userAvatar', result); - }; - reader.readAsDataURL(file); - } - } - - private showProcess(): void { - //console.log("showProcess called"); - currentMode = 'process'; - this.hideAllContent(); - - const headerTitle = document.getElementById('header-title'); - if (headerTitle) headerTitle.textContent = 'Process'; - - const processContent = document.getElementById('process-content'); - if (processContent) { - processContent.style.display = 'block'; - processContent.innerHTML = ` -
Process
-
- - - - - - - - - -
Process NameRoleNotifications
-
- `; - - // Utiliser mockProcessRows au lieu du localStorage - this.updateProcessTableContent(mockProcessRows); - } - } - - // Fonction utilitaire pour mettre à jour le contenu du tableau Process - private updateProcessTableContent(rows: any[]): void { - const tbody = document.querySelector('#process-table tbody'); - if (!tbody) return; - - tbody.innerHTML = rows - .map( - (row) => ` - - ${row.process} - ${row.role} - -
- - - ${row.notification?.messages?.filter((m: any) => !m.read).length || 0}/${row.notification?.messages?.length || 0} - -
- - - `, - ) - .join(''); - } - - private showProcessNotifications(processName: string): void { - const process = mockProcessRows.find((p) => p.process === processName); - if (!process) return; - - const modal = document.createElement('div'); - modal.className = 'notifications-modal'; - - let notificationsList = process.notification.messages - .map( - (msg) => ` -
-
- -
-
- ${msg.message} - ${msg.date} -
-
- `, - ) - .join(''); - - if (process.notification.messages.length === 0) { - notificationsList = '

No notifications

'; - } - - modal.innerHTML = ` -
-

${processName} Notifications

-
- ${notificationsList} -
- -
- `; - - document.body.appendChild(modal); - - // Mettre à jour le compteur de notifications - const countElement = document.querySelector(`.notification-count[data-process="${processName}"]`); - if (countElement) { - const notifCount = this.calculateNotifications(process.notification.messages); - countElement.textContent = `${notifCount.unread}/${notifCount.total}`; - } - - const closeButton = modal.querySelector('.close-notifications'); - closeButton?.addEventListener('click', () => { - modal.remove(); - this.showProcess(); // Rafraîchir l'affichage pour mettre à jour les compteurs - }); - } - - private handleLogout(): void { - localStorage.clear(); - window.location.href = '../login/login.html'; - } - - // Fonctions de gestion des contrats - private showContractPopup(contractId: string) { - // Empêcher la navigation par défaut - event?.preventDefault(); - // Check if the contract exists in mockContracts - const contract = mockContracts[contractId as keyof typeof mockContracts]; - if (!contract) { - console.error('Contract not found:', contractId); - return; - } - - // Créer la popup - const popup = document.createElement('div'); - popup.className = 'contract-popup-overlay'; - popup.innerHTML = ` -
- -

${contract.title}

-
-

Date: ${contract.date}

-

Parties: ${contract.parties.join(', ')}

-

Terms:

-
    - ${contract.terms.map((term) => `
  • ${term}
  • `).join('')} -
-

Content: ${contract.content}

-
-
- `; - - // Ajouter la popup au body - document.body.appendChild(popup); - - // Gérer la fermeture - const closeBtn = popup.querySelector('.close-contract-popup'); - const closePopup = () => popup.remove(); - - closeBtn?.addEventListener('click', closePopup); - popup.addEventListener('click', (e) => { - if (e.target === popup) closePopup(); - }); - } - - // Ajouter à l'objet window - - // Fonction utilitaire pour cacher tous les contenus - private hideAllContent(): void { - const contents = ['pairing-content', 'wallet-content', 'process-content', 'data-content']; - contents.forEach((id) => { - const element = document.getElementById(id); - if (element) { - element.style.display = 'none'; - } - }); - } - - // Fonctions d'affichage des sections - private showPairing(): void { - isAddingRow = false; - currentRow = null; - - currentMode = 'pairing'; - // Cacher tous les contenus - this.hideAllContent(); - - // Mettre à jour le titre - const headerElement = document.getElementById('parameter-header'); - if (headerElement) { - headerElement.textContent = 'Pairing'; - } - - // Afficher le contenu de pairing - const pairingContent = document.getElementById('pairing-content'); - - if (pairingContent) { - pairingContent.style.display = 'block'; - pairingContent.innerHTML = ` -
Pairing
-
- - - - - - - - - - -
SP AddressDevice NameSP Emojis
-
- -
-
- `; - - // Mettre à jour le contenu du tableau - const rows = JSON.parse(localStorage.getItem(STORAGE_KEYS.pairing) || '[]'); - this.updateTableContent(rows); - } - } - - private showWallet(): void { - isAddingRow = false; - currentRow = null; - - currentMode = 'wallet'; - this.hideAllContent(); - - // Mettre à jour le titre - const headerTitle = document.getElementById('header-title'); - if (headerTitle) headerTitle.textContent = 'Wallet'; - - const walletContent = document.getElementById('wallet-content'); - if (!walletContent) return; - walletContent.style.display = 'block'; - walletContent.innerHTML = ` -
Wallet
-
- - - - - - - - - -
LabelWalletType
-
- -
-
- `; - - const rows = JSON.parse(localStorage.getItem(STORAGE_KEYS.wallet) || '[]'); - this.updateWalletTableContent(rows); - } - - private updateWalletTableContent(rows: WalletRow[]): void { - const tbody = document.querySelector('#wallet-table tbody'); - if (!tbody) return; - - tbody.innerHTML = rows - .map( - (row) => ` - - ${row.column1} - ${row.column2} - ${row.column3} - - `, - ) - .join(''); - } - - private showData(): void { - //console.log("showData called"); - currentMode = 'data'; - this.hideAllContent(); - - const headerTitle = document.getElementById('header-title'); - if (headerTitle) headerTitle.textContent = 'Data'; - - const dataContent = document.getElementById('data-content'); - if (dataContent) { - dataContent.style.display = 'block'; - dataContent.innerHTML = ` -
Data
-
- - - - - - - - - - - - -
NameVisibilityRoleDurationLegalContract
-
- `; - - const rows = mockDataRows || JSON.parse(localStorage.getItem(STORAGE_KEYS.data) || '[]'); - this.updateDataTableContent(rows); - } - } - - // Fonctions de gestion du wallet - private addWalletRow(): void { - if (isAddingRow) return; - isAddingRow = true; - - const table = document.getElementById('wallet-table')?.getElementsByTagName('tbody')[0]; - if (!table) return; - - currentRow = table.insertRow(); - const placeholders = ['Label', 'Wallet', 'Type']; - - placeholders.forEach((placeholder) => { - const cell = currentRow!.insertCell(); - const input = document.createElement('input'); - input.type = 'text'; - input.placeholder = placeholder; - input.className = 'edit-input'; - cell.appendChild(input); - }); - - // Remplacer le bouton "Add a line" par les boutons de confirmation/annulation - const buttonContainer = document.querySelector('#wallet-content .button-container'); - if (!buttonContainer) return; - - buttonContainer.innerHTML = ` -
- - -
- `; - - this.updateActionButtons(); - } - - private confirmWalletRow(): void { - if (!currentRow) return; - - const inputs = Array.from(currentRow.getElementsByTagName('input')); - const allFieldsFilled = inputs.every((input) => input.value.trim() !== ''); - - if (allFieldsFilled) { - const newRow: WalletRow = { - column1: inputs[0].value.trim(), - column2: inputs[1].value.trim(), - column3: inputs[2].value.trim(), - }; - - const rows = JSON.parse(localStorage.getItem(STORAGE_KEYS.wallet) || '[]'); - rows.push(newRow); - localStorage.setItem(STORAGE_KEYS.wallet, JSON.stringify(rows)); - - isAddingRow = false; - currentRow = null; - this.showWallet(); - } else { - showAlert('Please complete all fields before confirming.'); - } - } - - private cancelWalletRow(): void { - if (!currentRow) return; - - currentRow.remove(); - isAddingRow = false; - currentRow = null; - - // Réinitialiser le conteneur de boutons avec le bouton "Add a line" - const buttonContainer = document.querySelector('#wallet-content .button-container'); - if (!buttonContainer) return; - - buttonContainer.innerHTML = ` - - `; - } - - private updateDataTableContent(rows: DataRow[]): void { - const tbody = document.querySelector('#data-table tbody'); - if (!tbody) return; - - tbody.innerHTML = rows - .map( - (row) => ` - - ${row.column1} - ${row.column2} - ${row.column3} - ${row.column4} - ${row.column5} - - ${row.column6} - - - `, - ) - .join(''); - } - - // Fonctions de gestion de l'avatar et de la bannière - private openAvatarPopup(): void { - const popup = document.getElementById('avatar-popup'); - if (!popup) return; - - // Récupérer les valeurs stockées - const savedName = localStorage.getItem('userName'); - const savedLastName = localStorage.getItem('userLastName'); - const savedAvatar = localStorage.getItem('userAvatar') || 'https://via.placeholder.com/150'; - const savedBanner = localStorage.getItem('userBanner') || 'https://via.placeholder.com/800x200'; - const savedAddress = localStorage.getItem('userAddress') || '🏠 🌍 🗽🎊😩-🎊😑😩'; - - popup.innerHTML = ` - - `; - - popup.style.display = 'block'; - this.setupEventListeners(popup); - - // Ajouter le gestionnaire d'événements pour la bannière - const bannerImg = popup.querySelector('#popup-banner-img'); - const bannerInput = popup.querySelector('#banner-upload') as HTMLInputElement; - - if (bannerImg && bannerInput) { - bannerImg.addEventListener('click', () => { - bannerInput.click(); - }); - } - - // Après avoir créé le popup, vérifier si recovery a déjà été exporté - const recoveryExported = localStorage.getItem('recoveryExported') === 'true'; - if (recoveryExported) { - const exportRecoveryBtn = popup.querySelector('.recovery-btn') as HTMLButtonElement; - if (exportRecoveryBtn) { - exportRecoveryBtn.disabled = true; - exportRecoveryBtn.style.opacity = '0.5'; - exportRecoveryBtn.style.cursor = 'not-allowed'; - } - } - } - - private setupEventListeners(popup: HTMLElement): void { - // Gestionnaire pour la fermeture - const closeBtn = popup.querySelector('.close-popup'); - if (closeBtn) { - closeBtn.addEventListener('click', () => { - popup.style.display = 'none'; - }); - } - - // Gestionnaire pour l'upload d'avatar - const avatarUpload = popup.querySelector('#avatar-upload') as HTMLInputElement; - if (avatarUpload) { - avatarUpload.addEventListener('change', (e: Event) => { - const file = (e.target as HTMLInputElement).files?.[0]; - if (file) { - const reader = new FileReader(); - reader.onload = (e: ProgressEvent) => { - const result = e.target?.result as string; - const popupAvatar = popup.querySelector('#popup-avatar-img') as HTMLImageElement; - const navAvatar = document.querySelector('.nav-wrapper .avatar') as HTMLImageElement; - const headerAvatar = document.querySelector('header-element')?.shadowRoot?.querySelector('.avatar') as HTMLImageElement; - - if (popupAvatar) popupAvatar.src = result; - if (navAvatar) navAvatar.src = result; - if (headerAvatar) headerAvatar.src = result; - - localStorage.setItem('userAvatar', result); - }; - reader.readAsDataURL(file); - } - }); - } - - // Gestionnaire pour l'upload de bannière - const bannerUpload = popup.querySelector('#banner-upload') as HTMLInputElement; - if (bannerUpload) { - bannerUpload.addEventListener('change', (e: Event) => { - const file = (e.target as HTMLInputElement).files?.[0]; - if (file) { - const reader = new FileReader(); - reader.onload = (e: ProgressEvent) => { - const result = e.target?.result as string; - const popupBanner = popup.querySelector('#popup-banner-img') as HTMLImageElement; - const navBanner = document.querySelector('.nav-wrapper .banner-image') as HTMLImageElement; - const headerBanner = document.querySelector('header-element')?.shadowRoot?.querySelector('.banner-image') as HTMLImageElement; - - if (popupBanner) popupBanner.src = result; - if (navBanner) navBanner.src = result; - if (headerBanner) headerBanner.src = result; - - localStorage.setItem('userBanner', result); - }; - reader.readAsDataURL(file); - } - }); - } - - // Gestionnaires pour les champs de texte - const nameInput = popup.querySelector('#userName') as HTMLInputElement; - const lastNameInput = popup.querySelector('#userLastName') as HTMLInputElement; - - if (nameInput) { - nameInput.addEventListener('input', () => { - const newName = nameInput.value; - localStorage.setItem('userName', newName); - - const headerName = document.querySelector('header-element')?.shadowRoot?.querySelector('.user-name'); - const navName = document.querySelector('.nav-wrapper .user-name'); - - if (headerName) headerName.textContent = newName; - if (navName) navName.textContent = newName; - }); - } - - if (lastNameInput) { - lastNameInput.addEventListener('input', () => { - const newLastName = lastNameInput.value; - console.log('Nom de famille mis à jour :', newLastName); - localStorage.setItem('userLastName', newLastName); - - const headerLastName = document.querySelector('header-element')?.shadowRoot?.querySelector('.user-lastname'); - const navLastName = document.querySelector('.nav-wrapper .user-lastname'); - - if (headerLastName) headerLastName.textContent = newLastName; - if (navLastName) navLastName.textContent = newLastName; - }); - } - } - - private closeAvatarPopup(): void { - const popup = document.querySelector('.avatar-popup'); - if (popup) popup.remove(); - } - - private loadAvatar(): void { - const savedAvatar = localStorage.getItem('userAvatar'); - if (savedAvatar) { - const avatarImg = document.querySelector('.avatar') as HTMLImageElement; - if (avatarImg) { - avatarImg.src = savedAvatar; - } - } - } - - private loadUserInfo(): void { - const savedName = localStorage.getItem('userName'); - const savedLastName = localStorage.getItem('userLastName'); - - console.log('Nom récupéré :', savedName); - console.log('Nom de famille récupéré :', savedLastName); - - if (savedName) { - const nameDisplay = document.querySelector('.user-name'); - if (nameDisplay) { - nameDisplay.textContent = savedName; - } - } - - if (savedLastName) { - const lastNameDisplay = document.querySelector('.user-lastname'); - if (lastNameDisplay) { - lastNameDisplay.textContent = savedLastName; - } - } - - this.loadAvatar(); - this.loadSavedBanner(); - } - - private updateNavbarName(name: string): void { - const nameElement = document.querySelector('.nav-wrapper .user-name'); - if (nameElement) { - nameElement.textContent = name; - } - } - - private updateNavbarLastName(lastName: string): void { - const lastNameElement = document.querySelector('.nav-wrapper .user-lastname'); - if (lastNameElement) { - lastNameElement.textContent = lastName; - } - } - - private initializeEventListeners() { - document.addEventListener('DOMContentLoaded', () => { - this.showPairing(); - }); - - const editableFields = document.querySelectorAll('.editable'); - editableFields.forEach((field) => { - field.addEventListener('click', () => { - if (!field.classList.contains('editing')) { - const currentValue = field.textContent || ''; - const input = document.createElement('input'); - input.type = 'text'; - input.value = currentValue; - input.className = 'edit-input'; - - field.textContent = ''; - field.appendChild(input); - field.classList.add('editing'); - input.focus(); - } - }); - }); - - const avatarInput = document.getElementById('avatar-upload') as HTMLInputElement; - if (avatarInput) { - avatarInput.addEventListener('change', this.handleAvatarUpload); - } - } - - connectedCallback() { - this.initializeEventListeners(); - this.loadSavedBanner(); - this.loadUserInfo(); - - const savedAvatar = localStorage.getItem('userAvatar'); - const savedBanner = localStorage.getItem('userBanner'); - const savedName = localStorage.getItem('userName'); - const savedLastName = localStorage.getItem('userLastName'); - - if (savedAvatar) { - const navAvatar = this.shadowRoot?.querySelector('.avatar') as HTMLImageElement; - if (navAvatar) navAvatar.src = savedAvatar; - } - - if (savedBanner) { - const navBanner = this.shadowRoot?.querySelector('.banner-image') as HTMLImageElement; - if (navBanner) navBanner.src = savedBanner; - } - - if (savedName) { - this.updateNavbarName(savedName); - } - if (savedLastName) { - this.updateNavbarLastName(savedLastName); - } - } -} - -customElements.define('account-element', AccountElement); -export { AccountElement }; +declare global { + interface Window { + initAccount: () => void; + showContractPopup: (contractId: string) => void; + showPairing: () => void; + showWallet: () => void; + showData: () => void; + addWalletRow: () => void; + confirmWalletRow: () => void; + cancelWalletRow: () => void; + openAvatarPopup: () => void; + closeAvatarPopup: () => void; + editDeviceName: (cell: HTMLTableCellElement) => void; + showNotifications: (processName: string) => void; + closeNotificationPopup: (event: Event) => void; + markAsRead: (processName: string, messageId: number, element: HTMLElement) => void; + exportRecovery: () => void; + confirmDeleteAccount: () => void; + deleteAccount: () => void; + updateNavbarBanner: (bannerUrl: string) => void; + saveBannerToLocalStorage: (bannerUrl: string) => void; + loadSavedBanner: () => void; + cancelAddRow: () => void; + saveName: (cell: HTMLElement, input: HTMLInputElement) => void; + showProcessNotifications: (processName: string) => void; + handleLogout: () => void; + initializeEventListeners: () => void; + showProcess: () => void; + updateNavbarName: (name: string) => void; + updateNavbarLastName: (lastName: string) => void; + showAlert: (title: string, text?: string, icon?: string) => void; + addRow: () => void; + confirmRow: () => void; + cancelRow: () => void; + deleteRow: (button: HTMLButtonElement) => void; + generateRecoveryWords: () => string[]; + exportUserData: () => void; + updateActionButtons: () => void; + } +} + +import Swal from 'sweetalert2'; +import { STORAGE_KEYS, defaultRows, mockProcessRows, mockNotifications, notificationMessages, mockDataRows, mockContracts, ALLOWED_ROLES } from '../../mocks/mock-account/constAccountMock'; +import { Row, WalletRow, DataRow, Notification, Contract, NotificationMessage } from '../../mocks/mock-account/interfacesAccountMock'; +import { addressToEmoji } from '../../utils/sp-address.utils'; +import { getCorrectDOM } from '../../utils/document.utils'; +import accountStyle from '../../../public/style/account.css?inline'; + +let isAddingRow = false; +let currentRow: HTMLTableRowElement | null = null; +let currentMode: keyof typeof STORAGE_KEYS = 'pairing'; + +export function showAlert(message: string): void { + // Créer la popup si elle n'existe pas + let alertPopup = document.querySelector('.alert-popup'); + if (!alertPopup) { + alertPopup = document.createElement('div'); + alertPopup.className = 'alert-popup'; + document.body.appendChild(alertPopup); + } + + // Définir le message et afficher la popup + alertPopup.textContent = message; + (alertPopup as HTMLElement).style.display = 'block'; + + // Cacher la popup après 3 secondes + setTimeout(() => { + (alertPopup as HTMLElement).style.display = 'none'; + }, 3000); +} + + +class AccountElement extends HTMLElement { + private dom: Node; + + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + this.dom = getCorrectDOM('account-element'); + + // Ajouter Font Awesome + const fontAwesome = document.createElement('link'); + fontAwesome.rel = 'stylesheet'; + fontAwesome.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css'; + this.shadowRoot!.appendChild(fontAwesome); + + const style = document.createElement('style'); + style.textContent = accountStyle; + this.shadowRoot!.appendChild(style); + + this.shadowRoot!.innerHTML = ` + + + + + + +
+ +
+
    + +
    +
    + Banner +
    +
    + Avatar + +
    +
    + + + + + +
+
    Pairing 🔗
+
    Wallet 👛
+
    Process ⚙️
+
    Data 💾
+
+ + +
+
+
+
+
+
+
+
+
+ `; + + + window.showPairing = () => this.showPairing(); + window.showWallet = () => this.showWallet(); + window.showProcess = () => this.showProcess(); + window.showData = () => this.showData(); + window.addWalletRow = () => this.addWalletRow(); + window.confirmWalletRow = () => this.confirmWalletRow(); + window.cancelWalletRow = () => this.cancelWalletRow(); + window.editDeviceName = (cell: HTMLTableCellElement) => this.editDeviceName(cell); + window.showProcessNotifications = (processName: string) => this.showProcessNotifications(processName); + window.handleLogout = () => this.handleLogout(); + window.confirmDeleteAccount = () => this.confirmDeleteAccount(); + window.showContractPopup = (contractId: string) => this.showContractPopup(contractId); + window.addRow = () => this.addRow(); + window.deleteRow = (button: HTMLButtonElement) => this.deleteRow(button); + window.confirmRow = () => this.confirmRow(); + window.cancelRow = () => this.cancelRow(); + window.updateNavbarBanner = (bannerUrl: string) => this.updateNavbarBanner(bannerUrl); + window.saveBannerToLocalStorage = (bannerUrl: string) => this.saveBannerToLocalStorage(bannerUrl); + window.loadSavedBanner = () => this.loadSavedBanner(); + window.closeNotificationPopup = (event: Event) => this.closeNotificationPopup(event); + window.markAsRead = (processName: string, messageId: number, element: HTMLElement) => this.markAsRead(processName, messageId, element); + window.exportRecovery = () => this.exportRecovery(); + window.generateRecoveryWords = () => this.generateRecoveryWords(); + window.exportUserData = () => this.exportUserData(); + window.updateActionButtons = () => this.updateActionButtons(); + window.openAvatarPopup = () => this.openAvatarPopup(); + window.closeAvatarPopup = () => this.closeAvatarPopup(); + + if (!localStorage.getItem('rows')) { + localStorage.setItem('rows', JSON.stringify(defaultRows)); + } + } + + connectedCallback() { + this.initializeEventListeners(); + this.loadSavedBanner(); + this.loadUserInfo(); + + const savedAvatar = localStorage.getItem('userAvatar'); + const savedBanner = localStorage.getItem('userBanner'); + const savedName = localStorage.getItem('userName'); + const savedLastName = localStorage.getItem('userLastName'); + + if (savedAvatar) { + const navAvatar = this.shadowRoot?.querySelector('.avatar') as HTMLImageElement; + if (navAvatar) navAvatar.src = savedAvatar; + } + + if (savedBanner) { + const navBanner = this.shadowRoot?.querySelector('.banner-image') as HTMLImageElement; + if (navBanner) navBanner.src = savedBanner; + } + + if (savedName) { + this.updateNavbarName(savedName); + } + if (savedLastName) { + this.updateNavbarLastName(savedLastName); + } + } + + + // Fonctions de gestion des comptes et de l'interface utilisateur + private confirmDeleteAccount(): void { + const modal = document.createElement('div'); + modal.className = 'confirm-delete-modal'; + modal.innerHTML = ` +

Delete Account

+

Are you sure you want to delete your account? This action cannot be undone.

+
+ + +
+ `; + + this.shadowRoot?.appendChild(modal); + modal.style.display = 'block'; + + const cancelBtn = modal.querySelector('.cancel-btn'); + const confirmBtn = modal.querySelector('.confirm-btn'); + + cancelBtn?.addEventListener('click', () => { + modal.remove(); + }); + + confirmBtn?.addEventListener('click', () => { + this.deleteAccount(); + modal.remove(); + }); +} + +private deleteAccount(): void { + localStorage.clear(); + window.location.href = '/login.html'; +} + +private updateNavbarBanner(imageUrl: string): void { + const navbarSection = this.shadowRoot?.querySelector('.nav-wrapper .avatar-section'); + if (!navbarSection) return; + + let bannerImg = navbarSection.querySelector('.banner-image'); + + if (!bannerImg) { + bannerImg = document.createElement('img'); + bannerImg.className = 'banner-image'; + navbarSection.insertBefore(bannerImg, navbarSection.firstChild); + } + + bannerImg.src = imageUrl; +} + +private saveBannerToLocalStorage(dataUrl: string): void { + localStorage.setItem('userBanner', dataUrl); +} + +private loadSavedBanner(): void { + const savedBanner = localStorage.getItem('userBanner'); + if (savedBanner) { + const bannerImg = this.shadowRoot?.getElementById('popup-banner-img') as HTMLImageElement; + if (bannerImg) { + bannerImg.src = savedBanner; + } + this.updateNavbarBanner(savedBanner); + } +} + + +private closeNotificationPopup(event: Event): void { + const target = event.target as HTMLElement; + const isOverlay = target.classList.contains('notification-popup-overlay'); + const isCloseButton = target.classList.contains('close-popup'); + if (!isOverlay && !isCloseButton) return; + + const popup = this.shadowRoot?.querySelector('.notification-popup-overlay'); + if (popup) popup.remove(); +} + +private markAsRead(processName: string, messageId: number, element: HTMLElement): void { + const process = mockProcessRows.find(p => p.process === processName); + if (!process) return; + + const message = process.notification.messages.find(m => m.id === messageId); + if (!message || message.read) return; + + message.read = true; + + element.classList.remove('unread'); + element.classList.add('read'); + + const statusIcon = element.querySelector('.notification-status'); + if (statusIcon) { + statusIcon.innerHTML = ` + + + `; + } + + const notifCount = this.calculateNotifications(process.notification.messages); + const countElement = this.shadowRoot?.querySelector(`.notification-count[data-process="${processName}"]`); + if (countElement) { + countElement.textContent = `${notifCount.unread}/${notifCount.total}`; + + const bellContainer = countElement.closest('.notification-container'); + const bell = bellContainer?.querySelector('svg'); // Changé de .fa-bell à svg + if (bell && bellContainer && notifCount.unread === 0) { + bellContainer.classList.remove('has-unread'); + (bell as SVGElement).style.fill = '#666'; // Utiliser fill au lieu de color pour SVG + } + } +} + +// Fonctions de gestion des données et de l'interface +private calculateNotifications(messages: NotificationMessage[]): { unread: number; total: number } { + const total = messages.length; + const unread = messages.filter(msg => !msg.read).length; + return { unread, total }; +} + +// Fonctions de récupération +private exportRecovery(): void { + Swal.fire({ + title: 'Recovery Words Export', + text: '4 words will be displayed. We strongly recommend writing them down on paper before exporting the account. Do you want to continue?', + icon: 'warning', + showCancelButton: true, + confirmButtonText: 'Confirm', + cancelButtonText: 'Cancel', + confirmButtonColor: '#C89666', + cancelButtonColor: '#6c757d', + // Ajouter des styles personnalisés + customClass: { + container: 'recovery-popup-container', + popup: 'recovery-popup' + } + }).then((result) => { + if (result.isConfirmed) { + const recoveryWords = this.generateRecoveryWords(); + localStorage.setItem('recoveryWords', JSON.stringify(recoveryWords)); + + Swal.fire({ + title: 'Your Recovery Words', + html: ` +
+ ${recoveryWords.map((word, index) => ` +
+ ${index + 1}. + ${word} +
+ `).join('')} +
+
+ Please write these words down carefully. They will be needed to recover your account. +
+ `, + showCancelButton: false, + confirmButtonText: 'I confirm the export', + confirmButtonColor: '#C89666', + allowOutsideClick: false, + allowEscapeKey: false, + customClass: { + container: 'recovery-popup-container', + popup: 'recovery-popup' + } + }).then((result) => { + if (result.isConfirmed) { + // Stocker l'état du bouton dans le localStorage + localStorage.setItem('recoveryExported', 'true'); + + const exportRecoveryBtn = this.shadowRoot?.querySelector('.recovery-btn') as HTMLButtonElement; + if (exportRecoveryBtn) { + exportRecoveryBtn.disabled = true; + exportRecoveryBtn.style.opacity = '0.5'; + exportRecoveryBtn.style.cursor = 'not-allowed'; + } + } + }); + } + }); +} + +private generateRecoveryWords(): string[] { + const wordsList = [ + 'apple', 'banana', 'orange', 'grape', 'kiwi', 'mango', 'peach', 'plum', + 'lemon', 'lime', 'cherry', 'melon', 'pear', 'fig', 'date', 'berry' + ]; + const recoveryWords: string[] = []; + while (recoveryWords.length < 4) { + const randomWord = wordsList[Math.floor(Math.random() * wordsList.length)]; + if (!recoveryWords.includes(randomWord)) { + recoveryWords.push(randomWord); + } + } + return recoveryWords; +} + +private exportUserData(): void { + const data: { [key: string]: string | null } = {}; + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key) { + const value = localStorage.getItem(key); + data[key] = value; + } + } + + const jsonData = JSON.stringify(data, null, 2); + const blob = new Blob([jsonData], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'user_data.json'; + this.shadowRoot?.appendChild(a); + a.click(); + this.shadowRoot?.removeChild(a); + URL.revokeObjectURL(url); +} + +private updateActionButtons(): void { + const buttonContainer = this.shadowRoot?.querySelector('.button-container'); + if (!buttonContainer) return; + + buttonContainer.innerHTML = ` +
+ + +
+ `; +} + +private getConfirmFunction(): string { + switch (currentMode) { + case 'wallet': + return 'window.confirmWalletRow()'; + case 'process': + return 'window.confirmProcessRow()'; + default: + return 'window.confirmRow()'; + } +} + +private getCancelFunction(): string { + switch (currentMode) { + case 'wallet': + return 'window.cancelWalletRow()'; + case 'process': + return 'window.cancelProcessRow()'; + default: + return 'window.cancelRow()'; + } +} + +// Fonctions de gestion des tableaux +private addRow(): void { + if (isAddingRow) return; + + isAddingRow = true; + const table = this.shadowRoot?.querySelector('#pairing-table tbody'); + if (!table) return; + + currentRow = table.insertRow(); + const cells = ['SP Address', 'Device Name', 'SP Emojis']; + + cells.forEach((_, index) => { + const cell = currentRow!.insertCell(); + const input = document.createElement('input'); + input.type = 'text'; + input.className = 'edit-input'; + + // Ajouter un événement pour mettre à jour automatiquement les emojis + if (index === 0) { + input.addEventListener('input', async (e) => { + const addressInput = e.target as HTMLInputElement; + const emojiCell = currentRow!.cells[2]; + const emojis = await addressToEmoji(addressInput.value); + if (emojiCell.querySelector('input')) { + (emojiCell.querySelector('input') as HTMLInputElement).value = emojis; + } + }); + } + + if (index === 2) { + input.readOnly = true; + } + + cell.appendChild(input); + }); + + const deleteCell = currentRow.insertCell(); + deleteCell.style.width = '40px'; + + this.updateActionButtons(); +} + +// Fonctions de mise à jour de l'interface +private updateTableContent(rows: Row[]): void { + const tbody = this.shadowRoot?.querySelector('#pairing-table tbody'); + if (!tbody) return; + + tbody.innerHTML = rows.map(row => ` + + ${row.column1} + ${row.column2} + ${row.column3} + + + + + `).join(''); +} + + + +private confirmRow(): void { + if (!currentRow) return; + + const inputs = currentRow.getElementsByTagName('input'); + const values: string[] = Array.from(inputs).map(input => input.value.trim()); + + // Vérification des champs vides + if (values.some(value => value === '')) { + showAlert('Please fill in all fields'); + return; + } + + // Vérification de la longueur de l'adresse SP + if (values[0].length !== 118) { + showAlert('SP Address must be exactly 118 characters long'); + return; + } + + const newRow: Row = { + column1: values[0], + column2: values[1], + column3: values[2] + }; + + const storageKey = STORAGE_KEYS[currentMode]; + const rows: Row[] = JSON.parse(localStorage.getItem(storageKey) || '[]'); + rows.push(newRow); + localStorage.setItem(storageKey, JSON.stringify(rows)); + + isAddingRow = false; + currentRow = null; + + this.resetButtonContainer(); + this.updateTableContent(rows); +} + +private cancelRow(): void { + if (!currentRow) return; + + currentRow.remove(); + isAddingRow = false; + currentRow = null; + + this.resetButtonContainer(); +} + +private resetButtonContainer(): void { + const buttonContainer = this.shadowRoot?.querySelector('.button-container'); + if (!buttonContainer) return; + + buttonContainer.innerHTML = ` + + `; +} + +private deleteRow(button: HTMLButtonElement): void { + const row = button.closest('tr'); + if (!row) return; + + const table = row.closest('tbody'); + if (!table) return; + + // Vérifier le nombre de lignes restantes + const remainingRows = table.getElementsByTagName('tr').length; + if (remainingRows <= 2) { + showAlert('You must keep at least 2 devices paired'); + return; + } + + // Animation de suppression + row.style.transition = 'opacity 0.3s'; + row.style.opacity = '0'; + + setTimeout(() => { + // Obtenir l'index avant la suppression + const index = Array.from(table.children).indexOf(row); + + // Supprimer la ligne du DOM + row.remove(); + + // Mettre à jour le localStorage + const storageKey = STORAGE_KEYS[currentMode]; + const rows = JSON.parse(localStorage.getItem(storageKey) || '[]'); + rows.splice(index, 1); + localStorage.setItem(storageKey, JSON.stringify(rows)); + }, 300); +} + +private editDeviceName(cell: HTMLTableCellElement): void { + if (cell.classList.contains('editing')) return; + + const currentValue = cell.textContent || ''; + const input = document.createElement('input'); + input.type = 'text'; + input.value = currentValue; + input.className = 'edit-input'; + + input.addEventListener('blur', () => this.finishEditing(cell, input)); + input.addEventListener('keypress', (e: KeyboardEvent) => { + if (e.key === 'Enter') { + this.finishEditing(cell, input); + } + }); + + cell.textContent = ''; + cell.appendChild(input); + cell.classList.add('editing'); + input.focus(); +} + +private finishEditing(cell: HTMLTableCellElement, input: HTMLInputElement): void { + const newValue = input.value.trim(); + if (newValue === '') { + cell.textContent = cell.getAttribute('data-original-value') || ''; + cell.classList.remove('editing'); + return; + } + + const row = cell.closest('tr'); + if (!row) return; + + const table = row.closest('tbody'); + if (!table) return; + + const index = Array.from(table.children).indexOf(row); + const storageKey = STORAGE_KEYS[currentMode]; + const rows: Row[] = JSON.parse(localStorage.getItem(storageKey) || '[]'); + + if (rows[index]) { + rows[index].column2 = newValue; + localStorage.setItem(storageKey, JSON.stringify(rows)); + } + + cell.textContent = newValue; + cell.classList.remove('editing'); +} +// Fonction pour gérer le téléchargement de l'avatar +private handleAvatarUpload(event: Event): void { + const input = event.target as HTMLInputElement; + const file = input.files?.[0]; + + if (file) { + const reader = new FileReader(); + reader.onload = (e: ProgressEvent) => { + const result = e.target?.result as string; + const popupAvatar = this.shadowRoot?.getElementById('popup-avatar-img') as HTMLImageElement; + const navAvatar = this.shadowRoot?.querySelector('.nav-wrapper .avatar') as HTMLImageElement; + + if (popupAvatar) popupAvatar.src = result; + if (navAvatar) navAvatar.src = result; + + localStorage.setItem('userAvatar', result); + }; + reader.readAsDataURL(file); + } +} + + +private showProcess(): void { + //console.log("showProcess called"); + currentMode = 'process'; + this.hideAllContent(); + + const headerTitle = this.shadowRoot?.getElementById('header-title'); + if (headerTitle) headerTitle.textContent = 'Process'; + + const processContent = this.shadowRoot?.getElementById('process-content'); + if (processContent) { + processContent.style.display = 'block'; + processContent.innerHTML = ` +
Process
+
+ + + + + + + + + +
Process NameRoleNotifications
+
+ `; + + + this.updateProcessTableContent(mockProcessRows); + } +} + +// Fonction utilitaire pour mettre à jour le contenu du tableau Process +private updateProcessTableContent(rows: any[]): void { + const tbody = this.shadowRoot?.querySelector('#process-table tbody'); + if (!tbody) return; + + tbody.innerHTML = rows.map(row => ` + + ${row.process} + ${row.role} + +
+ + + + + ${row.notification?.messages?.filter((m: any) => !m.read).length || 0}/${row.notification?.messages?.length || 0} + +
+ + + `).join(''); +} + + + +private showProcessNotifications(processName: string): void { + const process = mockProcessRows.find(p => p.process === processName); + if (!process) return; + + const modal = document.createElement('div'); + modal.className = 'notifications-modal'; + + let notificationsList = process.notification.messages.map(msg => ` +
+
+ ${msg.read ? + ` + + ` : + ` + + ` + } +
+
+ ${msg.message} + ${msg.date} +
+
+ `).join(''); + + if (process.notification.messages.length === 0) { + notificationsList = '

No notifications

'; + } + + modal.innerHTML = ` +
+

${processName} Notifications

+
+ ${notificationsList} +
+ +
+ `; + + this.shadowRoot?.appendChild(modal); + + // Mettre à jour le compteur de notifications + const countElement = this.shadowRoot?.querySelector(`.notification-count[data-process="${processName}"]`); + if (countElement) { + const notifCount = this.calculateNotifications(process.notification.messages); + countElement.textContent = `${notifCount.unread}/${notifCount.total}`; + } + + const closeButton = modal.querySelector('.close-notifications'); + closeButton?.addEventListener('click', () => { + modal.remove(); + this.showProcess(); // Rafraîchir l'affichage pour mettre à jour les compteurs + }); +} + + +private handleLogout(): void { + localStorage.clear(); + window.location.href = '../login/login.html'; +} + + +// Fonctions de gestion des contrats +private showContractPopup(contractId: string) { + // Empêcher la navigation par défaut + event?.preventDefault(); + // Check if the contract exists in mockContracts + const contract = mockContracts[contractId as keyof typeof mockContracts]; + if (!contract) { + console.error('Contract not found:', contractId); + return; + } + + // Créer la popup + const popup = document.createElement('div'); + popup.className = 'contract-popup-overlay'; + popup.innerHTML = ` +
+ +

${contract.title}

+
+

Date: ${contract.date}

+

Parties: ${contract.parties.join(', ')}

+

Terms:

+
    + ${contract.terms.map(term => `
  • ${term}
  • `).join('')} +
+

Content: ${contract.content}

+
+
+ `; + + // Ajouter la popup au body + this.shadowRoot?.appendChild(popup); + + // Gérer la fermeture + const closeBtn = popup.querySelector('.close-contract-popup'); + const closePopup = () => popup.remove(); + + closeBtn?.addEventListener('click', closePopup); + popup.addEventListener('click', (e) => { + if (e.target === popup) closePopup(); + }); +} + +// Ajouter à l'objet window + + +// Fonction utilitaire pour cacher tous les contenus +private hideAllContent(): void { + const contents = ['pairing-content', 'wallet-content', 'process-content', 'data-content']; + contents.forEach(id => { + const element = this.shadowRoot?.getElementById(id); + if (element) { + element.style.display = 'none'; + } + }); +} + +// Fonctions d'affichage des sections +private showPairing(): void { + + isAddingRow = false; + currentRow = null; + + currentMode = 'pairing'; + // Cacher tous les contenus + this.hideAllContent(); + + // Mettre à jour le titre + const headerElement = this.shadowRoot?.getElementById('parameter-header'); + if (headerElement) { + headerElement.textContent = 'Pairing'; + } + + + // Afficher le contenu de pairing + const pairingContent = this.shadowRoot?.getElementById('pairing-content'); + + if (pairingContent) { + pairingContent.style.display = 'block'; + pairingContent.innerHTML = ` +
Pairing
+
+ + + + + + + + + + +
SP AddressDevice NameSP Emojis
+
+ +
+
+ `; + + // Mettre à jour le contenu du tableau + const rows = JSON.parse(localStorage.getItem(STORAGE_KEYS.pairing) || '[]'); + this.updateTableContent(rows); + } +} + +private showWallet(): void { + isAddingRow = false; + currentRow = null; + + currentMode = 'wallet'; + this.hideAllContent(); + + // Mettre à jour le titre + const headerTitle = this.shadowRoot?.getElementById('header-title'); + if (headerTitle) headerTitle.textContent = 'Wallet'; + + const walletContent = this.shadowRoot?.getElementById('wallet-content'); + if (!walletContent) return; + walletContent.style.display = 'block'; + walletContent.innerHTML = ` +
Wallet
+
+ + + + + + + + + +
LabelWalletType
+
+ +
+
+ `; + + const rows = JSON.parse(localStorage.getItem(STORAGE_KEYS.wallet) || '[]'); + this.updateWalletTableContent(rows); +} + + +private updateWalletTableContent(rows: WalletRow[]): void { + const tbody = this.shadowRoot?.querySelector('#wallet-table tbody'); + if (!tbody) return; + + tbody.innerHTML = rows.map(row => ` + + ${row.column1} + ${row.column2} + ${row.column3} + + `).join(''); +} + +private showData(): void { + //console.log("showData called"); + currentMode = 'data'; + this.hideAllContent(); + + const headerTitle = this.shadowRoot?.getElementById('header-title'); + if (headerTitle) headerTitle.textContent = 'Data'; + + const dataContent = this.shadowRoot?.getElementById('data-content'); + if (dataContent) { + dataContent.style.display = 'block'; + dataContent.innerHTML = ` +
Data
+
+ + + + + + + + + + + + +
NameVisibilityRoleDurationLegalContract
+
+ `; + + const rows = mockDataRows || JSON.parse(localStorage.getItem(STORAGE_KEYS.data) || '[]'); + this.updateDataTableContent(rows); + } +} + +// Fonctions de gestion du wallet +private addWalletRow(): void { + if (isAddingRow) return; + isAddingRow = true; + + const table = this.shadowRoot?.getElementById('wallet-table')?.getElementsByTagName('tbody')[0]; + if (!table) return; + + currentRow = table.insertRow(); + const placeholders = ['Label', 'Wallet', 'Type']; + + placeholders.forEach(placeholder => { + const cell = currentRow!.insertCell(); + const input = document.createElement('input'); + input.type = 'text'; + input.placeholder = placeholder; + input.className = 'edit-input'; + cell.appendChild(input); + }); + + // Remplacer le bouton "Add a line" par les boutons de confirmation/annulation + const buttonContainer = this.shadowRoot?.querySelector('#wallet-content .button-container'); + if (!buttonContainer) return; + + buttonContainer.innerHTML = ` +
+ + +
+ `; + + this.updateActionButtons(); +} + +private confirmWalletRow(): void { + if (!currentRow) return; + + const inputs = Array.from(currentRow.getElementsByTagName('input')); + const allFieldsFilled = inputs.every(input => input.value.trim() !== ''); + + if (allFieldsFilled) { + const newRow: WalletRow = { + column1: inputs[0].value.trim(), + column2: inputs[1].value.trim(), + column3: inputs[2].value.trim() + }; + + const rows = JSON.parse(localStorage.getItem(STORAGE_KEYS.wallet) || '[]'); + rows.push(newRow); + localStorage.setItem(STORAGE_KEYS.wallet, JSON.stringify(rows)); + + isAddingRow = false; + currentRow = null; + this.showWallet(); + } else { + showAlert('Please complete all fields before confirming.'); + } +} + +private cancelWalletRow(): void { + if (!currentRow) return; + + currentRow.remove(); + isAddingRow = false; + currentRow = null; + + // Réinitialiser le conteneur de boutons avec le bouton "Add a line" + const buttonContainer = this.shadowRoot?.querySelector('#wallet-content .button-container'); + if (!buttonContainer) return; + + buttonContainer.innerHTML = ` + + `; + + +} + +private updateDataTableContent(rows: DataRow[]): void { + const tbody = this.shadowRoot?.querySelector('#data-table tbody'); + if (!tbody) return; + + tbody.innerHTML = rows.map(row => ` + + ${row.column1} + ${row.column2} + ${row.column3} + ${row.column4} + ${row.column5} + + ${row.column6} + + + `).join(''); +} + +// Fonctions de gestion de l'avatar et de la bannière +private openAvatarPopup(): void { + const popup = this.shadowRoot?.getElementById('avatar-popup'); + if (!popup) return; + + // Récuprer les valeurs stockées + const savedName = localStorage.getItem('userName'); + const savedLastName = localStorage.getItem('userLastName'); + const savedAvatar = localStorage.getItem('userAvatar') || 'https://via.placeholder.com/150'; + const savedBanner = localStorage.getItem('userBanner') || 'https://via.placeholder.com/800x200'; + const savedAddress = localStorage.getItem('userAddress') || '🏠 🌍 🗽🎊😩-🎊😑😩'; + + popup.innerHTML = ` + + `; + + popup.style.display = 'block'; + this.setupEventListeners(popup); + + // Ajouter le gestionnaire d'événements pour la bannière + const bannerImg = popup.querySelector('#popup-banner-img'); + const bannerInput = popup.querySelector('#banner-upload') as HTMLInputElement; + + if (bannerImg && bannerInput) { + bannerImg.addEventListener('click', () => { + bannerInput.click(); + }); + } + + const recoveryExported = localStorage.getItem('recoveryExported') === 'true'; + if (recoveryExported) { + const exportRecoveryBtn = popup.querySelector('.recovery-btn') as HTMLButtonElement; + if (exportRecoveryBtn) { + exportRecoveryBtn.disabled = true; + exportRecoveryBtn.style.opacity = '0.5'; + exportRecoveryBtn.style.cursor = 'not-allowed'; + } + } +} + +private setupEventListeners(popup: HTMLElement): void { + // Gestionnaire pour la fermeture + const closeBtn = popup.querySelector('.close-popup'); + if (closeBtn) { + closeBtn.addEventListener('click', () => { + popup.style.display = 'none'; + }); + } + + // Gestionnaire pour l'upload d'avatar + const avatarUpload = popup.querySelector('#avatar-upload') as HTMLInputElement; + if (avatarUpload) { + avatarUpload.addEventListener('change', (e: Event) => { + const file = (e.target as HTMLInputElement).files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e: ProgressEvent) => { + const result = e.target?.result as string; + // Mise à jour de l'avatar dans la preview et le popup + const popupAvatar = this.shadowRoot?.getElementById('popup-avatar-img') as HTMLImageElement; + const previewAvatar = this.shadowRoot?.querySelector('.preview-avatar') as HTMLImageElement; + + if (popupAvatar) popupAvatar.src = result; + if (previewAvatar) previewAvatar.src = result; + + localStorage.setItem('userAvatar', result); + }; + reader.readAsDataURL(file); + } + }); + } + + // Gestionnaire pour l'upload de bannière + const bannerUpload = popup.querySelector('#banner-upload') as HTMLInputElement; + if (bannerUpload) { + bannerUpload.addEventListener('change', (e: Event) => { + const file = (e.target as HTMLInputElement).files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e: ProgressEvent) => { + const result = e.target?.result as string; + // Mise à jour de la bannière dans la preview et le popup + const popupBanner = this.shadowRoot?.getElementById('popup-banner-img') as HTMLImageElement; + const previewBanner = this.shadowRoot?.querySelector('.preview-banner-img') as HTMLImageElement; + + if (popupBanner) popupBanner.src = result; + if (previewBanner) previewBanner.src = result; + + localStorage.setItem('userBanner', result); + }; + reader.readAsDataURL(file); + } + }); + } + + // Gestionnaires pour les champs de texte + const nameInput = popup.querySelector('#userName') as HTMLInputElement; + const lastNameInput = popup.querySelector('#userLastName') as HTMLInputElement; + + if (nameInput) { + nameInput.addEventListener('input', () => { + const newName = nameInput.value; + localStorage.setItem('userName', newName); + // Mise à jour du nom dans la preview + const previewName = this.shadowRoot?.querySelector('.preview-name'); + if (previewName) previewName.textContent = newName; + }); + } + + if (lastNameInput) { + lastNameInput.addEventListener('input', () => { + const newLastName = lastNameInput.value; + localStorage.setItem('userLastName', newLastName); + // Mise à jour du nom de famille dans la preview + const previewLastName = this.shadowRoot?.querySelector('.preview-lastname'); + if (previewLastName) previewLastName.textContent = newLastName; + }); + } +} + +private closeAvatarPopup(): void { + const popup = this.shadowRoot?.querySelector('.avatar-popup'); + if (popup) popup.remove(); +} + +private loadAvatar(): void { + const savedAvatar = localStorage.getItem('userAvatar'); + if (savedAvatar) { + const avatarImg = this.shadowRoot?.querySelector('.avatar') as HTMLImageElement; + if (avatarImg) { + avatarImg.src = savedAvatar; + } + } +} + +private loadUserInfo(): void { + const savedName = localStorage.getItem('userName'); + const savedLastName = localStorage.getItem('userLastName'); + const savedAvatar = localStorage.getItem('userAvatar'); + const savedBanner = localStorage.getItem('userBanner'); + + // Mise à jour du nom dans la preview + if (savedName) { + const previewName = this.shadowRoot?.querySelector('.preview-name'); + if (previewName) { + previewName.textContent = savedName; + } + } + + // Mise à jour du nom de famille dans la preview + if (savedLastName) { + const previewLastName = this.shadowRoot?.querySelector('.preview-lastname'); + if (previewLastName) { + previewLastName.textContent = savedLastName; + } + } + + // Mise à jour de l'avatar dans la preview + if (savedAvatar) { + const previewAvatar = this.shadowRoot?.querySelector('.preview-avatar') as HTMLImageElement; + if (previewAvatar) { + previewAvatar.src = savedAvatar; + } + } + + // Mise à jour de la bannière dans la preview + if (savedBanner) { + const previewBanner = this.shadowRoot?.querySelector('.preview-banner-img') as HTMLImageElement; + if (previewBanner) { + previewBanner.src = savedBanner; + } + } +} + +private updateNavbarName(name: string): void { + const nameElement = this.shadowRoot?.querySelector('.nav-wrapper .user-name'); + if (nameElement) { + nameElement.textContent = name; + } +} + +private updateNavbarLastName(lastName: string): void { + const lastNameElement = this.shadowRoot?.querySelector('.nav-wrapper .user-lastname'); + if (lastNameElement) { + lastNameElement.textContent = lastName; + } +} + +private updateProfilePreview(data: { + avatar?: string, + banner?: string, + name?: string, + lastName?: string +}): void { + if (data.avatar) { + const previewAvatar = this.shadowRoot?.querySelector('.preview-avatar') as HTMLImageElement; + if (previewAvatar) previewAvatar.src = data.avatar; + } + + if (data.banner) { + const previewBanner = this.shadowRoot?.querySelector('.preview-banner-img') as HTMLImageElement; + if (previewBanner) previewBanner.src = data.banner; + } + + if (data.name) { + const previewName = this.shadowRoot?.querySelector('.preview-name'); + if (previewName) previewName.textContent = data.name; + } + + if (data.lastName) { + const previewLastName = this.shadowRoot?.querySelector('.preview-lastname'); + if (previewLastName) previewLastName.textContent = data.lastName; + } +} + +private initializeEventListeners() { + this.shadowRoot?.addEventListener('DOMContentLoaded', () => { + this.showPairing(); + }); + + const editableFields = this.shadowRoot?.querySelectorAll('.editable'); + if (editableFields) { + editableFields.forEach(field => { + field.addEventListener('click', () => { + if (!field.classList.contains('editing')) { + const currentValue = field.textContent || ''; + const input = document.createElement('input'); + input.type = 'text'; + input.value = currentValue; + input.className = 'edit-input'; + + field.textContent = ''; + field.appendChild(input); + field.classList.add('editing'); + input.focus(); + } + }); + }); + } + + const avatarInput = this.shadowRoot?.getElementById('avatar-upload') as HTMLInputElement; + if (avatarInput) { + avatarInput.addEventListener('change', this.handleAvatarUpload.bind(this)); + } +} +} + +customElements.define('account-element', AccountElement); +export { AccountElement }; diff --git a/src/pages/chat/chat.html b/src/pages/chat/chat.html index 74c9b3b..e05d334 100755 --- a/src/pages/chat/chat.html +++ b/src/pages/chat/chat.html @@ -1,48 +1,13 @@ - + - - - - Messagerie - - - - -
- -
-
    - -
-
+ + Chat + - -
-
- -
-
- -
+ + + + - -
- - - - - - -
-
-
- - diff --git a/src/pages/chat/chat.ts b/src/pages/chat/chat.ts index 2639e9d..75047a8 100755 --- a/src/pages/chat/chat.ts +++ b/src/pages/chat/chat.ts @@ -1,527 +1,564 @@ -declare global { - interface Window { - toggleUserList: () => void; - switchUser: (userId: string | number) => void; - loadMemberChat: (memberId: string | number) => void; - } -} - -import { groupsMock } from '../../mocks/mock-signature/groupsMock'; -import { messagesMock as initialMessagesMock, messagesMock } from '../../mocks/mock-signature/messagesMock'; -import { membersMock } from '../../mocks/mock-signature/membersMocks'; -import { Message, DocumentSignature } from '../../models/signature.models'; -import { messageStore } from '../../utils/messageMock'; -import { Member } from '../../interface/memberInterface'; -import { Group } from '../../interface/groupInterface'; -import { getCorrectDOM } from '../../utils/document.utils'; - -let currentUser: Member = membersMock[0]; - -interface LocalNotification { - memberId: string; - text: string; - time: string; -} - -export function initChat() { - const chatElement = document.createElement('chat-element'); - const container = document.querySelector('.container'); - if (container) { - container.appendChild(chatElement); - } -} - -class ChatElement extends HTMLElement { - private selectedMemberId: string | null = null; - private messagesMock: any[] = []; - private dom: Node; - private notifications: LocalNotification[] = []; - private notificationBadge = document.querySelector('.notification-badge'); - private notificationBoard = document.getElementById('notification-board'); - private notificationBell = document.getElementById('notification-bell'); - private selectedSignatories: DocumentSignature[] = []; - private allMembers = membersMock.map((member) => ({ - id: member.id, - name: member.name, - roleName: 'Default Role', - })); - - constructor() { - super(); - this.attachShadow({ mode: 'open' }); - this.dom = getCorrectDOM('signature-element'); - - window.toggleUserList = this.toggleUserList.bind(this); - window.switchUser = this.switchUser.bind(this); - window.loadMemberChat = this.loadMemberChat.bind(this); - - // Initialiser les événements de notification - document.addEventListener('click', (event: Event): void => { - if (this.notificationBoard && this.notificationBoard.style.display === 'block' && !this.notificationBoard.contains(event.target as Node) && this.notificationBell && !this.notificationBell.contains(event.target as Node)) { - this.notificationBoard.style.display = 'none'; - } - }); - this.initMessageEvents(); - this.initFileUpload(); - } - - private initMessageEvents() { - // Pour le bouton Send - const sendButton = document.getElementById('send-button'); - if (sendButton) { - sendButton.addEventListener('click', () => this.sendMessage()); - } - - // Pour la touche Entrée - const messageInput = document.getElementById('message-input'); - if (messageInput) { - messageInput.addEventListener('keypress', (event: KeyboardEvent) => { - if (event.key === 'Enter' && !event.shiftKey) { - event.preventDefault(); - this.sendMessage(); - } - }); - } - } - - private initFileUpload() { - const fileInput = document.getElementById('file-input') as HTMLInputElement; - if (fileInput) { - fileInput.addEventListener('change', (event: Event) => { - const target = event.target as HTMLInputElement; - if (target.files && target.files.length > 0) { - this.sendFile(target.files[0]); - } - }); - } - } - - ///////////////////// Notification module ///////////////////// - // Delete a notification - private removeNotification(index: number) { - this.notifications?.splice(index, 1); // Ajout de ?. - this.renderNotifications(); - this.updateNotificationBadge(); - } - // Show notifications - private renderNotifications() { - if (!this.notificationBoard) return; - - // Reset the interface - this.notificationBoard.innerHTML = ''; - - // Displays "No notifications available" if there are no notifications - if (this.notifications.length === 0) { - this.notificationBoard.innerHTML = '
No notifications available
'; - return; - } - - // Add each notification to the list - this.notifications.forEach((notif, index) => { - const notifElement = document.createElement('div'); - notifElement.className = 'notification-item'; - notifElement.textContent = `${notif.text} at ${notif.time}`; - notifElement.onclick = () => { - this.loadMemberChat(notif.memberId); - this.removeNotification(index); - }; - this.notificationBoard?.appendChild(notifElement); - }); - } - private updateNotificationBadge() { - if (!this.notificationBadge) return; - const count = this.notifications.length; - this.notificationBadge.textContent = count > 99 ? '+99' : count.toString(); - (this.notificationBadge as HTMLElement).style.display = count > 0 ? 'block' : 'none'; - } - - // Add notification - private addNotification(memberId: string, message: Message) { - // Creating a new notification - const notification = { - memberId, - text: `New message from Member ${memberId}: ${message.text}`, - time: message.time, - }; - - // Added notification to list and interface - this.notifications.push(notification); - this.renderNotifications(); - this.updateNotificationBadge(); - } - - // Send a messsage - private sendMessage() { - const messageInput = document.getElementById('message-input') as HTMLInputElement; - if (!messageInput) return; - const messageText = messageInput.value.trim(); - - if (messageText === '' || this.selectedMemberId === null) { - return; - } - - const newMessage: Message = { - id: Date.now(), - sender: '4NK', - text: messageText, - time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), - type: 'text' as const, - }; - // Add and display the message immediately - messageStore.addMessage(this.selectedMemberId, newMessage); - this.messagesMock = messageStore.getMessages(); - this.loadMemberChat(this.selectedMemberId); - - // Reset the input - messageInput.value = ''; - - // Automatic response after 2 seconds - setTimeout(() => { - if (this.selectedMemberId) { - const autoReply = this.generateAutoReply(`Member ${this.selectedMemberId}`); - messageStore.addMessage(this.selectedMemberId, autoReply); - this.messagesMock = messageStore.getMessages(); - this.loadMemberChat(this.selectedMemberId); - this.addNotification(this.selectedMemberId, autoReply); - } - }, 2000); - } - - // Scroll down the conversation after loading messages - private scrollToBottom(container: HTMLElement) { - container.scrollTop = container.scrollHeight; - } - - // Load the list of members - private loadMemberChat(memberId: string | number) { - this.selectedMemberId = String(memberId); - const memberMessages = this.messagesMock.find((m) => String(m.memberId) === String(memberId)); - - // Find the process and the role of the member - let memberInfo = { processName: '', roleName: '', memberName: '' }; - groupsMock.forEach((process) => { - process.roles.forEach((role) => { - const member = role.members.find((m) => String(m.id) === String(memberId)); - if (member) { - memberInfo = { - processName: process.name, - roleName: role.name, - memberName: member.name, - }; - } - }); - }); - - const chatHeader = document.getElementById('chat-header'); - const messagesContainer = document.getElementById('messages'); - - if (!chatHeader || !messagesContainer) return; - - chatHeader.textContent = `Chat with ${memberInfo.roleName} ${memberInfo.memberName} from ${memberInfo.processName}`; - messagesContainer.innerHTML = ''; - - if (memberMessages) { - memberMessages.messages.forEach((message: Message) => { - const messageElement = document.createElement('div'); - messageElement.className = 'message-container'; - - const messageContent = document.createElement('div'); - messageContent.className = 'message'; - if (message.type === 'file') { - messageContent.innerHTML = `${message.fileName}`; - messageContent.classList.add('user'); - } else { - messageContent.innerHTML = `${message.sender}: ${message.text} ${message.time}`; - if (message.sender === '4NK') { - messageContent.classList.add('user'); - } - } - - messageElement.appendChild(messageContent); - messagesContainer.appendChild(messageElement); - }); - } - - this.scrollToBottom(messagesContainer); - } - - private toggleMembers(role: { members: { id: string | number; name: string }[] }, roleElement: HTMLElement) { - let memberList = roleElement.querySelector('.member-list'); - if (memberList) { - (memberList as HTMLElement).style.display = (memberList as HTMLElement).style.display === 'none' ? 'block' : 'none'; - return; - } - - memberList = document.createElement('ul'); - memberList.className = 'member-list'; - - role.members.forEach((member) => { - const memberItem = document.createElement('li'); - memberItem.textContent = member.name; - - memberItem.onclick = (event) => { - event.stopPropagation(); - this.loadMemberChat(member.id.toString()); - }; - - memberList.appendChild(memberItem); - }); - - roleElement.appendChild(memberList); - } - - // Toggle the list of Roles - private toggleRoles(group: Group, groupElement: HTMLElement) { - console.log('=== toggleRoles START ==='); - console.log('Group:', group.name); - console.log('Group roles:', group.roles); // Afficher tous les rôles disponibles - - let roleList = groupElement.querySelector('.role-list'); - console.log('Existing roleList:', roleList); - - if (roleList) { - const roleItems = roleList.querySelectorAll('.role-item'); - roleItems.forEach((roleItem) => { - console.log('Processing roleItem:', roleItem.innerHTML); // Voir le contenu HTML complet - - let container = roleItem.querySelector('.role-item-container'); - if (!container) { - container = document.createElement('div'); - container.className = 'role-item-container'; - - // Créer un span pour le nom du rôle - const nameSpan = document.createElement('span'); - nameSpan.className = 'role-name'; - nameSpan.textContent = roleItem.textContent?.trim() || ''; - - container.appendChild(nameSpan); - roleItem.textContent = ''; - roleItem.appendChild(container); - } - - // Récupérer le nom du rôle - const roleName = roleItem.textContent?.trim(); - console.log('Role name from textContent:', roleName); - - // Alternative pour obtenir le nom du rôle - const roleNameAlt = container.querySelector('.role-name')?.textContent; - console.log('Role name from span:', roleNameAlt); - - if (!container.querySelector('.folder-icon')) { - const folderButton = document.createElement('span'); - folderButton.className = 'folder-icon'; - - folderButton.addEventListener('click', (event) => { - event.stopPropagation(); - console.log('Clicked role name:', roleName); - console.log( - 'Available roles:', - group.roles.map((r) => r.name), - ); - - const role = group.roles.find((r) => r.name === roleName); - if (role) { - console.log('Found role:', role); - } else { - console.error('Role not found. Name:', roleName); - console.error('Available roles:', group.roles); - } - }); - - container.appendChild(folderButton); - } - }); - - (roleList as HTMLElement).style.display = (roleList as HTMLElement).style.display === 'none' ? 'block' : 'none'; - } - } - - private loadGroupList(): void { - const groupList = document.getElementById('group-list'); - if (!groupList) return; - - groupsMock.forEach((group) => { - const li = document.createElement('li'); - li.className = 'group-list-item'; - - // Create a flex container for the name and the icon - const container = document.createElement('div'); - container.className = 'group-item-container'; - - // Span for the process name - const nameSpan = document.createElement('span'); - nameSpan.textContent = group.name; - nameSpan.className = 'process-name'; - - // Add click event to show roles - nameSpan.addEventListener('click', (event) => { - event.stopPropagation(); - this.toggleRoles(group, li); - }); - - // Assemble the elements - container.appendChild(nameSpan); - li.appendChild(container); - - // Create and append the role list container - const roleList = document.createElement('ul'); - roleList.className = 'role-list'; - roleList.style.display = 'none'; - - // Add roles for this process - group.roles.forEach((role) => { - const roleItem = document.createElement('li'); - roleItem.className = 'role-item'; - roleItem.textContent = role.name; - roleItem.onclick = (event) => { - event.stopPropagation(); - this.toggleMembers(role, roleItem); - }; - roleList.appendChild(roleItem); - }); - - li.appendChild(roleList); - groupList.appendChild(li); - }); - } - - // Function to manage the list of users - private toggleUserList() { - const userList = getCorrectDOM('userList'); - if (!userList) return; - - if (!(userList as HTMLElement).classList.contains('show')) { - (userList as HTMLElement).innerHTML = membersMock - .map( - (member) => ` -
- ${member.avatar} -
- ${member.name} - ${member.email} -
-
- `, - ) - .join(''); - } - (userList as HTMLElement).classList.toggle('show'); - } - - private switchUser(userId: string | number) { - const user = membersMock.find((member) => member.id === userId); - if (!user) return; - currentUser = user; - this.updateCurrentUserDisplay(); - const userList = getCorrectDOM('userList') as HTMLElement; - userList?.classList.remove('show'); - } - - // Function to update the display of the current user - private updateCurrentUserDisplay() { - const userDisplay = getCorrectDOM('current-user') as HTMLElement; - if (userDisplay) { - userDisplay.innerHTML = ` - - `; - } - } - // Generate an automatic response - private generateAutoReply(senderName: string): Message { - return { - id: Date.now(), - sender: senderName, - text: 'OK...', - time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), - type: 'text' as const, - }; - } - - // Send a file - private sendFile(file: File) { - console.log('SendFile called with file:', file); - const reader = new FileReader(); - reader.onloadend = () => { - const fileData = reader.result; - const fileName = file.name; - console.log('File loaded:', fileName); - - if (this.selectedMemberId) { - messageStore.addMessage(this.selectedMemberId, { - id: Date.now(), - sender: '4NK', - fileName: fileName, - fileData: fileData, - time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), - type: 'file', - }); - console.log('Message added to store'); - - this.messagesMock = messageStore.getMessages(); - this.loadMemberChat(this.selectedMemberId); - } - }; - reader.readAsDataURL(file); - } - - private initializeEventListeners() { - document.addEventListener('DOMContentLoaded', (): void => {}); - - // Gestionnaire d'événements pour le chat - const sendBtn = this.shadowRoot?.querySelector('#send-button'); - if (sendBtn) { - sendBtn.addEventListener('click', this.sendMessage.bind(this)); - } - - const messageInput = this.shadowRoot?.querySelector('#message-input'); - if (messageInput) { - messageInput.addEventListener('keypress', (event: Event) => { - if ((event as KeyboardEvent).key === 'Enter') { - event.preventDefault(); - this.sendMessage(); - } - }); - } - - // Gestionnaire pour l'envoi de fichiers - const fileInput = this.shadowRoot?.querySelector('#file-input'); - if (fileInput) { - fileInput.addEventListener('change', (event: Event) => { - const file = (event.target as HTMLInputElement).files?.[0]; - if (file) { - this.sendFile(file); - } - }); - } - } - - connectedCallback() { - if (this.shadowRoot) { - this.shadowRoot.innerHTML = ` -
-
-
    -
    -
    - -
    -
    - `; - } - - this.messagesMock = messageStore.getMessages(); - if (this.messagesMock.length === 0) { - messageStore.setMessages(initialMessagesMock); - this.messagesMock = messageStore.getMessages(); - } - this.updateCurrentUserDisplay(); - this.initializeEventListeners(); - this.loadGroupList(); - } -} - -customElements.define('chat-element', ChatElement); -export { ChatElement }; +declare global { + interface Window { + toggleUserList: () => void; + switchUser: (userId: string | number) => void; + loadMemberChat: (memberId: string | number) => void; + } +} + +import { groupsMock } from '../../mocks/mock-signature/groupsMock'; +import { messagesMock as initialMessagesMock, messagesMock } from '../../mocks/mock-signature/messagesMock'; +import { membersMock } from '../../mocks/mock-signature/membersMocks'; +import { + Message, + DocumentSignature, + } from '../../models/signature.models'; +import { messageStore } from '../../utils/messageMock'; +import { Member } from '../../interface/memberInterface'; +import { Group } from '../../interface/groupInterface'; +import { getCorrectDOM } from '../../utils/document.utils'; +import chatStyle from '../../../public/style/chat.css?inline'; + + +let currentUser: Member = membersMock[0]; + +interface LocalNotification { + memberId: string; + text: string; + time: string; +} + +export function initChat() { + const chatElement = document.createElement('chat-element'); + const container = document.querySelector('.container'); + if (container) { + container.appendChild(chatElement); + } +} + +class ChatElement extends HTMLElement { + private selectedMemberId: string | null = null; + private messagesMock: any[] = []; + private dom: Node; + private notifications: LocalNotification[] = []; + private notificationBadge = document.querySelector('.notification-badge'); + private notificationBoard = document.getElementById('notification-board'); + private notificationBell = document.getElementById('notification-bell'); + private selectedSignatories: DocumentSignature[] = []; + private allMembers = membersMock.map(member => ({ + id: member.id, + name: member.name, + roleName: 'Default Role' + })); + + + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + this.messagesMock = messageStore.getMessages(); + this.dom = getCorrectDOM('signature-element'); + + this.shadowRoot!.innerHTML = ` + + +
    + +
    +
      +
    +
    + + +
    +
    + +
    +
    + +
    + + +
    + + + + +
    +
    +
    + `; + + window.toggleUserList = this.toggleUserList.bind(this); + window.switchUser = this.switchUser.bind(this); + window.loadMemberChat = this.loadMemberChat.bind(this); + + // Initialiser les événements de notification + document.addEventListener('click', (event: Event): void => { + if (this.notificationBoard && this.notificationBoard.style.display === 'block' && + !this.notificationBoard.contains(event.target as Node) && + this.notificationBell && !this.notificationBell.contains(event.target as Node)) { + this.notificationBoard.style.display = 'none'; + } + }); + this.initMessageEvents(); + this.initFileUpload(); + } + + private initMessageEvents() { + // Pour le bouton Send + const sendButton = this.shadowRoot?.querySelector('#send-button'); + if (sendButton) { + sendButton.addEventListener('click', () => this.sendMessage()); + } + + // Pour la touche Entrée + const messageInput = this.shadowRoot?.querySelector('#message-input'); + if (messageInput) { + messageInput.addEventListener('keypress', (event: Event) => { + const keyEvent = event as KeyboardEvent; // Cast en KeyboardEvent + if (keyEvent.key === 'Enter' && !keyEvent.shiftKey) { + event.preventDefault(); + this.sendMessage(); + } + }); + } + } + + private initFileUpload() { + const fileInput = this.shadowRoot?.querySelector('#file-input') as HTMLInputElement; + if (fileInput) { + fileInput.addEventListener('change', (event: Event) => { + const target = event.target as HTMLInputElement; + if (target.files && target.files.length > 0) { + this.sendFile(target.files[0]); + } + }); + } + } + + ///////////////////// Notification module ///////////////////// + // Delete a notification + private removeNotification(index: number) { + this.notifications?.splice(index, 1); // Ajout de ?. + this.renderNotifications(); + this.updateNotificationBadge(); + } + // Show notifications + private renderNotifications() { + if (!this.notificationBoard) return; + + // Reset the interface + this.notificationBoard.innerHTML = ''; + + // Displays "No notifications available" if there are no notifications + if (this.notifications.length === 0) { + this.notificationBoard.innerHTML = '
    No notifications available
    '; + return; + } + + // Add each notification to the list + this.notifications.forEach((notif, index) => { + const notifElement = document.createElement('div'); + notifElement.className = 'notification-item'; + notifElement.textContent = `${notif.text} at ${notif.time}`; + notifElement.onclick = () => { + this.loadMemberChat(notif.memberId); + this.removeNotification(index); + }; + this.notificationBoard?.appendChild(notifElement); + }); + } + private updateNotificationBadge() { + if (!this.notificationBadge) return; + const count = this.notifications.length; + this.notificationBadge.textContent = count > 99 ? '+99' : count.toString(); + (this.notificationBadge as HTMLElement).style.display = count > 0 ? 'block' : 'none'; + } + + + // Add notification + private addNotification(memberId: string, message: Message) { + // Creating a new notification + const notification = { + memberId, + text: `New message from Member ${memberId}: ${message.text}`, + time: message.time + }; + + // Added notification to list and interface + this.notifications.push(notification); + this.renderNotifications(); + this.updateNotificationBadge(); + } + +// Send a messsage + private sendMessage() { + const messageInput = this.shadowRoot?.querySelector('#message-input') as HTMLInputElement; + if (!messageInput) return; + const messageText = messageInput.value.trim(); + + if (messageText === '' || this.selectedMemberId === null) { + return; + } + + const newMessage: Message = { + id: Date.now(), + sender: "4NK", + text: messageText, + time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), + type: 'text' as const + }; + // Add and display the message immediately + messageStore.addMessage(this.selectedMemberId, newMessage); + this.messagesMock = messageStore.getMessages(); + this.loadMemberChat(this.selectedMemberId); + + // Reset the input + messageInput.value = ''; + + // Automatic response after 2 seconds + setTimeout(() => { + if (this.selectedMemberId) { + const autoReply = this.generateAutoReply(`Member ${this.selectedMemberId}`); + messageStore.addMessage(this.selectedMemberId, autoReply); + this.messagesMock = messageStore.getMessages(); + this.loadMemberChat(this.selectedMemberId); + this.addNotification(this.selectedMemberId, autoReply); + } + }, 2000); + } + + // Scroll down the conversation after loading messages + private scrollToBottom(container: Element) { + (container as HTMLElement).scrollTop = (container as HTMLElement).scrollHeight; + } + + + // Load the list of members + private loadMemberChat(memberId: string | number) { + this.selectedMemberId = String(memberId); + const memberMessages = this.messagesMock.find(m => String(m.memberId) === String(memberId)); + + // Find the process and the role of the member + let memberInfo = { processName: '', roleName: '', memberName: '' }; + groupsMock.forEach(process => { + process.roles.forEach(role => { + const member = role.members.find(m => String(m.id) === String(memberId)); + if (member) { + memberInfo = { + processName: process.name, + roleName: role.name, + memberName: member.name + }; + } + }); + }); + + const chatHeader = this.shadowRoot?.querySelector('#chat-header'); + const messagesContainer = this.shadowRoot?.querySelector('#messages'); + + if (!chatHeader || !messagesContainer) return; + + chatHeader.textContent = `Chat with ${memberInfo.roleName} ${memberInfo.memberName} from ${memberInfo.processName}`; + messagesContainer.innerHTML = ''; + + if (memberMessages) { + memberMessages.messages.forEach((message: Message) => { + const messageElement = document.createElement('div'); + messageElement.className = 'message-container'; + + const messageContent = document.createElement('div'); + messageContent.className = 'message'; + if (message.type === 'file') { + messageContent.innerHTML = `${message.fileName}`; + messageContent.classList.add('user'); + } else { + messageContent.innerHTML = `${message.sender}: ${message.text} ${message.time}`; + if (message.sender === "4NK") { + messageContent.classList.add('user'); + } + } + + messageElement.appendChild(messageContent); + messagesContainer.appendChild(messageElement); + }); + } + + + this.scrollToBottom(messagesContainer); + } + + private toggleMembers(role: { members: { id: string | number; name: string; }[] }, roleElement: HTMLElement) { + let memberList = roleElement.querySelector('.member-list'); + if (memberList) { + (memberList as HTMLElement).style.display = (memberList as HTMLElement).style.display === 'none' ? 'block' : 'none'; + return; + } + + memberList = document.createElement('ul'); + memberList.className = 'member-list'; + + role.members.forEach(member => { + const memberItem = document.createElement('li'); + memberItem.textContent = member.name; + + memberItem.onclick = (event) => { + event.stopPropagation(); + this.loadMemberChat(member.id.toString()); + }; + + memberList.appendChild(memberItem); + }); + + roleElement.appendChild(memberList); + } + + + // Toggle the list of Roles + private toggleRoles(group: Group, groupElement: HTMLElement) { + console.log('=== toggleRoles START ==='); + console.log('Group:', group.name); + console.log('Group roles:', group.roles); // Afficher tous les rôles disponibles + + let roleList = groupElement.querySelector('.role-list'); + console.log('Existing roleList:', roleList); + + if (roleList) { + const roleItems = roleList.querySelectorAll('.role-item'); + roleItems.forEach(roleItem => { + console.log('Processing roleItem:', roleItem.innerHTML); // Voir le contenu HTML complet + + let container = roleItem.querySelector('.role-item-container'); + if (!container) { + container = document.createElement('div'); + container.className = 'role-item-container'; + + // Créer un span pour le nom du rôle + const nameSpan = document.createElement('span'); + nameSpan.className = 'role-name'; + nameSpan.textContent = roleItem.textContent?.trim() || ''; + + container.appendChild(nameSpan); + roleItem.textContent = ''; + roleItem.appendChild(container); + } + + // Récupérer le nom du rôle + const roleName = roleItem.textContent?.trim(); + console.log('Role name from textContent:', roleName); + + // Alternative pour obtenir le nom du rôle + const roleNameAlt = container.querySelector('.role-name')?.textContent; + console.log('Role name from span:', roleNameAlt); + + if (!container.querySelector('.folder-icon')) { + const folderButton = document.createElement('span'); + folderButton.className = 'folder-icon'; + + folderButton.addEventListener('click', (event) => { + event.stopPropagation(); + console.log('Clicked role name:', roleName); + console.log('Available roles:', group.roles.map(r => r.name)); + + const role = group.roles.find(r => r.name === roleName); + if (role) { + console.log('Found role:', role); + } else { + console.error('Role not found. Name:', roleName); + console.error('Available roles:', group.roles); + } + }); + + container.appendChild(folderButton); + } + }); + + (roleList as HTMLElement).style.display = + (roleList as HTMLElement).style.display === 'none' ? 'block' : 'none'; + } + } + + + private loadGroupList(): void { + const groupList = this.shadowRoot?.querySelector('#group-list'); + if (!groupList) return; + + groupsMock.forEach(group => { + const li = document.createElement('li'); + li.className = 'group-list-item'; + + // Create a flex container for the name and the icon + const container = document.createElement('div'); + container.className = 'group-item-container'; + + // Span for the process name + const nameSpan = document.createElement('span'); + nameSpan.textContent = group.name; + nameSpan.className = 'process-name'; + + // Add click event to show roles + nameSpan.addEventListener('click', (event) => { + event.stopPropagation(); + this.toggleRoles(group, li); + }); + + // Assemble the elements + container.appendChild(nameSpan); + li.appendChild(container); + + // Create and append the role list container + const roleList = document.createElement('ul'); + roleList.className = 'role-list'; + roleList.style.display = 'none'; + + // Add roles for this process + group.roles.forEach(role => { + const roleItem = document.createElement('li'); + roleItem.className = 'role-item'; + roleItem.textContent = role.name; + roleItem.onclick = (event) => { + event.stopPropagation(); + this.toggleMembers(role, roleItem); + }; + roleList.appendChild(roleItem); + }); + + li.appendChild(roleList); + groupList.appendChild(li); + }); + } + + + // Function to manage the list of users + private toggleUserList() { + const userList = getCorrectDOM('userList'); + if (!userList) return; + + if (!(userList as HTMLElement).classList.contains('show')) { + (userList as HTMLElement).innerHTML = membersMock.map(member => ` +
    + ${member.avatar} +
    + ${member.name} + ${member.email} +
    +
    + `).join(''); + } + (userList as HTMLElement).classList.toggle('show'); + } + + private switchUser(userId: string | number) { + const user = membersMock.find(member => member.id === userId); + if (!user) return; + currentUser = user; + this.updateCurrentUserDisplay(); + const userList = getCorrectDOM('userList') as HTMLElement; + userList?.classList.remove('show'); + } + + // Function to update the display of the current user + private updateCurrentUserDisplay() { + const userDisplay = getCorrectDOM('current-user') as HTMLElement; + if (userDisplay) { + userDisplay.innerHTML = ` + + `; + } + } + // Generate an automatic response + private generateAutoReply(senderName: string): Message { + return { + id: Date.now(), + sender: senderName, + text: "OK...", + time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), + type: 'text' as const + }; + } + + // Send a file + private sendFile(file: File) { + console.log('SendFile called with file:', file); + const reader = new FileReader(); + reader.onloadend = () => { + const fileData = reader.result; + const fileName = file.name; + console.log('File loaded:', fileName); + + if (this.selectedMemberId) { + messageStore.addMessage(this.selectedMemberId, { + id: Date.now(), + sender: "4NK", + fileName: fileName, + fileData: fileData, + time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), + type: 'file' + }); + console.log('Message added to store'); + + this.messagesMock = messageStore.getMessages(); + this.loadMemberChat(this.selectedMemberId); + } + }; + reader.readAsDataURL(file); + } + + private initializeEventListeners() { + document.addEventListener('DOMContentLoaded', (): void => { + }); + + // Gestionnaire d'événements pour le chat + const sendBtn = this.shadowRoot?.querySelector('#send-button'); + if (sendBtn) { + sendBtn.addEventListener('click', this.sendMessage.bind(this)); + } + + const messageInput = this.shadowRoot?.querySelector('#message-input'); + if (messageInput) { + messageInput.addEventListener('keypress', (event: Event) => { + if ((event as KeyboardEvent).key === 'Enter') { + event.preventDefault(); + this.sendMessage(); + } + }); + } + + // Gestionnaire pour l'envoi de fichiers + const fileInput = this.shadowRoot?.querySelector('#file-input'); + if (fileInput) { + fileInput.addEventListener('change', (event: Event) => { + const file = (event.target as HTMLInputElement).files?.[0]; + if (file) { + this.sendFile(file); + } + }); + } + } + + + + connectedCallback() { + this.updateCurrentUserDisplay(); + this.initializeEventListeners(); + this.loadGroupList(); + + // Si un membre est sélectionné par défaut, charger ses messages + if (this.selectedMemberId) { + this.loadMemberChat(this.selectedMemberId); + } + } +} + +customElements.define('chat-element', ChatElement); +export { ChatElement }; + diff --git a/src/pages/signature/signature-component.ts b/src/pages/signature/signature-component.ts index 2402f9e..39e91b9 100644 --- a/src/pages/signature/signature-component.ts +++ b/src/pages/signature/signature-component.ts @@ -1,59 +1,58 @@ -import { SignatureElement } from './signature'; -import signatureCss from '../../../public/style/signature.css?raw'; -import Services from '../../services/service.js'; - -class SignatureComponent extends HTMLElement { - _callback: any; - signatureElement: SignatureElement | null = null; - - constructor() { - super(); - console.log('INIT'); - this.attachShadow({ mode: 'open' }); - - this.signatureElement = this.shadowRoot?.querySelector('signature-element') || null; - } - - connectedCallback() { - console.log('CALLBACKs'); - this.render(); - this.fetchData(); - - if (!customElements.get('signature-element')) { - customElements.define('signature-element', SignatureElement); - } - } - - async fetchData() { - if ((import.meta as any).env.VITE_IS_INDEPENDANT_LIB === false) { - const data = await (window as any).myService?.getProcesses(); - } else { - const service = await Services.getInstance(); - const data = await service.getProcesses(); - } - } - - set callback(fn) { - if (typeof fn === 'function') { - this._callback = fn; - } else { - console.error('Callback is not a function'); - } - } - - get callback() { - return this._callback; - } - - render() { - if (this.shadowRoot) { - // Créer l'élément signature-element - const signatureElement = document.createElement('signature-element'); - this.shadowRoot.innerHTML = ``; - this.shadowRoot.appendChild(signatureElement); - } - } -} - -export { SignatureComponent }; -customElements.define('signature-component', SignatureComponent); +import { SignatureElement } from './signature'; +import signatureCss from '../../../public/style/signature.css?raw' +import Services from '../../services/service.js' + +class SignatureComponent extends HTMLElement { + _callback: any + signatureElement: SignatureElement | null = null; + + constructor() { + super(); + console.log('INIT') + this.attachShadow({ mode: 'open' }); + + this.signatureElement = this.shadowRoot?.querySelector('signature-element') || null; + } + + connectedCallback() { + console.log('CALLBACKs') + this.render(); + this.fetchData(); + + if (!customElements.get('signature-element')) { + customElements.define('signature-element', SignatureElement); + } + } + + async fetchData() { + if ((import.meta as any).env.VITE_IS_INDEPENDANT_LIB === false) { + const data = await (window as any).myService?.getProcesses(); + } else { + const service = await Services.getInstance() + const data = await service.getProcesses(); + } + } + + set callback(fn) { + if (typeof fn === 'function') { + this._callback = fn; + } else { + console.error('Callback is not a function'); + } + } + + get callback() { + return this._callback; + } + + render() { + if(this.shadowRoot) { + const signatureElement = document.createElement('signature-element'); + this.shadowRoot.innerHTML = ``; + this.shadowRoot.appendChild(signatureElement); + } + } +} + +export { SignatureComponent } +customElements.define('signature-component', SignatureComponent); diff --git a/src/pages/signature/signature.html b/src/pages/signature/signature.html index c7bb274..a9b5ad3 100755 --- a/src/pages/signature/signature.html +++ b/src/pages/signature/signature.html @@ -1,46 +1,12 @@ - + - - - + + Signatures - - + + + + + - - -
    - -
    -
      -
      - - -
      -
      - -
      -
      - -
      - - -
      - - - - - - -
      -
      -
      - - diff --git a/src/pages/signature/signature.ts b/src/pages/signature/signature.ts index 6e0a4fa..6702d15 100755 --- a/src/pages/signature/signature.ts +++ b/src/pages/signature/signature.ts @@ -1,1716 +1,1758 @@ -declare global { - interface Window { - toggleUserList: () => void; - switchUser: (userId: string | number) => void; - closeProcessDetails: (groupId: number) => void; - loadMemberChat: (memberId: string | number) => void; - closeRoleDocuments: (roleName: string) => void; - newRequest: (params: RequestParams) => void; - submitRequest: () => void; - closeNewRequest: () => void; - closeModal: (button: HTMLElement) => void; - submitDocumentRequest: (documentId: number) => void; - submitNewDocument: (event: Event) => void; - submitCommonDocument: (event: Event) => void; - signDocument: (documentId: number, processId: number, isCommonDocument: boolean) => void; - confirmSignature: (documentId: number, processId: number, isCommonDocument: boolean) => void; - } -} - -import { groupsMock } from '../../mocks/mock-signature/groupsMock'; -import { messagesMock as initialMessagesMock, messagesMock } from '../../mocks/mock-signature/messagesMock'; -import { membersMock } from '../../mocks/mock-signature/membersMocks'; -import { Message, DocumentSignature, RequestParams } from '../../models/signature.models'; -import { messageStore } from '../../utils/messageMock'; -import { showAlert } from '../account/account'; -import { Member } from '../../interface/memberInterface'; -import { Group } from '../../interface/groupInterface'; -import { getCorrectDOM } from '../../utils/document.utils'; - -let currentUser: Member = membersMock[0]; - -interface LocalNotification { - memberId: string; - text: string; - time: string; -} - -class SignatureElement extends HTMLElement { - private selectedMemberId: string | null = null; - private messagesMock: any[] = []; - private dom: Node; - private notifications: LocalNotification[] = []; - private notificationBadge = document.querySelector('.notification-badge'); - private notificationBoard = document.getElementById('notification-board'); - private notificationBell = document.getElementById('notification-bell'); - private selectedSignatories: DocumentSignature[] = []; - private allMembers = membersMock.map((member) => ({ - id: member.id, - name: member.name, - roleName: 'Default Role', - })); - - private signDocument(documentId: number, processId: number, isCommonDocument: boolean = false): void { - if (typeof window === 'undefined' || typeof document === 'undefined') { - console.error('Cette fonction ne peut être exécutée que dans un navigateur'); - return; - } - - try { - const groups = JSON.parse(localStorage.getItem('groups') || JSON.stringify(groupsMock)); - const group = groups.find((g: Group) => g.id === processId); - - if (!group) { - throw new Error('Process not found'); - } - - let targetDoc; - if (isCommonDocument) { - targetDoc = group.commonDocuments.find((d: any) => d.id === documentId); - } else { - for (const role of group.roles) { - if (role.documents) { - targetDoc = role.documents.find((d: any) => d.id === documentId); - if (targetDoc) break; - } - } - } - - if (!targetDoc) { - throw new Error('Document not found'); - } - - const canSign = isCommonDocument ? targetDoc.signatures?.some((sig: DocumentSignature) => sig.member?.name === currentUser?.name && !sig.signed) : this.canUserSignDocument(targetDoc, currentUser?.name, currentUser); - - if (!canSign) { - showAlert('You do not have the necessary rights to sign this document.'); - return; - } - - // Create and insert the modal directly into the body - const modalHtml = ` - `; - - document.body.insertAdjacentHTML('beforeend', modalHtml); - - // Add the slider logic after creating the modal - const slider = document.getElementById('signatureSlider'); - const sliderTrack = slider?.parentElement; - let isDragging = false; - let startX: number; - let sliderLeft: number; - - if (slider && sliderTrack) { - slider.addEventListener('mousedown', initDrag.bind(this)); - document.addEventListener('mousemove', drag.bind(this)); - document.addEventListener('mouseup', stopDrag.bind(this)); - - // Touch events for mobile - slider.addEventListener('touchstart', initDrag.bind(this)); - document.addEventListener('touchmove', drag.bind(this)); - document.addEventListener('touchend', stopDrag.bind(this)); - } - - function initDrag(e: MouseEvent | TouchEvent) { - isDragging = true; - startX = 'touches' in e ? e.touches[0].clientX : e.clientX; - sliderLeft = slider?.offsetLeft || 0; - } - - function drag(this: SignatureElement, e: MouseEvent | TouchEvent) { - if (!isDragging || !slider || !sliderTrack) return; - - e.preventDefault(); - const rect = sliderTrack.getBoundingClientRect(); - const x = 'touches' in e ? e.touches[0].clientX : e.clientX; - - // Calculate the position relative to the track - let newLeft = x - rect.left - slider.offsetWidth / 2; - - // Limit the movement - const maxLeft = sliderTrack.offsetWidth - slider.offsetWidth; - newLeft = Math.max(0, Math.min(newLeft, maxLeft)); - - // Update the position - slider.style.left = `${newLeft}px`; - - // If the slider reaches 90% of the path, trigger the signature - if (newLeft > maxLeft * 0.9) { - stopDrag(e); - this.confirmSignature(documentId, processId, isCommonDocument); - } - } - - function stopDrag(e: MouseEvent | TouchEvent) { - if (!isDragging || !slider) return; - isDragging = false; - - // Reset the position if not enough dragged - if (slider.offsetLeft < (sliderTrack?.offsetWidth || 0) * 0.9) { - slider.style.left = '0px'; - } - } - } catch (error) { - console.error('Error displaying modal:', error); - showAlert(error instanceof Error ? error.message : 'Error displaying modal'); - } - } - - constructor() { - super(); - this.attachShadow({ mode: 'open' }); - this.dom = getCorrectDOM('signature-element'); - - window.toggleUserList = this.toggleUserList.bind(this); - window.switchUser = this.switchUser.bind(this); - window.closeProcessDetails = this.closeProcessDetails.bind(this); - window.loadMemberChat = this.loadMemberChat.bind(this); - window.closeRoleDocuments = this.closeRoleDocuments.bind(this); - window.newRequest = this.newRequest.bind(this); - window.submitRequest = this.submitRequest.bind(this); - window.closeNewRequest = this.closeNewRequest.bind(this); - window.closeModal = this.closeModal.bind(this); - window.submitNewDocument = this.submitNewDocument.bind(this); - window.submitCommonDocument = this.submitCommonDocument.bind(this); - window.signDocument = this.signDocument.bind(this); - window.confirmSignature = this.confirmSignature.bind(this); - window.submitDocumentRequest = this.submitDocumentRequest.bind(this); - - // Initialiser les événements de notification - document.addEventListener('click', (event: Event): void => { - if (this.notificationBoard && this.notificationBoard.style.display === 'block' && !this.notificationBoard.contains(event.target as Node) && this.notificationBell && !this.notificationBell.contains(event.target as Node)) { - this.notificationBoard.style.display = 'none'; - } - }); - this.initMessageEvents(); - this.initFileUpload(); - } - - private initMessageEvents() { - // Pour le bouton Send - const sendButton = document.getElementById('send-button'); - if (sendButton) { - sendButton.addEventListener('click', () => this.sendMessage()); - } - - // Pour la touche Entrée - const messageInput = document.getElementById('message-input'); - if (messageInput) { - messageInput.addEventListener('keypress', (event: KeyboardEvent) => { - if (event.key === 'Enter' && !event.shiftKey) { - event.preventDefault(); - this.sendMessage(); - } - }); - } - } - - private initFileUpload() { - const fileInput = document.getElementById('file-input') as HTMLInputElement; - if (fileInput) { - fileInput.addEventListener('change', (event: Event) => { - const target = event.target as HTMLInputElement; - if (target.files && target.files.length > 0) { - this.sendFile(target.files[0]); - } - }); - } - } - - private calculateDuration(startDate: string | null | undefined, endDate: string | null | undefined): number { - const start = new Date(startDate || ''); - const end = new Date(endDate || ''); - const duration = end.getTime() - start.getTime(); - return Math.floor(duration / (1000 * 60 * 60 * 24)); - } - - // Add this helper function - private canUserAccessDocument(document: any, roleId: string, currentUserRole: string): boolean { - // Modify the access logic - if (document.visibility === 'public') { - return true; // Can see but not necessarily sign - } - return roleId === currentUserRole; - } - - private canUserSignDocument(document: any, role: string, user: Member): boolean { - console.log('Checking signing rights for:', { - document, - role, - user, - userRoles: user.processRoles, - }); - - // Vérifier si l'utilisateur est dans la liste des signatures - const isSignatory = document.signatures?.some((sig: DocumentSignature) => sig.member && 'id' in sig.member && sig.member.id === user.id && !sig.signed); - - if (!isSignatory) { - console.log('User is not in signatures list or has already signed'); - return false; - } - - // Si l'utilisateur est dans la liste des signatures, il peut signer - return true; - } - - private closeProcessDetails(groupId: number) { - const detailsArea = document.getElementById(`process-details-${groupId}`); - const chatArea = document.getElementById('chat-area'); - - if (detailsArea) { - detailsArea.style.display = 'none'; - } - - if (chatArea) { - chatArea.style.display = 'block'; - } - } - - ///////////////////// Notification module ///////////////////// - // Delete a notification - private removeNotification(index: number) { - this.notifications?.splice(index, 1); // Ajout de ?. - this.renderNotifications(); - this.updateNotificationBadge(); - } - // Show notifications - private renderNotifications() { - if (!this.notificationBoard) return; - - // Reset the interface - this.notificationBoard.innerHTML = ''; - - // Displays "No notifications available" if there are no notifications - if (this.notifications.length === 0) { - this.notificationBoard.innerHTML = '
      No notifications available
      '; - return; - } - - // Add each notification to the list - this.notifications.forEach((notif, index) => { - const notifElement = document.createElement('div'); - notifElement.className = 'notification-item'; - notifElement.textContent = `${notif.text} at ${notif.time}`; - notifElement.onclick = () => { - this.loadMemberChat(notif.memberId); - this.removeNotification(index); - }; - this.notificationBoard?.appendChild(notifElement); - }); - } - private updateNotificationBadge() { - if (!this.notificationBadge) return; - const count = this.notifications.length; - this.notificationBadge.textContent = count > 99 ? '+99' : count.toString(); - (this.notificationBadge as HTMLElement).style.display = count > 0 ? 'block' : 'none'; - } - - // Add notification - private addNotification(memberId: string, message: Message) { - // Creating a new notification - const notification = { - memberId, - text: `New message from Member ${memberId}: ${message.text}`, - time: message.time, - }; - - // Added notification to list and interface - this.notifications.push(notification); - this.renderNotifications(); - this.updateNotificationBadge(); - } - // Send a messsage - private sendMessage() { - const messageInput = document.getElementById('message-input') as HTMLInputElement; - if (!messageInput) return; - const messageText = messageInput.value.trim(); - - if (messageText === '' || this.selectedMemberId === null) { - return; - } - - const newMessage: Message = { - id: Date.now(), - sender: '4NK', - text: messageText, - time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), - type: 'text' as const, - }; - // Add and display the message immediately - messageStore.addMessage(this.selectedMemberId, newMessage); - this.messagesMock = messageStore.getMessages(); - this.loadMemberChat(this.selectedMemberId); - - // Reset the input - messageInput.value = ''; - - // Automatic response after 2 seconds - setTimeout(() => { - if (this.selectedMemberId) { - const autoReply = this.generateAutoReply(`Member ${this.selectedMemberId}`); - messageStore.addMessage(this.selectedMemberId, autoReply); - this.messagesMock = messageStore.getMessages(); - this.loadMemberChat(this.selectedMemberId); - this.addNotification(this.selectedMemberId, autoReply); - } - }, 2000); - } - - private showProcessDetails(group: Group, groupId: number) { - console.log('Showing details for group:', groupId); - - // Close all existing process views - const allDetailsAreas = document.querySelectorAll('.process-details'); - allDetailsAreas.forEach((area) => { - (area as HTMLElement).style.display = 'none'; - }); - - const container = document.querySelector('.container'); - if (!container) { - console.error('Container not found'); - return; - } - - // Load the data from localStorage - const storedGroups = JSON.parse(localStorage.getItem('groups') || '[]'); - const storedGroup = storedGroups.find((g: Group) => g.id === groupId); - - // Use the data from localStorage if available, otherwise use the group passed as a parameter - const displayGroup = storedGroup || group; - - let detailsArea = document.getElementById(`process-details-${groupId}`); - if (!detailsArea) { - detailsArea = document.createElement('div'); - detailsArea.id = `process-details-${groupId}`; - detailsArea.className = 'process-details'; - container.appendChild(detailsArea); - } - - if (detailsArea) { - detailsArea.style.display = 'block'; - detailsArea.innerHTML = ` -
      -

      ${displayGroup.name}

      -
      -
      -
      -
      -
      -

      Description

      -

      ${displayGroup.description || 'No description available'}

      -
      -
      -

      Documents Communs

      -
      - ${displayGroup.commonDocuments - .map((document: any) => { - const totalSignatures = document.signatures?.length || 0; - const signedCount = document.signatures?.filter((sig: DocumentSignature) => sig.signed).length || 0; - const percentage = totalSignatures > 0 ? (signedCount / totalSignatures) * 100 : 0; - const isVierge = !document.createdAt || !document.deadline || !document.signatures?.length; - const canSign = document.signatures?.some((sig: DocumentSignature) => sig.member && 'id' in sig.member && sig.member.id === currentUser.id && !sig.signed); - - const signButton = !isVierge - ? ` - ${ - totalSignatures > 0 && signedCount < totalSignatures && canSign - ? ` - - ` - : '' - } - ` - : ''; - - return ` -
      -
      -

      ${isVierge ? `⚠️ ${document.name}` : document.name}

      - ${document.visibility} -
      -
      - ${ - !isVierge - ? ` -

      Created on: ${document.createdAt ? new Date(document.createdAt).toLocaleDateString() : 'N/A'}

      -

      Deadline: ${document.deadline ? new Date(document.deadline).toLocaleDateString() : 'N/A'}

      -

      Duration: ${this.calculateDuration(document.createdAt || '', document.deadline || '')} days

      -
      -
      Signatures:
      -
      - ${document.signatures - ?.map( - (sig: DocumentSignature) => ` -
      - ${sig.member.name} - - ${sig.signed ? `✓ Signed on ${sig.signedAt ? new Date(sig.signedAt).toLocaleDateString() : 'unknown date'}` : '⌛ Pending'} - -
      - `, - ) - .join('')} -
      -
      -
      -
      -

      ${signedCount} out of ${totalSignatures} signed (${percentage.toFixed(0)}%)

      -
      - ` - : ` -

      Document vierge - Waiting for creation

      - - ` - } - ${signButton} -
      -
      - `; - }) - .join('')} -
      -
      -
      -

      Roles and Documents

      - ${displayGroup.roles - .map((role: { name: string; documents?: any[] }) => { - // Filter the documents according to the access rights - const accessibleDocuments = (role.documents || []).filter((doc) => this.canUserAccessDocument(doc, role.name, currentUser.processRoles?.[0]?.role || '')); - - return ` -
      -

      ${role.name}

      -
      - ${accessibleDocuments - .map((document) => { - const isVierge = !document.createdAt || !document.deadline || document.signatures.length === 0; - - const canSign = this.canUserSignDocument(document, role.name, currentUser); - - const signButton = !isVierge - ? ` - ${ - document.signatures.length > 0 && document.signatures.filter((sig: DocumentSignature) => sig.signed).length < document.signatures.length && canSign - ? ` - - ` - : '' - } - ` - : ''; - - return ` -
      -
      -

      ${isVierge ? `⚠️ ${document.name}` : document.name}

      - ${document.visibility} -
      -
      - ${ - !isVierge - ? ` -

      Created on: ${document.createdAt ? new Date(document.createdAt).toLocaleDateString() : 'N/A'}

      -

      Deadline: ${document.deadline ? new Date(document.deadline).toLocaleDateString() : 'N/A'}

      -

      Duration: ${this.calculateDuration(document.createdAt || '', document.deadline || '')} days

      - ` - : '

      Document vierge - En attente de création

      ' - } -
      - ${ - !isVierge - ? ` -
      -
      Signatures:
      -
      - ${document.signatures - .map( - (sig: DocumentSignature) => ` -
      - ${sig.member.name} - - ${sig.signed ? `✓ Signé le ${sig.signedAt ? new Date(sig.signedAt).toLocaleDateString() : 'date inconnue'}` : '⌛ En attente'} - -
      - `, - ) - .join('')} -
      -
      -
      -
      -

      ${document.signatures.filter((sig: DocumentSignature) => sig.signed).length} out of ${document.signatures.length} signed (${((document.signatures.filter((sig: DocumentSignature) => sig.signed).length / document.signatures.length) * 100).toFixed(0)}%)

      -
      - ` - : '' - } - ${signButton} -
      - `; - }) - .join('')} -
      -
      - `; - }) - .join('')} -
      -
      -

      Members by Role

      -
      - ${displayGroup.roles - .map( - (role: { name: string; members: Array<{ id: string | number; name: string }> }) => ` -
      -

      ${role.name}

      -
        - ${role.members - .map( - (member) => ` -
      • ${member.name}
      • - `, - ) - .join('')} -
      -
      - `, - ) - .join('')} -
      -
      - `; - - const newCloseProcessButton = document.createElement('button'); - newCloseProcessButton.className = 'close-btn'; - newCloseProcessButton.textContent = 'x'; - newCloseProcessButton.addEventListener('click', () => this.closeProcessDetails(groupId)); - - const headerButtons = detailsArea.querySelector('.header-buttons'); - if (headerButtons) { - headerButtons.appendChild(newCloseProcessButton); - } - } - } - - // Scroll down the conversation after loading messages - private scrollToBottom(container: HTMLElement) { - container.scrollTop = container.scrollHeight; - } - - // Load the list of members - private loadMemberChat(memberId: string | number) { - this.selectedMemberId = String(memberId); - const memberMessages = this.messagesMock.find((m) => String(m.memberId) === String(memberId)); - - // Find the process and the role of the member - let memberInfo = { processName: '', roleName: '', memberName: '' }; - groupsMock.forEach((process) => { - process.roles.forEach((role) => { - const member = role.members.find((m) => String(m.id) === String(memberId)); - if (member) { - memberInfo = { - processName: process.name, - roleName: role.name, - memberName: member.name, - }; - } - }); - }); - - const chatHeader = document.getElementById('chat-header'); - const messagesContainer = document.getElementById('messages'); - - if (!chatHeader || !messagesContainer) return; - - chatHeader.textContent = `Chat with ${memberInfo.roleName} ${memberInfo.memberName} from ${memberInfo.processName}`; - messagesContainer.innerHTML = ''; - - if (memberMessages) { - memberMessages.messages.forEach((message: Message) => { - const messageElement = document.createElement('div'); - messageElement.className = 'message-container'; - - const messageContent = document.createElement('div'); - messageContent.className = 'message'; - if (message.type === 'file') { - messageContent.innerHTML = `${message.fileName}`; - messageContent.classList.add('user'); - } else { - messageContent.innerHTML = `${message.sender}: ${message.text} ${message.time}`; - if (message.sender === '4NK') { - messageContent.classList.add('user'); - } - } - - messageElement.appendChild(messageContent); - messagesContainer.appendChild(messageElement); - }); - } - - this.scrollToBottom(messagesContainer); - } - - private toggleMembers(role: { members: { id: string | number; name: string }[] }, roleElement: HTMLElement) { - let memberList = roleElement.querySelector('.member-list'); - if (memberList) { - (memberList as HTMLElement).style.display = (memberList as HTMLElement).style.display === 'none' ? 'block' : 'none'; - return; - } - - memberList = document.createElement('ul'); - memberList.className = 'member-list'; - - role.members.forEach((member) => { - const memberItem = document.createElement('li'); - memberItem.textContent = member.name; - - memberItem.onclick = (event) => { - event.stopPropagation(); - this.loadMemberChat(member.id.toString()); - }; - - memberList.appendChild(memberItem); - }); - - roleElement.appendChild(memberList); - } - - // Toggle the list of Roles - private toggleRoles(group: Group, groupElement: HTMLElement) { - console.log('=== toggleRoles START ==='); - console.log('Group:', group.name); - console.log('Group roles:', group.roles); // Afficher tous les rôles disponibles - - let roleList = groupElement.querySelector('.role-list'); - console.log('Existing roleList:', roleList); - - if (roleList) { - const roleItems = roleList.querySelectorAll('.role-item'); - roleItems.forEach((roleItem) => { - console.log('Processing roleItem:', roleItem.innerHTML); // Voir le contenu HTML complet - - let container = roleItem.querySelector('.role-item-container'); - if (!container) { - container = document.createElement('div'); - container.className = 'role-item-container'; - - // Créer un span pour le nom du rôle - const nameSpan = document.createElement('span'); - nameSpan.className = 'role-name'; - nameSpan.textContent = roleItem.textContent?.trim() || ''; - - container.appendChild(nameSpan); - roleItem.textContent = ''; - roleItem.appendChild(container); - } - - // Récupérer le nom du rôle - const roleName = roleItem.textContent?.trim(); - console.log('Role name from textContent:', roleName); - - // Alternative pour obtenir le nom du rôle - const roleNameAlt = container.querySelector('.role-name')?.textContent; - console.log('Role name from span:', roleNameAlt); - - if (!container.querySelector('.folder-icon')) { - const folderButton = document.createElement('span'); - folderButton.innerHTML = '📁'; - folderButton.className = 'folder-icon'; - - folderButton.addEventListener('click', (event) => { - event.stopPropagation(); - console.log('Clicked role name:', roleName); - console.log( - 'Available roles:', - group.roles.map((r) => r.name), - ); - - const role = group.roles.find((r) => r.name === roleName); - if (role) { - console.log('Found role:', role); - this.showRoleDocuments(role, group); - } else { - console.error('Role not found. Name:', roleName); - console.error('Available roles:', group.roles); - } - }); - - container.appendChild(folderButton); - } - }); - - (roleList as HTMLElement).style.display = (roleList as HTMLElement).style.display === 'none' ? 'block' : 'none'; - } - } - - private loadGroupList(): void { - const groupList = document.getElementById('group-list'); - if (!groupList) return; - - groupsMock.forEach((group) => { - const li = document.createElement('li'); - li.className = 'group-list-item'; - - // Create a flex container for the name and the icon - const container = document.createElement('div'); - container.className = 'group-item-container'; - - // Span for the process name - const nameSpan = document.createElement('span'); - nameSpan.textContent = group.name; - nameSpan.className = 'process-name'; - - // Add click event to show roles - nameSpan.addEventListener('click', (event) => { - event.stopPropagation(); - this.toggleRoles(group, li); - }); - - // Add the ⚙️ icon - const settingsIcon = document.createElement('span'); - settingsIcon.textContent = '⚙️'; - settingsIcon.className = 'settings-icon'; - settingsIcon.id = `settings-${group.id}`; - - settingsIcon.onclick = (event) => { - event.stopPropagation(); - this.showProcessDetails(group, group.id); - }; - - // Assemble the elements - container.appendChild(nameSpan); - container.appendChild(settingsIcon); - li.appendChild(container); - - // Create and append the role list container - const roleList = document.createElement('ul'); - roleList.className = 'role-list'; - roleList.style.display = 'none'; - - // Add roles for this process - group.roles.forEach((role) => { - const roleItem = document.createElement('li'); - roleItem.className = 'role-item'; - roleItem.textContent = role.name; - roleItem.onclick = (event) => { - event.stopPropagation(); - this.toggleMembers(role, roleItem); - }; - roleList.appendChild(roleItem); - }); - - li.appendChild(roleList); - groupList.appendChild(li); - }); - } - - // Function to manage the list of users - private toggleUserList() { - const userList = getCorrectDOM('userList'); - if (!userList) return; - - if (!(userList as HTMLElement).classList.contains('show')) { - (userList as HTMLElement).innerHTML = membersMock - .map( - (member) => ` -
      - ${member.avatar} -
      - ${member.name} - ${member.email} -
      -
      - `, - ) - .join(''); - } - (userList as HTMLElement).classList.toggle('show'); - } - - private switchUser(userId: string | number) { - const user = membersMock.find((member) => member.id === userId); - if (!user) return; - currentUser = user; - this.updateCurrentUserDisplay(); - const userList = getCorrectDOM('userList') as HTMLElement; - userList?.classList.remove('show'); - } - - // Function to update the display of the current user - private updateCurrentUserDisplay() { - const userDisplay = getCorrectDOM('current-user') as HTMLElement; - if (userDisplay) { - userDisplay.innerHTML = ` - - `; - } - } - // Generate an automatic response - private generateAutoReply(senderName: string): Message { - return { - id: Date.now(), - sender: senderName, - text: 'OK...', - time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), - type: 'text' as const, - }; - } - - // Send a file - private sendFile(file: File) { - console.log('SendFile called with file:', file); - const reader = new FileReader(); - reader.onloadend = () => { - const fileData = reader.result; - const fileName = file.name; - console.log('File loaded:', fileName); - - if (this.selectedMemberId) { - messageStore.addMessage(this.selectedMemberId, { - id: Date.now(), - sender: '4NK', - fileName: fileName, - fileData: fileData, - time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), - type: 'file', - }); - console.log('Message added to store'); - - this.messagesMock = messageStore.getMessages(); - this.loadMemberChat(this.selectedMemberId); - } - }; - reader.readAsDataURL(file); - } - - // Managing the sent file - private fileList: HTMLDivElement = document.getElementById('fileList') as HTMLDivElement; - private getFileList() { - const files = Array.from(this.fileList?.querySelectorAll('.file-item') || []).map((fileItem: Element) => { - const fileName = fileItem.querySelector('.file-name')?.textContent || ''; - return { - name: fileName, - url: (fileItem as HTMLElement).dataset.content || '#', - }; - }); - return files; - } - - // New function to display the documents of a role - private showRoleDocuments( - role: { - name: string; - documents?: Array<{ - name: string; - visibility: string; - createdAt: string | null | undefined; - deadline: string | null | undefined; - signatures: DocumentSignature[]; - id: number; - description?: string; - status?: string; - files?: Array<{ name: string; url: string }>; - }>; - id?: number; - }, - group: Group, - ) { - // Load the data from localStorage - const storedGroups = JSON.parse(localStorage.getItem('groups') || '[]'); - const storedGroup = storedGroups.find((g: Group) => g.id === group.id); - const storedRole = storedGroup?.roles.find((r: any) => r.name === role.name); - - // Use the data from localStorage if available, otherwise use the data passed as a parameter - const displayRole = storedRole || role; - - console.log('Showing documents for role:', displayRole.name, 'in group:', group.name); - // Close all existing document views first - const allDetailsAreas = document.querySelectorAll('.process-details'); - allDetailsAreas.forEach((area) => { - area.remove(); - }); - - const container = document.querySelector('.container'); - if (!container) { - console.error('Container not found'); - return; - } - - // Create a new details area - const detailsArea = document.createElement('div'); - detailsArea.id = `role-documents-${displayRole.name}`; - detailsArea.className = 'process-details'; - // Filter the accessible documents - const accessibleDocuments = (displayRole.documents || []).filter((doc: { name: string; visibility: string; createdAt: string | null | undefined; deadline: string | null | undefined; signatures: DocumentSignature[]; id: number; description?: string; status?: string }) => - this.canUserAccessDocument(doc, displayRole.name, currentUser.processRoles?.[0]?.role || ''), - ); - - detailsArea.innerHTML = ` - - `; - - container.appendChild(detailsArea); - } - - // Function to close the documents view of a role - private closeRoleDocuments(roleName: string) { - const detailsArea = document.getElementById(`role-documents-${roleName}`); - if (detailsArea) { - detailsArea.remove(); - } - } - - private handleFiles(files: FileList, fileList: HTMLDivElement) { - Array.from(files).forEach((file) => { - const reader = new FileReader(); - reader.onload = (e) => { - const fileContent = e.target?.result; - const existingFiles = fileList.querySelectorAll('.file-name'); - const isDuplicate = Array.from(existingFiles).some((existingFile) => existingFile.textContent === file.name); - - if (!isDuplicate) { - const fileItem = document.createElement('div'); - fileItem.className = 'file-item'; - fileItem.innerHTML = ` -
      - ${file.name} - (${(file.size / 1024).toFixed(1)} KB) -
      - - `; - fileItem.dataset.content = fileContent as string; - - const removeBtn = fileItem.querySelector('.remove-file'); - if (removeBtn) { - removeBtn.addEventListener('click', () => fileItem.remove()); - } - - fileList.appendChild(fileItem); - } - }; - reader.readAsDataURL(file); - }); - } - - // Function to manage the new request - private newRequest(params: RequestParams) { - // Add parameter validation - if (!params || !params.processId) { - console.error('Paramètres invalides:', params); - showAlert('Invalid parameters for new request'); - return; - } - - const modal = document.createElement('div'); - modal.className = 'modal-overlay'; - - // Retrieve the process with a verification - const process = groupsMock.find((g) => g.id === params.processId); - if (!process) { - console.error('Processus non trouvé:', params.processId); - showAlert('Process not found'); - return; - } - - // Determine the members with an additional verification - let membersToDisplay = []; - try { - if (params.roleName === 'common') { - membersToDisplay = process.roles.reduce((members: any[], role) => { - return members.concat( - role.members.map((member) => ({ - ...member, - roleName: role.name, - })), - ); - }, []); - } else { - const role = process.roles.find((r) => r.name === params.roleName); - if (!role) { - throw new Error(`Role ${params.roleName} not found`); - } - membersToDisplay = role.members.map((member) => ({ - ...member, - roleName: params.roleName, - })); - } - } catch (error) { - console.error('Error retrieving members:', error); - showAlert('Error retrieving members'); - return; - } - - modal.innerHTML = ` - - `; - - document.body.appendChild(modal); - - const dropZone = modal.querySelector('#dropZone') as HTMLDivElement; - const fileInput = modal.querySelector('#fileInput') as HTMLInputElement; - const fileList = modal.querySelector('#fileList') as HTMLDivElement; - - // Make the area clickable - dropZone.addEventListener('click', () => { - fileInput.click(); - }); - - // Manage the file selection - fileInput.addEventListener('change', (e: Event) => { - const target = e.target as HTMLInputElement; - if (target.files && target.files.length > 0) { - this.handleFiles(target.files, fileList); - } - }); - - // Manage the drag & drop - dropZone.addEventListener('dragover', (e: DragEvent) => { - e.preventDefault(); - dropZone.classList.add('dragover'); - }); - - dropZone.addEventListener('dragleave', () => { - dropZone.classList.remove('dragover'); - }); - - dropZone.addEventListener('drop', (e: DragEvent) => { - e.preventDefault(); - dropZone.classList.remove('dragover'); - if (e.dataTransfer?.files) { - this.handleFiles(e.dataTransfer.files, fileList); - } - }); - } - - private closeModal(button: HTMLElement) { - const modalOverlay = button.closest('.modal-overlay'); - if (modalOverlay) { - modalOverlay.remove(); - } - } - - private submitNewDocument(event: Event) { - event.preventDefault(); - - const form = document.getElementById('newDocumentForm') as HTMLFormElement; - if (!form) { - showAlert('Form not found'); - return; - } - - // Retrieve the files - const fileList = document.getElementById('fileList') as HTMLDivElement; - const files = Array.from(fileList?.querySelectorAll('.file-item') || []).map((fileItem) => { - const fileName = fileItem.querySelector('.file-name')?.textContent || ''; - return { - name: fileName, - url: (fileItem as HTMLElement).dataset.content || '#', - }; - }); - - // Retrieve the values from the form - const processId = Number((form.querySelector('#processId') as HTMLInputElement)?.value); - const documentId = Number((form.querySelector('#documentId') as HTMLInputElement)?.value); - const documentName = (form.querySelector('#documentName') as HTMLInputElement)?.value?.trim(); - const description = (form.querySelector('#description') as HTMLTextAreaElement)?.value?.trim(); - const deadline = (form.querySelector('#deadline') as HTMLInputElement)?.value; - const visibility = (form.querySelector('#visibility') as HTMLSelectElement)?.value; - - // Validation - if (!documentName || !description || !deadline) { - showAlert('Please fill in all required fields'); - return; - } - - try { - // Retrieve the current data - const groups = JSON.parse(localStorage.getItem('groups') || JSON.stringify(groupsMock)); - const group = groups.find((g: Group) => g.id === processId); - - if (!group) { - showAlert('Process not found'); - return; - } - - const role = group.roles.find((r: any) => r.documents?.some((d: any) => d.id === documentId)); - - if (!role) { - showAlert('Role not found'); - return; - } - - // Create the new document with the signatures of the role members - const updatedDocument = { - id: documentId, - name: documentName, - description: description, - createdAt: new Date().toISOString(), - deadline: deadline, - visibility: visibility, - status: 'pending', - signatures: role.members.map((member: { id: string | number; name: string }) => ({ - member: member, - signed: false, - signedAt: null, - })), - files: files, // Ajout des fichiers au document - }; - - // Update the document in the role - const documentIndex = role.documents.findIndex((d: any) => d.id === documentId); - if (documentIndex !== -1) { - role.documents[documentIndex] = updatedDocument; - } - - // Save in localStorage - localStorage.setItem('groups', JSON.stringify(groups)); - - // Also update groupsMock for consistency - const mockGroup = groupsMock.find((g) => g.id === processId); - if (mockGroup) { - const mockRole = mockGroup?.roles.find((r) => r.name === role.name); - if (mockRole?.documents) { - const mockDocIndex = mockRole.documents.findIndex((d) => d.id === documentId); - if (mockDocIndex !== -1) { - mockRole.documents[mockDocIndex] = { - ...updatedDocument, - status: undefined, - }; - } - } - } - - // Close the modal - if (event.target instanceof HTMLElement) { - this.closeModal(event.target); - } - - // Reload the documents view with the updated data - this.showRoleDocuments(role, group); - showAlert('Document updated successfully!'); - } catch (error) { - console.error('Error saving:', error); - showAlert('An error occurred while saving'); - } - } - - private submitCommonDocument(event: Event) { - event.preventDefault(); - - const form = document.getElementById('newDocumentForm') as HTMLFormElement; - if (!form) { - showAlert('Form not found'); - return; - } - - const processId = Number((form.querySelector('#processId') as HTMLInputElement)?.value); - const documentId = Number((form.querySelector('#documentId') as HTMLInputElement)?.value); - const documentName = (form.querySelector('#documentName') as HTMLInputElement)?.value?.trim(); - const description = (form.querySelector('#description') as HTMLTextAreaElement)?.value?.trim(); - const deadline = (form.querySelector('#deadline') as HTMLInputElement)?.value; - const visibility = (form.querySelector('#visibility') as HTMLSelectElement)?.value; - - if (!documentName || !description || !deadline) { - showAlert('Please fill in all required fields'); - return; - } - - try { - const groups = JSON.parse(localStorage.getItem('groups') || JSON.stringify(groupsMock)); - const group = groups.find((g: Group) => g.id === processId); - - if (!group) { - showAlert('Process not found'); - return; - } - - // Retrieve all members of all roles in the group - const allMembers = group.roles.reduce((acc: any[], role: any) => { - return acc.concat(role.members); - }, []); - - const fileList = document.getElementById('fileList') as HTMLDivElement; - const files = Array.from(fileList?.querySelectorAll('.file-item') || []).map((fileItem) => { - const fileName = fileItem.querySelector('.file-name')?.textContent || ''; - return { - name: fileName, - url: (fileItem as HTMLElement).dataset.content || '#', - }; - }); - - const updatedDocument = { - id: documentId, - name: documentName, - description: description, - createdAt: new Date().toISOString(), - deadline: deadline, - visibility: visibility, - status: 'pending', - signatures: allMembers.map((member: { id: string | number; name: string }) => ({ - member: member, - signed: false, - signedAt: null, - })), - files: files, - }; - - // Update the common document - const documentIndex = group.commonDocuments.findIndex((d: { id: number }) => d.id === documentId); - if (documentIndex !== -1) { - group.commonDocuments[documentIndex] = updatedDocument; - } - - localStorage.setItem('groups', JSON.stringify(groups)); - - if (event.target instanceof HTMLElement) { - this.closeModal(event.target); - } - - this.showProcessDetails(group, group.id); - showAlert('Document common updated successfully!'); - } catch (error) { - console.error('Error saving:', error); - showAlert('An error occurred while saving'); - } - } - - private submitRequest() { - showAlert('Request submitted!'); - } - - private closeNewRequest() { - const newRequestView = document.getElementById('new-request-view'); - if (newRequestView) { - newRequestView.style.display = 'none'; - newRequestView.remove(); - } - } - - private submitDocumentRequest(documentId: number) { - const createdAt = (document.getElementById('createdAt') as HTMLInputElement)?.value || ''; - const deadline = (document.getElementById('deadline') as HTMLInputElement)?.value || ''; - const visibility = (document.getElementById('visibility') as HTMLSelectElement)?.value || ''; - const description = (document.getElementById('description') as HTMLTextAreaElement)?.value || ''; - - const selectedMembers = Array.from(document.querySelectorAll('input[name="selected-members"]:checked')).map((checkbox) => (checkbox as HTMLInputElement).value); - - if (!createdAt || !deadline || selectedMembers.length === 0) { - showAlert('Please fill in all required fields and select at least one member.'); - return; - } - - console.log('Document submission:', { - documentId, - createdAt, - deadline, - visibility, - description, - selectedMembers, - }); - - showAlert('Document request submitted successfully!'); - this.closeNewRequest(); - } - - // FUNCTIONS FOR SIGNATURE - - // New function to confirm the signature - private confirmSignature(documentId: number, processId: number, isCommonDocument: boolean) { - try { - // Add console.log to see the current user - console.log('Current user:', currentUser); - - const groups = JSON.parse(localStorage.getItem('groups') || JSON.stringify(groupsMock)); - const group = groups.find((g: Group) => g.id === processId); - - if (!group) { - throw new Error('Process not found'); - } - - let targetDoc; - if (isCommonDocument) { - targetDoc = group.commonDocuments.find((d: any) => d.id === documentId); - } else { - for (const role of group.roles) { - if (role.documents) { - targetDoc = role.documents.find((d: any) => d.id === documentId); - if (targetDoc) break; - } - } - } - - if (!targetDoc) { - throw new Error('Document not found'); - } - - const userSignature = targetDoc.signatures.find((sig: DocumentSignature) => sig.member.name === currentUser.name); - - if (!userSignature) { - throw new Error(`The user ${currentUser.name} is not authorized to sign this document. Please log in with an authorized user.`); - } - - userSignature.signed = true; - userSignature.signedAt = new Date().toISOString(); - - localStorage.setItem('groups', JSON.stringify(groups)); - - const closeBtn = document.querySelector('.modal-overlay .close-btn'); - if (closeBtn instanceof HTMLElement) { - this.closeModal(closeBtn); - } - - if (isCommonDocument) { - this.showProcessDetails(group, processId); - } else { - const role = group.roles.find((r: any) => r.documents?.includes(targetDoc)); - if (role) { - this.showRoleDocuments(role, group); - } - } - - showAlert('Document signed successfully!'); - } catch (error) { - console.error('Error signing document:', error); - showAlert(error instanceof Error ? error.message : 'Error signing document'); - } - } - - private initializeEventListeners() { - document.addEventListener('DOMContentLoaded', (): void => { - const newRequestBtn = document.getElementById('newRequestBtn'); - if (newRequestBtn) { - newRequestBtn.addEventListener('click', (): void => { - this.newRequest({ - processId: 0, - processName: '', - roleId: 0, - roleName: '', - documentId: 0, - documentName: '', - }); - }); - } - }); - - // Gestionnaire d'événements pour le chat - const sendBtn = this.shadowRoot?.querySelector('#send-button'); - if (sendBtn) { - sendBtn.addEventListener('click', this.sendMessage.bind(this)); - } - - const messageInput = this.shadowRoot?.querySelector('#message-input'); - if (messageInput) { - messageInput.addEventListener('keypress', (event: Event) => { - if ((event as KeyboardEvent).key === 'Enter') { - event.preventDefault(); - this.sendMessage(); - } - }); - } - - // Gestionnaire pour l'envoi de fichiers - const fileInput = this.shadowRoot?.querySelector('#file-input'); - if (fileInput) { - fileInput.addEventListener('change', (event: Event) => { - const file = (event.target as HTMLInputElement).files?.[0]; - if (file) { - this.sendFile(file); - } - }); - } - } - - connectedCallback() { - if (this.shadowRoot) { - this.shadowRoot.innerHTML = ` -
      -
      -
        -
        -
        - -
        -
        - `; - } - - this.messagesMock = messageStore.getMessages(); - if (this.messagesMock.length === 0) { - messageStore.setMessages(initialMessagesMock); - this.messagesMock = messageStore.getMessages(); - } - this.updateCurrentUserDisplay(); - this.initializeEventListeners(); - this.loadGroupList(); - } -} - -customElements.define('signature-element', SignatureElement); -export { SignatureElement }; +import signatureStyle from '../../../public/style/signature.css?inline'; + +declare global { + interface Window { + toggleUserList: () => void; + switchUser: (userId: string | number) => void; + closeProcessDetails: (groupId: number) => void; + loadMemberChat: (memberId: string | number) => void; + closeRoleDocuments: (roleName: string) => void; + newRequest: (params: RequestParams) => void; + submitRequest: () => void; + closeNewRequest: () => void; + closeModal: (button: HTMLElement) => void; + submitDocumentRequest: (documentId: number) => void; + submitNewDocument: (event: Event) => void; + submitCommonDocument: (event: Event) => void; + signDocument: (documentId: number, processId: number, isCommonDocument: boolean) => void; + confirmSignature: (documentId: number, processId: number, isCommonDocument: boolean) => void; + } +} + +import { groupsMock } from '../../mocks/mock-signature/groupsMock'; +import { messagesMock as initialMessagesMock, messagesMock } from '../../mocks/mock-signature/messagesMock'; +import { membersMock } from '../../mocks/mock-signature/membersMocks'; +import { + Message, + DocumentSignature, + RequestParams} from '../../models/signature.models'; +import { messageStore } from '../../utils/messageMock'; +import { Member } from '../../interface/memberInterface'; +import { Group } from '../../interface/groupInterface'; +import { getCorrectDOM } from '../../utils/document.utils'; + + +let currentUser: Member = membersMock[0]; + +interface LocalNotification { + memberId: string; + text: string; + time: string; +} + +export function showAlert(message: string): void { + // Créer la popup si elle n'existe pas + let alertPopup = document.querySelector('.alert-popup'); + if (!alertPopup) { + alertPopup = document.createElement('div'); + alertPopup.className = 'alert-popup'; + document.body.appendChild(alertPopup); + } + + // Définir le message et afficher la popup + alertPopup.textContent = message; + (alertPopup as HTMLElement).style.display = 'block'; + + // Cacher la popup après 3 secondes + setTimeout(() => { + (alertPopup as HTMLElement).style.display = 'none'; + }, 3000); +} + + +class SignatureElement extends HTMLElement { + private selectedMemberId: string | null = null; + private messagesMock: any[] = []; + private dom: Node; + private notifications: LocalNotification[] = []; + private notificationBadge = document.querySelector('.notification-badge'); + private notificationBoard = document.getElementById('notification-board'); + private notificationBell = document.getElementById('notification-bell'); + private selectedSignatories: DocumentSignature[] = []; + private allMembers = membersMock.map(member => ({ + id: member.id, + name: member.name, + roleName: 'Default Role' + })); + + private signDocument(documentId: number, processId: number, isCommonDocument: boolean = false): void { + try { + if (typeof window === 'undefined' || typeof document === 'undefined') { + console.error('Cette fonction ne peut être exécutée que dans un navigateur'); + return; + } + + const groups = JSON.parse(localStorage.getItem('groups') || JSON.stringify(groupsMock)); + const group = groups.find((g: Group) => g.id === processId); + + if (!group) { + throw new Error('Process not found'); + } + + let targetDoc; + if (isCommonDocument) { + targetDoc = group.commonDocuments.find((d: any) => d.id === documentId); + } else { + for (const role of group.roles) { + if (role.documents) { + targetDoc = role.documents.find((d: any) => d.id === documentId); + if (targetDoc) break; + } + } + } + + if (!targetDoc) { + throw new Error('Document not found'); + } + + const canSign = isCommonDocument ? + targetDoc.signatures?.some((sig: DocumentSignature) => + sig.member?.name === currentUser?.name && !sig.signed + ) : + this.canUserSignDocument(targetDoc, currentUser?.name, currentUser); + + if (!canSign) { + showAlert("You do not have the necessary rights to sign this document."); + return; + } + + // Create and insert the modal directly into the body + const modalHtml = ` + `; + + // Créer la modal dans le shadowDOM au lieu du document principal + const modalElement = document.createElement('div'); + modalElement.className = 'modal-overlay'; + modalElement.innerHTML = modalHtml; + this.shadowRoot?.appendChild(modalElement); + + // Sélectionner les éléments dans le shadowDOM + const slider = modalElement.querySelector('#signatureSlider'); + const sliderTrack = slider?.parentElement; + let isDragging = false; + let startX: number; + let sliderLeft: number; + + if (slider && sliderTrack) { + slider.addEventListener('mousedown', (e: Event) => initDrag(e as MouseEvent)); + slider.addEventListener('touchstart', (e: Event) => initDrag(e as TouchEvent)); + + modalElement.addEventListener('mousemove', (e: Event) => drag.call(this, e as MouseEvent)); + modalElement.addEventListener('touchmove', (e: Event) => drag.call(this, e as TouchEvent)); + modalElement.addEventListener('mouseup', (e: Event) => stopDrag(e as MouseEvent)); + modalElement.addEventListener('touchend', (e: Event) => stopDrag(e as TouchEvent)); + } + + function initDrag(e: MouseEvent | TouchEvent) { + isDragging = true; + startX = 'touches' in e ? e.touches[0].clientX : e.clientX; + sliderLeft = (slider as HTMLElement)?.offsetLeft || 0; + } + + function drag(this: SignatureElement, e: MouseEvent | TouchEvent) { + if (!isDragging || !slider || !sliderTrack) return; + + e.preventDefault(); + const x = 'touches' in e ? e.touches[0].clientX : e.clientX; + const deltaX = x - startX; + + // Calculate the position relative to the track + let newLeft = sliderLeft + deltaX; + + // Limit the movement + const maxLeft = (sliderTrack as HTMLElement).offsetWidth - (slider as HTMLElement).offsetWidth; + newLeft = Math.max(0, Math.min(newLeft, maxLeft)); + + // Update the position + (slider as HTMLElement).style.left = `${newLeft}px`; + + // If the slider reaches 90% of the path, trigger the signature + if (newLeft > maxLeft * 0.9) { + stopDrag(e); + this.confirmSignature(documentId, processId, isCommonDocument); + } + } + + function stopDrag(e: MouseEvent | TouchEvent) { + if (!isDragging || !slider) return; + isDragging = false; + + // Reset the position if not enough dragged + if ((slider as HTMLElement).offsetLeft < ((sliderTrack as HTMLElement)?.offsetWidth || 0) * 0.9) { + (slider as HTMLElement).style.left = '0px'; + } + } + + } catch (error) { + console.error('Error displaying modal:', error); + showAlert(error instanceof Error ? error.message : 'Error displaying modal'); + } + } + + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + this.dom = getCorrectDOM('signature-element'); + + this.shadowRoot!.innerHTML = ` + +
        + +
        +
          +
        +
        + + +
        +
        + +
        +
        + +
        + + +
        + + + + +
        +
        +
        + `; + + window.toggleUserList = this.toggleUserList.bind(this); + window.switchUser = this.switchUser.bind(this); + window.closeProcessDetails = this.closeProcessDetails.bind(this); + window.loadMemberChat = this.loadMemberChat.bind(this); + window.closeRoleDocuments = this.closeRoleDocuments.bind(this); + window.newRequest = this.newRequest.bind(this); + window.submitRequest = this.submitRequest.bind(this); + window.closeNewRequest = this.closeNewRequest.bind(this); + window.closeModal = this.closeModal.bind(this); + window.submitNewDocument = this.submitNewDocument.bind(this); + window.submitCommonDocument = this.submitCommonDocument.bind(this); + window.signDocument = this.signDocument.bind(this); + window.confirmSignature = this.confirmSignature.bind(this); + window.submitDocumentRequest = this.submitDocumentRequest.bind(this); + + // Initialiser les événements de notification + document.addEventListener('click', (event: Event): void => { + if (this.notificationBoard && this.notificationBoard.style.display === 'block' && + !this.notificationBoard.contains(event.target as Node) && + this.notificationBell && !this.notificationBell.contains(event.target as Node)) { + this.notificationBoard.style.display = 'none'; + } + }); + this.initMessageEvents(); + this.initFileUpload(); + } + + private initMessageEvents() { + // Pour le bouton Send + const sendButton = this.shadowRoot?.getElementById('send-button'); + if (sendButton) { + sendButton.addEventListener('click', () => this.sendMessage()); + } + + // Pour la touche Entrée + const messageInput = this.shadowRoot?.getElementById('message-input'); + if (messageInput) { + messageInput.addEventListener('keypress', (event: KeyboardEvent) => { + if (event.key === 'Enter' && !event.shiftKey) { + event.preventDefault(); + this.sendMessage(); + } + }); + } + } + + private initFileUpload() { + const fileInput = this.shadowRoot?.getElementById('file-input') as HTMLInputElement; + if (fileInput) { + fileInput.addEventListener('change', (event: Event) => { + const target = event.target as HTMLInputElement; + if (target.files && target.files.length > 0) { + this.sendFile(target.files[0]); + } + }); + } + } + + + private calculateDuration(startDate: string | null | undefined, endDate: string | null | undefined): number { + const start = new Date(startDate || ''); + const end = new Date(endDate || ''); + const duration = end.getTime() - start.getTime(); + return Math.floor(duration / (1000 * 60 * 60 * 24)); + } + + // Add this helper function + private canUserAccessDocument(document: any, roleId: string, currentUserRole: string): boolean { + // Modify the access logic + if (document.visibility === 'public') { + return true; // Can see but not necessarily sign + } + return roleId === currentUserRole; + } + + private canUserSignDocument(document: any, role: string, user: Member): boolean { + console.log('Checking signing rights for:', { + document, + role, + user, + userRoles: user.processRoles + }); + + // Vérifier si l'utilisateur est dans la liste des signatures + const isSignatory = document.signatures?.some((sig: DocumentSignature) => + sig.member && 'id' in sig.member && sig.member.id === user.id && !sig.signed + ); + + if (!isSignatory) { + console.log('User is not in signatures list or has already signed'); + return false; + } + + // Si l'utilisateur est dans la liste des signatures, il peut signer + return true; + } + + private closeProcessDetails(groupId: number) { + const detailsArea = this.shadowRoot?.getElementById(`process-details-${groupId}`); + const chatArea = this.shadowRoot?.getElementById('chat-area'); + + if (detailsArea) { + detailsArea.style.display = 'none'; + } + + if (chatArea) { + chatArea.style.display = 'block'; + } + } + + ///////////////////// Notification module ///////////////////// + // Delete a notification + private removeNotification(index: number) { + this.notifications?.splice(index, 1); // Ajout de ?. + this.renderNotifications(); + this.updateNotificationBadge(); + } + // Show notifications + private renderNotifications() { + if (!this.notificationBoard) return; + + // Reset the interface + this.notificationBoard.innerHTML = ''; + + // Displays "No notifications available" if there are no notifications + if (this.notifications.length === 0) { + this.notificationBoard.innerHTML = '
        No notifications available
        '; + return; + } + + // Add each notification to the list + this.notifications.forEach((notif, index) => { + const notifElement = document.createElement('div'); + notifElement.className = 'notification-item'; + notifElement.textContent = `${notif.text} at ${notif.time}`; + notifElement.onclick = () => { + this.loadMemberChat(notif.memberId); + this.removeNotification(index); + }; + this.notificationBoard?.appendChild(notifElement); + }); + } + private updateNotificationBadge() { + if (!this.notificationBadge) return; + const count = this.notifications.length; + this.notificationBadge.textContent = count > 99 ? '+99' : count.toString(); + (this.notificationBadge as HTMLElement).style.display = count > 0 ? 'block' : 'none'; + } + + + // Add notification + private addNotification(memberId: string, message: Message) { + // Creating a new notification + const notification = { + memberId, + text: `New message from Member ${memberId}: ${message.text}`, + time: message.time + }; + + // Added notification to list and interface + this.notifications.push(notification); + this.renderNotifications(); + this.updateNotificationBadge(); + } +// Send a messsage + private sendMessage() { + const messageInput = this.shadowRoot?.getElementById('message-input') as HTMLInputElement; + if (!messageInput) return; + const messageText = messageInput.value.trim(); + + if (messageText === '' || this.selectedMemberId === null) { + return; + } + + const newMessage: Message = { + id: Date.now(), + sender: "4NK", + text: messageText, + time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), + type: 'text' as const + }; + // Add and display the message immediately + messageStore.addMessage(this.selectedMemberId, newMessage); + this.messagesMock = messageStore.getMessages(); + this.loadMemberChat(this.selectedMemberId); + + // Reset the input + messageInput.value = ''; + + // Automatic response after 2 seconds + setTimeout(() => { + if (this.selectedMemberId) { + const autoReply = this.generateAutoReply(`Member ${this.selectedMemberId}`); + messageStore.addMessage(this.selectedMemberId, autoReply); + this.messagesMock = messageStore.getMessages(); + this.loadMemberChat(this.selectedMemberId); + this.addNotification(this.selectedMemberId, autoReply); + } + }, 2000); +} + + + private showProcessDetails(group: Group, groupId: number) { + console.log('Showing details for group:', groupId); + + // Close all existing process views + const allDetailsAreas = this.shadowRoot?.querySelectorAll('.process-details'); + if (allDetailsAreas) { + allDetailsAreas.forEach(area => { + (area as HTMLElement).style.display = 'none'; + }); + } + + const container = this.shadowRoot?.querySelector('.container'); + if (!container) { + console.error('Container not found'); + return; + } + + // Load the data from localStorage + const storedGroups = JSON.parse(localStorage.getItem('groups') || '[]'); + const storedGroup = storedGroups.find((g: Group) => g.id === groupId); + + // Use the data from localStorage if available, otherwise use the group passed as a parameter + const displayGroup = storedGroup || group; + + let detailsArea = this.shadowRoot?.getElementById(`process-details-${groupId}`); + if (!detailsArea) { + detailsArea = document.createElement('div'); + detailsArea.id = `process-details-${groupId}`; + detailsArea.className = 'process-details'; + container.appendChild(detailsArea); + } + + if (detailsArea) { + detailsArea.style.display = 'block'; + detailsArea.innerHTML = ` +
        +

        ${displayGroup.name}

        +
        +
        +
        +
        +
        +

        Description

        +

        ${displayGroup.description || 'No description available'}

        +
        +
        +

        Documents Communs

        +
        + ${displayGroup.commonDocuments.map((document: any) => { + const totalSignatures = document.signatures?.length || 0; + const signedCount = document.signatures?.filter((sig: DocumentSignature) => sig.signed).length || 0; + const percentage = totalSignatures > 0 ? (signedCount / totalSignatures) * 100 : 0; + const isVierge = !document.createdAt || !document.deadline || !document.signatures?.length; + const canSign = document.signatures?.some((sig: DocumentSignature) => + sig.member && 'id' in sig.member && sig.member.id === currentUser.id && !sig.signed + ); + + const signButton = !isVierge ? ` + ${totalSignatures > 0 && signedCount < totalSignatures && canSign ? ` + + ` : ''} + ` : ''; + + return ` +
        +
        +

        ${isVierge ? `⚠️ ${document.name}` : document.name}

        + ${document.visibility} +
        +
        + ${!isVierge ? ` +

        Created on: ${document.createdAt ? new Date(document.createdAt).toLocaleDateString() : 'N/A'}

        +

        Deadline: ${document.deadline ? new Date(document.deadline).toLocaleDateString() : 'N/A'}

        +

        Duration: ${this.calculateDuration(document.createdAt || '', document.deadline || '')} days

        +
        +
        Signatures:
        +
        + ${document.signatures?.map((sig: DocumentSignature) => ` +
        + ${sig.member.name} + + ${sig.signed ? + `✓ Signed on ${sig.signedAt ? new Date(sig.signedAt).toLocaleDateString() : 'unknown date'}` : + '⌛ Pending'} + +
        + `).join('')} +
        +
        +
        +
        +

        ${signedCount} out of ${totalSignatures} signed (${percentage.toFixed(0)}%)

        +
        + ` : ` +

        Document vierge - Waiting for creation

        + + `} + ${signButton} +
        +
        + `; + }).join('')} +
        +
        +
        +

        Roles and Documents

        + ${displayGroup.roles.map((role: { name: string; documents?: any[] }) => { + // Filter the documents according to the access rights + const accessibleDocuments = (role.documents || []).filter(doc => + this.canUserAccessDocument(doc, role.name, currentUser.processRoles?.[0]?.role || '') + ); + + return ` +
        +

        ${role.name}

        +
        + ${accessibleDocuments.map(document => { + const isVierge = !document.createdAt || + !document.deadline || + document.signatures.length === 0; + + const canSign = this.canUserSignDocument(document, role.name, currentUser); + + const signButton = !isVierge ? ` + ${document.signatures.length > 0 && + document.signatures.filter((sig: DocumentSignature) => sig.signed).length < document.signatures.length && + canSign ? ` + + ` : ''} + ` : ''; + + return ` +
        +
        +

        ${isVierge ? `⚠️ ${document.name}` : document.name}

        + ${document.visibility} +
        +
        + ${!isVierge ? ` +

        Created on: ${document.createdAt ? new Date(document.createdAt).toLocaleDateString() : 'N/A'}

        +

        Deadline: ${document.deadline ? new Date(document.deadline).toLocaleDateString() : 'N/A'}

        +

        Duration: ${this.calculateDuration(document.createdAt || '', document.deadline || '')} days

        + ` : '

        Document vierge - Waiting for creation

        '} +
        + ${!isVierge ? ` +
        +
        Signatures:
        +
        + ${document.signatures.map((sig: DocumentSignature) => ` +
        + ${sig.member.name} + + ${sig.signed ? + `✓ Signé le ${sig.signedAt ? new Date(sig.signedAt).toLocaleDateString() : 'date inconnue'}` : + '⌛ En attente'} + +
        + `).join('')} +
        +
        +
        +
        +

        ${document.signatures.filter((sig: DocumentSignature) => sig.signed).length} out of ${document.signatures.length} signed (${(document.signatures.filter((sig: DocumentSignature) => sig.signed).length / document.signatures.length * 100).toFixed(0)}%)

        +
        + ` : ''} + ${signButton} +
        + `; + }).join('')} +
        +
        + `; + }).join('')} +
        +
        +

        Members by Role

        +
        + ${displayGroup.roles.map((role: { name: string; members: Array<{ id: string | number; name: string }> }) => ` +
        +

        ${role.name}

        +
          + ${role.members.map(member => ` +
        • ${member.name}
        • + `).join('')} +
        +
        + `).join('')} +
        +
        + `; + + + const newCloseProcessButton = document.createElement('button'); + newCloseProcessButton.className = 'close-btn'; + newCloseProcessButton.textContent = 'x'; + newCloseProcessButton.addEventListener('click', () => this.closeProcessDetails(groupId)); + + const headerButtons = detailsArea.querySelector('.header-buttons'); + if (headerButtons) { + headerButtons.appendChild(newCloseProcessButton); + } + } + } + + // Scroll down the conversation after loading messages + private scrollToBottom(container: HTMLElement) { + container.scrollTop = container.scrollHeight; + } + + + // Load the list of members + private loadMemberChat(memberId: string | number) { + this.selectedMemberId = String(memberId); + const memberMessages = this.messagesMock.find(m => String(m.memberId) === String(memberId)); + + // Find the process and the role of the member + let memberInfo = { processName: '', roleName: '', memberName: '' }; + groupsMock.forEach(process => { + process.roles.forEach(role => { + const member = role.members.find(m => String(m.id) === String(memberId)); + if (member) { + memberInfo = { + processName: process.name, + roleName: role.name, + memberName: member.name + }; + } + }); + }); + + const chatHeader = this.shadowRoot?.getElementById('chat-header'); + const messagesContainer = this.shadowRoot?.getElementById('messages'); + + if (!chatHeader || !messagesContainer) return; + + chatHeader.textContent = `Chat with ${memberInfo.roleName} ${memberInfo.memberName} from ${memberInfo.processName}`; + messagesContainer.innerHTML = ''; + + if (memberMessages) { + memberMessages.messages.forEach((message: Message) => { + const messageElement = document.createElement('div'); + messageElement.className = 'message-container'; + + const messageContent = document.createElement('div'); + messageContent.className = 'message'; + if (message.type === 'file') { + messageContent.innerHTML = `${message.fileName}`; + messageContent.classList.add('user'); + } else { + messageContent.innerHTML = `${message.sender}: ${message.text} ${message.time}`; + if (message.sender === "4NK") { + messageContent.classList.add('user'); + } + } + + messageElement.appendChild(messageContent); + messagesContainer.appendChild(messageElement); + }); + } + + + this.scrollToBottom(messagesContainer); + } + + private toggleMembers(role: { members: { id: string | number; name: string; }[] }, roleElement: HTMLElement) { + let memberList = roleElement.querySelector('.member-list'); + if (memberList) { + (memberList as HTMLElement).style.display = (memberList as HTMLElement).style.display === 'none' ? 'block' : 'none'; + return; + } + + memberList = document.createElement('ul'); + memberList.className = 'member-list'; + + role.members.forEach(member => { + const memberItem = document.createElement('li'); + memberItem.textContent = member.name; + + memberItem.onclick = (event) => { + event.stopPropagation(); + this.loadMemberChat(member.id.toString()); + }; + + memberList.appendChild(memberItem); + }); + + roleElement.appendChild(memberList); + } + + + // Toggle the list of Roles + private toggleRoles(group: Group, groupElement: HTMLElement) { + console.log('=== toggleRoles START ==='); + console.log('Group:', group.name); + console.log('Group roles:', group.roles); // Afficher tous les rôles disponibles + + let roleList = groupElement.querySelector('.role-list'); + console.log('Existing roleList:', roleList); + + if (roleList) { + const roleItems = roleList.querySelectorAll('.role-item'); + roleItems.forEach(roleItem => { + console.log('Processing roleItem:', roleItem.innerHTML); // Voir le contenu HTML complet + + let container = roleItem.querySelector('.role-item-container'); + if (!container) { + container = document.createElement('div'); + container.className = 'role-item-container'; + + // Créer un span pour le nom du rôle + const nameSpan = document.createElement('span'); + nameSpan.className = 'role-name'; + nameSpan.textContent = roleItem.textContent?.trim() || ''; + + container.appendChild(nameSpan); + roleItem.textContent = ''; + roleItem.appendChild(container); + } + + // Récupérer le nom du rôle + const roleName = roleItem.textContent?.trim(); + console.log('Role name from textContent:', roleName); + + // Alternative pour obtenir le nom du rôle + const roleNameAlt = container.querySelector('.role-name')?.textContent; + console.log('Role name from span:', roleNameAlt); + + if (!container.querySelector('.folder-icon')) { + const folderButton = document.createElement('span'); + folderButton.innerHTML = '📁'; + folderButton.className = 'folder-icon'; + + folderButton.addEventListener('click', (event) => { + event.stopPropagation(); + console.log('Clicked role name:', roleName); + console.log('Available roles:', group.roles.map(r => r.name)); + + const role = group.roles.find(r => r.name === roleName); + if (role) { + console.log('Found role:', role); + this.showRoleDocuments(role, group); + } else { + console.error('Role not found. Name:', roleName); + console.error('Available roles:', group.roles); + } + }); + + container.appendChild(folderButton); + } + }); + + (roleList as HTMLElement).style.display = + (roleList as HTMLElement).style.display === 'none' ? 'block' : 'none'; + } + } + + + private loadGroupList(): void { + const groupList = this.shadowRoot?.getElementById('group-list'); + if (!groupList) return; + + groupsMock.forEach(group => { + const li = document.createElement('li'); + li.className = 'group-list-item'; + + // Create a flex container for the name and the icon + const container = document.createElement('div'); + container.className = 'group-item-container'; + + // Span for the process name + const nameSpan = document.createElement('span'); + nameSpan.textContent = group.name; + nameSpan.className = 'process-name'; + + // Add click event to show roles + nameSpan.addEventListener('click', (event) => { + event.stopPropagation(); + this.toggleRoles(group, li); + }); + + // Add the ⚙️ icon + const settingsIcon = document.createElement('span'); + settingsIcon.textContent = '⚙️'; + settingsIcon.className = 'settings-icon'; + settingsIcon.id = `settings-${group.id}`; + + settingsIcon.onclick = (event) => { + event.stopPropagation(); + this.showProcessDetails(group, group.id); + }; + + // Assemble the elements + container.appendChild(nameSpan); + container.appendChild(settingsIcon); + li.appendChild(container); + + // Create and append the role list container + const roleList = document.createElement('ul'); + roleList.className = 'role-list'; + roleList.style.display = 'none'; + + // Add roles for this process + group.roles.forEach(role => { + const roleItem = document.createElement('li'); + roleItem.className = 'role-item'; + roleItem.textContent = role.name; + roleItem.onclick = (event) => { + event.stopPropagation(); + this.toggleMembers(role, roleItem); + }; + roleList.appendChild(roleItem); + }); + + li.appendChild(roleList); + groupList.appendChild(li); + }); + } + + + // Function to manage the list of users + private toggleUserList() { + const userList = getCorrectDOM('userList'); + if (!userList) return; + + if (!(userList as HTMLElement).classList.contains('show')) { + (userList as HTMLElement).innerHTML = membersMock.map(member => ` +
        + ${member.avatar} +
        + ${member.name} + ${member.email} +
        +
        + `).join(''); + } + (userList as HTMLElement).classList.toggle('show'); + } + + private switchUser(userId: string | number) { + const user = membersMock.find(member => member.id === userId); + if (!user) return; + currentUser = user; + this.updateCurrentUserDisplay(); + const userList = getCorrectDOM('userList') as HTMLElement; + userList?.classList.remove('show'); + } + + // Function to update the display of the current user + private updateCurrentUserDisplay() { + const userDisplay = getCorrectDOM('current-user') as HTMLElement; + if (userDisplay) { + userDisplay.innerHTML = ` + + `; + } + } + // Generate an automatic response + private generateAutoReply(senderName: string): Message { + return { + id: Date.now(), + sender: senderName, + text: "OK...", + time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), + type: 'text' as const + }; + } + + // Send a file + private sendFile(file: File) { + console.log('SendFile called with file:', file); + const reader = new FileReader(); + reader.onloadend = () => { + const fileData = reader.result; + const fileName = file.name; + console.log('File loaded:', fileName); + + if (this.selectedMemberId) { + messageStore.addMessage(this.selectedMemberId, { + id: Date.now(), + sender: "4NK", + fileName: fileName, + fileData: fileData, + time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), + type: 'file' + }); + console.log('Message added to store'); + + this.messagesMock = messageStore.getMessages(); + this.loadMemberChat(this.selectedMemberId); + } + }; + reader.readAsDataURL(file); + } + + // Managing the sent file + private fileList: HTMLDivElement = this.shadowRoot?.getElementById('fileList') as HTMLDivElement; + private getFileList() { + const files = Array.from(this.fileList?.querySelectorAll('.file-item') || []).map((fileItem: Element) => { + const fileName = fileItem.querySelector('.file-name')?.textContent || ''; + return { + name: fileName, + url: (fileItem as HTMLElement).dataset.content || '#', + }; + }); + return files; + } + + // New function to display the documents of a role + private showRoleDocuments(role: { + name: string; + documents?: Array<{ + name: string; + visibility: string; + createdAt: string | null | undefined; + deadline: string | null | undefined; + signatures: DocumentSignature[]; + id: number; + description?: string; + status?: string; + files?: Array<{ name: string; url: string }>; + }>; + id?: number; + }, group: Group) { + // Load the data from localStorage + const storedGroups = JSON.parse(localStorage.getItem('groups') || '[]'); + const storedGroup = storedGroups.find((g: Group) => g.id === group.id); + const storedRole = storedGroup?.roles.find((r: any) => r.name === role.name); + + // Use the data from localStorage if available, otherwise use the data passed as a parameter + const displayRole = storedRole || role; + + console.log('Showing documents for role:', displayRole.name, 'in group:', group.name); + // Close all existing document views first + const allDetailsAreas = this.shadowRoot?.querySelectorAll('.process-details'); + allDetailsAreas?.forEach(area => { + area.remove(); + }); + + const container = this.shadowRoot?.querySelector('.container'); + if (!container) { + console.error('Container not found'); + return; + } + + // Create a new details area + const detailsArea = document.createElement('div'); + detailsArea.id = `role-documents-${displayRole.name}`; + detailsArea.className = 'process-details'; + // Filter the accessible documents + const accessibleDocuments = (displayRole.documents || []).filter((doc: { + name: string; + visibility: string; + createdAt: string | null | undefined; + deadline: string | null | undefined; + signatures: DocumentSignature[]; + id: number; + description?: string; + status?: string; + }) => + this.canUserAccessDocument(doc, displayRole.name, currentUser.processRoles?.[0]?.role || '') + ); + + detailsArea.innerHTML = ` + + `; + + container.appendChild(detailsArea); + } + + // Function to close the documents view of a role + private closeRoleDocuments(roleName: string) { + const detailsArea = this.shadowRoot?.getElementById(`role-documents-${roleName}`); + if (detailsArea) { + + detailsArea.remove(); + } + } + + private handleFiles(files: FileList, fileList: HTMLDivElement) { + Array.from(files).forEach(file => { + const reader = new FileReader(); + reader.onload = (e) => { + const fileContent = e.target?.result; + const existingFiles = fileList.querySelectorAll('.file-name'); + const isDuplicate = Array.from(existingFiles).some( + existingFile => existingFile.textContent === file.name + ); + + if (!isDuplicate) { + const fileItem = document.createElement('div'); + fileItem.className = 'file-item'; + fileItem.innerHTML = ` +
        + ${file.name} + (${(file.size / 1024).toFixed(1)} KB) +
        + + `; + fileItem.dataset.content = fileContent as string; + + const removeBtn = fileItem.querySelector('.remove-file'); + if (removeBtn) { + removeBtn.addEventListener('click', () => fileItem.remove()); + } + + fileList.appendChild(fileItem); + } + }; + reader.readAsDataURL(file); + }); + } + + // Function to manage the new request + private newRequest(params: RequestParams) { + // Add parameter validation + if (!params || !params.processId) { + console.error('Paramètres invalides:', params); + showAlert('Invalid parameters for new request'); + return; + } + + const modal = document.createElement('div'); + modal.className = 'modal-overlay'; + + // Retrieve the process with a verification + const process = groupsMock.find(g => g.id === params.processId); + if (!process) { + console.error('Processus non trouvé:', params.processId); + showAlert('Process not found'); + return; + } + + // Determine the members with an additional verification + let membersToDisplay = []; + try { + if (params.roleName === 'common') { + membersToDisplay = process.roles.reduce((members: any[], role) => { + return members.concat(role.members.map(member => ({ + ...member, + roleName: role.name + }))); + }, []); + } else { + const role = process.roles.find(r => r.name === params.roleName); + if (!role) { + throw new Error(`Role ${params.roleName} not found`); + } + membersToDisplay = role.members.map(member => ({ + ...member, + roleName: params.roleName + })); + } + } catch (error) { + console.error('Error retrieving members:', error); + showAlert('Error retrieving members'); + return; + } + + + + modal.innerHTML = ` + + `; + + document.body.appendChild(modal); + + const dropZone = modal.querySelector('#dropZone') as HTMLDivElement; + const fileInput = modal.querySelector('#fileInput') as HTMLInputElement; + const fileList = modal.querySelector('#fileList') as HTMLDivElement; + + // Make the area clickable + dropZone.addEventListener('click', () => { + fileInput.click(); + }); + + // Manage the file selection + fileInput.addEventListener('change', (e: Event) => { + const target = e.target as HTMLInputElement; + if (target.files && target.files.length > 0) { + this.handleFiles(target.files, fileList); + } + }); + + // Manage the drag & drop + dropZone.addEventListener('dragover', (e: DragEvent) => { + e.preventDefault(); + dropZone.classList.add('dragover'); + }); + + dropZone.addEventListener('dragleave', () => { + dropZone.classList.remove('dragover'); + }); + + dropZone.addEventListener('drop', (e: DragEvent) => { + e.preventDefault(); + dropZone.classList.remove('dragover'); + if (e.dataTransfer?.files) { + this.handleFiles(e.dataTransfer.files, fileList); + } + }); + } + + private closeModal(button: HTMLElement) { + const modalOverlay = button.closest('.modal-overlay'); + if (modalOverlay) { + modalOverlay.remove(); + } + } + + private submitNewDocument(event: Event) { + event.preventDefault(); + + const form = document.getElementById('newDocumentForm') as HTMLFormElement; + if (!form) { + showAlert('Form not found'); + return; + } + + // Retrieve the files + const fileList = this.shadowRoot?.getElementById('fileList') as HTMLDivElement; + const files = Array.from(fileList?.querySelectorAll('.file-item') || []).map(fileItem => { + const fileName = fileItem.querySelector('.file-name')?.textContent || ''; + return { + name: fileName, + url: (fileItem as HTMLElement).dataset.content || '#', + }; + }); + + // Retrieve the values from the form + const processId = Number((form.querySelector('#processId') as HTMLInputElement)?.value); + const documentId = Number((form.querySelector('#documentId') as HTMLInputElement)?.value); + const documentName = (form.querySelector('#documentName') as HTMLInputElement)?.value?.trim(); + const description = (form.querySelector('#description') as HTMLTextAreaElement)?.value?.trim(); + const deadline = (form.querySelector('#deadline') as HTMLInputElement)?.value; + const visibility = (form.querySelector('#visibility') as HTMLSelectElement)?.value; + + // Validation + if (!documentName || !description || !deadline) { + showAlert('Please fill in all required fields'); + return; + } + + try { + // Retrieve the current data + const groups = JSON.parse(localStorage.getItem('groups') || JSON.stringify(groupsMock)); + const group = groups.find((g: Group) => g.id === processId); + + if (!group) { + showAlert('Process not found'); + return; + } + + const role = group.roles.find((r: any) => + r.documents?.some((d: any) => d.id === documentId) + ); + + if (!role) { + showAlert('Role not found'); + return; + } + + // Create the new document with the signatures of the role members + const updatedDocument = { + id: documentId, + name: documentName, + description: description, + createdAt: new Date().toISOString(), + deadline: deadline, + visibility: visibility, + status: "pending", + signatures: role.members.map((member: { id: string | number; name: string }) => ({ + member: member, + signed: false, + signedAt: null + })), + files: files // Ajout des fichiers au document + }; + + // Update the document in the role + const documentIndex = role.documents.findIndex((d: any) => d.id === documentId); + if (documentIndex !== -1) { + role.documents[documentIndex] = updatedDocument; + } + + // Save in localStorage + localStorage.setItem('groups', JSON.stringify(groups)); + + // Also update groupsMock for consistency + const mockGroup = groupsMock.find(g => g.id === processId); + if (mockGroup) { + const mockRole = mockGroup?.roles.find(r => r.name === role.name); + if (mockRole?.documents) { + const mockDocIndex = mockRole.documents.findIndex(d => d.id === documentId); + if (mockDocIndex !== -1) { + mockRole.documents[mockDocIndex] = { + ...updatedDocument, + status: undefined + }; + } + } + } + + // Close the modal + if (event.target instanceof HTMLElement) { + this.closeModal(event.target); + } + + // Reload the documents view with the updated data + this.showRoleDocuments(role, group); + showAlert('Document updated successfully!'); + + } catch (error) { + console.error('Error saving:', error); + showAlert('An error occurred while saving'); + } + } + + private submitCommonDocument(event: Event) { + event.preventDefault(); + + const form = document.getElementById('newDocumentForm') as HTMLFormElement; + if (!form) { + showAlert('Form not found'); + return; + } + + const processId = Number((form.querySelector('#processId') as HTMLInputElement)?.value); + const documentId = Number((form.querySelector('#documentId') as HTMLInputElement)?.value); + const documentName = (form.querySelector('#documentName') as HTMLInputElement)?.value?.trim(); + const description = (form.querySelector('#description') as HTMLTextAreaElement)?.value?.trim(); + const deadline = (form.querySelector('#deadline') as HTMLInputElement)?.value; + const visibility = (form.querySelector('#visibility') as HTMLSelectElement)?.value; + + if (!documentName || !description || !deadline) { + showAlert('Please fill in all required fields'); + return; + } + + try { + const groups = JSON.parse(localStorage.getItem('groups') || JSON.stringify(groupsMock)); + const group = groups.find((g: Group) => g.id === processId); + + if (!group) { + showAlert('Process not found'); + return; + } + + // Retrieve all members of all roles in the group + const allMembers = group.roles.reduce((acc: any[], role: any) => { + return acc.concat(role.members); + }, []); + + const fileList = this.shadowRoot?.getElementById('fileList') as HTMLDivElement; + const files = Array.from(fileList?.querySelectorAll('.file-item') || []).map(fileItem => { + const fileName = fileItem.querySelector('.file-name')?.textContent || ''; + return { + name: fileName, + url: (fileItem as HTMLElement).dataset.content || '#', + }; + }); + + const updatedDocument = { + id: documentId, + name: documentName, + description: description, + createdAt: new Date().toISOString(), + deadline: deadline, + visibility: visibility, + status: "pending", + signatures: allMembers.map((member: { id: string | number; name: string }) => ({ + member: member, + signed: false, + signedAt: null + })), + files: files + }; + + // Update the common document + const documentIndex = group.commonDocuments.findIndex((d: { id: number }) => d.id === documentId); + if (documentIndex !== -1) { + group.commonDocuments[documentIndex] = updatedDocument; + } + + localStorage.setItem('groups', JSON.stringify(groups)); + + if (event.target instanceof HTMLElement) { + this.closeModal(event.target); + } + + this.showProcessDetails(group, group.id); + showAlert('Document common updated successfully!'); + + } catch (error) { + console.error('Error saving:', error); + showAlert('An error occurred while saving'); + } + } + + + private submitRequest() { + + showAlert("Request submitted!"); + } + + private closeNewRequest() { + const newRequestView = document.getElementById('new-request-view'); + if (newRequestView) { + newRequestView.style.display = 'none'; + newRequestView.remove(); + } + } + + private submitDocumentRequest(documentId: number) { + const createdAt = (this.shadowRoot?.getElementById('createdAt') as HTMLInputElement)?.value || ''; + const deadline = (this.shadowRoot?.getElementById('deadline') as HTMLInputElement)?.value || ''; + const visibility = (this.shadowRoot?.getElementById('visibility') as HTMLSelectElement)?.value || ''; + const description = (this.shadowRoot?.getElementById('description') as HTMLTextAreaElement)?.value || ''; + + const selectedMembers = Array.from( + this.shadowRoot?.querySelectorAll('input[name="selected-members"]:checked') || [] + ).map(checkbox => (checkbox as HTMLInputElement).value); + + if (!createdAt || !deadline || selectedMembers.length === 0) { + showAlert('Please fill in all required fields and select at least one member.'); + return; + } + + console.log('Document submission:', { + documentId, + createdAt, + deadline, + visibility, + description, + selectedMembers + }); + + showAlert('Document request submitted successfully!'); + this.closeNewRequest(); + } + + // FUNCTIONS FOR SIGNATURE + + // New function to confirm the signature + private confirmSignature(documentId: number, processId: number, isCommonDocument: boolean) { + try { + // Add console.log to see the current user + console.log('Current user:', currentUser); + + const groups = JSON.parse(localStorage.getItem('groups') || JSON.stringify(groupsMock)); + const group = groups.find((g: Group) => g.id === processId); + + if (!group) { + throw new Error('Process not found'); + } + + let targetDoc; + if (isCommonDocument) { + targetDoc = group.commonDocuments.find((d: any) => d.id === documentId); + } else { + for (const role of group.roles) { + if (role.documents) { + targetDoc = role.documents.find((d: any) => d.id === documentId); + if (targetDoc) break; + } + } + } + + if (!targetDoc) { + throw new Error('Document not found'); + } + + const userSignature = targetDoc.signatures.find((sig: DocumentSignature) => + sig.member.name === currentUser.name + ); + + if (!userSignature) { + throw new Error(`The user ${currentUser.name} is not authorized to sign this document. Please log in with an authorized user.`); + } + + // Mettre à jour la signature + userSignature.signed = true; + userSignature.signedAt = new Date().toISOString(); + localStorage.setItem('groups', JSON.stringify(groups)); + + // Supprimer la modal de signature + const modalOverlay = this.shadowRoot?.querySelector('.modal-overlay'); + if (modalOverlay) { + modalOverlay.remove(); + } + + // Rafraîchir l'affichage + if (isCommonDocument) { + this.showProcessDetails(group, processId); + } else { + const role = group.roles.find((r: any) => r.documents?.includes(targetDoc)); + if (role) { + this.showRoleDocuments(role, group); + } + } + + showAlert('Document signed successfully!'); + + } catch (error) { + console.error('Error signing document:', error); + showAlert(error instanceof Error ? error.message : 'Error signing document'); + } + } + + + private initializeEventListeners() { + document.addEventListener('DOMContentLoaded', (): void => { + const newRequestBtn = this.shadowRoot?.getElementById('newRequestBtn'); + if (newRequestBtn) { + newRequestBtn.addEventListener('click', (): void => { + this.newRequest({ + processId: 0, + processName: '', + roleId: 0, + roleName: '', + documentId: 0, + documentName: '' + }); + }); + } + }); + + // Gestionnaire d'événements pour le chat + const sendBtn = this.shadowRoot?.querySelector('#send-button'); + if (sendBtn) { + sendBtn.addEventListener('click', this.sendMessage.bind(this)); + } + + const messageInput = this.shadowRoot?.querySelector('#message-input'); + if (messageInput) { + messageInput.addEventListener('keypress', (event: Event) => { + if ((event as KeyboardEvent).key === 'Enter') { + event.preventDefault(); + this.sendMessage(); + } + }); + } + + // Gestionnaire pour l'envoi de fichiers + const fileInput = this.shadowRoot?.querySelector('#file-input'); + if (fileInput) { + fileInput.addEventListener('change', (event: Event) => { + const file = (event.target as HTMLInputElement).files?.[0]; + if (file) { + this.sendFile(file); + } + }); + } + } + + connectedCallback() { + this.messagesMock = messageStore.getMessages(); + if (this.messagesMock.length === 0) { + messageStore.setMessages(initialMessagesMock); + this.messagesMock = messageStore.getMessages(); + } + this.updateCurrentUserDisplay(); + this.initializeEventListeners(); + this.loadGroupList(); + } +} + +customElements.define('signature-element', SignatureElement); +export { SignatureElement }; +