// Job Photos JavaScript const jobPhotoModule = { jobId: null, allPhotos: [], currentPhotoIndex: 0, _tagApi: null, init: function(jobId, tagSuggestions) { this.jobId = jobId; this._tagSuggestions = tagSuggestions || []; this.loadJobPhotos(); this.setupDragDrop(); this.setupFileInput(); // Initialise the tag widget each time the upload modal opens so it's always fresh const uploadModal = document.getElementById('uploadPhotoModal'); if (uploadModal) { uploadModal.addEventListener('show.bs.modal', () => { this._tagApi = initTagInput('photoTagsHidden', 'photoTagsContainer', { suggestions: this._tagSuggestions }); }); } }, loadJobPhotos: function() { console.log('loadJobPhotos called'); // Add cache-busting parameter to prevent browser from using cached data fetch(`/Jobs/GetJobPhotos?jobId=${this.jobId}&_t=${Date.now()}`, { cache: 'no-cache', headers: { 'Cache-Control': 'no-cache' } }) .then(response => response.json()) .then(data => { console.log('GetJobPhotos response:', data); if (data.success) { this.allPhotos = data.photos; this.renderPhotoGallery(data.photos); document.getElementById('photoCount').textContent = data.photos.length; } }) .catch(error => console.error('Error loading photos:', error)); }, renderPhotoGallery: function(photos) { const gallery = document.getElementById('photoGallery'); if (!gallery) { console.error('photoGallery element not found in DOM'); return; } // Clear gallery gallery.innerHTML = ''; if (photos.length === 0) { // Show "no photos" message gallery.innerHTML = `

No photos uploaded yet

