PageSourceSearch

https://milgot-app.milgot-tau.org.il/static/js/edit-report-modal.js

js milgot-tau.org.il collected 2026-10-08 03:14:52 UTC 12,645 bytes, 345 lines download raw bytes

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.