Show baseline/reference form in a modal with live month preview

Replaces the small inline form with a centred modal dialog. When both
dates are selected, a live chip list shows every month covered (up to
36 months) so it is immediately clear what periods will be loaded.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Paul Trowbridge 2026-04-01 12:24:20 -04:00
parent cfee3e96b9
commit 10441a4761
3 changed files with 146 additions and 17 deletions

View File

@ -375,10 +375,48 @@ function showLoadForm(op) {
state.loadDataOp = op;
document.getElementById('load-data-title').textContent =
op === 'baseline' ? 'Load Baseline' : 'Load Reference';
document.getElementById('load-data-form').classList.remove('hidden');
document.getElementById('load-date-from').value = '';
document.getElementById('load-date-to').value = '';
document.getElementById('load-note').value = '';
document.getElementById('load-date-preview').classList.add('hidden');
document.getElementById('load-data-modal').classList.remove('hidden');
document.getElementById('load-date-from').focus();
}
function hideLoadModal() {
document.getElementById('load-data-modal').classList.add('hidden');
}
function updateDatePreview() {
const fromVal = document.getElementById('load-date-from').value;
const toVal = document.getElementById('load-date-to').value;
const preview = document.getElementById('load-date-preview');
const chips = document.getElementById('load-date-chips');
const label = preview.querySelector('.load-preview-label');
if (!fromVal || !toVal) { preview.classList.add('hidden'); return; }
const from = new Date(fromVal + 'T00:00:00');
const to = new Date(toVal + 'T00:00:00');
if (isNaN(from) || isNaN(to) || from > to) { preview.classList.add('hidden'); return; }
const months = [];
const cur = new Date(from.getFullYear(), from.getMonth(), 1);
const end = new Date(to.getFullYear(), to.getMonth(), 1);
while (cur <= end) { months.push(new Date(cur)); cur.setMonth(cur.getMonth() + 1); }
const fmt = new Intl.DateTimeFormat('en-US', { month: 'short', year: 'numeric' });
label.textContent = `${months.length} month${months.length !== 1 ? 's' : ''} covered`;
if (months.length <= 36) {
chips.innerHTML = months.map(m => `<span class="date-chip">${fmt.format(m)}</span>`).join('');
} else {
chips.innerHTML = `<span class="date-chip-summary">${months.length} months — ${fmt.format(months[0])}${fmt.format(months[months.length - 1])}</span>`;
}
preview.classList.remove('hidden');
}
async function submitLoadData() {
const date_from = document.getElementById('load-date-from').value;
const date_to = document.getElementById('load-date-to').value;
@ -394,7 +432,7 @@ async function submitLoadData() {
try {
showStatus(`Loading ${state.loadDataOp}...`, 'info');
const result = await api('POST', `/versions/${state.selectedVersionId}/${state.loadDataOp}`, body);
document.getElementById('load-data-form').classList.add('hidden');
hideLoadModal();
showStatus(`${state.loadDataOp} loaded — ${result.rows_affected} rows`, 'success');
} catch (err) {
showStatus(err.message, 'error');
@ -879,9 +917,10 @@ document.addEventListener('DOMContentLoaded', () => {
document.getElementById('vbtn-toggle').addEventListener('click', toggleVersionStatus);
document.getElementById('vbtn-delete').addEventListener('click', deleteVersion);
document.getElementById('btn-load-submit').addEventListener('click', submitLoadData);
document.getElementById('btn-load-cancel').addEventListener('click', () => {
document.getElementById('load-data-form').classList.add('hidden');
});
document.getElementById('btn-load-cancel').addEventListener('click', hideLoadModal);
document.getElementById('btn-load-close').addEventListener('click', hideLoadModal);
document.getElementById('load-date-from').addEventListener('change', updateDatePreview);
document.getElementById('load-date-to').addEventListener('change', updateDatePreview);
// forecast view buttons
document.getElementById('btn-forecast-refresh').addEventListener('click', loadForecastData);

View File

@ -94,18 +94,6 @@
<button class="btn" id="vbtn-toggle">Close Version</button>
<button class="btn btn-danger" id="vbtn-delete">Delete</button>
</div>
<div id="load-data-form" class="inline-form hidden">
<h3 id="load-data-title">Load Baseline</h3>
<div class="form-row">
<label>Date From<input type="date" id="load-date-from" /></label>
<label>Date To<input type="date" id="load-date-to" /></label>
<label>Note<input type="text" id="load-note" placeholder="optional" /></label>
</div>
<div class="form-actions">
<button id="btn-load-submit" class="btn btn-primary">Load</button>
<button id="btn-load-cancel" class="btn">Cancel</button>
</div>
</div>
</div>
<!-- ===== FORECAST VIEW ===== -->
@ -170,6 +158,31 @@
</main>
</div>
<!-- Load baseline / reference modal -->
<div id="load-data-modal" class="modal-overlay hidden">
<div class="modal load-data-modal">
<div class="modal-header">
<span id="load-data-title">Load Baseline</span>
<button id="btn-load-close" class="btn-icon">×</button>
</div>
<div id="load-data-body">
<div class="load-form-fields">
<label>Date From<input type="date" id="load-date-from" /></label>
<label>Date To<input type="date" id="load-date-to" /></label>
<label>Note<input type="text" id="load-note" placeholder="optional" /></label>
</div>
<div id="load-date-preview" class="load-date-preview hidden">
<div class="load-preview-label"></div>
<div id="load-date-chips" class="date-chips"></div>
</div>
</div>
<div class="modal-footer">
<button id="btn-load-submit" class="btn btn-primary">Load</button>
<button id="btn-load-cancel" class="btn">Cancel</button>
</div>
</div>
</div>
<!-- Table preview modal -->
<div id="modal-overlay" class="modal-overlay hidden">
<div class="modal">

View File

@ -312,6 +312,83 @@ body {
#modal-body { padding: 16px 18px; overflow-y: auto; flex: 1; font-size: 12px; }
.modal-footer { padding: 10px 18px; border-top: 1px solid #eee; display: flex; justify-content: flex-end; gap: 8px; }
/* ============================================================
LOAD BASELINE / REFERENCE MODAL
============================================================ */
.load-data-modal { width: 480px; max-height: 80vh; }
#load-data-body {
padding: 20px 24px;
display: flex;
flex-direction: column;
gap: 20px;
overflow-y: auto;
}
.load-form-fields {
display: flex;
flex-direction: column;
gap: 12px;
}
.load-form-fields label {
font-size: 11px;
color: #555;
display: flex;
flex-direction: column;
gap: 4px;
}
.load-form-fields input[type=date],
.load-form-fields input[type=text] {
border: 1px solid #dce1e7;
padding: 7px 10px;
border-radius: 3px;
font-size: 13px;
color: #333;
width: 100%;
}
.load-form-fields input[type=date]:focus,
.load-form-fields input[type=text]:focus {
outline: none;
border-color: #2980b9;
box-shadow: 0 0 0 2px rgba(41,128,185,.15);
}
.load-date-preview { display: flex; flex-direction: column; gap: 8px; }
.load-date-preview.hidden { display: none; }
.load-preview-label {
font-size: 11px;
font-weight: 600;
color: #7f8c8d;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.date-chips {
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.date-chip {
background: #eaf4fb;
color: #1a6fa8;
border: 1px solid #c5dff0;
padding: 3px 9px;
border-radius: 12px;
font-size: 11px;
white-space: nowrap;
}
.date-chip-summary {
font-size: 12px;
color: #555;
font-style: italic;
}
.preview-section h4 { font-size: 12px; margin-bottom: 6px; color: #555; }
.preview-section + .preview-section { margin-top: 16px; }
.preview-table { border-collapse: collapse; width: 100%; font-size: 11px; }