636 lines
27 KiB
Plaintext
636 lines
27 KiB
Plaintext
{block content}
|
|
|
|
<!-- Načtení SortableJS pro Drag & Drop -->
|
|
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.2/Sortable.min.js"></script>
|
|
|
|
<div class="container-fluid">
|
|
<div class="content-card my-3">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<h2 class="h4 mb-0">
|
|
<i class="bi {if $isEdit}bi-pencil-square{else}bi-file-earmark-plus-fill{/if} me-2"></i>
|
|
{if $isEdit}Úprava faktury{else}Nová faktura{/if}
|
|
</h2>
|
|
<a n:href="Faktura:default" class="btn btn-outline-secondary btn-sm">
|
|
<i class="bi bi-arrow-left me-1"></i>Zpět na seznam
|
|
</a>
|
|
</div>
|
|
|
|
<form n:name="fakturaForm" id="faktura-form" class="row g-3">
|
|
<div n:if="$form->hasErrors()" class="col-12">
|
|
<div class="alert alert-danger mb-0">
|
|
<ul class="mb-0">
|
|
<li n:foreach="$form->errors as $error">{$error}</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Hlavička Faktury -->
|
|
<div class="col-md-6">
|
|
<label n:name="ZAKAZNIK_ID" class="form-label fw-bold">Odběratel *</label>
|
|
<select n:name="ZAKAZNIK_ID" class="form-select" data-suggest="zakaznik" {if $isEdit}data-locked="1"{/if}></select>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label n:name="CISLO" class="form-label fw-bold">Číslo faktury *</label>
|
|
<input n:name="CISLO" class="form-control">
|
|
<input n:name="CISLO_ABS">
|
|
</div>
|
|
|
|
<div class="col-md-4">
|
|
<label n:name="DATUM_VYSTAVENI" class="form-label">Datum vystavení</label>
|
|
<input n:name="DATUM_VYSTAVENI" class="form-control">
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label n:name="DATUM_SPLATNOSTI" class="form-label">Datum splatnosti</label>
|
|
<input n:name="DATUM_SPLATNOSTI" class="form-control">
|
|
</div>
|
|
<div class="col-md-4" n:if="$isEdit">
|
|
<label class="form-label">Stav úhrady</label>
|
|
<div class="form-control bg-light">
|
|
<span class="badge {if $stavUhrady === 'PAID'}bg-success{elseif $stavUhrady === 'PARTIAL'}bg-info text-dark{else}bg-warning text-dark{/if}">
|
|
{if $stavUhrady === 'PAID'}Zaplaceno{elseif $stavUhrady === 'PARTIAL'}Částečně uhrazeno{else}Neuhrazeno{/if}
|
|
</span>
|
|
<span class="ms-2 text-muted">zbývá {$zbyva|number:2, ',', ' '} CZK</span>
|
|
</div>
|
|
</div>
|
|
|
|
<hr class="my-4">
|
|
|
|
<!-- Položky Faktury -->
|
|
<div class="col-12">
|
|
<h5 class="mb-3"><i class="bi bi-list-check me-2"></i>Položky faktury</h5>
|
|
|
|
<div class="table-responsive">
|
|
<table class="table table-bordered table-cards align-middle" id="items-table">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th style="width: 40px;" class="text-center"></th> <!-- Sloupec pro Drag Handle -->
|
|
<th>Název položky / služby</th>
|
|
<th style="width: 100px;">Počet</th>
|
|
<th style="width: 90px;">Jedn.</th>
|
|
<th style="width: 130px;">Cena/Jedn.</th>
|
|
<th style="width: 100px;">Sleva %</th>
|
|
<th style="width: 130px;" class="text-end">Celkem</th>
|
|
<th style="width: 50px;"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="items-body">
|
|
{foreach $polozky as $index => $p}
|
|
<tr class="item-row">
|
|
<td data-label="Pořadí" class="text-center align-middle drag-handle" style="cursor: grab;">
|
|
<i class="bi bi-grip-vertical text-muted fs-5"></i>
|
|
</td>
|
|
<td data-label="Název"><input type="text" name="polozky[{$index}][NAZEV]" value="{$p->NAZEV}" class="form-control item-nazev" placeholder="Začněte psát název práce nebo zboží" required autocomplete="off"></td>
|
|
<td data-label="Počet"><input type="number" step="0.01" name="polozky[{$index}][POCET]" value="{$p->POCET}" class="form-control item-pocet text-end" required></td>
|
|
<td data-label="Jedn."><input type="text" name="polozky[{$index}][JEDNOTKA]" value="{$p->JEDNOTKA}" class="form-control"></td>
|
|
<td data-label="Cena/Jedn."><input type="number" step="0.01" name="polozky[{$index}][CENA_JEDNOTKA]" value="{$p->CENA_JEDNOTKA}" class="form-control item-cena text-end" required></td>
|
|
<td data-label="Sleva %"><input type="number" step="0.1" name="polozky[{$index}][SLEVA]" value="{$p->SLEVA}" class="form-control item-sleva text-end"></td>
|
|
<td data-label="Celkem" class="text-end fw-bold item-celkem-text">{$p->CENA|number:2, ',', ' '} CZK</td>
|
|
<td class="text-center cell-actions">
|
|
<button type="button" class="btn btn-outline-danger btn-sm btn-remove-row"><i class="bi bi-trash"></i></button>
|
|
</td>
|
|
</tr>
|
|
{/foreach}
|
|
</tbody>
|
|
<tfoot>
|
|
<tr>
|
|
<td colspan="6" class="text-end fw-bold fs-5 cell-span">Celková částka:</td>
|
|
<td class="text-end fw-bold fs-5 text-primary" id="total-sum">0,00 CZK</td>
|
|
<td class="cell-actions"></td>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
</div>
|
|
|
|
<button type="button" class="btn btn-outline-success btn-sm" id="btn-add-item">
|
|
<i class="bi bi-plus-circle me-1"></i>Přidat položku
|
|
</button>
|
|
</div>
|
|
|
|
<hr class="my-4">
|
|
|
|
<div class="col-12">
|
|
<label n:name="CINNOSTI" class="form-label fw-bold">Činnosti / Popis plnění</label>
|
|
<textarea n:name="CINNOSTI" class="form-control" rows="3" placeholder="Rozsah vykonaných prací..."></textarea>
|
|
</div>
|
|
|
|
<div class="col-12 mt-4">
|
|
<button n:name="save" class="btn btn-primary me-2">
|
|
<i class="bi bi-check-lg me-1"></i>Uložit fakturu
|
|
</button>
|
|
<a n:href="Faktura:default" class="btn btn-outline-secondary">Zrušit</a>
|
|
</div>
|
|
</form>
|
|
|
|
<div n:if="$isEdit" id="uhrady" class="mt-4 pt-3 border-top">
|
|
<h5 class="mb-3"><i class="bi bi-cash-coin me-2"></i>Úhrady</h5>
|
|
<p class="text-muted small">
|
|
Celkem {$celkemFaktury|number:2, ',', ' '} CZK,
|
|
uhrazeno {$zaplaceno|number:2, ',', ' '} CZK,
|
|
zbývá {$zbyva|number:2, ',', ' '} CZK.
|
|
</p>
|
|
|
|
<form n:name="platbyForm">
|
|
<div n:if="$presenter['platbyForm']->hasErrors()" class="alert alert-danger">
|
|
<ul class="mb-0">
|
|
<li n:foreach="$presenter['platbyForm']->errors as $error">{$error}</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="table-responsive mb-3">
|
|
<table class="table table-sm table-cards align-middle">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th style="width: 160px;">Datum</th>
|
|
<th style="width: 160px;" class="text-end">Částka</th>
|
|
<th>Poznámka</th>
|
|
<th style="width: 50px;"></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr n:if="count($platby) === 0">
|
|
<td colspan="4" class="text-muted cell-span">Zatím žádná platba.</td>
|
|
</tr>
|
|
<tr n:foreach="$form['platby']->getComponents() as $platbaId => $row">
|
|
<td data-label="Datum">{input $row['datum'] class: 'form-control'}</td>
|
|
<td data-label="Částka">{input $row['castka'] class: 'form-control text-end'}</td>
|
|
<td data-label="Poznámka">{input $row['poznamka'] class: 'form-control'}</td>
|
|
<td class="text-end cell-actions">
|
|
<a n:href="smazatPlatbu! platbaId => $platbaId"
|
|
class="btn btn-sm btn-outline-danger"
|
|
title="Smazat platbu"
|
|
onclick="return confirm('Opravdu smazat tuto platbu?');">
|
|
<i class="bi bi-trash"></i>
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td data-label="Datum">
|
|
{input $form['nova']['datum'] class: 'form-control'}
|
|
</td>
|
|
<td data-label="Částka">
|
|
{input $form['nova']['castka'] class: 'form-control text-end'}
|
|
</td>
|
|
<td data-label="Poznámka">
|
|
{input $form['nova']['poznamka'] class: 'form-control', placeholder: 'Nová platba'}
|
|
</td>
|
|
<td class="cell-actions"></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="mb-3">
|
|
<button type="button" class="btn btn-sm btn-outline-secondary me-1" id="edit-platba-dnes" data-zbyva="{$zbyvaInput}">Dnes</button>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" id="edit-platba-splatnost" data-date="{$splatnostIso}" data-zbyva="{$zbyvaInput}">Den splatnosti</button>
|
|
</div>
|
|
<button n:name="save" class="btn btn-success">
|
|
<i class="bi bi-check-lg me-1"></i>Uložit platby
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script n:if="$isEdit">
|
|
function prefillPlatba(date, zbyva) {
|
|
if (date) {
|
|
document.getElementById('edit-platba-datum').value = date;
|
|
}
|
|
const castka = Number(zbyva);
|
|
document.getElementById('edit-platba-castka').value = castka > 0 ? castka.toFixed(2) : '';
|
|
}
|
|
document.getElementById('edit-platba-dnes').addEventListener('click', function () {
|
|
const d = new Date();
|
|
const m = String(d.getMonth() + 1).padStart(2, '0');
|
|
const day = String(d.getDate()).padStart(2, '0');
|
|
prefillPlatba(d.getFullYear() + '-' + m + '-' + day, this.getAttribute('data-zbyva'));
|
|
});
|
|
document.getElementById('edit-platba-splatnost').addEventListener('click', function () {
|
|
prefillPlatba(this.getAttribute('data-date'), this.getAttribute('data-zbyva'));
|
|
});
|
|
</script>
|
|
|
|
<script type="application/json" id="katalog-polozek">{$katalogPolozekJson|noescape}</script>
|
|
|
|
{syntax off}
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
let rowIndex = document.querySelectorAll('.item-row').length;
|
|
|
|
const tbody = document.getElementById('items-body');
|
|
const btnAdd = document.getElementById('btn-add-item');
|
|
|
|
// 1. Inicializace SortableJS na <tbody>
|
|
if (typeof Sortable !== 'undefined') {
|
|
new Sortable(tbody, {
|
|
handle: '.drag-handle', // Přetahovat lze uchopením za ikonu
|
|
animation: 150,
|
|
ghostClass: 'table-active', // Zvýraznění přetahovaného řádku
|
|
onEnd: function () {
|
|
reindexRows();
|
|
calculateTotals();
|
|
}
|
|
});
|
|
}
|
|
|
|
// 2. Přereindexování name atributů podle nového pořadí řádků v DOMu
|
|
function reindexRows() {
|
|
document.querySelectorAll('.item-row').forEach((row, index) => {
|
|
row.querySelectorAll('input').forEach(input => {
|
|
const name = input.getAttribute('name');
|
|
if (name) {
|
|
input.setAttribute('name', name.replace(/polozky\[\d+\]/, `polozky[${index}]`));
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// 3. Výpočet součtů
|
|
function calculateTotals() {
|
|
let grandTotal = 0;
|
|
|
|
document.querySelectorAll('.item-row').forEach(row => {
|
|
const pocet = parseFloat(row.querySelector('.item-pocet').value) || 0;
|
|
const cena = parseFloat(row.querySelector('.item-cena').value) || 0;
|
|
const sleva = parseFloat(row.querySelector('.item-sleva').value) || 0;
|
|
|
|
const celkem = (pocet * cena) * (1 - (sleva / 100));
|
|
grandTotal += celkem;
|
|
|
|
row.querySelector('.item-celkem-text').textContent = celkem.toLocaleString('cs-CZ', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' CZK';
|
|
});
|
|
|
|
document.getElementById('total-sum').textContent = grandTotal.toLocaleString('cs-CZ', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' CZK';
|
|
}
|
|
|
|
function fold(value) {
|
|
return String(value).normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
|
|
}
|
|
|
|
function formatMoney(value) {
|
|
return Number(value).toLocaleString('cs-CZ', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
}
|
|
|
|
function bindSuggest(input, options) {
|
|
if (input.dataset.suggestBound) {
|
|
return;
|
|
}
|
|
input.dataset.suggestBound = '1';
|
|
input.setAttribute('autocomplete', 'off');
|
|
input.setAttribute('role', 'combobox');
|
|
input.setAttribute('aria-autocomplete', 'list');
|
|
input.setAttribute('aria-expanded', 'false');
|
|
|
|
const menu = document.createElement('ul');
|
|
menu.className = 'suggest-menu';
|
|
menu.hidden = true;
|
|
menu.setAttribute('role', 'listbox');
|
|
document.body.appendChild(menu);
|
|
input._suggestMenu = menu;
|
|
|
|
let activeIndex = -1;
|
|
let visible = [];
|
|
|
|
function closeMenu() {
|
|
menu.hidden = true;
|
|
input.setAttribute('aria-expanded', 'false');
|
|
activeIndex = -1;
|
|
}
|
|
|
|
function placeMenu() {
|
|
const rect = input.getBoundingClientRect();
|
|
const spaceBelow = window.innerHeight - rect.bottom;
|
|
menu.style.left = Math.max(8, rect.left) + 'px';
|
|
menu.style.width = Math.max(rect.width, 220) + 'px';
|
|
menu.hidden = false;
|
|
const height = menu.offsetHeight;
|
|
if (spaceBelow < height + 8 && rect.top > spaceBelow) {
|
|
menu.style.top = Math.max(8, rect.top - height - 4) + 'px';
|
|
} else {
|
|
menu.style.top = (rect.bottom + 4) + 'px';
|
|
}
|
|
}
|
|
|
|
function render() {
|
|
const query = input.value;
|
|
const foldedQuery = fold(query).trim();
|
|
let matched = options.items.filter(item => {
|
|
if (!foldedQuery) {
|
|
return true;
|
|
}
|
|
const hay = fold(item.label + ' ' + (item.meta || ''));
|
|
return foldedQuery.split(/\s+/).every(word => hay.includes(word));
|
|
});
|
|
if (!options.keepOrder) {
|
|
matched.sort((a, b) => {
|
|
const score = item => {
|
|
if (!foldedQuery) {
|
|
return 1;
|
|
}
|
|
const label = fold(item.label);
|
|
if (label.startsWith(foldedQuery)) {
|
|
return 0;
|
|
}
|
|
if (label.includes(foldedQuery)) {
|
|
return 1;
|
|
}
|
|
return 2;
|
|
};
|
|
return score(a) - score(b);
|
|
});
|
|
}
|
|
visible = matched.slice(0, options.limit || 40);
|
|
|
|
menu.innerHTML = '';
|
|
if (!visible.length) {
|
|
if (!foldedQuery && options.freeText) {
|
|
closeMenu();
|
|
return;
|
|
}
|
|
const empty = document.createElement('li');
|
|
empty.className = 'suggest-empty';
|
|
empty.textContent = options.emptyText || 'Nic nenalezeno';
|
|
menu.appendChild(empty);
|
|
activeIndex = -1;
|
|
placeMenu();
|
|
input.setAttribute('aria-expanded', 'true');
|
|
return;
|
|
}
|
|
|
|
visible.forEach((item, index) => {
|
|
const li = document.createElement('li');
|
|
li.className = 'suggest-option' + (index === activeIndex ? ' is-active' : '');
|
|
li.setAttribute('role', 'option');
|
|
li.textContent = item.label;
|
|
if (item.meta) {
|
|
const meta = document.createElement('small');
|
|
meta.textContent = item.meta;
|
|
li.appendChild(meta);
|
|
}
|
|
li.addEventListener('mousedown', event => {
|
|
event.preventDefault();
|
|
choose(index);
|
|
});
|
|
li.addEventListener('mouseenter', () => {
|
|
activeIndex = index;
|
|
menu.querySelectorAll('.suggest-option').forEach((node, i) => {
|
|
node.classList.toggle('is-active', i === activeIndex);
|
|
});
|
|
});
|
|
menu.appendChild(li);
|
|
});
|
|
|
|
if (activeIndex >= visible.length) {
|
|
activeIndex = visible.length - 1;
|
|
}
|
|
placeMenu();
|
|
input.setAttribute('aria-expanded', 'true');
|
|
const current = menu.querySelectorAll('.suggest-option')[activeIndex];
|
|
if (current) {
|
|
current.classList.add('is-active');
|
|
current.scrollIntoView({ block: 'nearest' });
|
|
}
|
|
}
|
|
|
|
function choose(index) {
|
|
const item = visible[index];
|
|
if (!item) {
|
|
return;
|
|
}
|
|
input.value = item.label;
|
|
closeMenu();
|
|
options.onPick(item, input);
|
|
}
|
|
|
|
function acceptExact() {
|
|
const typed = input.value.trim();
|
|
if (typed === '') {
|
|
options.onClear?.(input);
|
|
return;
|
|
}
|
|
const foldedTyped = fold(typed);
|
|
const hits = options.items.filter(item => fold(item.label) === foldedTyped);
|
|
if (hits.length === 1) {
|
|
input.value = hits[0].label;
|
|
options.onPick(hits[0], input);
|
|
return;
|
|
}
|
|
options.onClear?.(input);
|
|
}
|
|
|
|
input.addEventListener('focus', () => {
|
|
document.querySelectorAll('.suggest-menu').forEach(other => {
|
|
if (other !== menu) {
|
|
other.hidden = true;
|
|
}
|
|
});
|
|
activeIndex = -1;
|
|
render();
|
|
});
|
|
|
|
input.addEventListener('input', () => {
|
|
activeIndex = -1;
|
|
if (!options.freeText) {
|
|
options.onClear?.(input);
|
|
}
|
|
render();
|
|
});
|
|
|
|
input.addEventListener('keydown', event => {
|
|
if (event.key === 'ArrowDown') {
|
|
event.preventDefault();
|
|
if (menu.hidden) {
|
|
render();
|
|
}
|
|
if (!visible.length) {
|
|
return;
|
|
}
|
|
activeIndex = (activeIndex + 1) % visible.length;
|
|
render();
|
|
return;
|
|
}
|
|
if (event.key === 'ArrowUp') {
|
|
event.preventDefault();
|
|
if (!visible.length) {
|
|
return;
|
|
}
|
|
activeIndex = activeIndex <= 0 ? visible.length - 1 : activeIndex - 1;
|
|
render();
|
|
return;
|
|
}
|
|
if (event.key === 'Enter' && !menu.hidden) {
|
|
event.preventDefault();
|
|
if (activeIndex >= 0) {
|
|
choose(activeIndex);
|
|
} else if (visible.length === 1) {
|
|
choose(0);
|
|
}
|
|
return;
|
|
}
|
|
if (event.key === 'Escape') {
|
|
closeMenu();
|
|
}
|
|
});
|
|
|
|
input.addEventListener('blur', () => {
|
|
window.setTimeout(() => {
|
|
if (!options.freeText) {
|
|
acceptExact();
|
|
}
|
|
closeMenu();
|
|
}, 120);
|
|
});
|
|
|
|
const followInput = event => {
|
|
if (menu.hidden || event.target === menu) {
|
|
return;
|
|
}
|
|
placeMenu();
|
|
};
|
|
window.addEventListener('resize', followInput);
|
|
window.addEventListener('scroll', followInput, true);
|
|
}
|
|
|
|
function initZakaznikSuggest() {
|
|
const select = document.querySelector('select[data-suggest="zakaznik"]');
|
|
if (!select || select.dataset.locked === '1') {
|
|
return;
|
|
}
|
|
|
|
const items = Array.from(select.options)
|
|
.filter(option => option.value !== '')
|
|
.map(option => ({
|
|
value: option.value,
|
|
label: option.textContent.trim(),
|
|
}));
|
|
|
|
const wrap = document.createElement('div');
|
|
wrap.className = 'suggest';
|
|
const input = document.createElement('input');
|
|
input.type = 'text';
|
|
input.className = 'form-control';
|
|
input.placeholder = 'Začněte psát jméno, město nebo IČ';
|
|
input.id = select.id ? select.id + '-suggest' : 'zakaznik-suggest';
|
|
const label = document.querySelector('label[for="' + select.id + '"]');
|
|
if (label) {
|
|
label.htmlFor = input.id;
|
|
}
|
|
|
|
select.parentNode.insertBefore(wrap, select);
|
|
wrap.appendChild(input);
|
|
wrap.appendChild(select);
|
|
select.classList.add('suggest-native');
|
|
select.setAttribute('aria-hidden', 'true');
|
|
select.removeAttribute('required');
|
|
select.tabIndex = -1;
|
|
|
|
const selected = select.selectedOptions[0];
|
|
if (select.value && selected) {
|
|
input.value = selected.textContent.trim();
|
|
}
|
|
|
|
bindSuggest(input, {
|
|
items,
|
|
emptyText: 'Žádný zákazník neodpovídá',
|
|
onPick(item) {
|
|
select.value = item.value;
|
|
},
|
|
onClear() {
|
|
select.value = '';
|
|
},
|
|
});
|
|
}
|
|
|
|
let katalog = [];
|
|
try {
|
|
katalog = JSON.parse(document.getElementById('katalog-polozek').textContent || '[]');
|
|
} catch (error) {
|
|
katalog = [];
|
|
}
|
|
|
|
const katalogItems = katalog.map(item => ({
|
|
label: item.nazev,
|
|
meta: (item.jednotka ? item.jednotka + ' · ' : '') + formatMoney(item.cena) + ' CZK',
|
|
payload: item,
|
|
}));
|
|
|
|
function bindPolozkaSuggest(input) {
|
|
if (!input) {
|
|
return;
|
|
}
|
|
input.placeholder = input.placeholder || 'Začněte psát název práce nebo zboží';
|
|
bindSuggest(input, {
|
|
items: katalogItems,
|
|
freeText: true,
|
|
keepOrder: true,
|
|
emptyText: 'V předchozích fakturách nic podobného není',
|
|
onPick(item) {
|
|
const row = input.closest('.item-row');
|
|
if (!row) {
|
|
return;
|
|
}
|
|
const jednotka = row.querySelector('input[name*="[JEDNOTKA]"]');
|
|
const cena = row.querySelector('.item-cena');
|
|
if (jednotka && item.payload.jednotka) {
|
|
jednotka.value = item.payload.jednotka;
|
|
}
|
|
if (cena) {
|
|
cena.value = Number(item.payload.cena).toFixed(2);
|
|
}
|
|
calculateTotals();
|
|
},
|
|
});
|
|
}
|
|
|
|
initZakaznikSuggest();
|
|
document.querySelectorAll('.item-nazev').forEach(bindPolozkaSuggest);
|
|
|
|
// Přidání nového řádku
|
|
btnAdd.addEventListener('click', () => {
|
|
const tr = document.createElement('tr');
|
|
tr.className = 'item-row';
|
|
tr.innerHTML = `
|
|
<td data-label="Pořadí" class="text-center align-middle drag-handle" style="cursor: grab;">
|
|
<i class="bi bi-grip-vertical text-muted fs-5"></i>
|
|
</td>
|
|
<td data-label="Název"><input type="text" name="polozky[${rowIndex}][NAZEV]" class="form-control item-nazev" placeholder="Začněte psát název práce nebo zboží" required autocomplete="off"></td>
|
|
<td data-label="Počet"><input type="number" step="0.01" name="polozky[${rowIndex}][POCET]" value="1" class="form-control item-pocet text-end" required></td>
|
|
<td data-label="Jedn."><input type="text" name="polozky[${rowIndex}][JEDNOTKA]" value="ks" class="form-control"></td>
|
|
<td data-label="Cena/Jedn."><input type="number" step="0.01" name="polozky[${rowIndex}][CENA_JEDNOTKA]" value="0" class="form-control item-cena text-end" required></td>
|
|
<td data-label="Sleva %"><input type="number" step="0.1" name="polozky[${rowIndex}][SLEVA]" value="0" class="form-control item-sleva text-end"></td>
|
|
<td data-label="Celkem" class="text-end fw-bold item-celkem-text">0,00 CZK</td>
|
|
<td class="text-center cell-actions">
|
|
<button type="button" class="btn btn-outline-danger btn-sm btn-remove-row"><i class="bi bi-trash"></i></button>
|
|
</td>
|
|
`;
|
|
tbody.appendChild(tr);
|
|
bindPolozkaSuggest(tr.querySelector('.item-nazev'));
|
|
rowIndex++;
|
|
reindexRows();
|
|
calculateTotals();
|
|
});
|
|
|
|
// Event delegation pro smazání a přepočet
|
|
tbody.addEventListener('click', (e) => {
|
|
if (e.target.closest('.btn-remove-row')) {
|
|
const row = e.target.closest('.item-row');
|
|
row.querySelectorAll('.item-nazev').forEach(input => input._suggestMenu?.remove());
|
|
row.remove();
|
|
reindexRows();
|
|
calculateTotals();
|
|
}
|
|
});
|
|
|
|
tbody.addEventListener('input', (e) => {
|
|
if (e.target.matches('.item-pocet, .item-cena, .item-sleva')) {
|
|
calculateTotals();
|
|
}
|
|
});
|
|
|
|
if (document.querySelectorAll('.item-row').length === 0) {
|
|
btnAdd.click();
|
|
} else {
|
|
calculateTotals();
|
|
}
|
|
});
|
|
</script>
|
|
{/syntax} |