Click "Upload Photo" to add photos
`; return; } // Add photo cards photos.forEach((photo, index) => { const col = document.createElement('div'); col.className = 'col-md-4 col-sm-6'; col.innerHTML = `
${this.escapeHtml(photo.caption || 'Job photo')}
${photo.photoTypeDisplay}
${photo.caption ? `

${this.escapeHtml(photo.caption)}

` : ''}
`; gallery.appendChild(col); }); }, viewPhoto: function(index, isNavigating = false) { this.currentPhotoIndex = index; const photo = this.allPhotos[index]; document.getElementById('viewPhotoImage').src = `/Jobs/GetPhoto/${photo.id}`; document.getElementById('viewPhotoTitle').textContent = photo.photoTypeDisplay + ' Photo'; document.getElementById('photoPosition').textContent = `Photo ${index + 1} of ${this.allPhotos.length}`; document.getElementById('photoDetailCaption').textContent = photo.caption || 'No caption'; document.getElementById('photoDetailType').textContent = photo.photoTypeDisplay; document.getElementById('photoDetailDate').textContent = new Date(photo.uploadedDate).toLocaleDateString(); document.getElementById('photoDetailUploader').textContent = photo.uploadedByName; // Tags const tagsRow = document.getElementById('photoDetailTagsRow'); const tagsSpan = document.getElementById('photoDetailTags'); if (photo.tagsList && photo.tagsList.length > 0) { tagsSpan.innerHTML = photo.tagsList.map(t => `${this.escapeHtml(t)}` ).join(''); tagsRow.style.display = ''; } else { tagsRow.style.display = 'none'; } // Only show modal if not navigating (prevents backdrop duplication) if (!isNavigating) { const modalElement = document.getElementById('viewPhotoModal'); const modal = bootstrap.Modal.getInstance(modalElement) || new bootstrap.Modal(modalElement); modal.show(); } }, navigatePhoto: function(direction) { this.currentPhotoIndex += direction; if (this.currentPhotoIndex < 0) this.currentPhotoIndex = this.allPhotos.length - 1; if (this.currentPhotoIndex >= this.allPhotos.length) this.currentPhotoIndex = 0; // Pass true to indicate we're navigating (don't re-show modal) this.viewPhoto(this.currentPhotoIndex, true); }, uploadPhoto: function() { const fileInput = document.getElementById('photoFile'); const caption = document.getElementById('photoCaption').value; const photoType = document.getElementById('photoType').value; if (!fileInput.files || fileInput.files.length === 0) { showWarning('Please select a photo to upload', 'No File Selected'); return; } const formData = new FormData(); formData.append('jobId', this.jobId); formData.append('photo', fileInput.files[0]); formData.append('caption', caption); formData.append('photoType', photoType); formData.append('tags', document.getElementById('photoTagsHidden').value); const token = document.querySelector('input[name="__RequestVerificationToken"]').value; fetch('/Jobs/UploadPhoto', { method: 'POST', headers: { 'RequestVerificationToken': token }, body: formData }) .then(response => response.json()) .then(data => { console.log('Upload response:', data); if (data.success) { const modalElement = document.getElementById('uploadPhotoModal'); const modal = bootstrap.Modal.getInstance(modalElement); modal.hide(); // Wait for modal animation to complete setTimeout(() => { console.log('Reloading photos after upload...'); this.loadJobPhotos(); this.clearPhotoSelection(); this.showToast('Photo uploaded successfully', 'success'); }, 400); } else { this.showToast(data.message || 'Error uploading photo', 'danger'); } }) .catch(error => { console.error('Error:', error); this.showToast('An error occurred while uploading', 'danger'); }); }, deletePhoto: function() { if (!confirm('Are you sure you want to delete this photo?')) return; const photo = this.allPhotos[this.currentPhotoIndex]; const token = document.querySelector('input[name="__RequestVerificationToken"]').value; fetch('/Jobs/DeletePhoto?id=' + photo.id, { method: 'POST', headers: { 'RequestVerificationToken': token, 'Content-Type': 'application/json' } }) .then(response => response.json()) .then(data => { console.log('Delete response:', data); if (data.success) { bootstrap.Modal.getInstance(document.getElementById('viewPhotoModal')).hide(); // Wait for modal animation to complete setTimeout(() => { console.log('Reloading photos after delete...'); this.loadJobPhotos(); this.showToast('Photo deleted successfully', 'success'); }, 400); } else { this.showToast(data.message || 'Error deleting photo', 'danger'); } }) .catch(error => { console.error('Error:', error); this.showToast('An error occurred while deleting', 'danger'); }); }, setupDragDrop: function() { const dropZone = document.getElementById('dropZone'); const fileInput = document.getElementById('photoFile'); ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { dropZone.addEventListener(eventName, (e) => { e.preventDefault(); e.stopPropagation(); }, false); }); ['dragenter', 'dragover'].forEach(eventName => { dropZone.addEventListener(eventName, () => { dropZone.classList.add('drag-over'); }, false); }); ['dragleave', 'drop'].forEach(eventName => { dropZone.addEventListener(eventName, () => { dropZone.classList.remove('drag-over'); }, false); }); dropZone.addEventListener('drop', (e) => { const dt = e.dataTransfer; const files = dt.files; fileInput.files = files; this.handleFileSelect({ target: fileInput }); }, false); }, setupFileInput: function() { document.getElementById('photoFile').addEventListener('change', (e) => this.handleFileSelect(e)); }, handleFileSelect: function(e) { const file = e.target.files[0]; if (!file) return; if (!file.type.startsWith('image/')) { showError('Please select an image file', 'Invalid File Type'); return; } if (file.size > 10 * 1024 * 1024) { showError('File size must be less than 10 MB', 'File Too Large'); return; } const reader = new FileReader(); reader.onload = (e) => { document.getElementById('previewImage').src = e.target.result; document.getElementById('photoPreview').classList.remove('d-none'); document.getElementById('dropZone').style.display = 'none'; }; reader.readAsDataURL(file); }, clearPhotoSelection: function() { document.getElementById('photoFile').value = ''; document.getElementById('photoCaption').value = ''; document.getElementById('photoType').value = '1'; document.getElementById('photoPreview').classList.add('d-none'); document.getElementById('dropZone').style.display = 'block'; if (this._tagApi) this._tagApi.clear(); }, getPhotoTypeBadgeClass: function(type) { const classes = { 0: 'info', // Before 1: 'primary', // Progress 2: 'success', // After 3: 'warning', // Quality Check 4: 'danger', // Issue 5: 'success' // Completed }; return classes[type] || 'secondary'; }, escapeHtml: function(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }, showToast: function(message, type) { const alertClass = `alert-${type}`; const alert = document.createElement('div'); alert.className = `alert ${alertClass} alert-dismissible fade show position-fixed top-0 end-0 m-3`; alert.style.zIndex = '9999'; alert.innerHTML = ` ${message} `; document.body.appendChild(alert); setTimeout(() => alert.remove(), 3000); } };