feat: v4.1 - Czech diacritics in PDF (Liberation fonts), hour/min duration fields, day select in modal
Some checks failed
Build & Push Docker / build (push) Has been cancelled

This commit is contained in:
2026-02-20 17:39:38 +01:00
parent f0e7c3b093
commit 6c4ca5e9be
7 changed files with 175 additions and 37 deletions

View File

@@ -875,3 +875,40 @@ body {
.block-el:hover::after {
opacity: 1;
}
/* Duration row (hours + minutes) */
.duration-row {
display: flex;
gap: 8px;
align-items: center;
}
.duration-field {
display: flex;
align-items: center;
gap: 4px;
flex: 1;
}
.duration-field input[type="number"] {
width: 60px;
text-align: center;
padding: 6px 8px;
border: 1px solid var(--border);
border-radius: var(--radius-sm);
font-size: 14px;
color: var(--text);
background: var(--white);
}
.duration-field input[type="number"]:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.15);
}
.duration-unit {
font-size: 12px;
color: var(--text-light);
white-space: nowrap;
}

View File

@@ -168,7 +168,7 @@
</div>
<div class="form-group">
<label>Den</label>
<input type="date" id="modalBlockDate">
<select id="modalBlockDate"></select>
</div>
<div class="form-group">
<label>Typ programu</label>
@@ -177,16 +177,25 @@
<div class="form-row">
<div class="form-group">
<label>Začátek</label>
<input type="time" id="modalBlockStart">
<input type="time" id="modalBlockStart" step="900">
</div>
<div class="form-group">
<label>Konec</label>
<input type="time" id="modalBlockEnd">
<input type="time" id="modalBlockEnd" step="900">
</div>
</div>
<div class="form-group">
<label>Nebo trvání</label>
<input type="text" id="modalBlockDuration" placeholder="HH:MM (např. 1:30)">
<div class="duration-row">
<div class="duration-field">
<input type="number" id="modalDurHours" min="0" max="23" placeholder="0">
<span class="duration-unit">hod</span>
</div>
<div class="duration-field">
<input type="number" id="modalDurMinutes" min="0" max="59" step="15" placeholder="0">
<span class="duration-unit">min</span>
</div>
</div>
</div>
<div class="form-group">
<label>Garant</label>

View File

@@ -173,10 +173,11 @@ const App = {
document.getElementById('modalBlockEnd').value = block.end || '';
document.getElementById('modalBlockResponsible').value = block.responsible || '';
document.getElementById('modalBlockNotes').value = block.notes || '';
this._updateDuration();
this._populateTypeSelect(block.type_id);
document.getElementById('modalBlockDate').value = block.date || '';
this._populateDaySelect(block.date);
this._updateDuration();
document.getElementById('modalDeleteBtn').style.display = 'inline-block';
document.getElementById('blockModal').classList.remove('hidden');
},
@@ -190,10 +191,11 @@ const App = {
document.getElementById('modalBlockEnd').value = end || '10:00';
document.getElementById('modalBlockResponsible').value = '';
document.getElementById('modalBlockNotes').value = '';
document.getElementById('modalBlockDate').value = date || '';
this._updateDuration();
this._populateTypeSelect(null);
this._populateDaySelect(date);
this._updateDuration();
document.getElementById('modalDeleteBtn').style.display = 'none';
document.getElementById('blockModal').classList.remove('hidden');
},
@@ -210,18 +212,59 @@ const App = {
});
},
_populateDaySelect(selectedDate) {
const sel = document.getElementById('modalBlockDate');
sel.innerHTML = '';
const dates = this.getDates();
if (dates.length === 0) {
// Fallback: show today
const today = new Date().toISOString().slice(0, 10);
const opt = document.createElement('option');
opt.value = today;
opt.textContent = this._formatDateLabel(today);
sel.appendChild(opt);
return;
}
dates.forEach(date => {
const opt = document.createElement('option');
opt.value = date;
opt.textContent = this._formatDateLabel(date);
if (date === selectedDate) opt.selected = true;
sel.appendChild(opt);
});
// If none selected, default to first
if (!selectedDate || !dates.includes(selectedDate)) {
sel.value = dates[0];
}
},
_formatDateLabel(dateStr) {
const d = new Date(dateStr + 'T12:00:00');
const weekday = d.toLocaleDateString('cs-CZ', { weekday: 'long' });
const weekdayCap = weekday.charAt(0).toUpperCase() + weekday.slice(1);
return `${weekdayCap} (${d.getDate()}.${d.getMonth() + 1})`;
},
_updateDuration() {
const startVal = document.getElementById('modalBlockStart').value;
const endVal = document.getElementById('modalBlockEnd').value;
if (!startVal || !endVal) {
document.getElementById('modalBlockDuration').value = '';
document.getElementById('modalDurHours').value = '';
document.getElementById('modalDurMinutes').value = '';
return;
}
const s = this.parseTimeToMin(startVal);
let e = this.parseTimeToMin(endVal);
if (e < s) e += 24 * 60; // overnight
if (e <= s) e += 24 * 60; // overnight
const dur = e - s;
document.getElementById('modalBlockDuration').value = this.minutesToTime(dur);
document.getElementById('modalDurHours').value = Math.floor(dur / 60);
document.getElementById('modalDurMinutes').value = dur % 60;
},
_getDurationMinutes() {
const h = parseInt(document.getElementById('modalDurHours').value) || 0;
const m = parseInt(document.getElementById('modalDurMinutes').value) || 0;
return h * 60 + m;
},
_saveModal() {
@@ -334,19 +377,21 @@ const App = {
document.getElementById('modalSaveBtn').addEventListener('click', () => this._saveModal());
document.getElementById('modalDeleteBtn').addEventListener('click', () => this._deleteBlock());
// Duration ↔ end sync
document.getElementById('modalBlockDuration').addEventListener('input', (e) => {
const durStr = e.target.value.trim();
// Duration ↔ end sync (hours + minutes fields)
const durUpdater = () => {
const startVal = document.getElementById('modalBlockStart').value;
if (!startVal || !durStr) return;
const durParts = durStr.split(':').map(Number);
if (durParts.length < 2 || isNaN(durParts[0]) || isNaN(durParts[1])) return;
const durMin = durParts[0] * 60 + durParts[1];
if (durMin <= 0) return;
const durMin = this._getDurationMinutes();
if (!startVal || durMin <= 0) return;
const startMin = this.parseTimeToMin(startVal);
const endMin = startMin + durMin;
document.getElementById('modalBlockEnd').value = this.minutesToTime(endMin % 1440 || endMin);
});
// Allow overnight (> 24h notation handled as raw minutes)
const h = Math.floor(endMin / 60) % 24;
const m = endMin % 60;
document.getElementById('modalBlockEnd').value =
`${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
};
document.getElementById('modalDurHours').addEventListener('input', durUpdater);
document.getElementById('modalDurMinutes').addEventListener('input', durUpdater);
document.getElementById('modalBlockEnd').addEventListener('input', () => {
this._updateDuration();