| Server IP : 188.114.96.3 / Your IP : 216.73.216.143 Web Server : Apache System : Linux sv058.home.net.pl 6.8.2 husak_dirty_pages_fix #1 SMP PREEMPT_DYNAMIC Tue Apr 2 22:06:51 CEST 2024 x86_64 User : ( 3194769) PHP Version : 8.5.12 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /polana/admin/ |
Upload File : |
/* POLANA IMPORT R4 — ręczna korekta wierszy przed utworzeniem szkicu. */
(() => {
'use strict';
const mount = document.getElementById('polana-import-editor');
const dataNode = document.getElementById('polana-import-editor-data');
const form = document.getElementById('polana-import-confirm-form');
const field = document.getElementById('polana-edited-rows');
const submit = document.getElementById('polana-save-import-button');
const status = document.getElementById('polana-review-save-state');
if (!mount || !dataNode || !form || !field || !submit) return;
let rows;
try {
rows = JSON.parse(dataNode.textContent);
if (!Array.isArray(rows)) throw new Error('Niepoprawne dane podglądu.');
} catch (err) {
mount.textContent = 'Nie udało się uruchomić edytora. Odśwież stronę i wczytaj Worda ponownie.';
return;
}
let nextKey = 1;
rows = rows.map(row => ({
id: nextKey++,
kind: row.kind === 'category' ? 'category' : 'item',
name: String(row.name ?? ''),
price: String(row.price ?? ''),
price_text: String(row.price_text ?? ''),
description: String(row.description ?? '')
}));
let dirty = false;
let submitting = false;
const newRow = (kind, name = '') => ({id: nextKey++, kind, name, price:'', price_text:'', description:''});
const h = (tag, className, text) => {
const el = document.createElement(tag);
if (className) el.className = className;
if (text !== undefined) el.textContent = text;
return el;
};
function groupStarts() {
return rows.reduce((a, row, i) => {if(row.kind === 'category') a.push(i); return a;}, []);
}
function categoryAt(index) {
for (let i=index;i>=0;i--) if (rows[i].kind === 'category') return i;
return -1;
}
function refreshCounts() {
const categories = rows.filter(r=>r.kind==='category').length;
const items = rows.filter(r=>r.kind==='item').length;
const missing = rows.filter(r=>r.kind==='item' && !r.price.trim() && !r.price_text.trim()).length;
status.textContent = `${categories} kategorii, ${items} pozycji. ${missing ? 'Do uzupełnienia: ' + missing + ' cen. ' : ''}${dirty ? 'Zmiany jeszcze niezapisane.' : 'Przed zapisem sprawdź poprawność.'}`;
}
function changed() {dirty=true; refreshCounts();}
function swapCategory(index, direction) {
const starts = groupStarts();
const p = starts.indexOf(index), other = p + direction;
if (p < 0 || other < 0 || other >= starts.length) return;
// Przesuwa kategorię RAZEM z jej daniami, a nie sam nagłówek.
const segments = starts.map((start,j)=>rows.slice(start,starts[j+1] ?? rows.length));
[segments[p],segments[other]] = [segments[other],segments[p]];
rows=segments.flat();changed();render();
}
function shiftItem(index, direction) {
const other=index+direction;
if (other<0 || other>=rows.length || rows[other].kind !== 'item' || categoryAt(index)!==categoryAt(other)) return;
[rows[index],rows[other]]=[rows[other],rows[index]];
changed();render();
}
function moveToCategory(index, targetId) {
const moving=rows[index];
if (!moving || moving.kind!=='item') return;
const targetRow=rows.find(r=>r.id===targetId && r.kind==='category');
if (!targetRow) return;
rows.splice(index,1);
const catIndex=rows.indexOf(targetRow);
let end=catIndex+1;
while(end<rows.length && rows[end].kind!=='category') end++;
rows.splice(end,0,moving);
changed();render();
}
function setKind(index, kind) {
if (!['category','item'].includes(kind) || rows[index].kind===kind) return;
if (kind==='item' && rows[index].kind==='category') {
if (!window.confirm('Zamienić nagłówek na danie? Pozycje pod nim przejdą do poprzedniej kategorii. Uzupełnij cenę dania.')) {render();return;}
if (categoryAt(index-1)<0) { rows.unshift(newRow('category','Pozostałe')); index++; }
}
rows[index].kind=kind;
changed();render();
}
function removeRow(index) {
const row=rows[index];
if (!window.confirm(`Usunąć ${row.kind==='category'?'kategorię':'pozycję'} „${row.name || '(bez nazwy)'}” z podglądu? Dane w bazie pozostaną bez zmian.`)) return;
rows.splice(index,1);
if (rows.length && rows[0].kind!=='category') rows.unshift(newRow('category','Pozostałe'));
changed();render();
}
function addItemAfterCategory(catRow) {
const catIndex=rows.indexOf(catRow);
let end=catIndex+1;
while(end<rows.length && rows[end].kind!=='category') end++;
rows.splice(end,0,newRow('item',''));
changed();render();
}
function miniButton(label, title, cb, disabled=false) {
const btn=h('button','polana-mini',label);
btn.type='button'; btn.title=title; btn.setAttribute('aria-label',title);
btn.disabled=disabled;
btn.addEventListener('click',cb);
return btn;
}
function labelInput(caption, value, change, placeholder='') {
const label=h('label','',caption), input=h('input');
input.type='text'; input.value=value; input.placeholder=placeholder; input.autocomplete='off';
input.addEventListener('input', e=>change(e.target.value));
label.appendChild(input);return label;
}
function render() {
mount.replaceChildren();
const toolbar=h('div','polana-editor-toolbar');
toolbar.appendChild(miniButton('+ Kategoria','Dodaj kategorię',()=>{rows.push(newRow('category','Nowa kategoria'));changed();render();}));
toolbar.appendChild(miniButton('+ Danie / wino','Dodaj pozycję do ostatniej kategorii',()=>{
if (!rows.some(r=>r.kind==='category')) rows.unshift(newRow('category','Pozostałe'));
rows.push(newRow('item',''));changed();render();
}));
const instr=h('span','polana-edit-note','↑ ↓ zmieniają kolejność. „Kategoria / Danie” zmienia wygląd pozycji w menu i PDF.');
toolbar.appendChild(instr); mount.appendChild(toolbar);
rows.forEach((row,index)=>{
const category=row.kind==='category';
const section=h('article','polana-edit-row '+(category?'kind-category':'kind-item'));
section.dataset.rowid=String(row.id);
if (!category && !row.price.trim() && !row.price_text.trim()) section.classList.add('needs-price');
const kindWrap=h('label','','Rodzaj wiersza');
const kindSel=h('select');
[['category','Kategoria (nagłówek)'],['item','Danie / napój / wino']].forEach(([v,name])=>{
const opt=h('option','',name);opt.value=v;opt.selected=(v===row.kind);kindSel.appendChild(opt);
});
kindSel.addEventListener('change',e=>setKind(index,e.target.value));
kindWrap.appendChild(kindSel);section.appendChild(kindWrap);
section.appendChild(labelInput(category?'Nazwa kategorii':'Nazwa pozycji',row.name,v=>{row.name=v;changed();},'Wpisz pełną nazwę'));
if (!category) {
section.appendChild(labelInput('Cena w zł (np. 35)',row.price,v=>{
row.price=v;changed();section.classList.toggle('needs-price', !v.trim()&&!row.price_text.trim());
},'35 lub 35,50'));
section.appendChild(labelInput('Cena opisowa / wina (opcjonalna)',row.price_text,v=>{
row.price_text=v;changed();section.classList.toggle('needs-price', !v.trim()&&!row.price.trim());
},'125 ml – 50 zł / 750 ml – 236 zł'));
}
const actions=h('div','polana-edit-actions');
const cats=groupStarts();
if(category) {
const p=cats.indexOf(index);
actions.appendChild(miniButton('↑','Przenieś kategorię z jej daniami wyżej',()=>swapCategory(index,-1),p===0));
actions.appendChild(miniButton('↓','Przenieś kategorię z jej daniami niżej',()=>swapCategory(index,1),p===cats.length-1));
actions.appendChild(miniButton('+ Danie','Dodaj pozycję w tej kategorii',()=>addItemAfterCategory(row)));
} else {
actions.appendChild(miniButton('↑','Przenieś pozycję wyżej',()=>shiftItem(index,-1),index<=0||rows[index-1].kind==='category'));
actions.appendChild(miniButton('↓','Przenieś pozycję niżej',()=>shiftItem(index,1),index===rows.length-1||rows[index+1].kind==='category'));
if (cats.length>1) {
const choose=h('select');choose.setAttribute('aria-label','Przenieś do innej kategorii');
const defaultOption=h('option','','Przenieś do…');defaultOption.value='';choose.appendChild(defaultOption);
const own=categoryAt(index);
cats.forEach(catIdx=>{
if(catIdx===own) return;
const opt=h('option','',rows[catIdx].name);opt.value=String(rows[catIdx].id);choose.appendChild(opt);
});
choose.addEventListener('change',e=>{if(e.target.value)moveToCategory(index,Number(e.target.value));});
actions.appendChild(choose);
}
}
actions.appendChild(miniButton('×','Usuń ten wiersz z podglądu',()=>removeRow(index)));
section.appendChild(actions);mount.appendChild(section);
});
refreshCounts();
submit.disabled=false;
}
function serialize() {
return rows.map(({kind,name,price,price_text,description})=>({kind,name,price,price_text,description}));
}
form.addEventListener('submit',event=>{
if (submitting) {event.preventDefault();return;}
if (rows.length===0 || rows[0].kind!=='category') {
event.preventDefault();window.alert('Najpierw dodaj kategorię u góry karty.');return;
}
const invalid=rows.findIndex(r=>!r.name.trim() || (r.kind==='item'&&!r.price.trim()&&!r.price_text.trim()));
if (invalid>=0) {
event.preventDefault();
window.alert('Wiersz '+(invalid+1)+': wpisz nazwę oraz cenę dania (lub usuń pustą pozycję).');
mount.querySelectorAll('.polana-edit-row')[invalid]?.scrollIntoView({block:'center',behavior:'smooth'});
return;
}
if (!rows.some(r=>r.kind==='item')) {
event.preventDefault();window.alert('Dodaj co najmniej jedną pozycję.');return;
}
field.value=JSON.stringify(serialize());
if (field.value.length>350000) {event.preventDefault();window.alert('Karta jest za długa.');return;}
submitting=true;
submit.disabled=true;
});
window.addEventListener('beforeunload', event=>{
if (dirty && !submitting) {event.preventDefault();event.returnValue='';}
});
render();
})();