3a7340975f
Campaign CRUD API with cascading delete across all campaign-scoped tables, a systems endpoint to seed the create-campaign form, active campaign state wired to the sidebar chaos factor, and a campaign management view for creating/switching/deleting campaigns.
119 lines
3.7 KiB
JavaScript
119 lines
3.7 KiB
JavaScript
// Mythic Oracle — campaign management view
|
|
|
|
import { getCampaigns, createCampaign, deleteCampaign, getSystems } from './api.js';
|
|
import { setActiveCampaign, getActiveCampaign } from './app.js';
|
|
|
|
const container = document.getElementById('view-campaign-management');
|
|
|
|
let campaignsCache = [];
|
|
let systemsCache = [];
|
|
|
|
function escapeHtml(value) {
|
|
const div = document.createElement('div');
|
|
div.textContent = value;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
function campaignItemHtml(campaign) {
|
|
const active = getActiveCampaign();
|
|
const isActive = Boolean(active) && active.id === campaign.id;
|
|
|
|
return `
|
|
<li class="campaign-item${isActive ? ' active' : ''}" data-id="${campaign.id}">
|
|
<button type="button" class="campaign-select" data-id="${campaign.id}">
|
|
<span class="campaign-name">${escapeHtml(campaign.name)}</span>
|
|
<span class="campaign-system">${escapeHtml(campaign.system_name)}</span>
|
|
${isActive ? '<span class="campaign-active-badge">Active</span>' : ''}
|
|
</button>
|
|
<button type="button" class="campaign-delete" data-id="${campaign.id}" aria-label="Delete ${escapeHtml(campaign.name)}">×</button>
|
|
</li>
|
|
`;
|
|
}
|
|
|
|
function render() {
|
|
const systemOptions = systemsCache
|
|
.map((system) => `<option value="${system.id}">${escapeHtml(system.name)}</option>`)
|
|
.join('');
|
|
|
|
const campaignItems = campaignsCache.length
|
|
? campaignsCache.map(campaignItemHtml).join('')
|
|
: '<li class="campaign-empty">No campaigns yet — create one below to get started.</li>';
|
|
|
|
container.innerHTML = `
|
|
<h2>Campaign Management</h2>
|
|
|
|
<ul class="campaign-list">
|
|
${campaignItems}
|
|
</ul>
|
|
|
|
<form id="createCampaignForm" class="campaign-form">
|
|
<input type="text" id="campaignNameInput" placeholder="Campaign name" autocomplete="off" required>
|
|
<select id="campaignSystemSelect" required>
|
|
${systemOptions}
|
|
</select>
|
|
<button type="submit">Create Campaign</button>
|
|
</form>
|
|
`;
|
|
}
|
|
|
|
async function loadAndRender() {
|
|
[campaignsCache, systemsCache] = await Promise.all([getCampaigns(), getSystems()]);
|
|
render();
|
|
}
|
|
|
|
function bindEvents() {
|
|
container.addEventListener('submit', async (event) => {
|
|
if (event.target.id !== 'createCampaignForm') return;
|
|
event.preventDefault();
|
|
|
|
const nameInput = document.getElementById('campaignNameInput');
|
|
const systemSelect = document.getElementById('campaignSystemSelect');
|
|
const name = nameInput.value.trim();
|
|
const systemId = Number(systemSelect.value);
|
|
|
|
if (!name || !systemId) return;
|
|
|
|
const campaign = await createCampaign({ name, system_id: systemId });
|
|
await setActiveCampaign(campaign);
|
|
await loadAndRender();
|
|
});
|
|
|
|
container.addEventListener('click', async (event) => {
|
|
const selectBtn = event.target.closest('.campaign-select');
|
|
if (selectBtn) {
|
|
const id = Number(selectBtn.dataset.id);
|
|
const campaign = campaignsCache.find((c) => c.id === id);
|
|
if (campaign) {
|
|
await setActiveCampaign(campaign);
|
|
render();
|
|
}
|
|
return;
|
|
}
|
|
|
|
const deleteBtn = event.target.closest('.campaign-delete');
|
|
if (deleteBtn) {
|
|
const id = Number(deleteBtn.dataset.id);
|
|
const campaign = campaignsCache.find((c) => c.id === id);
|
|
if (!campaign) return;
|
|
|
|
const confirmed = confirm(
|
|
`Delete "${campaign.name}"? This permanently removes all of its threads, NPCs, notes, and character data.`
|
|
);
|
|
if (!confirmed) return;
|
|
|
|
await deleteCampaign(id);
|
|
if (getActiveCampaign()?.id === id) {
|
|
await setActiveCampaign(null);
|
|
}
|
|
await loadAndRender();
|
|
}
|
|
});
|
|
}
|
|
|
|
function init() {
|
|
bindEvents();
|
|
loadAndRender();
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', init);
|