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; } 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 showAlert(message: string): void { // Créer la popup si elle n'existe pas let alertPopup = this.shadowRoot?.querySelector('.alert-popup'); if (!alertPopup) { alertPopup = document.createElement('div'); alertPopup.className = 'alert-popup'; this.shadowRoot?.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); } 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) { this.showAlert("You do not have the necessary rights to sign this document."); return; } // Create and insert the modal directly into the body const modalHtml = ` `; const modalElement = document.createElement('div'); modalElement.className = 'modal-overlay'; modalElement.innerHTML = modalHtml; this.shadowRoot?.appendChild(modalElement); 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); this.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 = `
${currentUser.avatar} ${currentUser.name}
`; } } // 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); this.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); this.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); this.showAlert('Error retrieving members'); return; } modal.innerHTML = ` `; this.shadowRoot?.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 = this.shadowRoot?.getElementById('newDocumentForm') as HTMLFormElement; if (!form) { this.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) { this.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) { this.showAlert('Process not found'); return; } const role = group.roles.find((r: any) => r.documents?.some((d: any) => d.id === documentId) ); if (!role) { this.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); this.showAlert('Document updated successfully!'); } catch (error) { console.error('Error saving:', error); this.showAlert('An error occurred while saving'); } } private submitCommonDocument(event: Event) { event.preventDefault(); const form = this.shadowRoot?.getElementById('newDocumentForm') as HTMLFormElement; if (!form) { this.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) { this.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) { this.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); this.showAlert('Document common updated successfully!'); } catch (error) { console.error('Error saving:', error); this.showAlert('An error occurred while saving'); } } private submitRequest() { this.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) { this.showAlert('Please fill in all required fields and select at least one member.'); return; } console.log('Document submission:', { documentId, createdAt, deadline, visibility, description, selectedMembers }); this.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); } } this.showAlert('Document signed successfully!'); } catch (error) { console.error('Error signing document:', error); this.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 };