By signing this document, you confirm that you have read its contents.
➜
Drag to sign
`;
// Créer la modal dans le shadowDOM au lieu du document principal
const modalElement = document.createElement('div');
modalElement.className = 'modal-overlay';
modalElement.innerHTML = modalHtml;
this.shadowRoot?.appendChild(modalElement);
// Sélectionner les éléments dans le shadowDOM
const slider = modalElement.querySelector('#signatureSlider');
const sliderTrack = slider?.parentElement;
let isDragging = false;
let startX: number;
let sliderLeft: number;
if (slider && sliderTrack) {
slider.addEventListener('mousedown', (e: Event) => initDrag(e as MouseEvent));
slider.addEventListener('touchstart', (e: Event) => initDrag(e as TouchEvent));
modalElement.addEventListener('mousemove', (e: Event) => drag.call(this, e as MouseEvent));
modalElement.addEventListener('touchmove', (e: Event) => drag.call(this, e as TouchEvent));
modalElement.addEventListener('mouseup', (e: Event) => stopDrag(e as MouseEvent));
modalElement.addEventListener('touchend', (e: Event) => stopDrag(e as TouchEvent));
}
function initDrag(e: MouseEvent | TouchEvent) {
isDragging = true;
startX = 'touches' in e ? e.touches[0].clientX : e.clientX;
sliderLeft = (slider as HTMLElement)?.offsetLeft || 0;
}
function drag(this: SignatureElement, e: MouseEvent | TouchEvent) {
if (!isDragging || !slider || !sliderTrack) return;
e.preventDefault();
const x = 'touches' in e ? e.touches[0].clientX : e.clientX;
const deltaX = x - startX;
// Calculate the position relative to the track
let newLeft = sliderLeft + deltaX;
// Limit the movement
const maxLeft = (sliderTrack as HTMLElement).offsetWidth - (slider as HTMLElement).offsetWidth;
newLeft = Math.max(0, Math.min(newLeft, maxLeft));
// Update the position
(slider as HTMLElement).style.left = `${newLeft}px`;
// If the slider reaches 90% of the path, trigger the signature
if (newLeft > maxLeft * 0.9) {
stopDrag(e);
this.confirmSignature(documentId, processId, isCommonDocument);
}
}
function stopDrag(e: MouseEvent | TouchEvent) {
if (!isDragging || !slider) return;
isDragging = false;
// Reset the position if not enough dragged
if ((slider as HTMLElement).offsetLeft < ((sliderTrack as HTMLElement)?.offsetWidth || 0) * 0.9) {
(slider as HTMLElement).style.left = '0px';
}
}
} catch (error) {
console.error('Error displaying modal:', error);
showAlert(error instanceof Error ? error.message : 'Error displaying modal');
}
}
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.dom = getCorrectDOM('signature-element');
this.shadowRoot!.innerHTML = `
`;
window.toggleUserList = this.toggleUserList.bind(this);
window.switchUser = this.switchUser.bind(this);
window.closeProcessDetails = this.closeProcessDetails.bind(this);
window.loadMemberChat = this.loadMemberChat.bind(this);
window.closeRoleDocuments = this.closeRoleDocuments.bind(this);
window.newRequest = this.newRequest.bind(this);
window.submitRequest = this.submitRequest.bind(this);
window.closeNewRequest = this.closeNewRequest.bind(this);
window.closeModal = this.closeModal.bind(this);
window.submitNewDocument = this.submitNewDocument.bind(this);
window.submitCommonDocument = this.submitCommonDocument.bind(this);
window.signDocument = this.signDocument.bind(this);
window.confirmSignature = this.confirmSignature.bind(this);
window.submitDocumentRequest = this.submitDocumentRequest.bind(this);
// Initialiser les événements de notification
document.addEventListener('click', (event: Event): void => {
if (this.notificationBoard && this.notificationBoard.style.display === 'block' &&
!this.notificationBoard.contains(event.target as Node) &&
this.notificationBell && !this.notificationBell.contains(event.target as Node)) {
this.notificationBoard.style.display = 'none';
}
});
this.initMessageEvents();
this.initFileUpload();
}
private initMessageEvents() {
// Pour le bouton Send
const sendButton = this.shadowRoot?.getElementById('send-button');
if (sendButton) {
sendButton.addEventListener('click', () => this.sendMessage());
}
// Pour la touche Entrée
const messageInput = this.shadowRoot?.getElementById('message-input');
if (messageInput) {
messageInput.addEventListener('keypress', (event: KeyboardEvent) => {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
this.sendMessage();
}
});
}
}
private initFileUpload() {
const fileInput = this.shadowRoot?.getElementById('file-input') as HTMLInputElement;
if (fileInput) {
fileInput.addEventListener('change', (event: Event) => {
const target = event.target as HTMLInputElement;
if (target.files && target.files.length > 0) {
this.sendFile(target.files[0]);
}
});
}
}
private calculateDuration(startDate: string | null | undefined, endDate: string | null | undefined): number {
const start = new Date(startDate || '');
const end = new Date(endDate || '');
const duration = end.getTime() - start.getTime();
return Math.floor(duration / (1000 * 60 * 60 * 24));
}
// Add this helper function
private canUserAccessDocument(document: any, roleId: string, currentUserRole: string): boolean {
// Modify the access logic
if (document.visibility === 'public') {
return true; // Can see but not necessarily sign
}
return roleId === currentUserRole;
}
private canUserSignDocument(document: any, role: string, user: Member): boolean {
console.log('Checking signing rights for:', {
document,
role,
user,
userRoles: user.processRoles
});
// Vérifier si l'utilisateur est dans la liste des signatures
const isSignatory = document.signatures?.some((sig: DocumentSignature) =>
sig.member && 'id' in sig.member && sig.member.id === user.id && !sig.signed
);
if (!isSignatory) {
console.log('User is not in signatures list or has already signed');
return false;
}
// Si l'utilisateur est dans la liste des signatures, il peut signer
return true;
}
private closeProcessDetails(groupId: number) {
const detailsArea = this.shadowRoot?.getElementById(`process-details-${groupId}`);
const chatArea = this.shadowRoot?.getElementById('chat-area');
if (detailsArea) {
detailsArea.style.display = 'none';
}
if (chatArea) {
chatArea.style.display = 'block';
}
}
///////////////////// Notification module /////////////////////
// Delete a notification
private removeNotification(index: number) {
this.notifications?.splice(index, 1); // Ajout de ?.
this.renderNotifications();
this.updateNotificationBadge();
}
// Show notifications
private renderNotifications() {
if (!this.notificationBoard) return;
// Reset the interface
this.notificationBoard.innerHTML = '';
// Displays "No notifications available" if there are no notifications
if (this.notifications.length === 0) {
this.notificationBoard.innerHTML = '
No notifications available
';
return;
}
// Add each notification to the list
this.notifications.forEach((notif, index) => {
const notifElement = document.createElement('div');
notifElement.className = 'notification-item';
notifElement.textContent = `${notif.text} at ${notif.time}`;
notifElement.onclick = () => {
this.loadMemberChat(notif.memberId);
this.removeNotification(index);
};
this.notificationBoard?.appendChild(notifElement);
});
}
private updateNotificationBadge() {
if (!this.notificationBadge) return;
const count = this.notifications.length;
this.notificationBadge.textContent = count > 99 ? '+99' : count.toString();
(this.notificationBadge as HTMLElement).style.display = count > 0 ? 'block' : 'none';
}
// Add notification
private addNotification(memberId: string, message: Message) {
// Creating a new notification
const notification = {
memberId,
text: `New message from Member ${memberId}: ${message.text}`,
time: message.time
};
// Added notification to list and interface
this.notifications.push(notification);
this.renderNotifications();
this.updateNotificationBadge();
}
// Send a messsage
private sendMessage() {
const messageInput = this.shadowRoot?.getElementById('message-input') as HTMLInputElement;
if (!messageInput) return;
const messageText = messageInput.value.trim();
if (messageText === '' || this.selectedMemberId === null) {
return;
}
const newMessage: Message = {
id: Date.now(),
sender: "4NK",
text: messageText,
time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
type: 'text' as const
};
// Add and display the message immediately
messageStore.addMessage(this.selectedMemberId, newMessage);
this.messagesMock = messageStore.getMessages();
this.loadMemberChat(this.selectedMemberId);
// Reset the input
messageInput.value = '';
// Automatic response after 2 seconds
setTimeout(() => {
if (this.selectedMemberId) {
const autoReply = this.generateAutoReply(`Member ${this.selectedMemberId}`);
messageStore.addMessage(this.selectedMemberId, autoReply);
this.messagesMock = messageStore.getMessages();
this.loadMemberChat(this.selectedMemberId);
this.addNotification(this.selectedMemberId, autoReply);
}
}, 2000);
}
private showProcessDetails(group: Group, groupId: number) {
console.log('Showing details for group:', groupId);
// Close all existing process views
const allDetailsAreas = this.shadowRoot?.querySelectorAll('.process-details');
if (allDetailsAreas) {
allDetailsAreas.forEach(area => {
(area as HTMLElement).style.display = 'none';
});
}
const container = this.shadowRoot?.querySelector('.container');
if (!container) {
console.error('Container not found');
return;
}
// Load the data from localStorage
const storedGroups = JSON.parse(localStorage.getItem('groups') || '[]');
const storedGroup = storedGroups.find((g: Group) => g.id === groupId);
// Use the data from localStorage if available, otherwise use the group passed as a parameter
const displayGroup = storedGroup || group;
let detailsArea = this.shadowRoot?.getElementById(`process-details-${groupId}`);
if (!detailsArea) {
detailsArea = document.createElement('div');
detailsArea.id = `process-details-${groupId}`;
detailsArea.className = 'process-details';
container.appendChild(detailsArea);
}
if (detailsArea) {
detailsArea.style.display = 'block';
detailsArea.innerHTML = `
`;
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 = `