From 17397b7fa25eee77cc1abeb7116c92e6c0bae53d Mon Sep 17 00:00:00 2001 From: Pascal Date: Fri, 29 Nov 2024 14:07:41 +0100 Subject: [PATCH] signature_connection_not_ok --- package-lock.json | 47 + package.json | 1 + src/interface/groupInterface.ts | 22 + src/main.ts | 23 + src/models/signature.models.ts | 1 + src/pages/signature/signature-component.ts | 56 + src/pages/signature/signature.ts | 3212 ++++++++++---------- src/router.ts | 11 +- src/utils/document.utils.ts | 4 + tsconfig.json | 46 +- vite.config.ts | 20 +- 11 files changed, 1809 insertions(+), 1634 deletions(-) create mode 100644 src/main.ts create mode 100644 src/pages/signature/signature-component.ts create mode 100644 src/utils/document.utils.ts diff --git a/package-lock.json b/package-lock.json index d0b085d..95aa0fc 100755 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "@angular/elements": "^19.0.1", "@types/qrcode": "^1.5.5", "@vitejs/plugin-react": "^4.3.1", "@vitejs/plugin-vue": "^5.0.5", @@ -45,6 +46,37 @@ "node": ">=6.0.0" } }, + "node_modules/@angular/core": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-19.0.1.tgz", + "integrity": "sha512-+VpWcg2aC/dY9TM6fsj00enZ6RP5wpRqk/SeRe3UP3Je/n+vWIgHJTb1ZLNeOIvDaE86BhKPMwFS0QVjoEGQFA==", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.15.0" + } + }, + "node_modules/@angular/elements": { + "version": "19.0.1", + "resolved": "https://registry.npmjs.org/@angular/elements/-/elements-19.0.1.tgz", + "integrity": "sha512-HqNZ1DcsT+SVVXrqZIxveZEZiA+1ZeYggNWmh2Z19APyWTMXylkL7Tm4AFdbQItUXZDOVzbjycq8EQ/5Fdvfng==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/core": "19.0.1", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", @@ -5151,6 +5183,15 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -6941,6 +6982,12 @@ "engines": { "node": ">=8" } + }, + "node_modules/zone.js": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.0.tgz", + "integrity": "sha512-9oxn0IIjbCZkJ67L+LkhYWRyAy7axphb3VgE2MBDlOqnmHMPWGYMxJxBYFueFq/JGY2GMwS0rU+UCLunEmy5UA==", + "peer": true } } } diff --git a/package.json b/package.json index 898b808..a36f664 100755 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "webpack-dev-server": "^5.0.2" }, "dependencies": { + "@angular/elements": "^19.0.1", "@types/qrcode": "^1.5.5", "@vitejs/plugin-react": "^4.3.1", "@vitejs/plugin-vue": "^5.0.5", diff --git a/src/interface/groupInterface.ts b/src/interface/groupInterface.ts index e69de29..13bba03 100644 --- a/src/interface/groupInterface.ts +++ b/src/interface/groupInterface.ts @@ -0,0 +1,22 @@ +import { DocumentSignature } from "~/models/signature.models"; + +export interface Group { + id: number; + name: string; + description: string; + roles: Array<{ + name: string; + members: Array<{ id: string | number; name: string }>; + documents?: Array; + }>; + commonDocuments: Array<{ + id: number; + name: string; + visibility: string; + description: string; + createdAt?: string | null; + deadline?: string | null; + signatures?: DocumentSignature[]; + status?: string; + }>; +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..ec7d339 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,23 @@ +import { SignatureComponent } from './pages/signature/signature-component'; +import { SignatureElement } from './pages/signature/signature'; + +// Exporter les composants pour une utilisation externe +export { + SignatureComponent, + SignatureElement +}; + +// Déclarer les types pour TypeScript +declare global { + interface HTMLElementTagNameMap { + 'signature-component': SignatureComponent; + 'signature-element': SignatureElement; + } +} + +// Configuration pour le mode indépendant +if ((import.meta as any).env.VITE_IS_INDEPENDANT_LIB) { + // Initialiser les composants si nécessaire + customElements.define('signature-component', SignatureComponent); + customElements.define('signature-element', SignatureElement); +} \ No newline at end of file diff --git a/src/models/signature.models.ts b/src/models/signature.models.ts index 60c4062..d5b7888 100755 --- a/src/models/signature.models.ts +++ b/src/models/signature.models.ts @@ -15,6 +15,7 @@ export interface Group { deadline: string | null; signatures: DocumentSignature[]; status?: string; + files?: Array<{ name: string; url: string }>; }[]; }[]; } diff --git a/src/pages/signature/signature-component.ts b/src/pages/signature/signature-component.ts new file mode 100644 index 0000000..8bed95d --- /dev/null +++ b/src/pages/signature/signature-component.ts @@ -0,0 +1,56 @@ +import { SignatureElement } from './signature'; +import signatureHtml from './signature.html?raw' +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(); + } + + 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); diff --git a/src/pages/signature/signature.ts b/src/pages/signature/signature.ts index 4a0a1f7..cebe894 100755 --- a/src/pages/signature/signature.ts +++ b/src/pages/signature/signature.ts @@ -1,1063 +1,1069 @@ declare global { interface Window { - toggleUserList: typeof toggleUserList; - switchUser: typeof switchUser; - closeProcessDetails: typeof closeProcessDetails; - loadMemberChat: typeof loadMemberChat; - closeRoleDocuments: typeof closeRoleDocuments; - newRequest: typeof newRequest; - submitRequest: typeof submitRequest; - closeNewRequest: typeof closeNewRequest; - closeModal: typeof closeModal; - submitDocumentRequest: typeof submitDocumentRequest; - submitNewDocument: typeof submitNewDocument; - submitCommonDocument: typeof submitCommonDocument; - signDocument: typeof signDocument; - confirmSignature: typeof confirmSignature; + 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 } from '../../mocks/mock-signature/messagesMock'; +import { messagesMock as initialMessagesMock, messagesMock } from '../../mocks/mock-signature/messagesMock'; import { membersMock } from '../../mocks/mock-signature/membersMocks'; import { Message, - MemberMessages, DocumentSignature, - RequestParams, - Notification as ImportedNotification -} from '../../models/signature.models'; + 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'; +import signatureHtml from './signature.html?raw' +import signatureCss from '../../../public/style/signature.css?raw' let currentUser: Member = membersMock[0]; - -// Function to manage the list of users -function toggleUserList() { - const userList = document.getElementById('userList'); - if (!userList) return; - - if (!userList.classList.contains('show')) { - userList.innerHTML = membersMock.map(member => ` -
- ${member.avatar} -
- ${member.name} - ${member.email} -
-
- `).join(''); - } - userList?.classList.toggle('show'); -} - -// Function to switch user -function switchUser(userId: string | number) { - const user = membersMock.find(member => member.id === userId); - if (!user) return; - currentUser = user; - updateCurrentUserDisplay(); - const userList = document.getElementById('userList'); - userList?.classList.remove('show'); -} - -// Function to update the display of the current user -function updateCurrentUserDisplay() { - const userDisplay = document.getElementById('current-user'); - if (userDisplay) { - userDisplay.innerHTML = ` - - `; - } -} - -// Add the functions to the global scope -window.toggleUserList = toggleUserList; -window.switchUser = switchUser; - -// Initialize the display of the current user when the page loads -document.addEventListener('DOMContentLoaded', () => { - updateCurrentUserDisplay(); - - // Close the list if clicked elsewhere - document.addEventListener('click', (event) => { - const userList = document.getElementById('userList'); - const userSwitchBtn = document.getElementById('userSwitchBtn'); - if (userSwitchBtn && userList && !userSwitchBtn.contains(event.target as Node) && !userList.contains(event.target as Node)) { - userList.classList.remove('show'); - } - }); - - // Initialize the groups in localStorage if they don't exist - if (!localStorage.getItem('groups')) { - localStorage.setItem('groups', JSON.stringify(groupsMock)); - } -}); - -let messagesMock = messageStore.getMessages(); -if (messagesMock.length === 0) { - messageStore.setMessages(initialMessagesMock); - messagesMock = messageStore.getMessages(); -} - - -let selectedMemberId: string | null = null; - -// Load the list of groups -function loadGroupList() { - 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'; - nameSpan.onclick = (event) => { - event.stopPropagation(); - toggleRoles(group, li); - }; - - // Add the ⚙️ icon with a unique ID - const settingsIcon = document.createElement('span'); - settingsIcon.textContent = '⚙️'; - settingsIcon.className = 'settings-icon'; - settingsIcon.id = `settings-${group.id}`; // Unique ID based on the group ID - - const detailsArea = document.createElement('div'); - detailsArea.id = `process-details-${group.id}`; - detailsArea.className = 'process-details'; - detailsArea.style.display = 'none'; - - settingsIcon.onclick = (event) => { - event.stopPropagation(); - showProcessDetails(group, group.id); - }; - - // Assemble the elements - container.appendChild(nameSpan); - container.appendChild(settingsIcon); - li.appendChild(container); - groupList.appendChild(li); - }); -} - -// Toggle the list of Roles -function toggleRoles(group: Group, groupElement: HTMLElement) { - let roleList = groupElement.querySelector('.role-list'); - if (roleList) { - (roleList as HTMLElement).style.display = (roleList as HTMLElement).style.display === 'none' ? 'block' : 'none'; - return; - } - - roleList = document.createElement('ul'); - roleList.className = 'role-list'; - - group.roles.forEach(role => { - const roleItem = document.createElement('li'); - - // Create a flex container for the name and the icon - const container = document.createElement('div'); - container.className = 'role-item-container'; - container.style.display = 'flex'; - container.style.justifyContent = 'space-between'; - container.style.alignItems = 'center'; - - // Span for the role name - const nameSpan = document.createElement('span'); - nameSpan.textContent = role.name; - - // Folder button - const folderButton = document.createElement('span'); - folderButton.textContent = '📁'; - folderButton.className = 'folder-icon'; - folderButton.onclick = (event) => { - event.stopPropagation(); - showRoleDocuments(role, group); - }; - - // Assemble the elements - container.appendChild(nameSpan); - container.appendChild(folderButton); - roleItem.appendChild(container); - - roleItem.onclick = (event) => { - event.stopPropagation(); - toggleMembers(role, roleItem); - }; - - roleList.appendChild(roleItem); - }); - - groupElement.appendChild(roleList); -} - -function 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(); - loadMemberChat(member.id.toString()); - }; - - memberList.appendChild(memberItem); - }); - - roleElement.appendChild(memberList); -} - - - // Load the list of members -function loadMemberChat(memberId: string | number) { - selectedMemberId = String(memberId); - const memberMessages = 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); - }); - } - - - scrollToBottom(messagesContainer); -} - -// Scroll down the conversation after loading messages -function scrollToBottom(container: HTMLElement) { - container.scrollTop = container.scrollHeight; -} - -// Generate an automatic response -function 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 messsage -function sendMessage() { - const messageInput = document.getElementById('message-input') as HTMLInputElement; - if (!messageInput) return; - const messageText = messageInput.value.trim(); - - if (messageText === '' || 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(selectedMemberId, newMessage); - messagesMock = messageStore.getMessages(); - loadMemberChat(selectedMemberId); - - // Reset the input - messageInput.value = ''; - - // Automatic response after 2 seconds - setTimeout(() => { - if (selectedMemberId) { - const autoReply = generateAutoReply(`Member ${selectedMemberId}`); - messageStore.addMessage(selectedMemberId, autoReply); - messagesMock = messageStore.getMessages(); - loadMemberChat(selectedMemberId); - addNotification(selectedMemberId, autoReply); - } - }, 2000); -} - -// Add an event for the submit button -const sendBtn = document.getElementById('send-button'); -if (sendBtn) sendBtn.onclick = sendMessage; - -const messageInput = document.getElementById('message-input'); -if (messageInput) { - messageInput.addEventListener('keydown', function (event) { - if (event.key === 'Enter') { - event.preventDefault(); - sendMessage(); - } - }); -} - -// Send a file -function sendFile(file: File) { - const reader = new FileReader(); - reader.onloadend = function () { - const fileData = reader.result; - const fileName = file.name; - - const newFileMessage = { - id: Date.now(), - sender: "4NK", - fileName: fileName, - fileData: fileData, - time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), - type: 'file' - }; - - if (selectedMemberId) { - messageStore.addMessage(selectedMemberId, newFileMessage); - } - - messagesMock = messageStore.getMessages(); - - if (selectedMemberId) { - loadMemberChat(selectedMemberId); - } - }; - - reader.readAsDataURL(file); -} - -// Managing the sent file -document.getElementById('file-input')?.addEventListener('change', function (event) { - const file = (event.target as HTMLInputElement).files?.[0]; - if (file) { - sendFile(file); - } -}); - - -///////////////////// Notification module ///////////////////// -const notificationBadge = document.querySelector('.notification-badge'); -const notificationBoard = document.getElementById('notification-board'); -const notificationBell = document.getElementById('notification-bell'); - - interface LocalNotification { memberId: string; text: string; time: string; } -let notifications: LocalNotification[] = []; -let unreadCount = 0; -// Update notification badge -function updateNotificationBadge() { - if (!notificationBadge) return; - const count = notifications.length; - notificationBadge.textContent = count > 99 ? '+99' : count.toString(); - (notificationBadge as HTMLElement).style.display = count > 0 ? 'block' : 'none'; -} - -// Add notification -function 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 - notifications.push(notification); - renderNotifications(); - updateNotificationBadge(); -} - -// Show notifications -function renderNotifications() { - if (!notificationBoard) return; - - // Reset the interface - notificationBoard.innerHTML = ''; - - // Displays "No notifications available" if there are no notifications - if (notifications.length === 0) { - notificationBoard.innerHTML = '
No notifications available
'; - return; - } - - // Add each notification to the list - notifications.forEach((notif, index) => { - const notifElement = document.createElement('div'); - notifElement.className = 'notification-item'; - notifElement.textContent = `${notif.text} at ${notif.time}`; - notifElement.onclick = () => { - loadMemberChat(notif.memberId); - removeNotification(index); - }; - notificationBoard.appendChild(notifElement); - }); -} - -// Delete a notification -function removeNotification(index: number) { - notifications.splice(index, 1); - renderNotifications(); - updateNotificationBadge(); -} - -// Adds an event for deploying the notification list -if (notificationBell && notificationBoard) { - notificationBell.onclick = () => { - notificationBoard.style.display = notificationBoard.style.display === 'block' ? 'none' : 'block'; - }; -} - -// Close the notification board when clicking outside of it -document.addEventListener('click', (event) => { - if (notificationBoard && notificationBoard.style.display === 'block' && - !notificationBoard.contains(event.target as Node) && - notificationBell && !notificationBell.contains(event.target as Node)) { - notificationBoard.style.display = 'none'; - } -}); - -// ------------------ PROCESS DETAILS ------------------ -// Function to display the process details -function 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'; - }); - +export function initSignature() { + const signatureElement = document.createElement('signature-element'); const container = document.querySelector('.container'); - if (!container) { - console.error('Container not found'); - return; + if (container) { + container.appendChild(signatureElement); } +} - // 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; +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' + })); - 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); - } + 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; + } - 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 - ); + 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'); + } - const signButton = !isVierge ? ` - ${totalSignatures > 0 && signedCount < totalSignatures && canSign ? ` - + 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 = ` +