1// edit-report-modal.js 2// ×× ×× ×ת ××××× ×©× ×¢×¨××ת ××××ת ×¢× ×××רת ש××××¥ ××××× 3 4class EditReportModal { 5 constructor() { 6 this.modal = null; 7 this.assignmentSelect = null; 8 this.scholarshipSelect = null; 9 this.organizationSelect = null; 10 this.startTimeInput = null; 11 this.endTimeInput = null; 12 this.hoursDisplay = null; 13 this.hoursContent = null; 14 this.dateInput = null; 15 this.submitBtn = null; 16 this.form = null; 17 this.assignmentLookup = new Map(); 18 this.scholarshipOrgMapping = {}; 19 this.hasActiveAssignments = false; 20 } 21 22 init(modalElement) { 23 this.modal = modalElement; 24 if (!this.modal) { 25 return; 26 } 27 28 this.assignmentSelect = this.modal.querySelector('#assignmentSelect'); 29 this.scholarshipSelect = this.modal.querySelector('#scholarshipSelect'); 30 this.organizationSelect = this.modal.querySelector('#organizationSelect'); 31 this.startTimeInput = this.modal.querySelector('#startTimeInput'); 32 this.endTimeInput = this.modal.querySelector('#endTimeInput'); 33 this.hoursDisplay = this.modal.querySelector('#hoursDisplay'); 34 this.hoursContent = this.modal.querySelector('#hoursContent'); 35 this.dateInput = this.modal.querySelector('#dateInput'); 36 this.submitBtn = this.modal.querySelector('#submitBtn'); 37 this.form = this.modal.querySelector('#editReportForm'); 38 39 if (!this.assignmentSelect || !this.scholarshipSelect || !this.organizationSelect) { 40 console.error('[edit-report] Missing key DOM elements inside modal'); 41 return; 42 } 43 44 this.parseMapping(); 45 this.populateAssignmentSelect(); 46 this.applyAssignmentSelection(this.assignmentSelect.value); 47 this.attachEventListeners(); 48 this.calculateHours(); 49 this.toggleStateForAssignments(); 50 } 51 52 parseMapping() { 53 const mappingInput = this.modal.querySelector('#scholarshipOrgMappingData'); 54 try { 55 this.scholarshipOrgMapping = mappingInput 56 ? JSON.parse(mappingInput.value || '{}') 57 : {}; 58 } catch (error) { 59 console.warn('[edit-report] Failed parsing scholarshipOrgMapping', error); 60 this.scholarshipOrgMapping = {}; 61 } 62 63 const container = this.modal.querySelector('.container-edit-report'); 64 this.hasActiveAssignments = 65 container?.dataset.hasActiveAssignments === 'true' || 66 Object.keys(this.scholarshipOrgMapping || {}).length > 0; 67 } 68 69 populateAssignmentSelect() { 70 this.assignmentLookup.clear(); 71 this.assignmentSelect.innerHTML = ''; 72 73 const assignments = []; 74 const scholarshipOptions = Array.from(this.scholarshipSelect.options || []); 75 76 scholarshipOptions.forEach((option) => { 77 const scholarshipId = option.value; 78 if (!scholarshipId) { 79 return; 80 } 81 const scholarshipName = option.textContent.trim(); 82 const relatedOrgs = this.scholarshipOrgMapping[String(scholarshipId)] || []; 83 84 relatedOrgs.forEach((org) => { 85 const key = `${scholarshipId}::${org.id}`; 86 assignments.push({ 87 id: key, 88 scholarshipId, 89 scholarshipName, 90 orgId: String(org.id), 91 orgName: org.name, 92 }); 93 }); 94 }); 95 96 assignments.sort((a, b) => { 97 const scholarshipCompare = a.scholarshipName.localeCompare(b.scholarshipName, 'he'); 98 if (scholarshipCompare !== 0) { 99 return scholarshipCompare; 100 } 101 return a.orgName.localeCompare(b.orgName, 'he'); 102 }); 103 104 assignments.forEach((assignment) => { 105 const option = document.createElement('option'); 106 option.value = assignment.id; 107 option.textContent = `${assignment.scholarshipName} - ${assignment.orgName}`; 108 this.assignmentLookup.set(assignment.id, assignment); 109 this.assignmentSelect.appendChild(option); 110 }); 111 112 const initialValue = 113 this.scholarshipSelect.value && this.organizationSelect.value 114 ? `${this.scholarshipSelect.value}::${this.organizationSelect.value}` 115 : ''; 116 117 if (initialValue && this.assignmentLookup.has(initialValue)) { 118 this.assignmentSelect.value = initialValue; 119 } else if (assignments.length > 0) { 120 this.assignmentSelect.value = assignments[0].id; 121 } else { 122 this.assignmentSelect.value = ''; 123 } 124 } 125 126 attachEventListeners() { 127 this.assignmentSelect.addEventListener('change', () => { 128 this.applyAssignmentSelection(this.assignmentSelect.value); 129 if (this.assignmentSelect.value) { 130 this.clearError(this.assignmentSelect); 131 } 132 this.calculateHours(); 133 }); 134 135 this.startTimeInput?.addEventListener('change', () => this.calculateHours()); 136 this.startTimeInput?.addEventListener('input', () => this.calculateHours()); 137 this.endTimeInput?.addEventListener('change', () => this.calculateHours()); 138 this.endTimeInput?.addEventListener('input', () => this.calculateHours()); 139 this.dateInput?.addEventListener('change', () => this.validateDate()); 140 141 this.form?.addEventListener('submit', (event) => this.handleSubmit(event)); 142 } 143 144 toggleStateForAssignments() { 145 const hasAssignments = this.assignmentLookup.size > 0; 146 const allowSelection = this.hasActiveAssignments && hasAssignments; 147 148 if (!allowSelection) { 149 this.assignmentSelect.disabled = true; 150 this.assignmentSelect.value = ''; 151 this.applyAssignmentSelection(''); 152 this.submitBtn?.classList.add('btn-submit-edit-report-disabled'); 153 this.submitBtn?.setAttribute('disabled', 'disabled'); 154 return; 155 } 156 157 this.assignmentSelect.disabled = false;
158 if (this.submitBtn?.classList.contains('btn-submit-edit-report-disabled')) { 159 this.submitBtn.classList.remove('btn-submit-edit-report-disabled'); 160 this.submitBtn.removeAttribute('disabled'); 161 } 162 } 163 164 applyAssignmentSelection(value) { 165 if (!value || !this.assignmentLookup.has(value)) { 166 this.scholarshipSelect.value = ''; 167 this.organizationSelect.value = ''; 168 return; 169 } 170 const assignment = this.assignmentLookup.get(value); 171 this.scholarshipSelect.value = assignment.scholarshipId; 172 this.organizationSelect.value = assignment.orgId; 173 } 174 175 calculateHours() { 176 if (!this.startTimeInput || !this.endTimeInput || !this.hoursDisplay || !this.hoursContent) { 177 return; 178 } 179 180 const startTime = this.startTimeInput.value; 181 const endTime = this.endTimeInput.value; 182 183 if (startTime && endTime) { 184 const start = new Date(`2000-01-01T${startTime}`); 185 const end = new Date(`2000-01-01T${endTime}`); 186 187 if (end > start) { 188 const diff = (end - start) / (1000 * 60 * 60); 189 this.clearError(this.startTimeInput); 190 this.clearError(this.endTimeInput); 191 192 this.hoursDisplay.style.display = 'block'; 193 this.hoursDisplay.classList.remove('warning'); 194 195 if (diff > 12) { 196 this.hoursDisplay.classList.add('warning'); 197 this.hoursContent.innerHTML = `<i class="fas fa-exclamation-triangle"></i> <span>ס×"× ×©×¢×ת: <strong>${diff.toFixed(2)}</strong> (××תר ×-12 שע×ת!)</span>`; 198 } else { 199 this.hoursContent.innerHTML = `<i class="fas fa-calculator"></i> <span>ס×"× ×©×¢×ת: <strong>${diff.toFixed(2)}</strong></span>`; 200 } 201 } else { 202 this.hoursDisplay.style.display = 'none'; 203 this.showError(this.endTimeInput, 'שעת ×ס××× ××××ת ××××ת ××××רת ×שעת ××ת×××'); 204 } 205 } else { 206 this.hoursDisplay.style.display = 'none'; 207 this.clearError(this.startTimeInput); 208 this.clearError(this.endTimeInput); 209 } 210 } 211 212 validateDate() { 213 if (!this.dateInput) { 214 return true; 215 } 216 const selectedDate = new Date(this.dateInput.value); 217 const today = new Date(); 218 const minDate = new Date('2023-01-01'); 219 220 if (selectedDate > today) { 221 this.showError(this.dateInput, '×× × ××ª× ××××ר ת×ר×× ×¢×ª×××'); 222 return false; 223 } 224 if (selectedDate < minDate) { 225 this.showError(this.dateInput, '×× × ××ª× ××××ר ת×ר×× ××¤× × ×©× ×ª 2023'); 226 return false; 227 } 228 229 this.clearError(this.dateInput); 230 return true; 231 } 232 233 handleSubmit(event) { 234 let isValid = true; 235 236 if (!this.validateDate()) { 237 isValid = false; 238 } 239 240 if (!this.assignmentSelect.value) { 241 this.showError(this.assignmentSelect, '×××× ××××ר ש××××¥ ×××× ××ר×××'); 242 isValid = false; 243 } 244 245 const startTime = this.startTimeInput?.value; 246 const endTime = this.endTimeInput?.value; 247 248 if (startTime && endTime) { 249 const start = new Date(`2000-01-01T${startTime}`); 250 const end = new Date(`2000-01-01T${endTime}`); 251 252 if (end <= start) { 253 this.showError(this.endTimeInput, 'שעת ×ס××× ××××ת ××××ת ××××רת ×שעת ××ת×××'); 254 isValid = false; 255 } 256 } 257 258 if (!isValid) { 259 event.preventDefault(); 260 261 // Show toast notification for validation errors 262 if (typeof Swal !== 'undefined') { 263 Swal.fire({ 264 toast: true, 265 position: 'top-end', 266 icon: 'error', 267 title: '×× × ×ª×§× ×©××××ת ×××פס',
268 showConfirmButton: false, 269 timer: 3000, 270 timerProgressBar: true 271 }); 272 } 273 } else { 274 // Add loading state to submit button 275 if (this.submitBtn) { 276 this.submitBtn.classList.add('loading'); 277 this.submitBtn.disabled = true; 278 } 279 280 // Show submitting toast 281 if (typeof Swal !== 'undefined') { 282 Swal.fire({ 283 toast: true, 284 position: 'top-end', 285 icon: 'info', 286 title: '××¢××× ×××××...', 287 showConfirmButton: false, 288 timer: 2000 289 }); 290 } 291 } 292 } 293 294 showError(element, message) { 295 if (!element) { 296 return; 297 } 298 this.clearError(element); 299 element.classList.add('is-invalid'); 300 element.classList.remove('is-valid'); 301 302 const errorDiv = document.createElement('div'); 303 errorDiv.className = 'error-message-edit-report'; 304 errorDiv.textContent = message; 305 306 const container = this.getErrorContainer(element); 307 container?.appendChild(errorDiv); 308 } 309 310 clearError(element) { 311 if (!element) { 312 return; 313 } 314 element.classList.remove('is-invalid'); 315 element.style.borderColor = ''; 316 const container = this.getErrorContainer(element); 317 if (container) { 318 container.innerHTML = ''; 319 } 320 } 321 322 markAsValid(element) { 323 if (!element) { 324 return; 325 } 326 element.classList.remove('is-invalid'); 327 element.classList.add('is-valid'); 328 } 329 330 getErrorContainer(element) { 331 if (element.id === 'assignmentSelect') { 332 return this.modal.querySelector('#assignmentErrorContainer'); 333 } 334 return element.parentElement?.querySelector('.error-container-edit-report') || null; 335 } 336} 337 338window.editReportModal = new EditReportModal(); 339 340document.addEventListener('DOMContentLoaded', () => { 341 const standaloneContainer = document.querySelector('.container-edit-report'); 342 if (standaloneContainer && !standaloneContainer.closest('#editReportModal')) { 343 window.editReportModal.init(document); 344 } 345});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.