// js/main.js // EventBus const EventBus = { listeners: {}, on(event, callback) { if (!this.listeners[event]) { this.listeners[event] = []; } this.listeners[event].push(callback); }, emit(event, data) { if (this.listeners[event]) { this.listeners[event].forEach(callback => callback(data)); } } }; // NotificationService class NotificationService { constructor() { this.toastElement = document.getElementById('toast'); } showMessage(message, type = 'info') { this.toastElement.textContent = message; this.toastElement.className = `fixed bottom-4 right-4 px-6 py-3 rounded shadow-lg ${ type === 'error' ? 'bg-red-500' : 'bg-green-500' } text-white`; this.toastElement.classList.remove('hidden'); setTimeout(() => { this.toastElement.classList.add('hidden'); }, 3000); } showError(message) { this.showMessage(message, 'error'); } showSuccess(message) { this.showMessage(message, 'success'); } } // ApiClient class ApiClient { constructor({ baseUrl, authToken, csrfToken }) { this.baseUrl = baseUrl; this.authToken = authToken; this.csrfToken = csrfToken; } async request(endpoint, options = {}) { const url = `${this.baseUrl}${endpoint}`; const headers = { 'Content-Type': 'application/json', 'Authorization': `Token ${this.authToken}`, 'X-CSRF-Token': this.csrfToken }; try { const response = await fetch(url, { ...options, headers: { ...headers, ...options.headers } }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const contentType = response.headers.get("content-type"); if (contentType && contentType.includes("application/json")) { return await response.json(); } return await response.text(); } catch (error) { console.error('API request failed:', error); throw error; } } // API methods async getEvents() { return this.request('/new-events/'); } async getZekkenNumbers(eventCode) { return this.request(`/zekken_numbers/${eventCode}`); } async getTeamInfo(zekkenNumber) { return this.request(`/team_info/${zekkenNumber}/`); } // ... その他のAPI methods } // PointsCalculator class PointsCalculator { calculate({ checkins, latePoints = 0 }) { const totalPoints = this.calculateTotalPoints(checkins); const buyPoints = this.calculateBuyPoints(checkins); return { totalPoints, buyPoints, latePoints, finalPoints: totalPoints + buyPoints + latePoints }; } calculateTotalPoints(checkins) { return checkins.reduce((total, checkin) => { if (checkin.validate_location && !checkin.buy_flag) { return total + (checkin.checkin_point || 0); } return total; }, 0); } calculateBuyPoints(checkins) { return checkins.reduce((total, checkin) => { if (checkin.validate_location && checkin.buy_flag) { return total + (checkin.buy_point || 0); } return total; }, 0); } } // SupervisorPanel - メインアプリケーションクラス class SupervisorPanel { constructor(options) { this.element = options.element; this.apiClient = new ApiClient(options.apiConfig); this.notification = new NotificationService(); this.pointsCalculator = new PointsCalculator(); this.eventBus = EventBus; this.state = { currentEvent: null, currentZekken: null, teamData: null, checkins: [] }; } // ... SupervisorPanelの実装 ... } // アプリケーションの初期化 document.addEventListener('DOMContentLoaded', () => { const app = new SupervisorPanel({ element: document.getElementById('app'), apiConfig: { baseUrl: '/api', authToken: localStorage.getItem('authToken'), csrfToken: document.querySelector('meta[name="csrf-token"]').content } }); app.initialize(); });