Files
scenar-creator/app/static/js/app.js
Daneel e2bdadd0ce
Some checks failed
Build & Push Docker / build (push) Has been cancelled
feat: refactor to FastAPI architecture v2.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 16:28:21 +01:00

259 lines
9.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Main application logic for Scenar Creator SPA.
*/
window.currentDocument = null;
let typeCounter = 1;
let scheduleCounter = 1;
/* --- Tab switching --- */
function switchTab(event, tabId) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
event.target.classList.add('active');
document.getElementById(tabId).classList.add('active');
}
/* --- Status messages --- */
function showStatus(message, type) {
const el = document.getElementById('statusMessage');
el.textContent = message;
el.className = 'status-message ' + type;
el.style.display = 'block';
setTimeout(() => { el.style.display = 'none'; }, 5000);
}
/* --- Type management --- */
function addTypeRow() {
const container = document.getElementById('typesContainer');
const idx = typeCounter++;
const div = document.createElement('div');
div.className = 'type-row';
div.setAttribute('data-index', idx);
div.innerHTML = `
<input type="text" name="type_name_${idx}" placeholder="Kód typu" class="type-code">
<input type="text" name="type_desc_${idx}" placeholder="Popis">
<input type="color" name="type_color_${idx}" value="#0070C0">
<button type="button" class="btn btn-danger btn-sm" onclick="removeTypeRow(${idx})">X</button>
`;
container.appendChild(div);
updateTypeDatalist();
}
function removeTypeRow(idx) {
const row = document.querySelector(`.type-row[data-index="${idx}"]`);
if (row) row.remove();
updateTypeDatalist();
}
function updateTypeDatalist() {
const datalist = document.getElementById('availableTypes');
datalist.innerHTML = '';
document.querySelectorAll('#typesContainer .type-row').forEach(row => {
const nameInput = row.querySelector('input[name^="type_name_"]');
if (nameInput && nameInput.value.trim()) {
const opt = document.createElement('option');
opt.value = nameInput.value.trim();
datalist.appendChild(opt);
}
});
}
// Update datalist on type name changes
document.getElementById('typesContainer').addEventListener('input', function (e) {
if (e.target.name && e.target.name.startsWith('type_name_')) {
updateTypeDatalist();
}
});
/* --- Schedule management --- */
function addScheduleRow() {
const tbody = document.getElementById('scheduleBody');
const idx = scheduleCounter++;
const tr = document.createElement('tr');
tr.setAttribute('data-index', idx);
tr.innerHTML = `
<td><input type="date" name="datum_${idx}" required></td>
<td><input type="time" name="zacatek_${idx}" required></td>
<td><input type="time" name="konec_${idx}" required></td>
<td><input type="text" name="program_${idx}" required placeholder="Název bloku"></td>
<td><input type="text" name="typ_${idx}" list="availableTypes" required placeholder="Typ"></td>
<td><input type="text" name="garant_${idx}" placeholder="Garant"></td>
<td><input type="text" name="poznamka_${idx}" placeholder="Poznámka"></td>
<td><button type="button" class="btn btn-danger btn-sm" onclick="removeScheduleRow(${idx})">X</button></td>
`;
tbody.appendChild(tr);
}
function removeScheduleRow(idx) {
const row = document.querySelector(`#scheduleBody tr[data-index="${idx}"]`);
if (row) row.remove();
}
/* --- Build ScenarioDocument from builder form --- */
function buildDocumentFromForm() {
const title = document.getElementById('builderTitle').value.trim();
const detail = document.getElementById('builderDetail').value.trim();
if (!title || !detail) {
throw new Error('Název akce a detail jsou povinné');
}
// Collect types
const programTypes = [];
document.querySelectorAll('#typesContainer .type-row').forEach(row => {
const code = row.querySelector('input[name^="type_name_"]').value.trim();
const desc = row.querySelector('input[name^="type_desc_"]').value.trim();
const color = row.querySelector('input[name^="type_color_"]').value;
if (code) {
programTypes.push({ code, description: desc, color });
}
});
// Collect blocks
const blocks = [];
document.querySelectorAll('#scheduleBody tr').forEach(tr => {
const inputs = tr.querySelectorAll('input');
const datum = inputs[0].value;
const zacatek = inputs[1].value;
const konec = inputs[2].value;
const program = inputs[3].value.trim();
const typ = inputs[4].value.trim();
const garant = inputs[5].value.trim() || null;
const poznamka = inputs[6].value.trim() || null;
if (datum && zacatek && konec && program && typ) {
blocks.push({ datum, zacatek, konec, program, typ, garant, poznamka });
}
});
if (blocks.length === 0) {
throw new Error('Přidejte alespoň jeden blok');
}
return {
version: "1.0",
event: { title, detail },
program_types: programTypes,
blocks
};
}
/* --- Handle builder form submit (Excel) --- */
async function handleBuild(event) {
event.preventDefault();
try {
const doc = buildDocumentFromForm();
const blob = await API.postBlob('/api/generate-excel', doc);
API.downloadBlob(blob, 'scenar_timetable.xlsx');
showStatus('Excel vygenerován', 'success');
} catch (err) {
showStatus('Chyba: ' + err.message, 'error');
}
return false;
}
/* --- Handle builder PDF --- */
async function handleBuildPdf() {
try {
const doc = buildDocumentFromForm();
const blob = await API.postBlob('/api/generate-pdf', doc);
API.downloadBlob(blob, 'scenar_timetable.pdf');
showStatus('PDF vygenerován', 'success');
} catch (err) {
showStatus('Chyba: ' + err.message, 'error');
}
}
/* --- Handle Excel import --- */
async function handleImport(event) {
event.preventDefault();
const form = document.getElementById('importForm');
const formData = new FormData(form);
try {
const result = await API.postFormData('/api/import-excel', formData);
if (result.success && result.document) {
window.currentDocument = result.document;
showImportedDocument(result.document);
if (result.warnings && result.warnings.length > 0) {
showStatus('Import OK, warnings: ' + result.warnings.join('; '), 'success');
} else {
showStatus('Excel importován', 'success');
}
} else {
showStatus('Import failed: ' + (result.errors || []).join('; '), 'error');
}
} catch (err) {
showStatus('Chyba importu: ' + err.message, 'error');
}
return false;
}
/* --- Show imported document in editor --- */
function showImportedDocument(doc) {
const area = document.getElementById('editorArea');
area.style.display = 'block';
// Info
document.getElementById('importedInfo').innerHTML =
`<strong>${doc.event.title}</strong> — ${doc.event.detail}`;
// Types
const typesHtml = doc.program_types.map((pt, i) => `
<div class="imported-type-row">
<input type="text" value="${pt.code}" data-field="code" data-idx="${i}">
<input type="text" value="${pt.description}" data-field="description" data-idx="${i}">
<input type="color" value="${pt.color}" data-field="color" data-idx="${i}">
</div>
`).join('');
document.getElementById('importedTypesContainer').innerHTML = typesHtml;
// Blocks
const blocksHtml = doc.blocks.map(b =>
`<div class="block-item">${b.datum} ${b.zacatek}${b.konec} | <strong>${b.program}</strong> [${b.typ}] ${b.garant || ''}</div>`
).join('');
document.getElementById('importedBlocksContainer').innerHTML = blocksHtml;
}
/* --- Get current document (with any edits from import editor) --- */
function getCurrentDocument() {
if (!window.currentDocument) {
throw new Error('No document loaded');
}
// Update types from editor
const typeRows = document.querySelectorAll('#importedTypesContainer .imported-type-row');
if (typeRows.length > 0) {
window.currentDocument.program_types = Array.from(typeRows).map(row => ({
code: row.querySelector('[data-field="code"]').value.trim(),
description: row.querySelector('[data-field="description"]').value.trim(),
color: row.querySelector('[data-field="color"]').value,
}));
}
return window.currentDocument;
}
/* --- Generate Excel from imported data --- */
async function generateExcelFromImport() {
try {
const doc = getCurrentDocument();
const blob = await API.postBlob('/api/generate-excel', doc);
API.downloadBlob(blob, 'scenar_timetable.xlsx');
showStatus('Excel vygenerován', 'success');
} catch (err) {
showStatus('Chyba: ' + err.message, 'error');
}
}
/* --- Generate PDF from imported data --- */
async function generatePdfFromImport() {
try {
const doc = getCurrentDocument();
const blob = await API.postBlob('/api/generate-pdf', doc);
API.downloadBlob(blob, 'scenar_timetable.pdf');
showStatus('PDF vygenerován', 'success');
} catch (err) {
showStatus('Chyba: ' + err.message, 'error');
}
}