ihm_client/src/pages/signature/signature.ts
2024-11-26 18:02:31 +01:00

1765 lines
72 KiB
TypeScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
removeMember: typeof removeMember;
closeModal: typeof closeModal;
submitDocumentRequest: typeof submitDocumentRequest;
submitNewDocument: typeof submitNewDocument;
submitCommonDocument: typeof submitCommonDocument;
signDocument: typeof signDocument;
confirmSignature: typeof confirmSignature;
}
}
import { groupsMock } from '../../mocks/mock-signature/groupsMock';
import { messagesMock as initialMessagesMock } from '../../mocks/mock-signature/messagesMock';
import { membersMock } from '../../mocks/mock-signature/membersMocks';
import {
Message,
MemberMessages,
DocumentSignature,
RequestParams,
Notification as ImportedNotification
} from '../../models/signature.models';
import { messageStore } from '../../utils/messageMock';
import { showAlert } from '../account/account';
interface Member {
id: string | number;
name: string;
email?: string;
avatar?: string;
processRoles?: Array<{ processId: number | string; role: string }>;
}
let currentUser: Member = membersMock[0];
interface Group {
id: number;
name: string;
description: string;
roles: Array<{
name: string;
members: Array<{ id: string | number; name: string }>;
documents?: Array<any>;
}>;
commonDocuments: Array<{
id: number;
name: string;
visibility: string;
description: string;
createdAt?: string | null;
deadline?: string | null;
signatures?: DocumentSignature[];
status?: string;
}>;
}
// Fonction pour gérer la liste des utilisateurs
function toggleUserList() {
const userList = document.getElementById('userList');
if (!userList) return;
if (!userList.classList.contains('show')) {
// Remplir la liste des utilisateurs
userList.innerHTML = membersMock.map(member => `
<div class="user-list-item" onclick="switchUser('${member.id}')">
<span class="user-avatar">${member.avatar}</span>
<div>
<span class="user-name">${member.name}</span>
<span class="user-email">${member.email}</span>
</div>
</div>
`).join('');
}
userList?.classList.toggle('show');
}
// Fonction pour changer d'utilisateur
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');
}
// Fonction pour mettre à jour l'affichage de l'utilisateur
function updateCurrentUserDisplay() {
const userDisplay = document.getElementById('current-user');
if (userDisplay) {
userDisplay.innerHTML = `
<div class="current-user-info">
<span class="user-avatar">${currentUser.avatar}</span>
<span class="user-name">${currentUser.name}</span>
</div>
`;
}
}
// Ajouter les fonctions au scope global
window.toggleUserList = toggleUserList;
window.switchUser = switchUser;
// Initialiser l'affichage de l'utilisateur courant au chargement
document.addEventListener('DOMContentLoaded', () => {
updateCurrentUserDisplay();
// Fermer la liste si on clique ailleurs
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');
}
});
// Initialiser les groupes en localStorage s'ils n'existent pas
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';
// Créer un conteneur flex pour le nom et l'icône
const container = document.createElement('div');
container.className = 'group-item-container';
// Span pour le nom du processus
const nameSpan = document.createElement('span');
nameSpan.textContent = group.name;
nameSpan.className = 'process-name';
nameSpan.onclick = (event) => {
event.stopPropagation();
toggleRoles(group, li);
};
// Ajouter l'icône ⚙️ avec un ID unique
const settingsIcon = document.createElement('span');
settingsIcon.textContent = '⚙️';
settingsIcon.className = 'settings-icon';
settingsIcon.id = `settings-${group.id}`; // ID unique basé sur l'ID du groupe
// Créer une div pour la vue détaillée avec un ID unique correspondant
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);
};
// Assembler les éléments
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');
// Créer un conteneur flex pour le nom et l'icône
const container = document.createElement('div');
container.className = 'role-item-container';
container.style.display = 'flex';
container.style.justifyContent = 'space-between';
container.style.alignItems = 'center';
// Span pour le nom du rôle
const nameSpan = document.createElement('span');
nameSpan.textContent = role.name;
// Bouton dossier
const folderButton = document.createElement('span');
folderButton.textContent = '📁';
folderButton.className = 'folder-icon';
folderButton.onclick = (event) => {
event.stopPropagation();
showRoleDocuments(role, group);
};
// Assembler les éléments
container.appendChild(nameSpan);
container.appendChild(folderButton);
roleItem.appendChild(container);
roleItem.onclick = (event) => {
event.stopPropagation();
toggleMembers(role, roleItem);
};
roleList.appendChild(roleItem);
});
groupElement.appendChild(roleList);
}
// Toggle the list of membres
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));
// Trouver le processus et le rôle du membre
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 = `<a href="${message.fileData}" download="${message.fileName}" target="_blank">${message.fileName}</a>`;
messageContent.classList.add('user');
} else {
messageContent.innerHTML = `<strong>${message.sender}</strong>: ${message.text} <span style="float: right;">${message.time}</span>`;
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
};
// Ajouter et afficher le message immédiatement
messageStore.addMessage(selectedMemberId, newMessage);
messagesMock = messageStore.getMessages();
loadMemberChat(selectedMemberId);
// Réinitialiser l'input
messageInput.value = '';
// Réponse automatique après 2 secondes
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 = '<div class="no-notification">No notifications available</div>';
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 ------------------
// Fonction pour afficher les détails du processus
function showProcessDetails(group: Group, groupId: number) {
console.log('Showing details for group:', groupId);
// Fermer toutes les vues de processus existantes
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;
}
// Charger les données du localStorage
const storedGroups = JSON.parse(localStorage.getItem('groups') || '[]');
const storedGroup = storedGroups.find((g: Group) => g.id === groupId);
// Utiliser les données du localStorage si disponibles, sinon utiliser le groupe passé en paramètre
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 = `
<div class="process-details-header">
<h2>${displayGroup.name}</h2>
<div class="header-buttons">
</div>
</div>
<div class="process-details-content">
<div class="details-section">
<h3>Description</h3>
<p>${displayGroup.description || 'No description available'}</p>
</div>
<div class="details-section">
<h3>Documents Communs</h3>
<div class="documents-grid">
${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;
// Ajouter le bouton de signature pour tous les documents non vierges en mode dev
const signButton = !isVierge ? `
${totalSignatures > 0 && signedCount < totalSignatures &&
!document.signatures.find((sig: DocumentSignature) => sig.member.name === currentUser.name && sig.signed) ? `
<button class="sign-button" onclick="signDocument(${document.id}, ${groupId}, true)">
Signer le document
</button>
` : ''}
` : '';
return `
<div class="document-card ${document.visibility} ${isVierge ? 'vierge' : ''}">
<div class="document-header">
<h4>${isVierge ? `⚠️ ${document.name}` : document.name}</h4>
<span class="document-visibility">${document.visibility}</span>
</div>
<div class="document-info">
${!isVierge ? `
<p class="document-date">Created on: ${document.createdAt ? new Date(document.createdAt).toLocaleDateString() : 'N/A'}</p>
<p class="document-deadline">Deadline: ${document.deadline ? new Date(document.deadline).toLocaleDateString() : 'N/A'}</p>
<p class="document-duration">Duration: ${calculateDuration(document.createdAt || '', document.deadline || '')} days</p>
<div class="document-signatures">
<h5>Signatures:</h5>
<div class="signatures-list">
${document.signatures?.map((sig: DocumentSignature) => `
<div class="signature-item ${sig.signed ? 'signed' : 'pending'}">
<span class="signer-name">${sig.member.name}</span>
<span class="signature-status">
${sig.signed ?
`✓ Signé le ${sig.signedAt ? new Date(sig.signedAt).toLocaleDateString() : 'date inconnue'}` :
'⌛ En attente'}
</span>
</div>
`).join('')}
</div>
<div class="progress-bar">
<div class="progress" style="width: ${percentage}%;"></div>
</div>
<p>${signedCount} out of ${totalSignatures} signed (${percentage.toFixed(0)}%)</p>
</div>
` : `
<p>Document vierge - En attente de création</p>
<button class="new-request-btn" onclick="newRequest({
processId: ${displayGroup.id},
processName: '${displayGroup.name}',
roleId: 0,
roleName: 'common',
documentId: ${document.id},
documentName: '${document.name}'
})">New request</button>
`}
${signButton}
</div>
</div>
`;
}).join('')}
</div>
</div>
<div class="details-section">
<h3>Roles and Documents</h3>
${displayGroup.roles.map((role: { name: string; documents?: any[] }) => {
// Filtrer les documents selon les droits d'accès
const accessibleDocuments = (role.documents || []).filter(doc =>
canUserAccessDocument(doc, role.name, currentUser.processRoles?.[0]?.role || '')
);
return `
<div class="role-section">
<h4>${role.name}</h4>
<div class="documents-grid">
${accessibleDocuments.map(document => {
const isVierge = !document.createdAt ||
!document.deadline ||
document.signatures.length === 0;
const signButton = !isVierge ? `
${document.signatures.length > 0 &&
document.signatures.filter((sig: DocumentSignature) => sig.signed).length < document.signatures.length &&
!document.signatures.find((sig: DocumentSignature) => sig.member.name === currentUser.name && sig.signed) ? `
<button class="sign-button" onclick="signDocument(${document.id}, ${groupId}, false)">
Signer le document
</button>
` : ''}
` : '';
return `
<div class="document-card ${document.visibility} ${isVierge ? 'vierge' : ''}">
<div class="document-header">
<h4>${isVierge ? `⚠️ ${document.name}` : document.name}</h4>
<span class="document-visibility">${document.visibility}</span>
</div>
<div class="document-info">
${!isVierge ? `
<p class="document-date">Created on: ${document.createdAt ? new Date(document.createdAt).toLocaleDateString() : 'N/A'}</p>
<p class="document-deadline">Deadline: ${document.deadline ? new Date(document.deadline).toLocaleDateString() : 'N/A'}</p>
<p class="document-duration">Duration: ${calculateDuration(document.createdAt || '', document.deadline || '')} days</p>
` : '<p>Document vierge - En attente de création</p>'}
</div>
${!isVierge ? `
<div class="document-signatures">
<h5>Signatures:</h5>
<div class="signatures-list">
${document.signatures.map((sig: DocumentSignature) => `
<div class="signature-item ${sig.signed ? 'signed' : 'pending'}">
<span class="signer-name">${sig.member.name}</span>
<span class="signature-status">
${sig.signed ?
`✓ Signé le ${sig.signedAt ? new Date(sig.signedAt).toLocaleDateString() : 'date inconnue'}` :
'⌛ En attente'}
</span>
</div>
`).join('')}
</div>
<div class="progress-bar">
<div class="progress" style="width: ${document.signatures.filter((sig: DocumentSignature) => sig.signed).length / document.signatures.length * 100}%;"></div>
</div>
<p>${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)}%)</p>
</div>
` : ''}
${signButton}
</div>
`;
}).join('')}
</div>
</div>
`;
}).join('')}
</div>
<div class="details-section">
<h3>Members by Role</h3>
<div class="roles-grid">
${displayGroup.roles.map((role: { name: string; members: Array<{ id: string | number; name: string }> }) => `
<div class="role-block">
<h4>${role.name}</h4>
<ul class="members-list">
${role.members.map(member => `
<li onclick="loadMemberChat(${member.id})">${member.name}</li>
`).join('')}
</ul>
</div>
`).join('')}
</div>
</div>
`;
/**const newRequestBtn = document.createElement('button');
newRequestBtn.className = 'new-request-btn';
newRequestBtn.textContent = 'New request';
newRequestBtn.onclick = () => newRequest(group);**/
const newCloseProcessButton = document.createElement('button');
newCloseProcessButton.className = 'close-btn';
newCloseProcessButton.textContent = 'x';
newCloseProcessButton.addEventListener('click', () => closeProcessDetails(groupId));
const headerButtons = detailsArea.querySelector('.header-buttons');
if (headerButtons) {
/**headerButtons.appendChild(newRequestBtn);**/
headerButtons.appendChild(newCloseProcessButton);
}
}
}
// Fonction pour fermer les détails et revenir au chat
function 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';
}
}
window.closeProcessDetails = closeProcessDetails;
window.loadMemberChat = loadMemberChat;
// Nouvelle fonction pour afficher les documents d'un rôle
function 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;
}>;
id?: number;
}, group: Group) {
// Charger les données depuis le 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);
// Utiliser les données du localStorage si disponibles, sinon utiliser les données passées en paramètre
const displayRole = storedRole || role;
console.log('Showing documents for role:', displayRole.name, 'in group:', group.name);
// Fermer d'abord toutes les vues de documents existantes
const allDetailsAreas = document.querySelectorAll('.process-details');
allDetailsAreas.forEach(area => {
area.remove();
});
const container = document.querySelector('.container');
if (!container) {
console.error('Container not found');
return;
}
// Créer une nouvelle zone de détails
const detailsArea = document.createElement('div');
detailsArea.id = `role-documents-${displayRole.name}`;
detailsArea.className = 'process-details';
detailsArea.innerHTML = `
<div class="process-details-header">
<h2>${displayRole.name} Documents</h2>
<div class="header-buttons">
<button class="close-btn" onclick="closeRoleDocuments('${displayRole.name}')">✕</button>
</div>
</div>
<div class="process-details-content">
<div class="details-section">
<h3>Documents</h3>
<div class="documents-grid">
${(displayRole.documents || []).length > 0 ?
(displayRole.documents || []).map((document: {
name: string;
visibility: string;
createdAt: string | null | undefined;
deadline: string | null | undefined;
signatures: DocumentSignature[];
id: number;
description?: string;
status?: string;
}) => {
const totalSignatures = document.signatures.length;
const signedCount = document.signatures.filter((sig: DocumentSignature) => sig.signed).length;
const percentage = totalSignatures > 0 ? (signedCount / totalSignatures) * 100 : 0;
const isVierge = !document.createdAt ||
!document.deadline ||
document.signatures.length === 0;
return `
<div class="document-card ${document.visibility} ${isVierge ? 'vierge' : ''}">
<div class="document-header">
<h4>${isVierge ? `⚠️ ${document.name}` : document.name}</h4>
<span class="document-visibility">${document.visibility}</span>
</div>
<div class="document-info">
${!isVierge ? `
<p class="document-date">Created on: ${document.createdAt ? new Date(document.createdAt).toLocaleDateString() : 'N/A'}</p>
<p class="document-deadline">Deadline: ${document.deadline ? new Date(document.deadline).toLocaleDateString() : 'N/A'}</p>
<p class="document-duration">Duration: ${calculateDuration(document.createdAt, document.deadline)} days</p>
<div class="document-signatures">
<h5>Signatures:</h5>
<div class="signatures-list">
${document.signatures.map((sig: DocumentSignature) => `
<div class="signature-item ${sig.signed ? 'signed' : 'pending'}">
<span class="signer-name">${sig.member.name}</span>
<span class="signature-status">
${sig.signed ?
`✓ Signé le ${sig.signedAt ? new Date(sig.signedAt).toLocaleDateString() : 'date inconnue'}` :
'⌛ En attente'}
</span>
</div>
`).join('')}
</div>
<div class="progress-bar">
<div class="progress" style="width: ${percentage}%;"></div>
</div>
<p>${signedCount} out of ${totalSignatures} signed (${percentage.toFixed(0)}%)</p>
</div>
` : `
<p>Document vierge - En attente de création</p>
<button class="new-request-btn" onclick="newRequest({
processId: ${group.id},
processName: '${group.name}',
roleId: ${role.id},
roleName: '${role.name}',
documentId: ${document.id},
documentName: '${document.name}'
})">New request</button>
`}
</div>
</div>
`;
}).join('')
: '<p class="no-documents">No documents available for this role</p>'
}
</div>
</div>
</div>
`;
container.appendChild(detailsArea);
}
// Fonction pour fermer la vue des documents d'un rôle
function closeRoleDocuments(roleName: string) {
const detailsArea = document.getElementById(`role-documents-${roleName}`);
if (detailsArea) {
detailsArea.remove();
}
}
window.closeRoleDocuments = closeRoleDocuments;
window.switchUser = switchUser;
// Fonction pour calculer la durée entre deux dates
function 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));
}
window.newRequest = newRequest;
window.submitRequest = submitRequest;
window.closeNewRequest = closeNewRequest;
// Définir la fonction removeMember pour supprimer un membre par son ID
function removeMember(memberId: string | number) {
const memberElement = document.querySelector(`.selected-member[data-member-id="${memberId}"]`);
if (memberElement) {
memberElement.remove();
}
}
window.removeMember = removeMember;
// Fonction pour gérer la nouvelle demande
function newRequest(params: RequestParams) {
// Ajout de validation des paramètres
if (!params || !params.processId) {
console.error('Paramètres invalides:', params);
showAlert('Paramètres invalides pour la nouvelle demande');
return;
}
const modal = document.createElement('div');
modal.className = 'modal-overlay';
// Récupérer le processus avec une vérification
const process = groupsMock.find(g => g.id === params.processId);
if (!process) {
console.error('Processus non trouvé:', params.processId);
showAlert('Processus non trouvé');
return;
}
// Déterminer les membres avec une vérification supplémentaire
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(`Rôle ${params.roleName} non trouvé`);
}
membersToDisplay = role.members.map(member => ({
...member,
roleName: params.roleName
}));
}
} catch (error) {
console.error('Erreur lors de la récupération des membres:', error);
showAlert('Erreur lors de la récupération des membres');
return;
}
modal.innerHTML = `
<div class="modal-document">
<div class="modal-content-document">
<div class="details-header">
<h2>New Document Request</h2>
<span class="document-context">
Process: ${params.processName} | Role: ${params.roleName} | Document: ${params.documentName}
</span>
</div>
<form id="newDocumentForm" class="document-form">
<input type="hidden" id="processId" value="${params.processId}">
<input type="hidden" id="roleId" value="${params.roleId}">
<input type="hidden" id="documentId" value="${params.documentId}">
<div class="form-left">
<div class="form-row">
<div class="form-group half">
<label for="documentName">Document Name*:</label>
<input type="text" id="documentName" required value="${params.documentName}">
</div>
<div class="form-group half">
<label for="createdAt">Created At:</label>
<input type="text" id="createdAt" value="${new Date().toLocaleDateString()}" readonly>
</div>
</div>
<div class="form-group">
<label for="description">Description:</label>
<textarea id="description"></textarea>
</div>
<div class="form-row">
<div class="form-group half">
<label for="visibility">Visibility*:</label>
<select id="visibility" required>
<option value="public">Public</option>
<option value="confidential">Confidential</option>
<option value="private">Private</option>
</select>
</div>
<div class="form-group half">
<label for="deadline">Deadline:</label>
<input type="date"
id="deadline"
min="${new Date().toISOString().split('T')[0]}"
required>
</div>
</div>
<div class="form-group">
<label>Import Files</label>
<div class="file-upload-container" id="dropZone">
<input type="file" id="fileInput" multiple accept=".pdf,.doc,.docx,.txt">
<p>Drop files here or click to select files</p>
</div>
<div id="fileList" class="file-list"></div>
</div>
<div class="form-group">
<label>Required Signatories:</label>
<div class="required-signatories">
${membersToDisplay.map(member => `
<div class="signatory-item">
<span class="member-name">${member.name}</span>
<span class="role-info">${member.roleName}</span>
</div>
`).join('')}
</div>
</div>
</div>
</form>
<div class="modal-footer">
<button class="cancel-btn" onclick="closeModal(this)">Cancel</button>
${params.roleName === 'common'
? '<button class="confirm-btn" onclick="submitCommonDocument(event)">Request</button>'
: '<button class="confirm-btn" onclick="submitNewDocument(event)">Request</button>'
}
</div>
</div>
</div>
`;
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;
// Rendre la zone cliquable
dropZone.addEventListener('click', () => {
fileInput.click();
});
// Gérer la sélection de fichiers
fileInput.addEventListener('change', (e: Event) => {
const target = e.target as HTMLInputElement;
if (target.files && target.files.length > 0) {
handleFiles(target.files);
}
});
// Gérer le 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) {
handleFiles(e.dataTransfer.files);
}
});
function handleFiles(files: FileList) {
Array.from(files).forEach(file => {
const reader = new FileReader();
reader.onload = (e) => {
const fileContent = e.target?.result;
// Vérifier si le fichier n'est pas déjà dans la liste
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 = `
<div class="file-info">
<span class="file-name">${file.name}</span>
<span class="file-size">(${(file.size / 1024).toFixed(1)} KB)</span>
</div>
<button type="button" class="remove-file">×</button>
`;
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 closeModal(button: HTMLElement) {
const modalOverlay = button.closest('.modal-overlay');
if (modalOverlay) {
modalOverlay.remove();
}
}
window.closeModal = closeModal;
let selectedSignatories: DocumentSignature[] = [];
function submitNewDocument(event: Event) {
event.preventDefault();
const form = document.getElementById('newDocumentForm') as HTMLFormElement;
if (!form) {
showAlert('Formulaire non trouvé');
return;
}
// Récupération des fichiers
const fileList = document.getElementById('fileList');
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 || '#',
};
});
// Récupération des valeurs du formulaire
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('Veuillez remplir tous les champs obligatoires');
return;
}
try {
// Récupérer les données actuelles
const groups = JSON.parse(localStorage.getItem('groups') || JSON.stringify(groupsMock));
const group = groups.find((g: Group) => g.id === processId);
if (!group) {
showAlert('Processus non trouvé');
return;
}
const role = group.roles.find((r: any) => {
return r.documents?.some((d: any) => d.id === documentId);
});
if (!role) {
showAlert('Rôle non trouvé');
return;
}
// Créer le nouveau document avec les signatures des membres du rôle
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
};
// Mettre à jour le document dans le rôle
const documentIndex = role.documents.findIndex((d: any) => d.id === documentId);
if (documentIndex !== -1) {
role.documents[documentIndex] = updatedDocument;
}
// Sauvegarder dans le localStorage
localStorage.setItem('groups', JSON.stringify(groups));
// Mettre à jour également groupsMock pour la cohérence
const mockGroup = groupsMock.find(g => g.id === processId);
if (mockGroup) {
const mockRole = mockGroup.roles.find(r => r.name === role.name);
if (mockRole && mockRole.documents) {
const mockDocIndex = mockRole.documents.findIndex(d => d.id === documentId);
if (mockDocIndex !== -1) {
mockRole.documents[mockDocIndex] = {
...updatedDocument,
status: undefined
};
}
}
}
// Fermer le modal
if (event.target instanceof HTMLElement) {
closeModal(event.target);
}
// Recharger la vue des documents avec les données mises à jour
showRoleDocuments(role, group);
showAlert('Document mis à jour avec succès!');
} catch (error) {
console.error('Erreur lors de la sauvegarde:', error);
showAlert('Une erreur est survenue lors de la sauvegarde');
}
}
window.submitNewDocument = submitNewDocument;
function submitCommonDocument(event: Event) {
event.preventDefault();
const form = document.getElementById('newDocumentForm') as HTMLFormElement;
if (!form) {
showAlert('Formulaire non trouvé');
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('Veuillez remplir tous les champs obligatoires');
return;
}
try {
const groups = JSON.parse(localStorage.getItem('groups') || JSON.stringify(groupsMock));
const group = groups.find((g: Group) => g.id === processId);
if (!group) {
showAlert('Processus non trouvé');
return;
}
// Récupérer tous les membres de tous les rôles du groupe
const allMembers = group.roles.reduce((acc: any[], role: any) => {
return acc.concat(role.members);
}, []);
const fileList = document.getElementById('fileList');
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
};
// Mettre à jour le document commun
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) {
closeModal(event.target);
}
showProcessDetails(group, group.id);
showAlert('Document commun mis à jour avec succès!');
} catch (error) {
console.error('Erreur lors de la sauvegarde:', error);
showAlert('Une erreur est survenue lors de la sauvegarde');
}
}
window.submitCommonDocument = submitCommonDocument;
function submitRequest() {
showAlert("Request submitted!");
}
function closeNewRequest() {
const newRequestView = document.getElementById('new-request-view');
if (newRequestView) {
newRequestView.style.display = 'none';
newRequestView.remove();
}
}
window.closeNewRequest = closeNewRequest;
document.addEventListener('DOMContentLoaded', function() {
const newRequestBtn = document.getElementById('newRequestBtn');
if (newRequestBtn) {
newRequestBtn.addEventListener('click', () => {
newRequest({
processId: 0,
processName: '',
roleId: 0,
roleName: '',
documentId: 0,
documentName: ''
});
});
}
});
function 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!');
closeNewRequest();
}
window.submitDocumentRequest = submitDocumentRequest;
// Define allMembers using membersMock
const allMembers = membersMock.map(member => ({
id: member.id,
name: member.name,
roleName: 'Default Role'
}));
const addMembersBtn = document.getElementById('addMembersBtn');
if (addMembersBtn) {
addMembersBtn.addEventListener('click', function() {
const selectedMembers: string[] = []; // Assuming member IDs are strings
// Logic to display a list of members to select
const membersToSelect = allMembers.map(member => `
<div class="member-checkbox">
<input type="checkbox" id="member-${member.id}" value="${member.id}">
<label for="member-${member.id}">${member.name} (${member.roleName})</label>
</div>
`).join('');
// Create a modal for member selection
const modalContent = `
<div class="modal">
<h4>Select Members</h4>
<div>${membersToSelect}</div>
<button id="confirmSelectionBtn">Confirm</button>
</div>
`;
// Append the modal to the body
document.body.insertAdjacentHTML('beforeend', modalContent);
// Add event for the confirmation button
const confirmBtn = document.getElementById('confirmSelectionBtn');
if (confirmBtn) {
confirmBtn.addEventListener('click', function() {
// Retrieve selected members
const selectedCheckboxes = document.querySelectorAll('.modal input[type="checkbox"]:checked');
selectedCheckboxes.forEach((checkbox: Element) => {
selectedMembers.push((checkbox as HTMLInputElement).value);
});
// Add selected members to the list
const membersList = document.getElementById('members-list');
if (membersList) {
selectedMembers.forEach(memberId => {
const member = allMembers.find(m => m.id === parseInt(memberId));
if (member) {
membersList.insertAdjacentHTML('beforeend', `<div>${member.name} (${member.roleName})</div>`);
}
});
}
// Close the modal
document.querySelector('.modal')?.remove();
});
}
});
}
// Fonction d'initialisation pour le router
export function initSignature() {
// Réinitialiser l'affichage
const groupList = document.getElementById('group-list');
if (groupList) {
groupList.innerHTML = ''; // Nettoyer la liste existante
}
// Recharger les messages depuis le store
messagesMock = messageStore.getMessages();
if (messagesMock.length === 0) {
messageStore.setMessages(initialMessagesMock);
messagesMock = messageStore.getMessages();
}
// Réinitialiser l'interface
updateCurrentUserDisplay();
loadGroupList();
// Si un membre était sélectionné, recharger son chat
if (selectedMemberId) {
loadMemberChat(selectedMemberId);
}
// Event listeners
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');
}
});
// Réattacher les event listeners pour les messages
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();
}
});
}
}
// Ajouter cette interface pour la signature
interface SignatureResponse {
success: boolean;
message: string;
documentId: number;
memberId: string | number;
signedAt: string;
}
// Ajouter cette fonction pour signer un document
function signDocument(documentId: number, processId: number, isCommonDocument: boolean = false) {
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('Processus non trouvé');
}
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 non trouvé');
}
// Créer et insérer la modal directement dans le body
const modalHtml = `
<div class="modal-overlay" id="signatureModal">
<div class="modal-document">
<div class="modal-content-document">
<div class="details-header">
<h2>Signature du document</h2>
<button class="close-btn" onclick="closeModal(this)">×</button>
</div>
<div class="document-details">
<h3>${targetDoc.name}</h3>
<div class="info-section">
<div class="info-row">
<span class="label">Créé le:</span>
<span class="value">${new Date(targetDoc.createdAt).toLocaleDateString()}</span>
</div>
<div class="info-row">
<span class="label">Date limite:</span>
<span class="value">${new Date(targetDoc.deadline).toLocaleDateString()}</span>
</div>
<div class="info-row">
<span class="label">Visibilité:</span>
<span class="value">${targetDoc.visibility}</span>
</div>
</div>
<div class="description-section">
<h4>Description:</h4>
<p>${targetDoc.description || 'Aucune description disponible'}</p>
</div>
<div class="signatures-section">
<h4>État des signatures:</h4>
<div class="signatures-list">
${targetDoc.signatures.map((sig: DocumentSignature) => `
<div class="signature-item ${sig.signed ? 'signed' : 'pending'}">
<span class="signer-name">${sig.member.name}</span>
<span class="signature-status">
${sig.signed ?
`✓ Signé le ${sig.signedAt ? new Date(sig.signedAt).toLocaleDateString() : 'date inconnue'}` :
'⌛ En attente'}
</span>
</div>
`).join('')}
</div>
</div>
${targetDoc.files ? `
<div class="files-section">
<h4>Fichiers joints:</h4>
<div class="files-list">
${Array.isArray(targetDoc.files) && targetDoc.files.length > 0 ?
targetDoc.files.map((file: any) => `
<div class="file-item">
<span class="file-icon"><3E><><EFBFBD></span>
<span class="file-name">${file.name}</span>
<a href="${file.url}" class="download-link" download="${file.name}">
<span class="download-icon">⬇</span>
</a>
</div>
`).join('')
: '<p>Aucun fichier joint</p>'
}
</div>
</div>
` : ''}
<div class="confirmation-section">
<p class="warning-text">En signant ce document, vous confirmez avoir pris connaissance de son contenu.</p>
<div class="signature-slider-container">
<div class="slider-track">
<div class="slider-handle" id="signatureSlider" draggable="true">
<span class="slider-arrow">➜</span>
</div>
<span class="slider-text">Glisser pour signer</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>`;
document.body.insertAdjacentHTML('beforeend', modalHtml);
// Ajouter la logique du slider après création de la 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);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', stopDrag);
// Touch events pour mobile
slider.addEventListener('touchstart', initDrag);
document.addEventListener('touchmove', drag);
document.addEventListener('touchend', stopDrag);
}
function initDrag(e: MouseEvent | TouchEvent) {
isDragging = true;
startX = 'touches' in e ? e.touches[0].clientX : e.clientX;
sliderLeft = slider?.offsetLeft || 0;
}
function drag(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;
// Calculer la position relative à la track
let newLeft = x - rect.left - (slider.offsetWidth / 2);
// Limiter le déplacement
const maxLeft = sliderTrack.offsetWidth - slider.offsetWidth;
newLeft = Math.max(0, Math.min(newLeft, maxLeft));
// Mettre à jour la position
slider.style.left = `${newLeft}px`;
// Si le slider atteint 90% du chemin, déclencher la signature
if (newLeft > maxLeft * 0.9) {
stopDrag(e);
confirmSignature(documentId, processId, isCommonDocument);
}
}
function stopDrag(e: MouseEvent | TouchEvent) {
if (!isDragging || !slider) return;
isDragging = false;
// Réinitialiser la position si pas assez glissé
if (slider.offsetLeft < (sliderTrack?.offsetWidth || 0) * 0.9) {
slider.style.left = '0px';
}
}
} catch (error) {
console.error('Erreur lors de l\'affichage de la modal:', error);
showAlert(error instanceof Error ? error.message : 'Erreur lors de l\'affichage de la modal');
}
}
// Nouvelle fonction pour confirmer la signature
function confirmSignature(documentId: number, processId: number, isCommonDocument: boolean) {
try {
// Ajout du console.log pour voir l'utilisateur actuel
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('Processus non trouvé');
}
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 non trouvé');
}
const userSignature = targetDoc.signatures.find((sig: DocumentSignature) =>
sig.member.name === currentUser.name
);
if (!userSignature) {
throw new Error(`L'utilisateur ${currentUser.name} n'est pas autorisé à signer ce document. Veuillez vous connecter avec un utilisateur autorisé.`);
}
userSignature.signed = true;
userSignature.signedAt = new Date().toISOString();
localStorage.setItem('groups', JSON.stringify(groups));
// Modification ici : utiliser document.querySelector au lieu de document
const closeBtn = document.querySelector('.modal-overlay .close-btn');
if (closeBtn instanceof HTMLElement) {
closeModal(closeBtn);
}
if (isCommonDocument) {
showProcessDetails(group, processId);
} else {
const role = group.roles.find((r: any) => r.documents?.includes(targetDoc));
if (role) {
showRoleDocuments(role, group);
}
}
showAlert('Document signé avec succès!');
} catch (error) {
console.error('Erreur lors de la signature:', error);
showAlert(error instanceof Error ? error.message : 'Erreur lors de la signature');
}
}
window.confirmSignature = confirmSignature;
window.signDocument = signDocument;
if (typeof window !== 'undefined') {
(window as any).signDocument = signDocument;
(window as any).confirmSignature = confirmSignature;
}
// Ajouter cette fonction helper
function canUserAccessDocument(document: any, roleId: string, currentUserRole: string): boolean {
// Si l'utilisateur a le même rôle, il peut voir tous les documents
if (roleId === currentUserRole) {
return true;
}
// Sinon, il ne peut voir que les documents publics
return document.visibility === 'public';
}