
Added missing children's story field to both create and edit forms in the schedule management section. This field was already supported in JSON imports but was missing from the manual entry forms.
1844 lines
78 KiB
JavaScript
1844 lines
78 KiB
JavaScript
const API_BASE = 'https://api.rockvilletollandsda.church/api';
|
|
let token = localStorage.getItem('adminToken') || '';
|
|
let currentEvents = [];
|
|
let recurringTypes = [];
|
|
|
|
// Centralized API wrapper with token validation
|
|
async function apiCall(url, options = {}) {
|
|
// Add authorization header if token exists
|
|
const headers = {
|
|
...options.headers,
|
|
...(token ? { 'Authorization': 'Bearer ' + token } : {})
|
|
};
|
|
|
|
const response = await fetch(url, {
|
|
...options,
|
|
headers
|
|
});
|
|
|
|
// Handle token expiration
|
|
if (response.status === 401) {
|
|
console.warn('Token expired or invalid, redirecting to login');
|
|
handleSessionExpiry();
|
|
throw new Error('Session expired');
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
function handleSessionExpiry() {
|
|
// Clear invalid token
|
|
token = '';
|
|
localStorage.removeItem('adminToken');
|
|
|
|
// Show login screen
|
|
document.getElementById('dashboard').classList.add('hidden');
|
|
document.getElementById('login').classList.remove('hidden');
|
|
|
|
// Show user-friendly message
|
|
showError('loginError', 'Your session has expired. Please log in again.');
|
|
|
|
// Close any open modals
|
|
closeModal();
|
|
}
|
|
|
|
// Validate token on page load
|
|
async function validateToken() {
|
|
if (!token) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
// Try a simple API call to validate the token
|
|
const response = await apiCall(API_BASE + '/admin/events/pending');
|
|
return response.ok;
|
|
} catch (error) {
|
|
if (error.message === 'Session expired') {
|
|
return false;
|
|
}
|
|
// Network errors or other issues - assume token is still valid
|
|
console.warn('Could not validate token due to network error:', error);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
function getImageUrl(event) {
|
|
// Backend stores complete URLs, frontend just uses them directly
|
|
return event.image || null;
|
|
}
|
|
|
|
async function loadRecurringTypes() {
|
|
try {
|
|
const response = await fetch(API_BASE + '/config/recurring-types');
|
|
const data = await response.json();
|
|
|
|
if (Array.isArray(data)) {
|
|
// Convert API array to value/label objects, skip 'none'
|
|
recurringTypes = data.filter(type => type !== 'none').map(type => ({
|
|
value: type, // Keep original lowercase value
|
|
label: formatRecurringTypeLabel(type)
|
|
}));
|
|
} else {
|
|
// Fallback to default types if API fails
|
|
recurringTypes = [
|
|
{ value: 'daily', label: 'Daily' },
|
|
{ value: 'weekly', label: 'Weekly' },
|
|
{ value: 'biweekly', label: 'Biweekly' },
|
|
{ value: 'first_tuesday', label: 'First Tuesday' }
|
|
];
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load recurring types:', error);
|
|
// Fallback to default types
|
|
recurringTypes = [
|
|
{ value: 'daily', label: 'Daily' },
|
|
{ value: 'weekly', label: 'Weekly' },
|
|
{ value: 'biweekly', label: 'Biweekly' },
|
|
{ value: 'first_tuesday', label: 'First Tuesday' }
|
|
];
|
|
}
|
|
}
|
|
|
|
function formatRecurringTypeLabel(type) {
|
|
switch (type) {
|
|
case 'daily': return 'Daily';
|
|
case 'weekly': return 'Weekly';
|
|
case 'biweekly': return 'Every Two Weeks';
|
|
case 'monthly': return 'Monthly';
|
|
case 'first_tuesday': return 'First Tuesday of Month';
|
|
case '2nd_3rd_saturday_monthly': return '2nd/3rd Saturday Monthly';
|
|
default:
|
|
// Capitalize first letter of each word
|
|
return type.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
|
|
}
|
|
}
|
|
|
|
function generateRecurringTypeOptions(selectedValue) {
|
|
console.log('generateRecurringTypeOptions called with selectedValue:', selectedValue);
|
|
console.log('Available recurringTypes:', recurringTypes);
|
|
|
|
let options = '<option value=""' + (!selectedValue ? ' selected' : '') + '>No Recurrence</option>';
|
|
|
|
recurringTypes.forEach(type => {
|
|
const selected = selectedValue === type.value ? ' selected' : '';
|
|
console.log(`Comparing "${selectedValue}" === "${type.value}": ${selectedValue === type.value}`);
|
|
options += '<option value="' + type.value + '"' + selected + '>' + type.label + '</option>';
|
|
});
|
|
|
|
console.log('Generated options HTML:', options);
|
|
return options;
|
|
}
|
|
|
|
function handleLogin() {
|
|
const username = document.getElementById('username').value;
|
|
const password = document.getElementById('password').value;
|
|
|
|
fetch(API_BASE + '/auth/login', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({ username: username, password: password })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success && data.data.token) {
|
|
token = data.data.token;
|
|
localStorage.setItem('adminToken', token);
|
|
showDashboard();
|
|
loadStats();
|
|
} else {
|
|
showError('loginError', 'Invalid credentials');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showError('loginError', 'Login failed: ' + error.message);
|
|
});
|
|
}
|
|
|
|
function showDashboard() {
|
|
document.getElementById('login').classList.add('hidden');
|
|
document.getElementById('dashboard').classList.remove('hidden');
|
|
loadRecurringTypes();
|
|
}
|
|
|
|
function handleLogout() {
|
|
token = '';
|
|
localStorage.removeItem('adminToken');
|
|
document.getElementById('dashboard').classList.add('hidden');
|
|
document.getElementById('login').classList.remove('hidden');
|
|
document.getElementById('password').value = '';
|
|
}
|
|
|
|
function showError(elementId, message) {
|
|
const errorDiv = document.getElementById(elementId);
|
|
errorDiv.textContent = message;
|
|
errorDiv.classList.remove('hidden');
|
|
setTimeout(() => errorDiv.classList.add('hidden'), 5000);
|
|
}
|
|
|
|
async function loadStats() {
|
|
try {
|
|
// Load pending events count
|
|
const pendingResponse = await apiCall(API_BASE + '/admin/events/pending');
|
|
const pendingData = await pendingResponse.json();
|
|
|
|
if (pendingData.success) {
|
|
document.getElementById('pendingCount').textContent = pendingData.data ? pendingData.data.length : 0;
|
|
}
|
|
} catch (error) {
|
|
if (error.message !== 'Session expired') {
|
|
console.error('Failed to load pending stats:', error);
|
|
}
|
|
}
|
|
|
|
try {
|
|
// Load total events count (this endpoint might not need auth)
|
|
const totalResponse = await fetch(API_BASE + '/events');
|
|
const totalData = await totalResponse.json();
|
|
|
|
if (totalData.success) {
|
|
document.getElementById('totalCount').textContent = totalData.data.total || 0;
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load total stats:', error);
|
|
}
|
|
}
|
|
|
|
async function loadPending() {
|
|
try {
|
|
const response = await apiCall(API_BASE + '/admin/events/pending');
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
currentEvents = Array.isArray(data.data) ? data.data : [];
|
|
renderPendingEvents(currentEvents);
|
|
document.getElementById('pendingCount').textContent = data.data ? data.data.length : 0;
|
|
} else {
|
|
showContentError('Failed to load pending events');
|
|
}
|
|
} catch (error) {
|
|
if (error.message !== 'Session expired') {
|
|
showContentError('Error loading pending events: ' + error.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
function loadAllEvents() {
|
|
fetch(API_BASE + '/events')
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
const events = data.data.items || [];
|
|
renderAllEvents(events);
|
|
} else {
|
|
showContentError('Failed to load events');
|
|
}
|
|
})
|
|
.catch(error => showContentError('Error loading events: ' + error.message));
|
|
}
|
|
|
|
function renderPendingEvents(events) {
|
|
const content = document.getElementById('content');
|
|
|
|
if (events.length === 0) {
|
|
content.innerHTML = '<div class="empty-state"><div class="empty-icon success">🎉</div><p class="empty-title">No pending events!</p><p>All caught up. Great job managing your church events!</p></div>';
|
|
return;
|
|
}
|
|
|
|
let html = '<div class="content-header"><h2 class="content-title"><span>📋</span>Pending Events</h2><div class="content-badge pending">' + events.length + ' pending</div></div>';
|
|
|
|
events.forEach(event => {
|
|
const imageUrl = getImageUrl(event);
|
|
html += '<div class="event-item"><div class="event-content">';
|
|
html += '<div class="event-image">';
|
|
if (imageUrl) {
|
|
html += '<img src="' + escapeHtml(imageUrl) + '" alt="Event image" onerror="this.parentElement.innerHTML=\'📅\'">';
|
|
} else {
|
|
html += '📅';
|
|
}
|
|
html += '</div>';
|
|
html += '<div class="event-details">';
|
|
html += '<h3 class="event-title">' + escapeHtml(event.title) + '</h3>';
|
|
html += '<div class="event-description">' + renderHtmlContent(event.description) + '</div>';
|
|
html += '<div class="event-meta">';
|
|
html += '<div class="meta-item"><span>📅</span>' + formatDate(event.start_time) + '</div>';
|
|
html += '<div class="meta-item"><span>📍</span>' + escapeHtml(event.location) + '</div>';
|
|
html += '<div class="meta-item"><span>🏷️</span><span class="category-badge pending">' + escapeHtml(event.category) + '</span></div>';
|
|
if (event.submitter_email) {
|
|
html += '<div class="meta-item"><span>✉️</span>' + escapeHtml(event.submitter_email) + '</div>';
|
|
}
|
|
html += '</div></div>';
|
|
html += '<div class="event-actions">';
|
|
html += '<button onclick="showApproveModal(\'' + event.id + '\')" class="btn-action btn-approve"><span>✓</span>Approve</button>';
|
|
html += '<button onclick="showRejectModal(\'' + event.id + '\')" class="btn-action btn-reject"><span>✗</span>Reject</button>';
|
|
html += '<button onclick="deleteEvent(\'' + event.id + '\')" class="btn-action btn-delete"><span>🗑️</span>Delete</button>';
|
|
html += '</div></div></div>';
|
|
});
|
|
|
|
content.innerHTML = html;
|
|
}
|
|
|
|
function renderAllEvents(events) {
|
|
const content = document.getElementById('content');
|
|
|
|
if (events.length === 0) {
|
|
content.innerHTML = '<div class="empty-state"><div class="empty-icon info">📅</div><p class="empty-title">No approved events yet.</p><p>Events will appear here once you approve pending submissions.</p></div>';
|
|
return;
|
|
}
|
|
|
|
let html = '<div class="content-header"><h2 class="content-title"><span>📅</span>All Events</h2><div class="content-badge total">' + events.length + ' published</div></div>';
|
|
|
|
events.forEach(event => {
|
|
const imageUrl = getImageUrl(event);
|
|
html += '<div class="event-item"><div class="event-content">';
|
|
html += '<div class="event-image">';
|
|
if (imageUrl) {
|
|
html += '<img src="' + escapeHtml(imageUrl) + '" alt="Event image" onerror="this.parentElement.innerHTML=\'📅\'">';
|
|
} else {
|
|
html += '📅';
|
|
}
|
|
html += '</div>';
|
|
html += '<div class="event-details">';
|
|
html += '<h3 class="event-title">' + escapeHtml(event.title) + '</h3>';
|
|
html += '<div class="event-description">' + renderHtmlContent(event.description) + '</div>';
|
|
html += '<div class="event-meta">';
|
|
html += '<div class="meta-item"><span>📅</span>' + formatDate(event.start_time) + '</div>';
|
|
html += '<div class="meta-item"><span>📍</span>' + escapeHtml(event.location) + '</div>';
|
|
html += '<div class="meta-item"><span>🏷️</span><span class="category-badge approved">' + escapeHtml(event.category) + '</span></div>';
|
|
if (event.is_featured) {
|
|
html += '<div class="meta-item"><span>⭐</span><span class="category-badge featured">Featured</span></div>';
|
|
}
|
|
html += '</div></div>';
|
|
html += '<div class="event-actions">';
|
|
html += '<button onclick="editEvent(\'' + event.id + '\')" class="btn-action btn-edit"><span>✏️</span>Edit</button>';
|
|
html += '<button onclick="deleteApprovedEvent(\'' + event.id + '\')" class="btn-action btn-delete"><span>🗑️</span>Delete</button>';
|
|
html += '</div></div></div>';
|
|
});
|
|
|
|
content.innerHTML = html;
|
|
}
|
|
|
|
function showApproveModal(eventId) {
|
|
const event = currentEvents.find(e => e.id === eventId);
|
|
if (!event) return;
|
|
|
|
document.getElementById('modalTitle').textContent = 'Approve Event';
|
|
document.getElementById('modalContent').innerHTML = '<div class="form-grid"><div><h4>' + escapeHtml(event.title) + '</h4><p>' + escapeHtml(event.description) + '</p></div><div><label for="approveNotes">Admin Notes (optional)</label><textarea id="approveNotes" rows="3" placeholder="Add any notes for this approval..."></textarea></div></div>';
|
|
document.getElementById('modalActions').innerHTML = '<button onclick="approveEvent(\'' + eventId + '\')" class="btn-approve"><span>✓</span>Approve Event</button>';
|
|
document.getElementById('modal').classList.remove('hidden');
|
|
}
|
|
|
|
function showRejectModal(eventId) {
|
|
const event = currentEvents.find(e => e.id === eventId);
|
|
if (!event) return;
|
|
|
|
document.getElementById('modalTitle').textContent = 'Reject Event';
|
|
document.getElementById('modalContent').innerHTML = '<div class="form-grid"><div><h4>' + escapeHtml(event.title) + '</h4><p>' + escapeHtml(event.description) + '</p></div><div><label for="rejectNotes">Reason for rejection *</label><textarea id="rejectNotes" rows="3" placeholder="Please explain why this event is being rejected..." required></textarea></div></div>';
|
|
document.getElementById('modalActions').innerHTML = '<button onclick="rejectEvent(\'' + eventId + '\')" class="btn-reject"><span>✗</span>Reject Event</button>';
|
|
document.getElementById('modal').classList.remove('hidden');
|
|
}
|
|
|
|
function closeModal() {
|
|
document.getElementById('modal').classList.add('hidden');
|
|
}
|
|
|
|
function approveEvent(eventId) {
|
|
const notes = document.getElementById('approveNotes').value;
|
|
|
|
fetch(API_BASE + '/admin/events/pending/' + eventId + '/approve', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': 'Bearer ' + token,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ admin_notes: notes || null })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
closeModal();
|
|
loadPending();
|
|
loadStats();
|
|
alert('Event approved successfully!');
|
|
} else {
|
|
alert('Failed to approve event: ' + (data.message || 'Unknown error'));
|
|
}
|
|
})
|
|
.catch(error => alert('Error approving event: ' + error.message));
|
|
}
|
|
|
|
function rejectEvent(eventId) {
|
|
const notes = document.getElementById('rejectNotes').value;
|
|
|
|
if (!notes.trim()) {
|
|
alert('Please provide a reason for rejection');
|
|
return;
|
|
}
|
|
|
|
fetch(API_BASE + '/admin/events/pending/' + eventId + '/reject', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': 'Bearer ' + token,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify({ admin_notes: notes })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
closeModal();
|
|
loadPending();
|
|
loadStats();
|
|
alert('Event rejected successfully!');
|
|
} else {
|
|
alert('Failed to reject event: ' + (data.message || 'Unknown error'));
|
|
}
|
|
})
|
|
.catch(error => alert('Error rejecting event: ' + error.message));
|
|
}
|
|
|
|
function deleteEvent(eventId) {
|
|
if (!confirm('Are you sure you want to delete this pending event?')) return;
|
|
|
|
fetch(API_BASE + '/admin/events/pending/' + eventId, {
|
|
method: 'DELETE',
|
|
headers: { 'Authorization': 'Bearer ' + token }
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
loadPending();
|
|
loadStats();
|
|
alert('Event deleted successfully!');
|
|
} else {
|
|
alert('Failed to delete event: ' + (data.message || 'Unknown error'));
|
|
}
|
|
})
|
|
.catch(error => alert('Error deleting event: ' + error.message));
|
|
}
|
|
|
|
function deleteApprovedEvent(eventId) {
|
|
if (!confirm('Are you sure you want to delete this approved event?')) return;
|
|
|
|
fetch(API_BASE + '/admin/events/' + eventId, {
|
|
method: 'DELETE',
|
|
headers: { 'Authorization': 'Bearer ' + token }
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
loadAllEvents();
|
|
loadStats();
|
|
alert('Event deleted successfully!');
|
|
} else {
|
|
alert('Failed to delete event: ' + (data.message || 'Unknown error'));
|
|
}
|
|
})
|
|
.catch(error => alert('Error deleting event: ' + error.message));
|
|
}
|
|
|
|
function editEvent(eventId) {
|
|
fetch(API_BASE + '/events/' + eventId)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success && data.data) {
|
|
showEditModal(data.data);
|
|
} else {
|
|
alert('Failed to load event details');
|
|
}
|
|
})
|
|
.catch(error => alert('Error loading event: ' + error.message));
|
|
}
|
|
|
|
function showEditModal(event) {
|
|
document.getElementById('modalTitle').textContent = 'Edit Event';
|
|
|
|
const startTime = event.start_time ? new Date(event.start_time).toISOString().slice(0, 16) : '';
|
|
const endTime = event.end_time ? new Date(event.end_time).toISOString().slice(0, 16) : '';
|
|
|
|
const modalHTML = '<div class="form-grid">' +
|
|
'<div class="form-grid cols-2">' +
|
|
'<div><label for="editTitle">Title</label><input id="editTitle" type="text" value="' + escapeHtml(event.title) + '"></div>' +
|
|
'<div><label for="editCategory">Category</label><select id="editCategory">' +
|
|
'<option value="Service"' + (event.category === 'Service' ? ' selected' : '') + '>Service</option>' +
|
|
'<option value="Ministry"' + (event.category === 'Ministry' ? ' selected' : '') + '>Ministry</option>' +
|
|
'<option value="Social"' + (event.category === 'Social' ? ' selected' : '') + '>Social</option>' +
|
|
'<option value="Other"' + (event.category === 'Other' ? ' selected' : '') + '>Other</option>' +
|
|
'</select></div>' +
|
|
'</div>' +
|
|
'<div><label for="editDescription">Description</label><textarea id="editDescription" rows="3">' + escapeHtml(event.description) + '</textarea></div>' +
|
|
'<div class="form-grid cols-2">' +
|
|
'<div><label for="editStartTime">Start Time</label><input id="editStartTime" type="datetime-local" value="' + startTime + '"></div>' +
|
|
'<div><label for="editEndTime">End Time</label><input id="editEndTime" type="datetime-local" value="' + endTime + '"></div>' +
|
|
'</div>' +
|
|
'<div class="form-grid cols-2">' +
|
|
'<div><label for="editLocation">Location</label><input id="editLocation" type="text" value="' + escapeHtml(event.location) + '"></div>' +
|
|
'<div><label for="editLocationUrl">Location URL (optional)</label><input id="editLocationUrl" type="url" value="' + (event.location_url || '') + '" placeholder="https://..."></div>' +
|
|
'</div>' +
|
|
'<div class="form-grid cols-2">' +
|
|
'<div><label for="editRecurringType">Recurring Type</label><select id="editRecurringType"><option value="' + escapeHtml(event.recurring_type || '') + '">' + escapeHtml(event.recurring_type || 'No Recurrence') + '</option></select></div>' +
|
|
'<div><label><input id="editFeatured" type="checkbox"' + (event.is_featured ? ' checked' : '') + '> Featured Event</label></div>' +
|
|
'</div>' +
|
|
'<div class="form-grid cols-2">' +
|
|
'<div>' +
|
|
'<label for="editEventImage">Event Image</label>' +
|
|
'<input type="file" id="editEventImage" accept="image/*" style="width: 100%;">' +
|
|
'<div id="editEventImagePreview" style="margin-top: 10px; text-align: center;">' +
|
|
(event.image ? '<div style="margin-bottom: 10px;"><strong>Current:</strong><br><img src="' + escapeHtml(event.image) + '" style="max-width: 200px; max-height: 150px; border-radius: 8px;" onerror="this.parentElement.innerHTML=\'❌ Current image unavailable\'"></div>' : '') +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<div>' +
|
|
'<label for="editEventThumbnail">Thumbnail Image</label>' +
|
|
'<input type="file" id="editEventThumbnail" accept="image/*" style="width: 100%;">' +
|
|
'<div id="editEventThumbnailPreview" style="margin-top: 10px; text-align: center;">' +
|
|
(event.thumbnail ? '<div style="margin-bottom: 10px;"><strong>Current:</strong><br><img src="' + escapeHtml(event.thumbnail) + '" style="max-width: 150px; max-height: 100px; border-radius: 8px;" onerror="this.parentElement.innerHTML=\'❌ Current thumbnail unavailable\'"></div>' : '') +
|
|
'</div>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'</div>';
|
|
|
|
document.getElementById('modalContent').innerHTML = modalHTML;
|
|
document.getElementById('modalActions').innerHTML = '<button onclick="saveEventEdit(\'' + event.id + '\')" class="btn-edit"><span>💾</span>Save Changes</button>';
|
|
|
|
// Add image preview functionality for both file inputs
|
|
document.getElementById('editEventImage').addEventListener('change', function(e) {
|
|
const file = e.target.files[0];
|
|
const preview = document.getElementById('editEventImagePreview');
|
|
|
|
if (file) {
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
preview.innerHTML =
|
|
(event.image ? '<div style="margin-bottom: 10px;"><strong>Current:</strong><br><img src="' + escapeHtml(event.image) + '" style="max-width: 200px; max-height: 150px; border-radius: 8px;" onerror="this.parentElement.innerHTML=\'❌ Current image unavailable\'"></div>' : '') +
|
|
'<div style="margin-top: 10px;"><strong>New:</strong><br><img src="' + e.target.result + '" style="max-width: 200px; max-height: 150px; border-radius: 8px;"></div>';
|
|
};
|
|
reader.readAsDataURL(file);
|
|
} else {
|
|
preview.innerHTML = event.image ? '<div style="margin-bottom: 10px;"><strong>Current:</strong><br><img src="' + escapeHtml(event.image) + '" style="max-width: 200px; max-height: 150px; border-radius: 8px;" onerror="this.parentElement.innerHTML=\'❌ Current image unavailable\'"></div>' : '';
|
|
}
|
|
});
|
|
|
|
document.getElementById('editEventThumbnail').addEventListener('change', function(e) {
|
|
const file = e.target.files[0];
|
|
const preview = document.getElementById('editEventThumbnailPreview');
|
|
|
|
if (file) {
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
preview.innerHTML =
|
|
(event.thumbnail ? '<div style="margin-bottom: 10px;"><strong>Current:</strong><br><img src="' + escapeHtml(event.thumbnail) + '" style="max-width: 150px; max-height: 100px; border-radius: 8px;" onerror="this.parentElement.innerHTML=\'❌ Current thumbnail unavailable\'"></div>' : '') +
|
|
'<div style="margin-top: 10px;"><strong>New:</strong><br><img src="' + e.target.result + '" style="max-width: 150px; max-height: 100px; border-radius: 8px;"></div>';
|
|
};
|
|
reader.readAsDataURL(file);
|
|
} else {
|
|
preview.innerHTML = event.thumbnail ? '<div style="margin-bottom: 10px;"><strong>Current:</strong><br><img src="' + escapeHtml(event.thumbnail) + '" style="max-width: 150px; max-height: 100px; border-radius: 8px;" onerror="this.parentElement.innerHTML=\'❌ Current thumbnail unavailable\'"></div>' : '';
|
|
}
|
|
});
|
|
|
|
// Populate recurring type dropdown with API options
|
|
populateRecurringTypeDropdown(event.recurring_type);
|
|
|
|
document.getElementById('modal').classList.remove('hidden');
|
|
}
|
|
|
|
function populateRecurringTypeDropdown(currentValue) {
|
|
const select = document.getElementById('editRecurringType');
|
|
if (!select) return;
|
|
|
|
// Clear existing options except the first one (current value)
|
|
while (select.options.length > 1) {
|
|
select.removeChild(select.lastChild);
|
|
}
|
|
|
|
// Add "No Recurrence" option if not already the current value
|
|
if (currentValue && currentValue !== '') {
|
|
const noRecurrenceOption = document.createElement('option');
|
|
noRecurrenceOption.value = '';
|
|
noRecurrenceOption.textContent = 'No Recurrence';
|
|
select.appendChild(noRecurrenceOption);
|
|
}
|
|
|
|
// Add all available recurring types from API
|
|
recurringTypes.forEach(type => {
|
|
// Skip if this is already the current value (already shown as first option)
|
|
if (type.value !== currentValue) {
|
|
const option = document.createElement('option');
|
|
option.value = type.value;
|
|
option.textContent = type.label;
|
|
select.appendChild(option);
|
|
}
|
|
});
|
|
}
|
|
|
|
function saveEventEdit(eventId) {
|
|
const title = document.getElementById('editTitle').value;
|
|
const description = document.getElementById('editDescription').value;
|
|
const location = document.getElementById('editLocation').value;
|
|
const category = document.getElementById('editCategory').value;
|
|
const startTime = document.getElementById('editStartTime').value;
|
|
const endTime = document.getElementById('editEndTime').value;
|
|
const locationUrl = document.getElementById('editLocationUrl').value;
|
|
const recurringType = document.getElementById('editRecurringType').value;
|
|
const imageFile = document.getElementById('editEventImage').files[0];
|
|
const thumbnailFile = document.getElementById('editEventThumbnail').files[0];
|
|
|
|
// Validate required fields
|
|
if (!title || !description || !location || !category || !startTime || !endTime) {
|
|
alert('Please fill in all required fields (title, description, location, category, start time, end time)');
|
|
return;
|
|
}
|
|
|
|
const eventData = {
|
|
title: title.trim(),
|
|
description: description.trim(),
|
|
location: location.trim(),
|
|
category: category,
|
|
start_time: new Date(startTime).toISOString(),
|
|
end_time: new Date(endTime).toISOString(),
|
|
is_featured: document.getElementById('editFeatured').checked
|
|
};
|
|
|
|
if (locationUrl && locationUrl.trim()) eventData.location_url = locationUrl.trim();
|
|
if (recurringType) {
|
|
eventData.recurring_type = recurringType;
|
|
}
|
|
|
|
console.log('Sending event data:', JSON.stringify(eventData, null, 2));
|
|
|
|
// First update the event text data
|
|
fetch(API_BASE + '/admin/events/' + eventId, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Authorization': 'Bearer ' + token,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(eventData)
|
|
})
|
|
.then(response => {
|
|
console.log('Response status:', response.status);
|
|
if (!response.ok) {
|
|
return response.text().then(text => {
|
|
console.error('Error response:', text);
|
|
throw new Error(`HTTP ${response.status}: ${text}`);
|
|
});
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
console.log('Success response:', data);
|
|
if (data.success) {
|
|
// Upload images if selected
|
|
const uploadPromises = [];
|
|
|
|
if (imageFile) {
|
|
const formData = new FormData();
|
|
formData.append('file', imageFile);
|
|
|
|
const imageUpload = fetch(API_BASE + '/upload/events/' + eventId + '/image', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': 'Bearer ' + token
|
|
},
|
|
body: formData
|
|
});
|
|
uploadPromises.push(imageUpload);
|
|
}
|
|
|
|
if (thumbnailFile) {
|
|
const formData = new FormData();
|
|
formData.append('file', thumbnailFile);
|
|
|
|
const thumbnailUpload = fetch(API_BASE + '/upload/events/' + eventId + '/thumbnail', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': 'Bearer ' + token
|
|
},
|
|
body: formData
|
|
});
|
|
uploadPromises.push(thumbnailUpload);
|
|
}
|
|
|
|
if (uploadPromises.length > 0) {
|
|
Promise.all(uploadPromises)
|
|
.then(responses => {
|
|
// Check if any uploads failed
|
|
const failedUploads = responses.filter(r => !r.ok);
|
|
if (failedUploads.length > 0) {
|
|
console.warn('Some image uploads failed');
|
|
}
|
|
|
|
closeModal();
|
|
loadAllEvents();
|
|
|
|
if (failedUploads.length === 0) {
|
|
alert('Event updated with images successfully!');
|
|
} else {
|
|
alert('Event updated, but some image uploads failed. Please try uploading the images again.');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Image upload error:', error);
|
|
closeModal();
|
|
loadAllEvents();
|
|
alert('Event updated, but image upload failed: ' + error.message);
|
|
});
|
|
} else {
|
|
closeModal();
|
|
loadAllEvents();
|
|
alert('Event updated successfully!');
|
|
}
|
|
} else {
|
|
alert('Failed to update event: ' + (data.message || 'Unknown error'));
|
|
}
|
|
})
|
|
.catch(error => alert('Error updating event: ' + error.message));
|
|
}
|
|
|
|
// SCHEDULE MANAGEMENT FUNCTIONALITY
|
|
async function showSchedules() {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/admin/schedule`, {
|
|
headers: { 'Authorization': 'Bearer ' + token }
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success && data.data) {
|
|
renderSchedules(data.data);
|
|
} else {
|
|
renderSchedules([]);
|
|
}
|
|
} catch (error) {
|
|
showContentError('Error loading schedules: ' + error.message);
|
|
}
|
|
}
|
|
|
|
function renderSchedules(schedules) {
|
|
const content = document.getElementById('content');
|
|
|
|
let html = '<div class="content-header">' +
|
|
'<h2 class="content-title"><span>👥</span>Schedule Management</h2>' +
|
|
'<div style="display: flex; gap: 1rem;">' +
|
|
'<button onclick="showCreateScheduleModal()" class="btn-edit"><span>✨</span>Create Schedule</button>' +
|
|
'<button onclick="showImportScheduleModal()" class="btn-approve"><span>📁</span>Import JSON</button>' +
|
|
'</div>' +
|
|
'</div>';
|
|
|
|
if (schedules.length === 0) {
|
|
html += '<div class="empty-state">' +
|
|
'<div class="empty-icon purple">👥</div>' +
|
|
'<p class="empty-title">No schedules yet.</p>' +
|
|
'<p>Create individual schedules or import from your JSON file.</p>' +
|
|
'</div>';
|
|
} else {
|
|
schedules.forEach(schedule => {
|
|
html += '<div class="event-item">' +
|
|
'<div class="event-content">' +
|
|
'<div class="event-details">' +
|
|
'<h3 class="event-title">Schedule for ' + formatDate(schedule.date) + '</h3>' +
|
|
'<div class="event-meta" style="grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));">' +
|
|
(schedule.song_leader ? '<div class="meta-item"><span>🎵</span>Song Leader: ' + escapeHtml(schedule.song_leader) + '</div>' : '') +
|
|
(schedule.ss_teacher ? '<div class="meta-item"><span>📚</span>SS Teacher: ' + escapeHtml(schedule.ss_teacher) + '</div>' : '') +
|
|
(schedule.ss_leader ? '<div class="meta-item"><span>👨🏫</span>SS Leader: ' + escapeHtml(schedule.ss_leader) + '</div>' : '') +
|
|
(schedule.scripture ? '<div class="meta-item"><span>📖</span>Scripture: ' + escapeHtml(schedule.scripture) + '</div>' : '') +
|
|
(schedule.childrens_story ? '<div class="meta-item"><span>👶</span>Children\'s Story: ' + escapeHtml(schedule.childrens_story) + '</div>' : '') +
|
|
(schedule.sermon_speaker ? '<div class="meta-item"><span>🎙️</span>Speaker: ' + escapeHtml(schedule.sermon_speaker) + '</div>' : '') +
|
|
(schedule.special_music ? '<div class="meta-item"><span>🎼</span>Special Music: ' + escapeHtml(schedule.special_music) + '</div>' : '') +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<div class="event-actions">' +
|
|
'<button onclick="editSchedule(\'' + schedule.id + '\')" class="btn-action btn-edit"><span>✏️</span>Edit</button>' +
|
|
'<button onclick="deleteSchedule(\'' + schedule.id + '\')" class="btn-action btn-delete"><span>🗑️</span>Delete</button>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'</div>';
|
|
});
|
|
}
|
|
|
|
content.innerHTML = html;
|
|
}
|
|
|
|
function showImportScheduleModal() {
|
|
document.getElementById('modalTitle').textContent = 'Import Schedule from JSON';
|
|
document.getElementById('modalContent').innerHTML =
|
|
'<div class="form-grid">' +
|
|
'<div>' +
|
|
'<label for="scheduleJsonFile">Select JSON File</label>' +
|
|
'<input id="scheduleJsonFile" type="file" accept=".json" required>' +
|
|
'<small style="color: var(--muted-color); margin-top: 0.5rem; display: block;">Upload your quarterly schedule JSON file</small>' +
|
|
'</div>' +
|
|
'<div id="importPreview" style="display: none;">' +
|
|
'<h4>Preview:</h4>' +
|
|
'<div id="importPreviewContent"></div>' +
|
|
'</div>' +
|
|
'<div id="importProgress" style="display: none;">' +
|
|
'<div class="progress-bar">' +
|
|
'<div id="progressFill" class="progress-fill" style="width: 0%;"></div>' +
|
|
'</div>' +
|
|
'<p id="progressText">Processing...</p>' +
|
|
'</div>' +
|
|
'</div>';
|
|
|
|
document.getElementById('modalActions').innerHTML =
|
|
'<button id="importButton" onclick="importScheduleFromJson()" class="btn-approve">' +
|
|
'<span>📁</span>Import Schedules' +
|
|
'</button>';
|
|
document.getElementById('modal').classList.remove('hidden');
|
|
|
|
// Add file change listener for preview
|
|
document.getElementById('scheduleJsonFile').addEventListener('change', function(e) {
|
|
const file = e.target.files[0];
|
|
if (file) {
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
try {
|
|
const data = JSON.parse(e.target.result);
|
|
showImportPreview(data);
|
|
} catch (error) {
|
|
alert('Invalid JSON file: ' + error.message);
|
|
}
|
|
};
|
|
reader.readAsText(file);
|
|
}
|
|
});
|
|
}
|
|
|
|
function showImportPreview(data) {
|
|
const preview = document.getElementById('importPreview');
|
|
const content = document.getElementById('importPreviewContent');
|
|
|
|
if (data.scheduleData && data.scheduleData.length > 0) {
|
|
let html = '<p><strong>Found ' + data.scheduleData.length + ' schedule entries</strong></p>';
|
|
html += '<div style="max-height: 200px; overflow-y: auto; border: 1px solid #ddd; padding: 1rem; border-radius: 0.5rem;">';
|
|
|
|
data.scheduleData.slice(0, 5).forEach(entry => {
|
|
const date = new Date(entry.date).toLocaleDateString();
|
|
html += '<div style="margin-bottom: 0.5rem;">';
|
|
html += '<strong>' + date + '</strong>: ';
|
|
const roles = [];
|
|
if (entry.songLeader) roles.push('Song Leader: ' + entry.songLeader);
|
|
if (entry.scripture) roles.push('Scripture: ' + entry.scripture);
|
|
if (entry.sermonSpeaker) roles.push('Speaker: ' + entry.sermonSpeaker);
|
|
html += roles.length > 0 ? roles.join(', ') : 'No assignments';
|
|
html += '</div>';
|
|
});
|
|
|
|
if (data.scheduleData.length > 5) {
|
|
html += '<p><em>... and ' + (data.scheduleData.length - 5) + ' more entries</em></p>';
|
|
}
|
|
|
|
html += '</div>';
|
|
content.innerHTML = html;
|
|
preview.style.display = 'block';
|
|
} else {
|
|
alert('No schedule data found in the JSON file');
|
|
}
|
|
}
|
|
|
|
async function importScheduleFromJson() {
|
|
const fileInput = document.getElementById('scheduleJsonFile');
|
|
const file = fileInput.files[0];
|
|
|
|
if (!file) {
|
|
alert('Please select a JSON file first');
|
|
return;
|
|
}
|
|
|
|
const importButton = document.getElementById('importButton');
|
|
const progressDiv = document.getElementById('importProgress');
|
|
const progressFill = document.getElementById('progressFill');
|
|
const progressText = document.getElementById('progressText');
|
|
|
|
importButton.disabled = true;
|
|
progressDiv.style.display = 'block';
|
|
|
|
try {
|
|
const reader = new FileReader();
|
|
reader.onload = async function(e) {
|
|
try {
|
|
const data = JSON.parse(e.target.result);
|
|
|
|
if (!data.scheduleData || !Array.isArray(data.scheduleData)) {
|
|
throw new Error('Invalid JSON format - missing scheduleData array');
|
|
}
|
|
|
|
const entries = data.scheduleData;
|
|
let imported = 0;
|
|
let errors = 0;
|
|
|
|
for (let i = 0; i < entries.length; i++) {
|
|
const entry = entries[i];
|
|
const progress = ((i + 1) / entries.length) * 100;
|
|
|
|
progressFill.style.width = progress + '%';
|
|
progressText.textContent = `Processing ${i + 1} of ${entries.length}...`;
|
|
|
|
try {
|
|
const scheduleData = {
|
|
date: new Date(entry.date).toISOString().split('T')[0],
|
|
song_leader: entry.songLeader || null,
|
|
ss_teacher: entry.ssTeacher || null,
|
|
ss_leader: entry.ssLeader || null,
|
|
mission_story: entry.missionStory || null,
|
|
special_program: entry.specialProgram || null,
|
|
sermon_speaker: entry.sermonSpeaker || null,
|
|
scripture: entry.scripture || null,
|
|
offering: entry.offering || null,
|
|
deacons: entry.deacons || null,
|
|
special_music: entry.specialMusic || null,
|
|
childrens_story: entry.childrensStory || null,
|
|
afternoon_program: entry.afternoonProgram || null
|
|
};
|
|
|
|
const response = await fetch(API_BASE + '/admin/schedule', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': 'Bearer ' + token,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(scheduleData)
|
|
});
|
|
|
|
if (response.ok) {
|
|
imported++;
|
|
} else {
|
|
errors++;
|
|
console.error('Failed to import entry for', scheduleData.date);
|
|
}
|
|
} catch (error) {
|
|
errors++;
|
|
console.error('Error processing entry:', error);
|
|
}
|
|
|
|
// Small delay to show progress
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
}
|
|
|
|
progressText.textContent = `Import complete! ${imported} imported, ${errors} errors`;
|
|
|
|
setTimeout(() => {
|
|
closeModal();
|
|
showSchedules();
|
|
alert(`Import complete!\n${imported} schedules imported successfully\n${errors} errors occurred`);
|
|
}, 2000);
|
|
|
|
} catch (error) {
|
|
progressText.textContent = 'Import failed: ' + error.message;
|
|
importButton.disabled = false;
|
|
alert('Import failed: ' + error.message);
|
|
}
|
|
};
|
|
reader.readAsText(file);
|
|
} catch (error) {
|
|
progressText.textContent = 'Error reading file: ' + error.message;
|
|
importButton.disabled = false;
|
|
alert('Error reading file: ' + error.message);
|
|
}
|
|
}
|
|
|
|
function showCreateScheduleModal() {
|
|
document.getElementById('modalTitle').textContent = 'Create New Schedule';
|
|
document.getElementById('modalContent').innerHTML = `
|
|
<div class="form-grid">
|
|
<div>
|
|
<label for="scheduleDate">Date</label>
|
|
<input id="scheduleDate" type="date" required>
|
|
</div>
|
|
<div class="form-grid cols-2">
|
|
<div>
|
|
<label for="songLeader">Song Leader</label>
|
|
<input id="songLeader" type="text" placeholder="e.g., Lisa">
|
|
</div>
|
|
<div>
|
|
<label for="ssTeacher">SS Teacher</label>
|
|
<input id="ssTeacher" type="text" placeholder="e.g., John Smith">
|
|
</div>
|
|
</div>
|
|
<div class="form-grid cols-2">
|
|
<div>
|
|
<label for="ssLeader">SS Leader</label>
|
|
<input id="ssLeader" type="text" placeholder="e.g., Wayne Tino">
|
|
</div>
|
|
<div>
|
|
<label for="missionStory">Mission Story</label>
|
|
<input id="missionStory" type="text" placeholder="e.g., Jerry Travers">
|
|
</div>
|
|
</div>
|
|
<div class="form-grid cols-2">
|
|
<div>
|
|
<label for="scripture">Scripture Reading</label>
|
|
<input id="scripture" type="text" placeholder="e.g., Orville Castillo">
|
|
</div>
|
|
<div>
|
|
<label for="offering">Offering</label>
|
|
<input id="offering" type="text" placeholder="e.g., Audley Brown">
|
|
</div>
|
|
</div>
|
|
<div class="form-grid cols-2">
|
|
<div>
|
|
<label for="specialMusic">Special Music</label>
|
|
<input id="specialMusic" type="text" placeholder="e.g., Michelle Maitland">
|
|
</div>
|
|
<div>
|
|
<label for="childrensStory">Children's Story</label>
|
|
<input id="childrensStory" type="text" placeholder="e.g., Sarah Johnson">
|
|
</div>
|
|
</div>
|
|
<div class="form-grid cols-2">
|
|
<div>
|
|
<label for="sermonSpeaker">Sermon Speaker</label>
|
|
<input id="sermonSpeaker" type="text" placeholder="e.g., Pastor Joseph Piresson">
|
|
</div>
|
|
<div></div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.getElementById('modalActions').innerHTML = `
|
|
<button onclick="createSchedule()" class="btn-edit">
|
|
<span>✨</span>Create Schedule
|
|
</button>
|
|
`;
|
|
document.getElementById('modal').classList.remove('hidden');
|
|
}
|
|
|
|
function createSchedule() {
|
|
const date = document.getElementById('scheduleDate').value;
|
|
|
|
if (!date) {
|
|
alert('Please select a date for the schedule');
|
|
return;
|
|
}
|
|
|
|
const scheduleData = {
|
|
date: date,
|
|
song_leader: document.getElementById('songLeader').value || null,
|
|
ss_teacher: document.getElementById('ssTeacher').value || null,
|
|
ss_leader: document.getElementById('ssLeader').value || null,
|
|
mission_story: document.getElementById('missionStory').value || null,
|
|
scripture: document.getElementById('scripture').value || null,
|
|
offering: document.getElementById('offering').value || null,
|
|
special_music: document.getElementById('specialMusic').value || null,
|
|
childrens_story: document.getElementById('childrensStory').value || null,
|
|
sermon_speaker: document.getElementById('sermonSpeaker').value || null
|
|
};
|
|
|
|
fetch(API_BASE + '/admin/schedule', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': 'Bearer ' + token,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(scheduleData)
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
closeModal();
|
|
showSchedules();
|
|
alert('Schedule created successfully!');
|
|
} else {
|
|
alert('Failed to create schedule: ' + (data.message || 'Unknown error'));
|
|
}
|
|
})
|
|
.catch(error => alert('Error creating schedule: ' + error.message));
|
|
}
|
|
|
|
function editSchedule(scheduleId) {
|
|
fetch(API_BASE + '/admin/schedule', {
|
|
headers: { 'Authorization': 'Bearer ' + token }
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success && data.data) {
|
|
const schedule = data.data.find(s => s.id === scheduleId);
|
|
if (schedule) {
|
|
showEditScheduleModal(schedule);
|
|
} else {
|
|
alert('Schedule not found');
|
|
}
|
|
} else {
|
|
alert('Failed to load schedule details');
|
|
}
|
|
})
|
|
.catch(error => alert('Error loading schedule: ' + error.message));
|
|
}
|
|
|
|
function showEditScheduleModal(schedule) {
|
|
document.getElementById('modalTitle').textContent = 'Edit Schedule';
|
|
|
|
// Format the date properly for HTML date input (YYYY-MM-DD)
|
|
let formattedDate = '';
|
|
if (schedule.date) {
|
|
if (schedule.date.includes('T')) {
|
|
formattedDate = schedule.date.split('T')[0];
|
|
} else {
|
|
formattedDate = schedule.date;
|
|
}
|
|
}
|
|
|
|
document.getElementById('modalContent').innerHTML = `
|
|
<div class="form-grid">
|
|
<div>
|
|
<label for="editScheduleDate">Date</label>
|
|
<input id="editScheduleDate" type="date" value="${formattedDate}" required>
|
|
</div>
|
|
<div class="form-grid cols-2">
|
|
<div>
|
|
<label for="editSongLeader">Song Leader</label>
|
|
<input id="editSongLeader" type="text" value="${schedule.song_leader || ''}">
|
|
</div>
|
|
<div>
|
|
<label for="editSsTeacher">SS Teacher</label>
|
|
<input id="editSsTeacher" type="text" value="${schedule.ss_teacher || ''}">
|
|
</div>
|
|
</div>
|
|
<div class="form-grid cols-2">
|
|
<div>
|
|
<label for="editSsLeader">SS Leader</label>
|
|
<input id="editSsLeader" type="text" value="${schedule.ss_leader || ''}">
|
|
</div>
|
|
<div>
|
|
<label for="editMissionStory">Mission Story</label>
|
|
<input id="editMissionStory" type="text" value="${schedule.mission_story || ''}">
|
|
</div>
|
|
</div>
|
|
<div class="form-grid cols-2">
|
|
<div>
|
|
<label for="editScripture">Scripture Reading</label>
|
|
<input id="editScripture" type="text" value="${schedule.scripture || ''}">
|
|
</div>
|
|
<div>
|
|
<label for="editOffering">Offering</label>
|
|
<input id="editOffering" type="text" value="${schedule.offering || ''}">
|
|
</div>
|
|
</div>
|
|
<div class="form-grid cols-2">
|
|
<div>
|
|
<label for="editSpecialMusic">Special Music</label>
|
|
<input id="editSpecialMusic" type="text" value="${schedule.special_music || ''}">
|
|
</div>
|
|
<div>
|
|
<label for="editChildrensStory">Children's Story</label>
|
|
<input id="editChildrensStory" type="text" value="${schedule.childrens_story || ''}">
|
|
</div>
|
|
</div>
|
|
<div class="form-grid cols-2">
|
|
<div>
|
|
<label for="editSermonSpeaker">Sermon Speaker</label>
|
|
<input id="editSermonSpeaker" type="text" value="${schedule.sermon_speaker || ''}">
|
|
</div>
|
|
<div></div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
document.getElementById('modalActions').innerHTML = `
|
|
<button onclick="saveScheduleEdit('${schedule.id}')" class="btn-edit">
|
|
<span>💾</span>Save Changes
|
|
</button>
|
|
`;
|
|
document.getElementById('modal').classList.remove('hidden');
|
|
}
|
|
|
|
function saveScheduleEdit(scheduleId) {
|
|
const date = document.getElementById('editScheduleDate').value;
|
|
|
|
if (!date) {
|
|
alert('Please select a date for the schedule');
|
|
return;
|
|
}
|
|
|
|
const scheduleData = {
|
|
date: date,
|
|
song_leader: document.getElementById('editSongLeader').value || null,
|
|
ss_teacher: document.getElementById('editSsTeacher').value || null,
|
|
ss_leader: document.getElementById('editSsLeader').value || null,
|
|
mission_story: document.getElementById('editMissionStory').value || null,
|
|
scripture: document.getElementById('editScripture').value || null,
|
|
offering: document.getElementById('editOffering').value || null,
|
|
special_music: document.getElementById('editSpecialMusic').value || null,
|
|
childrens_story: document.getElementById('editChildrensStory').value || null,
|
|
sermon_speaker: document.getElementById('editSermonSpeaker').value || null
|
|
};
|
|
|
|
fetch(API_BASE + '/admin/schedule/' + date, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Authorization': 'Bearer ' + token,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(scheduleData)
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
closeModal();
|
|
showSchedules();
|
|
alert('Schedule updated successfully!');
|
|
} else {
|
|
alert('Failed to update schedule: ' + (data.message || 'Unknown error'));
|
|
}
|
|
})
|
|
.catch(error => alert('Error updating schedule: ' + error.message));
|
|
}
|
|
|
|
function deleteSchedule(scheduleId) {
|
|
if (!confirm('Are you sure you want to delete this schedule? This action cannot be undone.')) return;
|
|
|
|
// We need to find the date for this schedule ID
|
|
fetch(API_BASE + '/admin/schedule', {
|
|
headers: { 'Authorization': 'Bearer ' + token }
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success && data.data) {
|
|
const schedule = data.data.find(s => s.id === scheduleId);
|
|
if (schedule) {
|
|
return fetch(API_BASE + '/admin/schedule/' + schedule.date, {
|
|
method: 'DELETE',
|
|
headers: { 'Authorization': 'Bearer ' + token }
|
|
});
|
|
} else {
|
|
throw new Error('Schedule not found');
|
|
}
|
|
} else {
|
|
throw new Error('Failed to load schedules');
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showSchedules();
|
|
alert('Schedule deleted successfully!');
|
|
} else {
|
|
alert('Failed to delete schedule: ' + (data.message || 'Unknown error'));
|
|
}
|
|
})
|
|
.catch(error => alert('Error deleting schedule: ' + error.message));
|
|
}
|
|
|
|
// BULLETINS FUNCTIONALITY
|
|
async function showBulletins() {
|
|
try {
|
|
// Fetch all bulletins with pagination
|
|
let allBulletins = [];
|
|
let page = 1;
|
|
let hasMore = true;
|
|
|
|
while (hasMore) {
|
|
const response = await fetch(`${API_BASE}/bulletins?page=${page}&per_page=50`, {
|
|
headers: token ? { 'Authorization': 'Bearer ' + token } : {}
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success && data.data && data.data.items) {
|
|
allBulletins = allBulletins.concat(data.data.items);
|
|
hasMore = data.data.has_more;
|
|
page++;
|
|
} else {
|
|
hasMore = false;
|
|
if (!data.success) {
|
|
throw new Error(data.message || 'API returned success: false');
|
|
}
|
|
}
|
|
}
|
|
|
|
renderBulletins(allBulletins);
|
|
} catch (error) {
|
|
showContentError('Error loading bulletins: ' + error.message);
|
|
}
|
|
}
|
|
|
|
function renderBulletins(bulletins) {
|
|
const content = document.getElementById('content');
|
|
|
|
if (bulletins.length === 0) {
|
|
content.innerHTML =
|
|
'<div class="empty-state">' +
|
|
'<div class="empty-icon purple">🗞️</div>' +
|
|
'<p class="empty-title">No bulletins yet.</p>' +
|
|
'<p>Create your first church bulletin to get started.</p>' +
|
|
'<button onclick="showCreateBulletinModal()" class="btn-edit"><span>✨</span>Create First Bulletin</button>' +
|
|
'</div>';
|
|
return;
|
|
}
|
|
|
|
let html = '<div class="content-header"><h2 class="content-title"><span>🗞️</span>Church Bulletins</h2><button onclick="showCreateBulletinModal()" class="btn-edit"><span>✨</span>Create New</button></div>';
|
|
|
|
bulletins.forEach(bulletin => {
|
|
// Create a content preview from available sections
|
|
let contentPreview = '';
|
|
const sections = [bulletin.sabbath_school, bulletin.divine_worship, bulletin.scripture_reading];
|
|
const availableContent = sections.find(section => section && section.trim());
|
|
|
|
if (availableContent) {
|
|
// Strip HTML tags and get first 150 characters
|
|
contentPreview = stripHtmlTags(availableContent).substring(0, 150);
|
|
} else {
|
|
contentPreview = 'Church bulletin for ' + (bulletin.date || 'Unknown date');
|
|
}
|
|
|
|
const showMore = availableContent && availableContent.length > 150 ? '...' : '';
|
|
|
|
html +=
|
|
'<div class="event-item">' +
|
|
'<div class="event-content">' +
|
|
'<div class="event-image">' +
|
|
(bulletin.cover_image ?
|
|
'<img src="' + escapeHtml(bulletin.cover_image) + '" alt="Bulletin cover image" onerror="this.parentElement.innerHTML=\'🗞️\'">' :
|
|
'🗞️'
|
|
) +
|
|
'</div>' +
|
|
'<div class="event-details">' +
|
|
'<h3 class="event-title">' + escapeHtml(bulletin.title || 'Church Bulletin') + '</h3>' +
|
|
'<p class="event-description">' + escapeHtml(contentPreview) + showMore + '</p>' +
|
|
'<div class="event-meta">' +
|
|
'<div class="meta-item"><span>📅</span>' + formatDate(bulletin.date || bulletin.created_at) + '</div>' +
|
|
'<div class="meta-item"><span>📄</span>' + (bulletin.pdf_path ? 'PDF Available' : 'No PDF') + '</div>' +
|
|
'<div class="meta-item"><span>📝</span>' + (bulletin.is_active ? '<span class="category-badge featured">Active</span>' : '<span class="category-badge approved">Inactive</span>') + '</div>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<div class="event-actions">' +
|
|
'<button onclick="viewBulletin(\'' + bulletin.id + '\')" class="btn-action btn-edit"><span>👁️</span>View</button>' +
|
|
'<button onclick="editBulletin(\'' + bulletin.id + '\')" class="btn-action btn-edit"><span>✏️</span>Edit</button>' +
|
|
'<button onclick="deleteBulletin(\'' + bulletin.id + '\')" class="btn-action btn-delete"><span>🗑️</span>Delete</button>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'</div>';
|
|
});
|
|
|
|
content.innerHTML = html;
|
|
}
|
|
|
|
function viewBulletin(bulletinId) {
|
|
fetch(API_BASE + '/bulletins/' + bulletinId)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success && data.data) {
|
|
showViewBulletinModal(data.data);
|
|
} else {
|
|
alert('Failed to load bulletin details');
|
|
}
|
|
})
|
|
.catch(error => alert('Error loading bulletin: ' + error.message));
|
|
}
|
|
|
|
function showViewBulletinModal(bulletin) {
|
|
document.getElementById('modalTitle').textContent = bulletin.title || 'Church Bulletin';
|
|
|
|
let modalContent = '<div class="form-grid">';
|
|
|
|
// Display cover image if available
|
|
if (bulletin.cover_image) {
|
|
modalContent += '<div style="text-align: center; margin-bottom: 20px;"><img src="' + escapeHtml(bulletin.cover_image) + '" alt="Bulletin cover image" style="max-width: 100%; max-height: 300px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);"></div>';
|
|
}
|
|
|
|
// Display bulletin date
|
|
if (bulletin.date) {
|
|
modalContent += '<div><h4>📅 Date</h4><p>' + formatDate(bulletin.date) + '</p></div>';
|
|
}
|
|
|
|
// Display Sabbath School section
|
|
if (bulletin.sabbath_school) {
|
|
modalContent += '<div><h4>📚 Sabbath School</h4><div>' + renderHtmlContent(bulletin.sabbath_school) + '</div></div>';
|
|
}
|
|
|
|
// Display Divine Worship section
|
|
if (bulletin.divine_worship) {
|
|
modalContent += '<div><h4>⛪ Divine Worship</h4><div>' + renderHtmlContent(bulletin.divine_worship) + '</div></div>';
|
|
}
|
|
|
|
// Display Scripture Reading
|
|
if (bulletin.scripture_reading) {
|
|
modalContent += '<div><h4>📖 Scripture Reading</h4><div>' + renderHtmlContent(bulletin.scripture_reading) + '</div></div>';
|
|
}
|
|
|
|
// Display Sunset times
|
|
if (bulletin.sunset) {
|
|
modalContent += '<div><h4>🌅 Sunset</h4><div>' + renderHtmlContent(bulletin.sunset) + '</div></div>';
|
|
}
|
|
|
|
// PDF download link
|
|
if (bulletin.pdf_file || bulletin.pdf_path) {
|
|
let pdfUrl;
|
|
if (bulletin.pdf_path) {
|
|
// If pdf_path already contains the full URL, use it as-is
|
|
pdfUrl = bulletin.pdf_path.startsWith('http') ?
|
|
bulletin.pdf_path :
|
|
'https://api.rockvilletollandsda.church/' + bulletin.pdf_path.replace(/^\/+/, '');
|
|
} else {
|
|
// For pdf_file, construct the URL
|
|
pdfUrl = 'https://api.rockvilletollandsda.church/uploads/bulletins/' + bulletin.pdf_file;
|
|
}
|
|
modalContent += '<div><h4>📄 PDF Download</h4><p><a href="' + pdfUrl + '" target="_blank" style="color: var(--church-primary);">Download PDF</a></p></div>';
|
|
}
|
|
|
|
modalContent += '</div>';
|
|
|
|
document.getElementById('modalContent').innerHTML = modalContent;
|
|
document.getElementById('modalActions').innerHTML = '';
|
|
document.getElementById('modal').classList.remove('hidden');
|
|
}
|
|
|
|
function showCreateBulletinModal() {
|
|
document.getElementById('modalTitle').textContent = 'Create New Bulletin';
|
|
document.getElementById('modalContent').innerHTML =
|
|
'<div class="form-grid">' +
|
|
'<div class="form-grid cols-2">' +
|
|
'<div>' +
|
|
'<label for="bulletinTitle">Title</label>' +
|
|
'<input id="bulletinTitle" type="text" placeholder="Sunday Bulletin - June 28, 2025" required>' +
|
|
'</div>' +
|
|
'<div>' +
|
|
'<label for="bulletinDate">Date</label>' +
|
|
'<input id="bulletinDate" type="date" required>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<div>' +
|
|
'<label for="bulletinSabbathSchool">Sabbath School</label>' +
|
|
'<textarea id="bulletinSabbathSchool" rows="4" placeholder="Enter Sabbath School program details..."></textarea>' +
|
|
'</div>' +
|
|
'<div>' +
|
|
'<label for="bulletinDivineWorship">Divine Worship</label>' +
|
|
'<textarea id="bulletinDivineWorship" rows="6" placeholder="Enter Divine Worship service details..."></textarea>' +
|
|
'</div>' +
|
|
'<div>' +
|
|
'<label for="bulletinScripture">Scripture Reading</label>' +
|
|
'<textarea id="bulletinScripture" rows="4" placeholder="Enter scripture reading..."></textarea>' +
|
|
'</div>' +
|
|
'<div>' +
|
|
'<label for="bulletinSunset">Sunset Times</label>' +
|
|
'<textarea id="bulletinSunset" rows="2" placeholder="Sunset Tonight: 8:29 pm\\nSunset Next Friday: 8:31 pm"></textarea>' +
|
|
'</div>' +
|
|
'<div>' +
|
|
'<label for="bulletinCoverImage">Cover Image</label>' +
|
|
'<input type="file" id="bulletinCoverImage" accept="image/*" style="width: 100%;">' +
|
|
'<div id="bulletinCoverImagePreview" style="margin-top: 10px; text-align: center;"></div>' +
|
|
'</div>' +
|
|
'<div>' +
|
|
'<label>' +
|
|
'<input id="bulletinIsActive" type="checkbox" checked>' +
|
|
' Set as active bulletin' +
|
|
'</label>' +
|
|
'</div>' +
|
|
'</div>';
|
|
|
|
document.getElementById('modalActions').innerHTML =
|
|
'<button onclick="createBulletin()" class="btn-edit">' +
|
|
'<span>✨</span>Create Bulletin' +
|
|
'</button>';
|
|
|
|
// Add image preview functionality for file input
|
|
document.getElementById('bulletinCoverImage').addEventListener('change', function(e) {
|
|
const file = e.target.files[0];
|
|
const preview = document.getElementById('bulletinCoverImagePreview');
|
|
|
|
if (file) {
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
preview.innerHTML = '<img src="' + e.target.result + '" style="max-width: 200px; max-height: 150px; border-radius: 8px;">';
|
|
};
|
|
reader.readAsDataURL(file);
|
|
} else {
|
|
preview.innerHTML = '';
|
|
}
|
|
});
|
|
|
|
document.getElementById('modal').classList.remove('hidden');
|
|
}
|
|
|
|
function createBulletin() {
|
|
const title = document.getElementById('bulletinTitle').value;
|
|
const date = document.getElementById('bulletinDate').value;
|
|
const sabbathSchool = document.getElementById('bulletinSabbathSchool').value;
|
|
const divineWorship = document.getElementById('bulletinDivineWorship').value;
|
|
const scripture = document.getElementById('bulletinScripture').value;
|
|
const sunset = document.getElementById('bulletinSunset').value;
|
|
const isActive = document.getElementById('bulletinIsActive').checked;
|
|
const coverImageFile = document.getElementById('bulletinCoverImage').files[0];
|
|
|
|
if (!title.trim()) {
|
|
alert('Please enter a title for the bulletin');
|
|
return;
|
|
}
|
|
|
|
if (!date) {
|
|
alert('Please select a date for the bulletin');
|
|
return;
|
|
}
|
|
|
|
const bulletinData = {
|
|
title: title,
|
|
date: date,
|
|
is_active: isActive
|
|
};
|
|
|
|
// Add optional sections if they have content
|
|
if (sabbathSchool.trim()) bulletinData.sabbath_school = sabbathSchool;
|
|
if (divineWorship.trim()) bulletinData.divine_worship = divineWorship;
|
|
if (scripture.trim()) bulletinData.scripture_reading = scripture;
|
|
if (sunset.trim()) bulletinData.sunset = sunset;
|
|
|
|
// First create the bulletin
|
|
fetch(API_BASE + '/admin/bulletins', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': 'Bearer ' + token,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(bulletinData)
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
const bulletinId = data.data.id;
|
|
|
|
// If cover image is selected, upload it
|
|
if (coverImageFile) {
|
|
const formData = new FormData();
|
|
formData.append('file', coverImageFile);
|
|
|
|
console.log('Uploading cover image:', coverImageFile.name, 'to bulletin:', bulletinId);
|
|
|
|
return fetch(API_BASE + '/upload/bulletins/' + bulletinId + '/cover', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': 'Bearer ' + token
|
|
},
|
|
body: formData
|
|
})
|
|
.then(response => {
|
|
console.log('Upload response status:', response.status);
|
|
if (!response.ok) {
|
|
return response.text().then(text => {
|
|
console.error('Upload error response:', text);
|
|
throw new Error(`HTTP ${response.status}: ${text}`);
|
|
});
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(uploadData => {
|
|
console.log('Upload success:', uploadData);
|
|
if (uploadData.success) {
|
|
closeModal();
|
|
showBulletins();
|
|
alert('Bulletin created with cover image successfully!');
|
|
} else {
|
|
alert('Bulletin created but cover image upload failed: ' + (uploadData.message || 'Unknown error'));
|
|
closeModal();
|
|
showBulletins();
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Upload error:', error);
|
|
alert('Bulletin created but cover image upload failed: ' + error.message);
|
|
closeModal();
|
|
showBulletins();
|
|
});
|
|
} else {
|
|
closeModal();
|
|
showBulletins();
|
|
alert('Bulletin created successfully!');
|
|
}
|
|
} else {
|
|
alert('Failed to create bulletin: ' + (data.message || 'Unknown error'));
|
|
}
|
|
})
|
|
.catch(error => alert('Error creating bulletin: ' + error.message));
|
|
}
|
|
|
|
function editBulletin(bulletinId) {
|
|
fetch(API_BASE + '/bulletins/' + bulletinId)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success && data.data) {
|
|
showEditBulletinModal(data.data);
|
|
} else {
|
|
alert('Failed to load bulletin details');
|
|
}
|
|
})
|
|
.catch(error => alert('Error loading bulletin: ' + error.message));
|
|
}
|
|
|
|
function showEditBulletinModal(bulletin) {
|
|
document.getElementById('modalTitle').textContent = 'Edit Bulletin';
|
|
document.getElementById('modalContent').innerHTML =
|
|
'<div class="form-grid">' +
|
|
'<div class="form-grid cols-2">' +
|
|
'<div>' +
|
|
'<label for="editBulletinTitle">Title</label>' +
|
|
'<input id="editBulletinTitle" type="text" value="' + escapeHtml(bulletin.title || '') + '" required>' +
|
|
'</div>' +
|
|
'<div>' +
|
|
'<label for="editBulletinDate">Date</label>' +
|
|
'<input id="editBulletinDate" type="date" value="' + (bulletin.date || '') + '" required>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<div>' +
|
|
'<label for="editBulletinSabbathSchool">Sabbath School</label>' +
|
|
'<textarea id="editBulletinSabbathSchool" rows="4">' + escapeHtml(stripHtmlTags(bulletin.sabbath_school || '')) + '</textarea>' +
|
|
'</div>' +
|
|
'<div>' +
|
|
'<label for="editBulletinDivineWorship">Divine Worship</label>' +
|
|
'<textarea id="editBulletinDivineWorship" rows="6">' + escapeHtml(stripHtmlTags(bulletin.divine_worship || '')) + '</textarea>' +
|
|
'</div>' +
|
|
'<div>' +
|
|
'<label for="editBulletinScripture">Scripture Reading</label>' +
|
|
'<textarea id="editBulletinScripture" rows="4">' + escapeHtml(stripHtmlTags(bulletin.scripture_reading || '')) + '</textarea>' +
|
|
'</div>' +
|
|
'<div>' +
|
|
'<label for="editBulletinSunset">Sunset Times</label>' +
|
|
'<textarea id="editBulletinSunset" rows="2">' + escapeHtml(stripHtmlTags(bulletin.sunset || '')) + '</textarea>' +
|
|
'</div>' +
|
|
'<div>' +
|
|
'<label for="editBulletinCoverImage">Cover Image</label>' +
|
|
'<input type="file" id="editBulletinCoverImage" accept="image/*" style="width: 100%;">' +
|
|
'<div id="editBulletinCoverImagePreview" style="margin-top: 10px; text-align: center;">' +
|
|
(bulletin.cover_image ? '<div style="margin-bottom: 10px;"><strong>Current:</strong><br><img src="' + escapeHtml(bulletin.cover_image) + '" style="max-width: 200px; max-height: 150px; border-radius: 8px;" onerror="this.parentElement.innerHTML=\'❌ Current image unavailable\'"></div>' : '') +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<div>' +
|
|
'<label>' +
|
|
'<input id="editBulletinIsActive" type="checkbox"' + (bulletin.is_active ? ' checked' : '') + '>' +
|
|
' Set as active bulletin' +
|
|
'</label>' +
|
|
'</div>' +
|
|
'</div>';
|
|
|
|
document.getElementById('modalActions').innerHTML =
|
|
'<button onclick="saveBulletinEdit(\'' + bulletin.id + '\')" class="btn-edit">' +
|
|
'<span>💾</span>Save Changes' +
|
|
'</button>';
|
|
|
|
// Add image preview functionality for file input
|
|
document.getElementById('editBulletinCoverImage').addEventListener('change', function(e) {
|
|
const file = e.target.files[0];
|
|
const preview = document.getElementById('editBulletinCoverImagePreview');
|
|
|
|
if (file) {
|
|
const reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
preview.innerHTML =
|
|
(bulletin.cover_image ? '<div style="margin-bottom: 10px;"><strong>Current:</strong><br><img src="' + escapeHtml(bulletin.cover_image) + '" style="max-width: 200px; max-height: 150px; border-radius: 8px;" onerror="this.parentElement.innerHTML=\'❌ Current image unavailable\'"></div>' : '') +
|
|
'<div style="margin-top: 10px;"><strong>New:</strong><br><img src="' + e.target.result + '" style="max-width: 200px; max-height: 150px; border-radius: 8px;"></div>';
|
|
};
|
|
reader.readAsDataURL(file);
|
|
} else {
|
|
// If no file selected, show only original image if it exists
|
|
preview.innerHTML = bulletin.cover_image ? '<div style="margin-bottom: 10px;"><strong>Current:</strong><br><img src="' + escapeHtml(bulletin.cover_image) + '" style="max-width: 200px; max-height: 150px; border-radius: 8px;" onerror="this.parentElement.innerHTML=\'❌ Current image unavailable\'"></div>' : '';
|
|
}
|
|
});
|
|
|
|
document.getElementById('modal').classList.remove('hidden');
|
|
}
|
|
|
|
function saveBulletinEdit(bulletinId) {
|
|
const title = document.getElementById('editBulletinTitle').value;
|
|
const date = document.getElementById('editBulletinDate').value;
|
|
const sabbathSchool = document.getElementById('editBulletinSabbathSchool').value;
|
|
const divineWorship = document.getElementById('editBulletinDivineWorship').value;
|
|
const scripture = document.getElementById('editBulletinScripture').value;
|
|
const sunset = document.getElementById('editBulletinSunset').value;
|
|
const isActive = document.getElementById('editBulletinIsActive').checked;
|
|
const coverImageFile = document.getElementById('editBulletinCoverImage').files[0];
|
|
|
|
if (!title.trim()) {
|
|
alert('Please enter a title for the bulletin');
|
|
return;
|
|
}
|
|
|
|
if (!date) {
|
|
alert('Please select a date for the bulletin');
|
|
return;
|
|
}
|
|
|
|
const bulletinData = {
|
|
title: title,
|
|
date: date,
|
|
is_active: isActive,
|
|
sabbath_school: sabbathSchool || '',
|
|
divine_worship: divineWorship || '',
|
|
scripture_reading: scripture || '',
|
|
sunset: sunset || ''
|
|
};
|
|
|
|
// First update the bulletin text content
|
|
fetch(API_BASE + '/admin/bulletins/' + bulletinId, {
|
|
method: 'PUT',
|
|
headers: {
|
|
'Authorization': 'Bearer ' + token,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(bulletinData)
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
// If cover image is selected, upload it
|
|
if (coverImageFile) {
|
|
const formData = new FormData();
|
|
formData.append('file', coverImageFile);
|
|
|
|
console.log('Uploading cover image:', coverImageFile.name, 'to bulletin:', bulletinId);
|
|
|
|
return fetch(API_BASE + '/upload/bulletins/' + bulletinId + '/cover', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': 'Bearer ' + token
|
|
},
|
|
body: formData
|
|
})
|
|
.then(response => {
|
|
console.log('Upload response status:', response.status);
|
|
if (!response.ok) {
|
|
return response.text().then(text => {
|
|
console.error('Upload error response:', text);
|
|
throw new Error(`HTTP ${response.status}: ${text}`);
|
|
});
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(uploadData => {
|
|
console.log('Upload success:', uploadData);
|
|
if (uploadData.success) {
|
|
closeModal();
|
|
showBulletins();
|
|
alert('Bulletin updated with new cover image successfully!');
|
|
} else {
|
|
alert('Bulletin updated but cover image upload failed: ' + (uploadData.message || 'Unknown error'));
|
|
closeModal();
|
|
showBulletins();
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Upload error:', error);
|
|
alert('Bulletin updated but cover image upload failed: ' + error.message);
|
|
closeModal();
|
|
showBulletins();
|
|
});
|
|
} else {
|
|
closeModal();
|
|
showBulletins();
|
|
alert('Bulletin updated successfully!');
|
|
}
|
|
} else {
|
|
alert('Failed to update bulletin: ' + (data.message || 'Unknown error'));
|
|
}
|
|
})
|
|
.catch(error => alert('Error updating bulletin: ' + error.message));
|
|
}
|
|
|
|
function deleteBulletin(bulletinId) {
|
|
if (!confirm('Are you sure you want to delete this bulletin? This action cannot be undone.')) return;
|
|
|
|
fetch(API_BASE + '/admin/bulletins/' + bulletinId, {
|
|
method: 'DELETE',
|
|
headers: { 'Authorization': 'Bearer ' + token }
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showBulletins();
|
|
alert('Bulletin deleted successfully!');
|
|
} else {
|
|
alert('Failed to delete bulletin: ' + (data.message || 'Unknown error'));
|
|
}
|
|
})
|
|
.catch(error => alert('Error deleting bulletin: ' + error.message));
|
|
}
|
|
|
|
function showContentError(message) {
|
|
document.getElementById('content').innerHTML = '<div class="empty-state"><p style="color: var(--danger-color); font-size: 1.25rem;">⚠️ ' + message + '</p></div>';
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
if (!text) return '';
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
function renderHtmlContent(html) {
|
|
if (!html) return '';
|
|
return html
|
|
.replace(/<p style="[^"]*">/g, '<p>')
|
|
.replace(/<\/p>/g, '</p>')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/&/g, '&');
|
|
}
|
|
|
|
function stripHtmlTags(html) {
|
|
if (!html) return '';
|
|
// Create a temporary div to parse HTML and extract text content
|
|
const div = document.createElement('div');
|
|
div.innerHTML = html;
|
|
return div.textContent || div.innerText || '';
|
|
}
|
|
|
|
function formatDate(dateString) {
|
|
if (!dateString) return 'N/A';
|
|
try {
|
|
// Just display the date/time as-is from the database without timezone conversion
|
|
if (dateString.includes('T')) {
|
|
// For datetime strings like "2025-06-14T19:00:00.000Z"
|
|
// Just remove the T and Z and show readable format
|
|
const [datePart, timePart] = dateString.split('T');
|
|
const cleanTime = timePart.replace(/\.\d{3}Z?$/, ''); // Remove milliseconds and Z
|
|
return `${datePart} ${cleanTime}`;
|
|
}
|
|
// If it's already just a date, return as-is
|
|
return dateString;
|
|
} catch {
|
|
return dateString;
|
|
}
|
|
}
|
|
|
|
// Initialize app with token validation
|
|
async function initializeApp() {
|
|
if (token) {
|
|
console.log('Found stored token, validating...');
|
|
const isValid = await validateToken();
|
|
|
|
if (isValid) {
|
|
console.log('Token is valid, showing dashboard');
|
|
showDashboard();
|
|
loadStats();
|
|
} else {
|
|
console.log('Token is invalid, showing login');
|
|
// Token is invalid, clear it and show login
|
|
token = '';
|
|
localStorage.removeItem('adminToken');
|
|
}
|
|
}
|
|
|
|
if (!token) {
|
|
// Load recurring types even when not logged in, so they're ready when needed
|
|
loadRecurringTypes();
|
|
}
|
|
}
|
|
|
|
// Start the app
|
|
initializeApp();
|
|
|
|
// Event listeners
|
|
document.addEventListener('keydown', function(event) {
|
|
if (event.key === 'Escape') {
|
|
closeModal();
|
|
}
|
|
});
|
|
|
|
document.getElementById('modal').addEventListener('click', function(event) {
|
|
if (event.target === this) {
|
|
closeModal();
|
|
}
|
|
}); |