596 lines
19 KiB
JavaScript
596 lines
19 KiB
JavaScript
/**
|
|
* Messaging JavaScript Component
|
|
* Handles message composition, attachments, and interface interactions
|
|
*/
|
|
|
|
// Prevent duplicate declaration
|
|
if (typeof MessagingManager === 'undefined') {
|
|
class MessagingManager {
|
|
constructor(options = {}) {
|
|
this.options = {
|
|
maxAttachmentSize: options.maxAttachmentSize || 10, // MB
|
|
allowedFileTypes: options.allowedFileTypes || ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'jpg', 'jpeg', 'png', 'gif'],
|
|
uploadUrl: options.uploadUrl || '/admin/messages/upload-attachment',
|
|
removeAttachmentUrl: options.removeAttachmentUrl || '/admin/messages/remove-attachment',
|
|
sendMessageUrl: options.sendMessageUrl || '/admin/messages/send',
|
|
...options
|
|
};
|
|
|
|
this.attachments = [];
|
|
this.tempAttachmentIds = [];
|
|
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
this.setupElements();
|
|
this.bindEvents();
|
|
this.initializeRichEditor();
|
|
}
|
|
|
|
setupElements() {
|
|
this.messageForm = document.getElementById('message-form');
|
|
this.recipientsSelect = document.getElementById('recipients');
|
|
this.subjectInput = document.getElementById('subject');
|
|
this.contentEditor = document.getElementById('content');
|
|
this.attachmentInput = document.getElementById('attachment-input');
|
|
this.attachmentsList = document.getElementById('attachments-list');
|
|
this.requireAcknowledgment = document.getElementById('require_acknowledgment');
|
|
this.sendButton = document.getElementById('send-message-btn');
|
|
this.saveDraftButton = document.getElementById('save-draft-btn');
|
|
this.attachmentButton = document.getElementById('attachment-btn');
|
|
}
|
|
|
|
bindEvents() {
|
|
// File attachment
|
|
if (this.attachmentInput) {
|
|
this.attachmentInput.addEventListener('change', (e) => {
|
|
this.handleFileSelection(e.target.files);
|
|
});
|
|
}
|
|
|
|
if (this.attachmentButton) {
|
|
this.attachmentButton.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
this.attachmentInput?.click();
|
|
});
|
|
}
|
|
|
|
// Form submission
|
|
if (this.messageForm) {
|
|
this.messageForm.addEventListener('submit', (e) => {
|
|
e.preventDefault();
|
|
this.sendMessage();
|
|
});
|
|
}
|
|
|
|
// Save draft
|
|
if (this.saveDraftButton) {
|
|
this.saveDraftButton.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
this.saveDraft();
|
|
});
|
|
}
|
|
|
|
// Auto-save draft every 30 seconds
|
|
setInterval(() => {
|
|
this.autoSaveDraft();
|
|
}, 30000);
|
|
|
|
// Recipients validation
|
|
if (this.recipientsSelect) {
|
|
this.recipientsSelect.addEventListener('change', () => {
|
|
this.validateForm();
|
|
});
|
|
}
|
|
|
|
// Subject validation
|
|
if (this.subjectInput) {
|
|
this.subjectInput.addEventListener('input', () => {
|
|
this.validateForm();
|
|
});
|
|
}
|
|
}
|
|
|
|
initializeRichEditor() {
|
|
if (this.contentEditor && typeof tinymce !== 'undefined') {
|
|
tinymce.init({
|
|
selector: '#content',
|
|
height: 300,
|
|
menubar: false,
|
|
plugins: [
|
|
'advlist autolink lists link image charmap print preview anchor',
|
|
'searchreplace visualblocks code fullscreen',
|
|
'insertdatetime media table paste code help wordcount'
|
|
],
|
|
toolbar: 'undo redo | formatselect | bold italic backcolor | \
|
|
alignleft aligncenter alignright alignjustify | \
|
|
bullist numlist outdent indent | removeformat | help',
|
|
setup: (editor) => {
|
|
editor.on('change', () => {
|
|
this.validateForm();
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
async handleFileSelection(files) {
|
|
for (let file of files) {
|
|
if (this.validateFile(file)) {
|
|
await this.uploadFile(file);
|
|
}
|
|
}
|
|
// Clear the input
|
|
if (this.attachmentInput) {
|
|
this.attachmentInput.value = '';
|
|
}
|
|
}
|
|
|
|
validateFile(file) {
|
|
// Check file size
|
|
const maxSizeBytes = this.options.maxAttachmentSize * 1024 * 1024;
|
|
if (file.size > maxSizeBytes) {
|
|
this.showError(trans('admin.ATTACHMENT_TOO_LARGE'));
|
|
return false;
|
|
}
|
|
|
|
// Check file type
|
|
const extension = file.name.split('.').pop().toLowerCase();
|
|
if (!this.options.allowedFileTypes.includes(extension)) {
|
|
this.showError(trans('admin.INVALID_FILE_TYPE'));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
async uploadFile(file) {
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
|
|
try {
|
|
this.showUploadProgress(file.name);
|
|
|
|
const response = await fetch(this.options.uploadUrl, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''
|
|
},
|
|
body: formData
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok && data.success) {
|
|
this.addAttachment(data.attachment);
|
|
this.tempAttachmentIds.push(data.attachment.id);
|
|
} else {
|
|
this.showError(data.message || trans('admin.UPLOAD_FAILED'));
|
|
}
|
|
} catch (error) {
|
|
console.error('Upload error:', error);
|
|
this.showError(trans('admin.UPLOAD_FAILED'));
|
|
} finally {
|
|
this.hideUploadProgress();
|
|
}
|
|
}
|
|
|
|
addAttachment(attachment) {
|
|
this.attachments.push(attachment);
|
|
this.renderAttachments();
|
|
}
|
|
|
|
async removeAttachment(attachmentId) {
|
|
try {
|
|
const response = await fetch(this.options.removeAttachmentUrl, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''
|
|
},
|
|
body: JSON.stringify({ id: attachmentId })
|
|
});
|
|
|
|
if (response.ok) {
|
|
this.attachments = this.attachments.filter(att => att.id !== attachmentId);
|
|
this.tempAttachmentIds = this.tempAttachmentIds.filter(id => id !== attachmentId);
|
|
this.renderAttachments();
|
|
}
|
|
} catch (error) {
|
|
console.error('Remove attachment error:', error);
|
|
}
|
|
}
|
|
|
|
renderAttachments() {
|
|
if (!this.attachmentsList) return;
|
|
|
|
if (this.attachments.length === 0) {
|
|
this.attachmentsList.innerHTML = '';
|
|
return;
|
|
}
|
|
|
|
const attachmentsHtml = this.attachments.map(attachment => `
|
|
<div class="attachment-item d-flex align-items-center justify-content-between p-2 border rounded mb-2">
|
|
<div class="d-flex align-items-center">
|
|
<i class="${this.getFileIcon(attachment.file_name)} me-2"></i>
|
|
<span class="file-name">${this.escapeHtml(attachment.original_name)}</span>
|
|
<span class="file-size text-muted ms-2">(${this.formatFileSize(attachment.file_size)})</span>
|
|
</div>
|
|
<button type="button" class="btn btn-sm btn-outline-danger"
|
|
onclick="messagingManager.removeAttachment(${attachment.id})">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
`).join('');
|
|
|
|
this.attachmentsList.innerHTML = attachmentsHtml;
|
|
}
|
|
|
|
getFileIcon(fileName) {
|
|
const extension = fileName.split('.').pop().toLowerCase();
|
|
const icons = {
|
|
'pdf': 'fas fa-file-pdf text-danger',
|
|
'doc': 'fas fa-file-word text-primary',
|
|
'docx': 'fas fa-file-word text-primary',
|
|
'xls': 'fas fa-file-excel text-success',
|
|
'xlsx': 'fas fa-file-excel text-success',
|
|
'jpg': 'fas fa-file-image text-info',
|
|
'jpeg': 'fas fa-file-image text-info',
|
|
'png': 'fas fa-file-image text-info',
|
|
'gif': 'fas fa-file-image text-info'
|
|
};
|
|
return icons[extension] || 'fas fa-file text-secondary';
|
|
}
|
|
|
|
formatFileSize(bytes) {
|
|
if (bytes === 0) return '0 Bytes';
|
|
const k = 1024;
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
}
|
|
|
|
async sendMessage() {
|
|
if (!this.validateForm()) return;
|
|
|
|
const formData = this.getFormData();
|
|
formData.attachment_ids = this.tempAttachmentIds;
|
|
|
|
try {
|
|
this.setSendingState(true);
|
|
|
|
const response = await fetch(this.options.sendMessageUrl, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''
|
|
},
|
|
body: JSON.stringify(formData)
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok && data.success) {
|
|
this.showSuccess(trans('admin.MESSAGE_SENT'));
|
|
this.resetForm();
|
|
// Redirect to inbox or sent messages
|
|
setTimeout(() => {
|
|
window.location.href = data.redirect || '/admin/messages';
|
|
}, 1500);
|
|
} else {
|
|
this.showError(data.message || trans('admin.SEND_FAILED'));
|
|
}
|
|
} catch (error) {
|
|
console.error('Send message error:', error);
|
|
this.showError(trans('admin.SEND_FAILED'));
|
|
} finally {
|
|
this.setSendingState(false);
|
|
}
|
|
}
|
|
|
|
async saveDraft() {
|
|
const formData = this.getFormData();
|
|
formData.is_draft = true;
|
|
formData.attachment_ids = this.tempAttachmentIds;
|
|
|
|
try {
|
|
const response = await fetch(this.options.sendMessageUrl, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''
|
|
},
|
|
body: JSON.stringify(formData)
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok && data.success) {
|
|
this.showSuccess(trans('admin.DRAFT_SAVED'));
|
|
}
|
|
} catch (error) {
|
|
console.error('Save draft error:', error);
|
|
}
|
|
}
|
|
|
|
autoSaveDraft() {
|
|
const hasContent = this.hasFormContent();
|
|
if (hasContent) {
|
|
this.saveDraft();
|
|
}
|
|
}
|
|
|
|
getFormData() {
|
|
const recipients = Array.from(this.recipientsSelect?.selectedOptions || [])
|
|
.map(option => option.value);
|
|
|
|
const content = this.contentEditor && typeof tinymce !== 'undefined'
|
|
? tinymce.get('content')?.getContent() || ''
|
|
: this.contentEditor?.value || '';
|
|
|
|
return {
|
|
recipients: recipients,
|
|
subject: this.subjectInput?.value || '',
|
|
content: content,
|
|
require_acknowledgment: this.requireAcknowledgment?.checked || false,
|
|
attachment_ids: this.tempAttachmentIds
|
|
};
|
|
} validateForm() {
|
|
const data = this.getFormData();
|
|
const maxRecipients = window.messagingConfig?.maxRecipients || 20;
|
|
|
|
let isValid = data.recipients.length > 0 &&
|
|
data.subject.trim() !== '' &&
|
|
data.content.trim() !== '';
|
|
|
|
// Check max recipients limit
|
|
if (data.recipients.length > maxRecipients) {
|
|
isValid = false;
|
|
this.showError(`Maximum ${maxRecipients} recipients allowed.`);
|
|
}
|
|
|
|
if (this.sendButton) {
|
|
this.sendButton.disabled = !isValid;
|
|
}
|
|
|
|
return isValid;
|
|
}
|
|
|
|
hasFormContent() {
|
|
const data = this.getFormData();
|
|
return data.recipients.length > 0 ||
|
|
data.subject.trim() !== '' ||
|
|
data.content.trim() !== '' ||
|
|
this.attachments.length > 0;
|
|
}
|
|
|
|
resetForm() {
|
|
if (this.recipientsSelect) {
|
|
Array.from(this.recipientsSelect.options).forEach(option => {
|
|
option.selected = false;
|
|
});
|
|
}
|
|
|
|
if (this.subjectInput) this.subjectInput.value = '';
|
|
|
|
if (this.contentEditor && typeof tinymce !== 'undefined') {
|
|
tinymce.get('content')?.setContent('');
|
|
} else if (this.contentEditor) {
|
|
this.contentEditor.value = '';
|
|
}
|
|
|
|
if (this.requireAcknowledgment) {
|
|
this.requireAcknowledgment.checked = false;
|
|
}
|
|
|
|
this.attachments = [];
|
|
this.tempAttachmentIds = [];
|
|
this.renderAttachments();
|
|
this.validateForm();
|
|
}
|
|
|
|
setSendingState(sending) {
|
|
if (this.sendButton) {
|
|
this.sendButton.disabled = sending;
|
|
this.sendButton.innerHTML = sending
|
|
? `<i class="fas fa-spinner fa-spin"></i> ${trans('admin.SENDING')}`
|
|
: trans('admin.SEND_MESSAGE');
|
|
}
|
|
}
|
|
|
|
showUploadProgress(fileName) {
|
|
// You can implement a progress indicator here
|
|
console.log(`Uploading ${fileName}...`);
|
|
}
|
|
|
|
hideUploadProgress() {
|
|
// Hide progress indicator
|
|
}
|
|
|
|
showSuccess(message) {
|
|
// Implement success notification
|
|
if (typeof Swal !== 'undefined') {
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: trans('admin.SUCCESS'),
|
|
text: message,
|
|
timer: 3000,
|
|
showConfirmButton: false
|
|
});
|
|
} else {
|
|
alert(message);
|
|
}
|
|
}
|
|
|
|
showError(message) {
|
|
// Implement error notification
|
|
if (typeof Swal !== 'undefined') {
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: trans('admin.ERROR'),
|
|
text: message
|
|
});
|
|
} else {
|
|
alert(message);
|
|
}
|
|
}
|
|
|
|
escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
}
|
|
|
|
// Bulk actions for message lists
|
|
class MessageBulkActions {
|
|
constructor() {
|
|
this.selectedMessages = new Set();
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
this.bindEvents();
|
|
}
|
|
|
|
bindEvents() {
|
|
// Select all checkbox
|
|
const selectAllCheckbox = document.getElementById('select-all-messages');
|
|
if (selectAllCheckbox) {
|
|
selectAllCheckbox.addEventListener('change', (e) => {
|
|
this.selectAll(e.target.checked);
|
|
});
|
|
}
|
|
|
|
// Individual message checkboxes
|
|
document.addEventListener('change', (e) => {
|
|
if (e.target.matches('.message-checkbox')) {
|
|
this.toggleMessage(e.target.value, e.target.checked);
|
|
}
|
|
});
|
|
|
|
// Bulk action buttons
|
|
document.addEventListener('click', (e) => {
|
|
if (e.target.matches('#bulk-mark-read')) {
|
|
e.preventDefault();
|
|
this.bulkMarkRead();
|
|
} else if (e.target.matches('#bulk-star')) {
|
|
e.preventDefault();
|
|
this.bulkStar();
|
|
} else if (e.target.matches('#bulk-archive')) {
|
|
e.preventDefault();
|
|
this.bulkArchive();
|
|
} else if (e.target.matches('#bulk-delete')) {
|
|
e.preventDefault();
|
|
this.bulkDelete();
|
|
}
|
|
});
|
|
}
|
|
|
|
selectAll(checked) {
|
|
const checkboxes = document.querySelectorAll('.message-checkbox');
|
|
checkboxes.forEach(checkbox => {
|
|
checkbox.checked = checked;
|
|
this.toggleMessage(checkbox.value, checked);
|
|
});
|
|
}
|
|
|
|
toggleMessage(messageId, selected) {
|
|
if (selected) {
|
|
this.selectedMessages.add(messageId);
|
|
} else {
|
|
this.selectedMessages.delete(messageId);
|
|
}
|
|
this.updateBulkActionsVisibility();
|
|
}
|
|
|
|
updateBulkActionsVisibility() {
|
|
const bulkActions = document.getElementById('bulk-actions');
|
|
const hasSelected = this.selectedMessages.size > 0;
|
|
|
|
if (bulkActions) {
|
|
bulkActions.style.display = hasSelected ? 'block' : 'none';
|
|
}
|
|
}
|
|
|
|
async bulkMarkRead() {
|
|
await this.performBulkAction('mark-read');
|
|
}
|
|
|
|
async bulkStar() {
|
|
await this.performBulkAction('star');
|
|
}
|
|
|
|
async bulkArchive() {
|
|
await this.performBulkAction('archive');
|
|
}
|
|
|
|
async bulkDelete() {
|
|
if (!confirm(trans('admin.CONFIRM_DELETE_SELECTED'))) return;
|
|
await this.performBulkAction('delete');
|
|
}
|
|
|
|
async performBulkAction(action) {
|
|
if (this.selectedMessages.size === 0) return;
|
|
|
|
try {
|
|
const response = await fetch('/admin/messages/bulk-action', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''
|
|
},
|
|
body: JSON.stringify({
|
|
action: action,
|
|
message_ids: Array.from(this.selectedMessages)
|
|
})
|
|
});
|
|
|
|
if (response.ok) {
|
|
window.location.reload();
|
|
} else {
|
|
throw new Error('Bulk action failed');
|
|
}
|
|
} catch (error) {
|
|
console.error('Bulk action error:', error);
|
|
alert(trans('admin.BULK_ACTION_FAILED'));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Global helper function for translations
|
|
function trans(key, replacements = {}) {
|
|
const translations = window.translations || {};
|
|
let translation = translations[key] || key;
|
|
|
|
Object.keys(replacements).forEach(search => {
|
|
translation = translation.replace(`:${search}`, replacements[search]);
|
|
});
|
|
|
|
return translation;
|
|
}
|
|
|
|
// Auto-initialize when DOM is ready
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
if (document.getElementById('message-form')) {
|
|
window.messagingManager = new MessagingManager();
|
|
}
|
|
|
|
if (document.querySelector('.message-checkbox')) {
|
|
window.messageBulkActions = new MessageBulkActions();
|
|
}
|
|
});
|
|
|
|
// Export for module systems
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = { MessagingManager, MessageBulkActions };
|
|
}
|
|
|
|
} // End of MessagingManager class guard
|