import { Controller } from '@hotwired/stimulus' import { Toast } from 'maps_maplibre/components/toast' /** * Controller for visit creation modal in Maps V2 */ export default class extends Controller { static targets = [ 'modal', 'form', 'modalTitle', 'nameInput', 'startTimeInput', 'endTimeInput', 'latitudeInput', 'longitudeInput', 'submitButton' ] static values = { apiKey: String } connect() { console.log('[Visit Creation V2] Controller connected') this.marker = null this.mapController = null this.editingVisitId = null this.setupEventListeners() } setupEventListeners() { document.addEventListener('visit:edit', (e) => { this.openForEdit(e.detail.visit) }) } disconnect() { this.cleanup() } /** * Open the modal with coordinates */ open(lat, lng, mapController) { console.log('[Visit Creation V2] Opening modal', { lat, lng }) this.editingVisitId = null this.mapController = mapController this.latitudeInputTarget.value = lat this.longitudeInputTarget.value = lng // Set modal title and button for creation if (this.hasModalTitleTarget) { this.modalTitleTarget.textContent = 'Create New Visit' } if (this.hasSubmitButtonTarget) { this.submitButtonTarget.textContent = 'Create Visit' } // Set default times const now = new Date() const oneHourLater = new Date(now.getTime() + (60 * 60 * 1000)) this.startTimeInputTarget.value = this.formatDateTime(now) this.endTimeInputTarget.value = this.formatDateTime(oneHourLater) // Show modal this.modalTarget.classList.add('modal-open') // Focus on name input setTimeout(() => this.nameInputTarget.focus(), 100) // Add marker to map this.addMarker(lat, lng) } /** * Open the modal for editing an existing visit */ openForEdit(visit) { console.log('[Visit Creation V2] Opening modal for edit', visit) this.editingVisitId = visit.id // Set modal title and button for editing if (this.hasModalTitleTarget) { this.modalTitleTarget.textContent = 'Edit Visit' } if (this.hasSubmitButtonTarget) { this.submitButtonTarget.textContent = 'Update Visit' } // Fill form with visit data this.nameInputTarget.value = visit.name || '' this.latitudeInputTarget.value = visit.latitude this.longitudeInputTarget.value = visit.longitude // Convert timestamps to datetime-local format this.startTimeInputTarget.value = this.formatDateTime(new Date(visit.started_at)) this.endTimeInputTarget.value = this.formatDateTime(new Date(visit.ended_at)) // Show modal this.modalTarget.classList.add('modal-open') // Focus on name input setTimeout(() => this.nameInputTarget.focus(), 100) // Try to get map controller from the maps--maplibre controller const mapElement = document.querySelector('[data-controller*="maps--maplibre"]') if (mapElement) { const app = window.Stimulus || window.Application this.mapController = app?.getControllerForElementAndIdentifier(mapElement, 'maps--maplibre') } // Add marker to map this.addMarker(visit.latitude, visit.longitude) } /** * Close the modal */ close() { console.log('[Visit Creation V2] Closing modal') // Hide modal this.modalTarget.classList.remove('modal-open') // Reset form this.formTarget.reset() // Reset editing state this.editingVisitId = null // Remove marker this.removeMarker() } /** * Handle form submission */ async submit(event) { event.preventDefault() const isEdit = this.editingVisitId !== null console.log(`[Visit Creation V2] Submitting form (${isEdit ? 'edit' : 'create'})`) const formData = new FormData(this.formTarget) const visitData = { visit: { name: formData.get('name'), started_at: formData.get('started_at'), ended_at: formData.get('ended_at'), latitude: parseFloat(formData.get('latitude')), longitude: parseFloat(formData.get('longitude')), status: 'confirmed' } } try { const url = isEdit ? `/api/v1/visits/${this.editingVisitId}` : '/api/v1/visits' const method = isEdit ? 'PATCH' : 'POST' const response = await fetch(url, { method: method, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.apiKeyValue}`, 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')?.content || '' }, body: JSON.stringify(visitData) }) if (!response.ok) { const errorData = await response.json() throw new Error(errorData.error || `Failed to ${isEdit ? 'update' : 'create'} visit`) } const visit = await response.json() console.log(`[Visit Creation V2] Visit ${isEdit ? 'updated' : 'created'} successfully`, visit) // Show success message this.showToast(`Visit ${isEdit ? 'updated' : 'created'} successfully`, 'success') // Close modal this.close() // Dispatch event to notify map controller const eventName = isEdit ? 'visit:updated' : 'visit:created' document.dispatchEvent(new CustomEvent(eventName, { detail: { visit } })) } catch (error) { console.error(`[Visit Creation V2] Error ${isEdit ? 'updating' : 'creating'} visit:`, error) this.showToast(error.message || `Failed to ${isEdit ? 'update' : 'create'} visit`, 'error') } } /** * Add marker to map */ addMarker(lat, lng) { if (!this.mapController) return // Remove existing marker if any this.removeMarker() // Create marker element const el = document.createElement('div') el.className = 'visit-creation-marker' el.innerHTML = '📍' el.style.fontSize = '30px' // Use maplibregl if available (from mapController) const maplibregl = window.maplibregl if (maplibregl) { this.marker = new maplibregl.Marker({ element: el }) .setLngLat([lng, lat]) .addTo(this.mapController.map) } } /** * Remove marker from map */ removeMarker() { if (this.marker) { this.marker.remove() this.marker = null } } /** * Clean up resources */ cleanup() { this.removeMarker() } /** * Format date for datetime-local input */ formatDateTime(date) { return date.toISOString().slice(0, 16) } /** * Show toast notification */ showToast(message, type = 'info') { Toast[type](message) } }