plugins_Waivers_assets_js_controllers_waiver-calendar-controller.js

import { Controller } from "@hotwired/stimulus"

/**
 * Waiver Calendar Controller
 *
 * Renders a monthly calendar showing gatherings color-coded by waiver status.
 * Red = no waivers, Yellow = partial, Green = complete, Blue = closed.
 *
 * Values:
 * - url: API endpoint for calendar data
 *
 * Targets:
 * - calendar: Container for the calendar grid
 * - monthLabel: Displays current month/year
 * - prevBtn: Previous month button
 * - nextBtn: Next month button
 */
class WaiverCalendarController extends Controller {
    static targets = ["calendar", "monthLabel", "prevBtn", "nextBtn"]
    static values = { url: String }

    connect() {
        const now = new Date()
        this.year = now.getFullYear()
        this.month = now.getMonth() + 1
        this.loadMonth()
    }

    prevMonth() {
        this.month--
        if (this.month < 1) {
            this.month = 12
            this.year--
        }
        this.loadMonth()
    }

    nextMonth() {
        this.month++
        if (this.month > 12) {
            this.month = 1
            this.year++
        }
        this.loadMonth()
    }

    async loadMonth() {
        const sep = this.urlValue.includes('?') ? '&' : '?'
        const url = `${this.urlValue}${sep}year=${this.year}&month=${this.month}`

        try {
            const response = await fetch(url, {
                headers: { 'Accept': 'application/json' }
            })
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}`)
            }
            const data = await response.json()
            this.monthLabelTarget.textContent = data.monthName
            this.renderCalendar(data)
        } catch (error) {
            console.error('Failed to load calendar data:', error)
            this.calendarTarget.innerHTML =
                '<div class="alert alert-danger">Failed to load calendar data.</div>'
        }
    }

    renderCalendar(data) {
        const year = data.year
        const month = data.month
        const firstDay = new Date(year, month - 1, 1)
        const lastDay = new Date(year, month, 0)
        const daysInMonth = lastDay.getDate()
        const startDow = firstDay.getDay() // 0=Sun

        // Build event lookup by date (only show on start_date to avoid clutter)
        const eventsByDate = {}
        for (const evt of data.events) {
            const key = evt.start_date
            if (!eventsByDate[key]) eventsByDate[key] = []
            eventsByDate[key].push(evt)
        }

        const today = new Date()
        const todayKey = this.dateKey(today)

        let html = '<div class="waiver-calendar-grid">'

        // Day headers
        const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
        for (const name of dayNames) {
            html += `<div class="waiver-calendar-header">${name}</div>`
        }

        // Leading empty cells from previous month
        const prevMonth = new Date(year, month - 1, 0)
        const prevDays = prevMonth.getDate()
        for (let i = startDow - 1; i >= 0; i--) {
            const day = prevDays - i
            html += `<div class="waiver-calendar-day other-month"><span class="waiver-calendar-day-number">${day}</span></div>`
        }

        // Current month days
        for (let d = 1; d <= daysInMonth; d++) {
            const dateObj = new Date(year, month - 1, d)
            const key = this.dateKey(dateObj)
            const isToday = key === todayKey
            const classes = ['waiver-calendar-day']
            if (isToday) classes.push('today')

            html += `<div class="${classes.join(' ')}"><span class="waiver-calendar-day-number">${d}</span>`

            const dayEvents = eventsByDate[key] || []
            for (const evt of dayEvents) {
                html += this.renderEvent(evt)
            }

            html += '</div>'
        }

        // Trailing empty cells
        const totalCells = startDow + daysInMonth
        const remaining = totalCells % 7 === 0 ? 0 : 7 - (totalCells % 7)
        for (let i = 1; i <= remaining; i++) {
            html += `<div class="waiver-calendar-day other-month"><span class="waiver-calendar-day-number">${i}</span></div>`
        }

        html += '</div>'
        this.calendarTarget.innerHTML = html
    }

    renderEvent(evt) {
        const statusColors = {
            missing: '#dc3545',
            partial: '#ffc107',
            complete: '#198754',
            closed: '#0d6efd'
        }
        const color = statusColors[evt.status] || '#6c757d'
        const multiDayClass = evt.multi_day ? ' multi-day' : ''
        const title = `${evt.name} (${evt.branch})`

        let badges = ''

        if (evt.status === 'closed') {
            badges += '<span class="badge bg-primary"><i class="bi bi-lock-fill"></i> Closed</span>'
        } else {
            if (evt.uploaded > 0) {
                badges += `<span class="badge bg-success">${evt.uploaded} Uploaded</span>`
            }
            if (evt.exempted > 0) {
                badges += `<span class="badge bg-info">${evt.exempted} Exempted</span>`
            }
            if (evt.pending > 0) {
                badges += `<span class="badge bg-warning text-dark">${evt.pending} Pending</span>`
            }
            if (evt.ready_to_close) {
                badges += '<span class="badge bg-info"><i class="bi bi-check2-square"></i> Ready to Close</span>'
            }
            if (evt.uploaded === 0 && evt.exempted === 0 && evt.pending === 0) {
                badges += '<span class="badge bg-danger">No Waivers</span>'
            }
        }

        return `<a href="${this.escapeHtml(evt.url)}" class="waiver-calendar-item${multiDayClass}" style="background-color: ${color}22; border-left-color: ${color};" title="${this.escapeHtml(title)}">` +
            `<div class="fw-bold">${this.escapeHtml(evt.name)}</div>` +
            `<div class="text-muted small text-truncate">${this.escapeHtml(evt.branch)}</div>` +
            `<div class="waiver-calendar-badges">${badges}</div>` +
            `</a>`
    }

    dateKey(d) {
        return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
    }

    escapeHtml(str) {
        const div = document.createElement('div')
        div.textContent = str
        return div.innerHTML
    }
}

if (!window.Controllers) {
    window.Controllers = {};
}
window.Controllers["waiver-calendar"] = WaiverCalendarController;