1/** 2 * Main Application Module 3 * Handles Swipers, News Loading, Share Functionality, and Calendar 4 */ 5 6class AppManager { 7 constructor() { 8 this.newsManager = null; 9 this.calendar = null; 10 this.init(); 11 } 12 13 init() { 14 // Wait for DOM to be ready 15 if (document.readyState === 'loading') { 16 document.addEventListener('DOMContentLoaded', () => this.initializeApp()); 17 } else { 18 this.initializeApp(); 19 } 20 } 21 22 initializeApp() { 23 this.initializeSwipers(); 24 this.initializeShareButtons(); 25 this.calendar = new CustomCalendar(this.newsManager); 26 this.newsManager = new NewsManager(this.calendar); 27 this.initializeCustomScrollbars(); 28 } 29 30 /** 31 * Initialize all Swiper instances 32 */ 33 initializeSwipers() { 34 const swiperConfigs = [ 35 { 36 selector: '.global-specials-swiper', 37 options: { 38 slidesPerView: 1, 39 spaceBetween: 0, 40 breakpoints: { 41 640: { slidesPerView: 2, spaceBetween: 20 }, 42 969: { slidesPerView: 3, spaceBetween: 40 } 43 }, 44 navigation: { 45 nextEl: '.global-specials-slider-btn-next', 46 prevEl: '.global-specials-slider-btn-prev' 47 } 48 } 49 }, 50 { 51 selector: '.event-holder .swiper', 52 options: { 53 loop: false, 54 slidesPerView: 1, 55 spaceBetween: 0, 56 breakpoints: { 57 969: { slidesPerView: 2, spaceBetween: 30 } 58 }, 59 navigation: { 60 nextEl: '.event-slider-btn-next', 61 prevEl: '.event-slider-btn-prev' 62 } 63 } 64 } 65 ]; 66 67 swiperConfigs.forEach(config => { 68 const element = document.querySelector(config.selector); 69 if (element) { 70 new Swiper(config.selector, config.options); 71 } 72 }); 73 } 74 75 /** 76 * Initialize share button functionality 77 */ 78 initializeCustomScrollbars() { 79 const containers = document.querySelectorAll('.event-items-container'); 80 81 containers.forEach(container => { 82 const scrollbarContainer = container.parentElement.querySelector('.scrollbar-container'); 83 if (!scrollbarContainer) return; 84 const scrollbar = scrollbarContainer.querySelector('.scrollbar'); 85 if (!scrollbar) return; 86 87 const updateScrollbar = () => { 88 const scrollHeight = container.scrollHeight; 89 const clientHeight = container.clientHeight; 90 const scrollbarContainerHeight = scrollbarContainer.clientHeight; 91 // Calculate thumb height based on visible ratio (clientHeight / scrollHeight) 92 // Ensure the thumb is not too small for usability 93 const minThumbHeight = 30; // px â you can tweak this 94 let thumbHeight = (clientHeight / scrollHeight) * scrollbarContainerHeight; 95 thumbHeight = Math.max(minThumbHeight, Math.min(thumbHeight, scrollbarContainerHeight)); 96 97 if (scrollHeight <= clientHeight) { 98 // No scrolling needed, hide scrollbar 99 scrollbar.style.display = 'none'; 100 return; 101 } 102 103 scrollbar.style.display = 'block'; 104 105 // Apply thumb height 106 scrollbar.style.height = `${thumbHeight}px`; 107 108 // Calculate how far we can move the thumb 109 const maxThumbPosition = scrollbarContainerHeight - thumbHeight; 110 111 // Calculate current scroll percentage 112 const scrollTop = container.scrollTop; 113 const maxScroll = scrollHeight - clientHeight; 114 const scrollPercent = scrollTop / maxScroll; 115 116 // Position the thumb 117 const thumbPosition = scrollPercent * maxThumbPosition; 118 scrollbar.style.top = `${thumbPosition}px`; 119 }; 120 121 // Initialize 122 updateScrollbar(); 123 124 // Bind events 125 container.addEventListener('scroll', updateScrollbar); 126 window.addEventListener('resize', updateScrollbar); 127 }); 128 } 129 130 initializeShareButtons() { 131 const shareButtons = document.querySelectorAll('.share-btn'); 132 133 shareButtons.forEach(button => { 134 button.addEventListener('click', this.handleShare.bind(this)); 135 }); 136 } 137 138 /** 139 * Handle share button clicks 140 */ 141 async handleShare(event) { 142 const button = event.currentTarget; 143 const link = button.dataset.link; 144 145 if (!navigator.share) { 146 console.warn('Web Share API not supported'); 147 return; 148 } 149 150 const shareData = { 151 title: 'JAECOO', 152 text: 'Check out this JAECOO post!', 153 url: `${window.location.origin}${link}` 154 }; 155 156 try { 157 await navigator.share(shareData); 158 } catch (error) { 159 if (error.name !== 'AbortError') { 160 console.error('Share failed:', error); 161 } 162 } 163 } 164} 165 166/** 167 * News Manager Class 168 * Handles loading and displaying news items 169 */ 170class NewsManager { 171 constructor(calendar) { 172 this.containers = document.querySelectorAll('.news-container'); 173 this.viewMoreBtn = document.getElementById('view-more'); 174 this.initialItems = 6; 175 this.additionalItems = 3; 176 this.currentItems = this.initialItems; 177 178 // Elements for filtering 179 this.tabs = document.querySelectorAll('.tab-btn'); 180 this.stateSelect = document.querySelector('.state-dropdown select'); 181 this.eventItems = document.querySelectorAll('.event-item'); 182 this.currentTab = 'Today'; 183 this.calendar = calendar; 184 // Register to receive date selection events from the calendar 185 if (this.calendar && typeof this.calendar.setDateSelectCallback === 'function') { 186 this.calendar.setDateSelectCallback((date) => this.filterByDate(date)); 187 } 188 189 this.init(); 190 } 191 192 init() { 193 if (this.containers.length === 0) return; 194 195 this.showInitialItems(); 196 this.setupViewMoreButton(); 197 this.setupFilters(); 198 this.applyFilters(); 199 } 200 201 showInitialItems() {
202 this.containers.forEach((container, index) => { 203 container.style.display = index < this.initialItems ? 'flex' : 'none'; 204 }); 205 } 206 207 setupViewMoreButton() { 208 if (!this.viewMoreBtn) return; 209 210 if (this.containers.length <= this.initialItems) { 211 this.viewMoreBtn.style.display = 'none'; 212 return; 213 } 214 215 // Bind the method to maintain context 216 this.viewMoreBtn.addEventListener('click', this.showMoreNews.bind(this)); 217 218 // Also expose globally for backward compatibility 219 window.showMoreNews = this.showMoreNews.bind(this); 220 } 221 222 // ----------------------------- 223 // Filtering helpers 224 // ----------------------------- 225 226 eventCount() { 227 const visibleItems = Array.from(this.eventItems).filter(el => { 228 return window.getComputedStyle(el).display !== 'none'; 229 }); 230 return visibleItems.length ? visibleItems.length : 0; 231 } 232 233 setupFilters() { 234 if (this.tabs.length === 0) return; 235
236 this.tabs.forEach(tab => { 237 tab.addEventListener('click', () => this.handleTabClick(tab)); 238 }); 239 240 if (this.stateSelect) { 241 this.stateSelect.addEventListener('change', () => this.applyFilters()); 242 } 243 244 // Mobile dropdown for tab filtering 245 this.tabSelect = document.querySelector('.tab-select'); 246 if (this.tabSelect) { 247 this.tabSelect.addEventListener('change', () => { 248 this.currentTab = this.tabSelect.value; 249 // Sync button active state 250 this.tabs.forEach(t => { 251 if (t.textContent.trim() === this.currentTab) { 252 t.classList.add('active'); 253 } else { 254 t.classList.remove('active'); 255 } 256 }); 257 258 // Highlight calendar accordingly 259 if (this.calendar) { 260 if (this.currentTab === 'Today') { 261 this.calendar.selectToday(); 262 this.calendar.selectionMode = 'date'; 263 } else if (this.currentTab === 'This Week') { 264 this.calendar.selectCurrentWeek(); 265 this.calendar.selectionMode = 'week'; 266 } else if (this.currentTab === 'Next Week') { 267 this.calendar.selectRelativeWeek(1); 268 this.calendar.selectionMode = 'week'; 269 } 270 } 271 272 this.applyFilters(); 273 }); 274 } 275 } 276 277 278 279 handleTabClick(tab) {
280 this.tabs.forEach(t => t.classList.remove('active')); 281 tab.classList.add('active'); 282 this.currentTab = tab.textContent.trim(); 283 284 // Highlight calendar accordingly 285 if (this.calendar) { 286 if (this.currentTab === 'Today') { 287 this.calendar.selectToday(); 288 } else if (this.currentTab === 'This Week') { 289 this.calendar.selectCurrentWeek(); 290 } else if (this.currentTab === 'Next Week') { 291 this.calendar.selectRelativeWeek(1); 292 } 293 294 } 295 296 this.applyFilters(); 297 } 298 299 getDateRange() { 300 const today = new Date(); 301 today.setHours(0, 0, 0, 0); 302 let start = new Date(today); 303 let end = new Date(today); 304 305 if (this.currentTab === 'This Week' || this.currentTab === 'Next Week') { 306 const dayOfWeek = today.getDay(); // 0 (Sun) - 6 (Sat) 307 // Start of current week (Sunday) 308 start.setDate(today.getDate() - dayOfWeek); 309 end = new Date(start); 310 end.setDate(start.getDate() + 6); 311 312 if (this.currentTab === 'Next Week') { 313 start.setDate(start.getDate() + 7); 314 end.setDate(end.getDate() + 7); 315 } 316 } 317 318 return { start, end }; 319 } 320 321 applyFilters() { 322 if (this.eventItems.length === 0) return; 323 324 const stateFilter = this.stateSelect && this.stateSelect.value ? this.stateSelect.value : null; 325 const { start, end } = this.getDateRange(); 326 327 this.eventItems.forEach(item => { 328 const eventStartStr = item.dataset.eventStartDate; 329 const eventEndStr = item.dataset.eventEndDate || eventStartStr; 330 const eventState = item.dataset.eventState; 331 332 const showByState = !stateFilter || stateFilter === eventState; 333 let showByDate = true; 334 335 const parseDate = (str) => { 336 if (!str) return null; 337 if (str.includes('-')) { 338 const [y, m, d] = str.split('-').map(Number); 339 return new Date(y, m - 1, d); 340 } 341 return new Date(str); 342 }; 343 344 if (eventStartStr) { 345 const eventStart = parseDate(eventStartStr); 346 const eventEnd = parseDate(eventEndStr); 347 eventStart.setHours(0, 0, 0, 0); 348 eventEnd.setHours(23, 59, 59, 999); 349 // Show the event if its date range overlaps with the current filter range 350 showByDate = eventStart <= end && eventEnd >= start; 351 } 352 353 item.style.display = (showByState && showByDate) ? 'flex' : 'none'; 354 }); 355 356 this.calendar.updateSelectionInfo(this.eventCount()); 357 } 358 359 /** 360 * Filter events by a specific date 361 * @param {Date} date 362 */ 363 filterByDate(date) { 364 const stateFilter = this.stateSelect && this.stateSelect.value ? this.stateSelect.value : null; 365 const start = new Date(date); 366 start.setHours(0,0,0,0); 367 const end = new Date(date); 368 end.setHours(23,59,59,999); 369 370 this.eventItems.forEach(item => { 371 const eventStartStr = item.dataset.eventStartDate; 372 const eventEndStr = item.dataset.eventEndDate || eventStartStr; 373 const eventState = item.dataset.eventState; 374 375 const showByState = !stateFilter || stateFilter === eventState; 376 let showByDate = true; 377 378 const parseDate = (str) => { 379 if (!str) return null; 380 if (str.includes('-')) { 381 const [y, m, d] = str.split('-').map(Number); 382 return new Date(y, m - 1, d); 383 } 384 return new Date(str); 385 }; 386 387 if (eventStartStr) { 388 const eventStart = parseDate(eventStartStr); 389 const eventEnd = parseDate(eventEndStr); 390 eventStart.setHours(0, 0, 0, 0); 391 eventEnd.setHours(23, 59, 59, 999); 392 const selectedTime = start.getTime(); // Beginning of selected day 393 showByDate = eventStart.getTime() <= selectedTime && eventEnd.getTime() >= selectedTime; 394 } 395 396 item.style.display = (showByState && showByDate) ? 'flex' : 'none'; 397 }); 398 399 if (this.calendar && typeof this.calendar.updateSelectionInfo === 'function') { 400 this.calendar.updateSelectionInfo(this.eventCount()); 401 } 402 } 403 404 // ----------------------------- 405 // Existing methods 406 // ----------------------------- 407 showMoreNews() { 408 const endIndex = Math.min( 409 this.currentItems + this.additionalItems, 410 this.containers.length 411 ); 412 413 for (let i = this.currentItems; i < endIndex; i++) { 414 this.containers[i].style.display = 'flex'; 415 } 416 417 this.currentItems = endIndex; 418 419 if (this.currentItems >= this.containers.length) { 420 this.viewMoreBtn.style.display = 'none'; 421 } 422 } 423} 424 425/** 426 * Custom Calendar Class 427 * Handles calendar functionality with date and week selection 428 */ 429class CustomCalendar { 430 constructor(newsManager) { 431 this.newsManager = newsManager; 432 this.currentDate = new Date(); 433 this.selectedDates = new Set(); 434 this.selectedWeek = null; 435 this.selectedWeekRef = null; 436 this.selectionMode = 'date'; 437 this.eventDates = new Set(); 438 439 this.monthNames = [ 440 'January', 'February', 'March', 'April', 'May', 'June', 441 'July', 'August', 'September', 'October', 'November', 'December' 442 ]; 443 444 this.noEvent = document.querySelector('.no-event'); 445 446 // Cache DOM elements 447 this.elements = { 448 prevMonth: document.getElementById('prevMonth'),
449 nextMonth: document.getElementById('nextMonth'), 450 monthYear: document.getElementById('monthYear'), 451 calendarWeeks: document.getElementById('calendarWeeks'), 452 selectionInfo: document.getElementById('selectionInfo') 453 }; 454 455 this.init(); 456 } 457 458 init() { 459 if (!this.validateElements()) return; 460 461 this.bindEvents(); 462 // Collect initial event dates from DOM before first render 463 this.updateEventDatesFromDOM(false); 464 this.render(); 465 466 // Auto-select today's date on init 467 this.selectToday(); 468 469 // Expose globally for backward compatibility 470 window.calendar = this; 471 } 472 473 validateElements() { 474 return Object.values(this.elements).every(element => element !== null); 475 } 476 477 bindEvents() { 478 this.elements.prevMonth.addEventListener('click', () => { 479 this.currentDate.setMonth(this.currentDate.getMonth() - 1); 480 this.updateWeekSelectionForMonth(); 481 this.render(); 482 }); 483 484 this.elements.nextMonth.addEventListener('click', () => { 485 this.currentDate.setMonth(this.currentDate.getMonth() + 1); 486 this.updateWeekSelectionForMonth(); 487 this.render(); 488 }); 489 } 490 491 /** 492 * Update selectedWeek based on stored reference date when month changes 493 */ 494 updateWeekSelectionForMonth() { 495 if (!this.selectedWeekRef) { 496 this.selectedWeek = null; 497 return; 498 } 499 500 console.log(this) 501 const ref = this.selectedWeekRef; 502 if (ref.getFullYear() === this.currentDate.getFullYear() && ref.getMonth() === this.currentDate.getMonth()) { 503 // Compute week index of ref in current month view 504 const firstDay = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth(), 1); 505 const startDate = new Date(firstDay); 506 startDate.setDate(startDate.getDate() - firstDay.getDay()); 507 const diffDays = Math.floor((ref - startDate) / (24 * 60 * 60 * 1000)); 508 this.selectedWeek = Math.floor(diffDays / 7); 509 // this.selectionMode = 'week'; 510 } else { 511 this.selectedWeek = null; 512 // this.selectionMode = 'date'; 513 } 514 } 515 516 render() { 517 this.renderHeader(); 518 this.renderCalendar(); 519 this.updateSelectionInfo(); 520 } 521 522 renderHeader() { 523 const monthYear = `${this.monthNames[this.currentDate.getMonth()]} ${this.currentDate.getFullYear()}`; 524 this.elements.monthYear.textContent = monthYear; 525 } 526 527 renderCalendar() { 528 // Helper to build YYYY-MM-DD in local time 529 } 530 531 /** 532 * Format a Date object as YYYY-MM-DD using local timezone 533 */ 534 formatDate(dateObj) { 535 const y = dateObj.getFullYear(); 536 const m = String(dateObj.getMonth() + 1).padStart(2, '0'); 537 const d = String(dateObj.getDate()).padStart(2, '0'); 538 return `${y}-${m}-${d}`; 539 } 540 541 renderCalendar() { 542 this.elements.calendarWeeks.innerHTML = ''; 543 544 const year = this.currentDate.getFullYear();
545 const month = this.currentDate.getMonth(); 546 const firstDay = new Date(year, month, 1); 547 const startDate = new Date(firstDay); 548 startDate.setDate(startDate.getDate() - firstDay.getDay()); 549 550 const today = new Date(); 551 today.setHours(0, 0, 0, 0); 552 553 // Create document fragment for better performance 554 const fragment = document.createDocumentFragment(); 555 556 // Generate 6 weeks 557 for (let week = 0; week < 5; week++) { 558 const weekRow = this.createWeekRow(week, startDate, month, today); 559 fragment.appendChild(weekRow); 560 } 561 562 this.elements.calendarWeeks.appendChild(fragment); 563 } 564 565 createWeekRow(week, startDate, month, today) { 566 const weekRow = document.createElement('div'); 567 weekRow.className = 'calendar-week'; 568 weekRow.dataset.week = week; 569 570 // Add week click handler with event delegation 571 // weekRow.addEventListener('click', (e) => { 572 // if (!e.target.classList.contains('calendar-day')) { 573 // this.selectWeek(week); 574 // } 575 // }); 576 577 // Create days for this week 578 for (let day = 0; day < 7; day++) { 579 const currentDate = new Date(startDate); 580 currentDate.setDate(startDate.getDate() + (week * 7) + day); 581 582 const dayElement = this.createDayElement(currentDate, month, week, today); 583 weekRow.appendChild(dayElement); 584 } 585 586 if (this.selectedWeek === week && this.selectionMode === 'week') { 587 weekRow.classList.add('selected-week'); 588 } 589 590 return weekRow; 591 } 592 593 createDayElement(currentDate, month, week, today) { 594 const dayElement = document.createElement('div'); 595 dayElement.className = 'calendar-day'; 596 dayElement.textContent = currentDate.getDate(); 597 598 const dateString = this.formatDate(currentDate); 599 600 dayElement.dataset.date = dateString; 601 602 // Add appropriate classes 603 const classes = []; 604 if (currentDate.getMonth() !== month) classes.push('other-month'); 605 if (currentDate.getTime() === today.getTime()) classes.push('today'); 606 if (this.selectedDates.has(dateString)) classes.push('selected'); 607 if (this.selectedWeek !== null && this.selectedWeek === week && currentDate.getMonth() === month) { 608 classes.push('in-selected-week'); 609 } 610 // Mark dates that have at least one event on that day 611 if (this.eventDates.has(dateString)) classes.push('has-event'); 612 613 dayElement.classList.add(...classes); 614 615 // Add click handler 616 dayElement.addEventListener('click', (e) => { 617 e.stopPropagation(); 618 this.selectDate(dateString); 619 app.calendar.selectionMode = 'date'; 620 621 if(dateString === this.formatDate(today)) { 622 app.newsManager.handleTabClick(app.newsManager.tabs[0]); 623 return; 624 } 625 else { 626 app.newsManager.tabs.forEach(t => t.classList.remove('active')); 627 } 628 }); 629 630 return dayElement; 631 } 632 633 /** 634 * Scan the DOM for .event-item elements and populate eventDates set. 635 * If onlyVisible is true, only consider currently visible event items 636 * (after filters), otherwise include all. 637 */ 638 updateEventDatesFromDOM(onlyVisible = false) { 639 this.eventDates.clear(); 640 const items = document.querySelectorAll('.event-item'); 641 const parseDate = (str) => { 642 if (!str) return null; 643 if (str.includes('-')) { 644 const [y, m, d] = str.split('-').map(Number); 645 return new Date(y, m - 1, d); 646 } 647 return new Date(str); 648 }; 649 650 items.forEach(item => { 651 if (onlyVisible) { 652 const style = window.getComputedStyle(item); 653 if (style.display === 'none') return; 654 } 655 656 const startStr = item.dataset.eventStartDate; 657 const endStr = item.dataset.eventEndDate || startStr; 658 const start = parseDate(startStr); 659 const end = parseDate(endStr); 660 if (!start || !end) return; 661 start.setHours(0,0,0,0); 662 end.setHours(0,0,0,0); 663 664 // Add each date in the inclusive range to the set 665 const cur = new Date(start); 666 while (cur.getTime() <= end.getTime()) { 667 this.eventDates.add(this.formatDate(cur)); 668 cur.setDate(cur.getDate() + 1); 669 } 670 }); 671 } 672 673 selectDate(dateString) { 674 this.selectedWeek = null; 675 this.selectionMode = 'date'; 676 677 // Toggle date selection 678 if (this.selectedDates.has(dateString)) {
679 // this.selectedDates.delete(dateString); 680 return; 681 } else { 682 this.selectedDates.clear(); // Single selection 683 this.selectedDates.add(dateString); 684 } 685 686 this.render(); 687 688 // Notify external listener (e.g., NewsManager) about selected date 689 if (typeof this.dateSelectCallback === 'function') { 690 this.dateSelectCallback(new Date(dateString)); 691 } 692 } 693 694 selectWeek(weekIndex) { 695 this.selectedDates.clear(); 696 this.selectionMode = 'week'; 697 // Toggle week selection 698 this.selectedWeek = weekIndex; 699 this.render(); 700 } 701 702 setDateSelectCallback(callback) { 703 this.dateSelectCallback = callback; 704 } 705 706 updateSelectionInfo(eventCount) { 707 let content = ''; 708 709 // if (this.selectionMode === 'date' && this.selectedDates.size > 0) { 710 // const dates = Array.from(this.selectedDates) 711 // .map(date => new Date(date).toLocaleDateString()) 712 // .join(', '); 713 // content = `<span class="selection-type">Selected Date:</span> ${dates}`; 714 // content += '<button class="clear-btn" onclick="calendar.clearSelection()">Clear</button>'; 715 // } else if (this.selectionMode === 'week' && this.selectedWeek !== null) { 716 // const weekDates = this.getWeekDates(this.selectedWeek); 717 // content = `<span class="selection-type">Selected Week:</span> ${weekDates}`; 718 // content += '<button class="clear-btn" onclick="calendar.clearSelection()">Clear</button>'; 719 // } else { 720 // content = 'Total Events: '; 721 // } 722 723 content = 'Total Events: ' + (eventCount ?? 0); 724 725 if(eventCount === 0) { 726 this.noEvent.classList.remove('hidden'); 727 } else { 728 this.noEvent.classList.add('hidden'); 729 } 730 731 this.elements.selectionInfo.innerHTML = content; 732 } 733 734 getWeekDates(weekIndex) { 735 const year = this.currentDate.getFullYear();
736 const month = this.currentDate.getMonth(); 737 const firstDay = new Date(year, month, 1); 738 const startDate = new Date(firstDay); 739 startDate.setDate(startDate.getDate() - firstDay.getDay()); 740 741 const weekStart = new Date(startDate); 742 weekStart.setDate(startDate.getDate() + (weekIndex * 7)); 743 744 const weekEnd = new Date(weekStart); 745 weekEnd.setDate(weekStart.getDate() + 6); 746 747 return `${weekStart.toLocaleDateString()} - ${weekEnd.toLocaleDateString()}`; 748 } 749 750 clearSelection() { 751 this.selectedDates.clear(); 752 this.selectedWeek = null; 753 this.selectedWeekRef = null; 754 this.render(); 755 } 756 757 // ----------------------------- 758 // Convenience selection helpers 759 // ----------------------------- 760 selectToday() { 761 const todayStr = this.formatDate(new Date()); 762 this.selectDate(todayStr); 763 } 764 765 /** 766 * Select the calendar week that contains the provided date 767 * @param {Date} dateObj 768 */ 769 selectWeekByDate(dateObj) { 770 // Ensure the calendar is on the same month as the target date 771 this.currentDate = new Date(dateObj.getFullYear(), dateObj.getMonth(), 1); 772 773 const firstDay = new Date(this.currentDate.getFullYear(), this.currentDate.getMonth(), 1); 774 const startDate = new Date(firstDay); 775 startDate.setDate(startDate.getDate() - firstDay.getDay()); 776 777 const diffDays = Math.floor((dateObj - startDate) / (24 * 60 * 60 * 1000)); 778 const weekIndex = Math.floor(diffDays / 7); 779 780 this.selectWeek(weekIndex); 781 this.selectedWeekRef = new Date(dateObj.getFullYear(), dateObj.getMonth(), dateObj.getDate()); 782 } 783 784 /** Highlight current week */ 785 selectCurrentWeek() { 786 this.selectWeekByDate(new Date()); 787 } 788 789 /** Highlight week relative to current (e.g., +1 for next week, -1 for previous) */ 790 selectRelativeWeek(offset) { 791 const ref = new Date(); 792 ref.setDate(ref.getDate() + (offset * 7)); 793 this.selectWeekByDate(ref); 794 } 795 796 // Public API methods 797 getSelectedDates() { 798 return Array.from(this.selectedDates); 799 } 800 801 getSelectedWeek() { 802 return this.selectedWeek === null ? null : this.getWeekDates(this.selectedWeek); 803 } 804} 805 806// Initialize the application 807const app = new AppManager(); 808 809// Legacy jQuery support (if needed) 810if (typeof $ !== 'undefined') { 811 $(document).ready(() => { 812 console.log('jQuery compatibility layer loaded'); 813 }); 814}
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.