2026-04-01 17:43:39 +02:00
|
|
|
|
/* WooDoo Frontend JS */
|
2026-04-01 13:58:27 +02:00
|
|
|
|
( function () {
|
|
|
|
|
|
'use strict';
|
|
|
|
|
|
|
2026-04-01 17:43:39 +02:00
|
|
|
|
// ══════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
// INVOICE EMAIL
|
|
|
|
|
|
// ══════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
const inv = window.WooDooInvoices;
|
|
|
|
|
|
if ( inv ) {
|
|
|
|
|
|
document.querySelectorAll( '.woodoo-send-invoice' ).forEach( function ( btn ) {
|
|
|
|
|
|
btn.addEventListener( 'click', function () {
|
|
|
|
|
|
const invoiceId = this.dataset.id;
|
|
|
|
|
|
const $btn = this;
|
|
|
|
|
|
const row = $btn.closest( 'tr' );
|
|
|
|
|
|
|
|
|
|
|
|
// Remove any previous feedback in this row
|
|
|
|
|
|
const prev = row.querySelector( '.woodoo-inline-msg' );
|
|
|
|
|
|
if ( prev ) prev.remove();
|
|
|
|
|
|
|
|
|
|
|
|
$btn.disabled = true;
|
|
|
|
|
|
$btn.textContent = 'Enviando…';
|
|
|
|
|
|
|
|
|
|
|
|
const body = new URLSearchParams( {
|
|
|
|
|
|
action : 'woodoo_send_invoice_email',
|
|
|
|
|
|
nonce : inv.nonce,
|
|
|
|
|
|
invoice_id : invoiceId,
|
|
|
|
|
|
} );
|
|
|
|
|
|
|
|
|
|
|
|
fetch( inv.ajax_url, { method: 'POST', body } )
|
|
|
|
|
|
.then( r => r.json() )
|
|
|
|
|
|
.then( function ( res ) {
|
|
|
|
|
|
if ( res.success ) {
|
|
|
|
|
|
$btn.textContent = '✓ Enviado';
|
|
|
|
|
|
$btn.style.color = '#065f46';
|
|
|
|
|
|
// Show success message below the row
|
|
|
|
|
|
const td = document.createElement( 'td' );
|
|
|
|
|
|
td.colSpan = 6;
|
|
|
|
|
|
td.className = 'woodoo-inline-msg woodoo-inline-msg--ok';
|
|
|
|
|
|
td.textContent = res.data.message;
|
|
|
|
|
|
const msgRow = document.createElement( 'tr' );
|
|
|
|
|
|
msgRow.className = 'woodoo-inline-msg';
|
|
|
|
|
|
msgRow.appendChild( td );
|
|
|
|
|
|
row.insertAdjacentElement( 'afterend', msgRow );
|
|
|
|
|
|
// Auto-hide after 6s
|
|
|
|
|
|
setTimeout( function () {
|
|
|
|
|
|
msgRow.remove();
|
|
|
|
|
|
$btn.disabled = false;
|
|
|
|
|
|
$btn.textContent = '✉ Reenviar';
|
|
|
|
|
|
$btn.style.color = '';
|
|
|
|
|
|
}, 6000 );
|
|
|
|
|
|
} else {
|
|
|
|
|
|
showInvoiceError( row, res.data || 'Error desconocido.' );
|
|
|
|
|
|
$btn.disabled = false;
|
|
|
|
|
|
$btn.textContent = '✉ Reenviar';
|
|
|
|
|
|
}
|
|
|
|
|
|
} )
|
|
|
|
|
|
.catch( function () {
|
|
|
|
|
|
showInvoiceError( row, 'Error de conexión. Inténtalo de nuevo.' );
|
|
|
|
|
|
$btn.disabled = false;
|
|
|
|
|
|
$btn.textContent = '✉ Reenviar';
|
|
|
|
|
|
} );
|
|
|
|
|
|
} );
|
|
|
|
|
|
} );
|
|
|
|
|
|
|
|
|
|
|
|
function showInvoiceError( row, msg ) {
|
|
|
|
|
|
const prev = row.querySelector( '.woodoo-inline-msg' );
|
|
|
|
|
|
if ( prev ) prev.remove();
|
|
|
|
|
|
const td = document.createElement( 'td' );
|
|
|
|
|
|
td.colSpan = 6;
|
|
|
|
|
|
td.className = 'woodoo-inline-msg woodoo-inline-msg--err';
|
|
|
|
|
|
td.textContent = msg;
|
|
|
|
|
|
const msgRow = document.createElement( 'tr' );
|
|
|
|
|
|
msgRow.className = 'woodoo-inline-msg';
|
|
|
|
|
|
msgRow.appendChild( td );
|
|
|
|
|
|
row.insertAdjacentElement( 'afterend', msgRow );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ══════════════════════════════════════════════════════════════════════
|
|
|
|
|
|
// CALENDAR BOOKING
|
|
|
|
|
|
// ══════════════════════════════════════════════════════════════════════
|
2026-04-01 13:58:27 +02:00
|
|
|
|
const cfg = window.WooDooCalendar;
|
2026-04-01 17:43:39 +02:00
|
|
|
|
if ( ! cfg ) return;
|
|
|
|
|
|
|
|
|
|
|
|
const i18n = cfg.i18n;
|
|
|
|
|
|
const ajaxUrl = cfg.ajax_url;
|
|
|
|
|
|
const nonce = cfg.nonce;
|
|
|
|
|
|
|
|
|
|
|
|
const dateInput = document.getElementById( 'woodoo-booking-date' );
|
|
|
|
|
|
const slotsWrap = document.getElementById( 'woodoo-slots-wrap' );
|
|
|
|
|
|
const slotsGrid = document.getElementById( 'woodoo-slots-grid' );
|
|
|
|
|
|
const confirmBlock = document.getElementById( 'woodoo-booking-confirm' );
|
|
|
|
|
|
const bookBtn = document.getElementById( 'woodoo-book-btn' );
|
|
|
|
|
|
const notesArea = document.getElementById( 'woodoo-booking-notes' );
|
|
|
|
|
|
const slotLabel = document.getElementById( 'woodoo-selected-slot-label' );
|
|
|
|
|
|
const successMsg = document.getElementById( 'woodoo-booking-success' );
|
|
|
|
|
|
const errorMsg = document.getElementById( 'woodoo-booking-error' );
|
2026-04-01 13:58:27 +02:00
|
|
|
|
|
|
|
|
|
|
let selectedStart = null;
|
|
|
|
|
|
let selectedEnd = null;
|
|
|
|
|
|
|
2026-04-01 17:43:39 +02:00
|
|
|
|
// ── Date change → load slots ─────────────────────────────────────────
|
2026-04-01 13:58:27 +02:00
|
|
|
|
if ( dateInput ) {
|
|
|
|
|
|
dateInput.addEventListener( 'change', function () {
|
|
|
|
|
|
const date = this.value;
|
|
|
|
|
|
if ( ! date ) return;
|
|
|
|
|
|
|
|
|
|
|
|
slotsGrid.innerHTML = '<em>' + esc( i18n.loading ) + '</em>';
|
2026-04-01 17:43:39 +02:00
|
|
|
|
slotsWrap.style.display = 'block';
|
2026-04-01 13:58:27 +02:00
|
|
|
|
confirmBlock.style.display = 'none';
|
|
|
|
|
|
successMsg.style.display = 'none';
|
|
|
|
|
|
errorMsg.style.display = 'none';
|
|
|
|
|
|
selectedStart = selectedEnd = null;
|
|
|
|
|
|
bookBtn.disabled = true;
|
|
|
|
|
|
|
|
|
|
|
|
post( 'woodoo_get_slots', { date } )
|
|
|
|
|
|
.then( res => {
|
|
|
|
|
|
if ( ! res.success ) {
|
|
|
|
|
|
slotsGrid.innerHTML = '<em>' + esc( i18n.error ) + '</em>';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
const slots = res.data;
|
|
|
|
|
|
if ( ! slots.length ) {
|
|
|
|
|
|
slotsGrid.innerHTML = '<em>' + esc( i18n.no_slots ) + '</em>';
|
|
|
|
|
|
confirmBlock.style.display = 'none';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
slotsGrid.innerHTML = '';
|
|
|
|
|
|
slots.forEach( slot => {
|
|
|
|
|
|
const btn = document.createElement( 'button' );
|
2026-04-01 17:43:39 +02:00
|
|
|
|
btn.type = 'button';
|
|
|
|
|
|
btn.className = 'woodoo-slot';
|
2026-04-01 13:58:27 +02:00
|
|
|
|
btn.textContent = formatTime( slot.start ) + ' – ' + formatTime( slot.end );
|
|
|
|
|
|
btn.dataset.start = slot.start;
|
|
|
|
|
|
btn.dataset.end = slot.end;
|
|
|
|
|
|
btn.addEventListener( 'click', onSlotClick );
|
|
|
|
|
|
slotsGrid.appendChild( btn );
|
|
|
|
|
|
} );
|
|
|
|
|
|
} )
|
2026-04-01 17:43:39 +02:00
|
|
|
|
.catch( () => { slotsGrid.innerHTML = '<em>' + esc( i18n.error ) + '</em>'; } );
|
2026-04-01 13:58:27 +02:00
|
|
|
|
} );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function onSlotClick( e ) {
|
2026-04-01 17:43:39 +02:00
|
|
|
|
document.querySelectorAll( '.woodoo-slot' ).forEach( b => b.classList.remove( 'selected' ) );
|
2026-04-01 13:58:27 +02:00
|
|
|
|
const btn = e.currentTarget;
|
|
|
|
|
|
btn.classList.add( 'selected' );
|
|
|
|
|
|
selectedStart = btn.dataset.start;
|
|
|
|
|
|
selectedEnd = btn.dataset.end;
|
|
|
|
|
|
|
|
|
|
|
|
slotLabel.textContent =
|
2026-04-01 17:43:39 +02:00
|
|
|
|
new Date( selectedStart ).toLocaleDateString( 'es-ES', {
|
2026-04-01 13:58:27 +02:00
|
|
|
|
weekday: 'long', year: 'numeric', month: 'long', day: 'numeric',
|
2026-04-01 17:43:39 +02:00
|
|
|
|
} ) + ', ' + formatTime( selectedStart ) + ' – ' + formatTime( selectedEnd );
|
2026-04-01 13:58:27 +02:00
|
|
|
|
|
|
|
|
|
|
confirmBlock.style.display = 'block';
|
|
|
|
|
|
bookBtn.disabled = false;
|
|
|
|
|
|
successMsg.style.display = 'none';
|
|
|
|
|
|
errorMsg.style.display = 'none';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Confirm booking ──────────────────────────────────────────────────
|
|
|
|
|
|
if ( bookBtn ) {
|
|
|
|
|
|
bookBtn.addEventListener( 'click', function () {
|
|
|
|
|
|
if ( ! selectedStart || ! selectedEnd ) return;
|
2026-04-01 17:43:39 +02:00
|
|
|
|
bookBtn.disabled = true;
|
2026-04-01 13:58:27 +02:00
|
|
|
|
bookBtn.textContent = i18n.booking;
|
|
|
|
|
|
errorMsg.style.display = 'none';
|
|
|
|
|
|
|
|
|
|
|
|
post( 'woodoo_book_slot', {
|
|
|
|
|
|
start : selectedStart,
|
|
|
|
|
|
end : selectedEnd,
|
|
|
|
|
|
notes : notesArea ? notesArea.value : '',
|
|
|
|
|
|
} )
|
|
|
|
|
|
.then( res => {
|
|
|
|
|
|
if ( res.success ) {
|
2026-04-01 17:43:39 +02:00
|
|
|
|
successMsg.textContent = res.data.message;
|
2026-04-01 13:58:27 +02:00
|
|
|
|
successMsg.style.display = 'block';
|
|
|
|
|
|
confirmBlock.style.display = 'none';
|
|
|
|
|
|
slotsWrap.style.display = 'none';
|
2026-04-01 17:43:39 +02:00
|
|
|
|
dateInput.value = '';
|
2026-04-01 13:58:27 +02:00
|
|
|
|
selectedStart = selectedEnd = null;
|
|
|
|
|
|
refreshMeetings();
|
|
|
|
|
|
} else {
|
2026-04-01 17:43:39 +02:00
|
|
|
|
showCalError( res.data || i18n.error );
|
|
|
|
|
|
bookBtn.disabled = false;
|
2026-04-01 13:58:27 +02:00
|
|
|
|
bookBtn.textContent = i18n.book_btn;
|
|
|
|
|
|
}
|
|
|
|
|
|
} )
|
|
|
|
|
|
.catch( () => {
|
2026-04-01 17:43:39 +02:00
|
|
|
|
showCalError( i18n.error );
|
|
|
|
|
|
bookBtn.disabled = false;
|
2026-04-01 13:58:27 +02:00
|
|
|
|
bookBtn.textContent = i18n.book_btn;
|
|
|
|
|
|
} );
|
|
|
|
|
|
} );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Cancel meeting ───────────────────────────────────────────────────
|
|
|
|
|
|
document.querySelectorAll( '.woodoo-cancel-meeting' ).forEach( btn => {
|
|
|
|
|
|
btn.addEventListener( 'click', function () {
|
|
|
|
|
|
if ( ! confirm( i18n.cancel_confirm ) ) return;
|
|
|
|
|
|
const eventId = this.dataset.eventId;
|
|
|
|
|
|
const card = this.closest( '.woodoo-meeting-card' );
|
2026-04-01 17:43:39 +02:00
|
|
|
|
this.disabled = true;
|
2026-04-01 13:58:27 +02:00
|
|
|
|
this.textContent = i18n.cancelling;
|
|
|
|
|
|
|
|
|
|
|
|
post( 'woodoo_cancel_meeting', { event_id: eventId } )
|
|
|
|
|
|
.then( res => {
|
|
|
|
|
|
if ( res.success ) {
|
2026-04-01 17:43:39 +02:00
|
|
|
|
card.style.opacity = '0.4';
|
2026-04-01 13:58:27 +02:00
|
|
|
|
card.style.transition = 'opacity .3s';
|
|
|
|
|
|
setTimeout( () => card.remove(), 350 );
|
|
|
|
|
|
} else {
|
|
|
|
|
|
alert( res.data || i18n.error );
|
2026-04-01 17:43:39 +02:00
|
|
|
|
this.disabled = false;
|
|
|
|
|
|
this.textContent = 'Cancelar';
|
2026-04-01 13:58:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
} )
|
|
|
|
|
|
.catch( () => {
|
2026-04-01 17:43:39 +02:00
|
|
|
|
this.disabled = false;
|
|
|
|
|
|
this.textContent = 'Cancelar';
|
2026-04-01 13:58:27 +02:00
|
|
|
|
} );
|
|
|
|
|
|
} );
|
|
|
|
|
|
} );
|
|
|
|
|
|
|
|
|
|
|
|
// ── Refresh meetings list ────────────────────────────────────────────
|
|
|
|
|
|
function refreshMeetings() {
|
|
|
|
|
|
const listEl = document.getElementById( 'woodoo-meetings-list' );
|
|
|
|
|
|
if ( ! listEl ) return;
|
|
|
|
|
|
post( 'woodoo_get_meetings', {} )
|
|
|
|
|
|
.then( res => {
|
2026-04-01 17:43:39 +02:00
|
|
|
|
if ( ! res.success || ! res.data.length ) {
|
|
|
|
|
|
listEl.innerHTML = '<p class="woodoo-empty">No tienes reuniones programadas.</p>';
|
2026-04-01 13:58:27 +02:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-04-01 17:43:39 +02:00
|
|
|
|
const meses = [ '', 'ene', 'feb', 'mar', 'abr', 'may', 'jun',
|
|
|
|
|
|
'jul', 'ago', 'sep', 'oct', 'nov', 'dic' ];
|
2026-04-01 13:58:27 +02:00
|
|
|
|
let html = '<div class="woodoo-meetings-grid">';
|
2026-04-01 17:43:39 +02:00
|
|
|
|
res.data.forEach( ev => {
|
2026-04-01 13:58:27 +02:00
|
|
|
|
const start = new Date( ev.start.replace( ' ', 'T' ) );
|
|
|
|
|
|
const end = new Date( ev.stop.replace( ' ', 'T' ) );
|
|
|
|
|
|
html +=
|
|
|
|
|
|
'<div class="woodoo-meeting-card" data-event-id="' + ev.id + '">' +
|
|
|
|
|
|
'<div class="woodoo-meeting-card__date">' +
|
2026-04-01 17:43:39 +02:00
|
|
|
|
'<span class="woodoo-meeting-card__day">' + pad( start.getDate() ) + '</span>' +
|
|
|
|
|
|
'<span class="woodoo-meeting-card__month">' + ( meses[ start.getMonth() + 1 ] || '' ) + '</span>' +
|
2026-04-01 13:58:27 +02:00
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="woodoo-meeting-card__info">' +
|
|
|
|
|
|
'<strong>' + esc( ev.name ) + '</strong>' +
|
2026-04-01 17:43:39 +02:00
|
|
|
|
'<span class="woodoo-meeting-card__time">' + timeStr( start ) + ' – ' + timeStr( end ) + '</span>' +
|
2026-04-01 13:58:27 +02:00
|
|
|
|
( ev.videocall_location
|
2026-04-01 17:43:39 +02:00
|
|
|
|
? '<a href="' + esc( ev.videocall_location ) + '" target="_blank" rel="noopener" class="woodoo-meeting-card__video">Unirse a la videollamada</a>'
|
2026-04-01 13:58:27 +02:00
|
|
|
|
: '' ) +
|
|
|
|
|
|
'</div>' +
|
|
|
|
|
|
'<div class="woodoo-meeting-card__actions">' +
|
2026-04-01 17:43:39 +02:00
|
|
|
|
'<button class="woodoo-cancel-meeting woodoo-btn woodoo-btn--sm woodoo-btn--outline" data-event-id="' + ev.id + '">Cancelar</button>' +
|
2026-04-01 13:58:27 +02:00
|
|
|
|
'</div>' +
|
|
|
|
|
|
'</div>';
|
|
|
|
|
|
} );
|
|
|
|
|
|
html += '</div>';
|
|
|
|
|
|
listEl.innerHTML = html;
|
|
|
|
|
|
} );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Helpers ──────────────────────────────────────────────────────────
|
|
|
|
|
|
function post( action, data ) {
|
|
|
|
|
|
const body = new URLSearchParams( Object.assign( { action, nonce }, data ) );
|
2026-04-01 17:43:39 +02:00
|
|
|
|
return fetch( ajaxUrl, { method: 'POST', body } ).then( r => r.json() );
|
2026-04-01 13:58:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-01 17:43:39 +02:00
|
|
|
|
function formatTime( dt ) { return dt.slice( 11, 16 ); }
|
|
|
|
|
|
function timeStr( d ) { return pad( d.getHours() ) + ':' + pad( d.getMinutes() ); }
|
|
|
|
|
|
function pad( n ) { return String( n ).padStart( 2, '0' ); }
|
2026-04-01 13:58:27 +02:00
|
|
|
|
|
|
|
|
|
|
function esc( str ) {
|
|
|
|
|
|
return String( str )
|
2026-04-01 17:43:39 +02:00
|
|
|
|
.replace( /&/g, '&' ).replace( /</g, '<' )
|
|
|
|
|
|
.replace( />/g, '>' ).replace( /"/g, '"' );
|
2026-04-01 13:58:27 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-01 17:43:39 +02:00
|
|
|
|
function showCalError( msg ) {
|
|
|
|
|
|
errorMsg.textContent = msg;
|
2026-04-01 13:58:27 +02:00
|
|
|
|
errorMsg.style.display = 'block';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} )();
|