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
Some checks failed
Build & Push Docker / build (push) Has been cancelled
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user