Margin calculation seems incorrect
I’m calculating margin like this:
const margin = ((totalBruttoWithCommission - (netNetNetPrice * 0.65)) / totalBruttoWithCommission) * 100;
But this sometimes results in values over 100%, or negative values, even when the data looks correct. I suspect the formula or the base values may be off.
net/net/net price not correctly imported from CSV
The CSV may contain a column labeled something like “Cena net / net / net” or “NetNetNet”. I try to detect it like this:
const netNetNetPriceIdx = headers.findIndex(h => h.replace(/[^a-zA-Z]/g, '').toLowerCase().includes('netnetnet'));
When reading values:
let rawNetNetNetPrice = row[netNetNetPriceIdx].trim();
rawNetNetNetPrice = rawNetNetNetPrice.replace(/[^0-9,.]/g, '').replace(',', '.');
netNetNetPrice = parseFloat(rawNetNetNetPrice);
But many values come out as NaN, especially when they contain currency symbols or spaces.
What I’m trying to achieve:
-
Accurately import the correct net/net/net unit price from CSV.
-
Multiply it by pack size.
-
Use it in the margin calculation reliably.
What I’ve tried:
-
Normalizing headers with regex.
-
Cleaning up values with replace().
-
Falling back to estimating the price as 65% of net if CSV value is invalid.
Still, the margin values are off and data inconsistencies are common.
<!DOCTYPE html>html lang="pl"><ead>et charset="UTF-8" /><maname="viewport" content="width=device-width, initial-scale=1" /><titKalkulator Wielopaków </title><stylbody {ffamil:e UI', Arial, sans-serif;padding 2.5rem;bund: liear-gradi0deg, #e0e7ff 0%, #f7f7f7 100%);color: #22;}.calculatr{bund: white;ng5rem 2.5re 2rrem;max-width: 110rgin: 2rem auto;border-radius: 8p-shadow: 0 8px 32px44,62,80,0.13);borpx solid #e3ef0;}hxt-align: cente;argin-bottom: 2remfont-2.2rem;color: #d3a5a;letspg 1pabel {font-wight: argin-top:1.2rem;di block;color: #2d3ette-spacing:0}input[type=number],ute=tex],[type=file] {padd.55rem 0.7rem;width;boxsizing: boox;margi-top: border: 1px solid#bf;er-radius 6px;font-size: 1rem;bckground: #f8faf;tran: border 02s;}input[tmber]:focs, type=text]:focu bordepx solid #2978b5;ou none;backgroud: #f0f6ff;le {border-ollapsease;width: 100%in-top: 1.rem;font-1.05rem;bacground: #fffrdadius: 8px;oerflow: hidde;box-shadow: 0 2px 8px44,62,8,0.06);}th, td {p 0.7rem 0.5rem;r: 1px solid #e3e8f0;-: centeh{background: #e6f0fa;co2d3a5a;font-: 700;letter-spainpx;}tbody tr:hoverground-coor: #f0tranition: backgrous}utton {marg: 1rem;padding: 0.6rem 1.3rem;cursor: poi;gond: lgraient(90deg,#2978b5 4fc3f7 100%);coor: white;r:none;border-radi6pont-s.05rem;font-weight: ox-hadow: 0 2pxgba(44,6280,0.08)sition: backgroun 0.2sx-ow 0.2s;}button {background: linear-gradideg, #225c8a 60%, #29b6f6 100boao: 0 4x rba(44,62,80,0.button.delete-btn {bacd: #d9534f;colore;font-weight: 500;padding: 0.rem 1rem;border-radus: 5px;-top: ;box-s one;transitackgrond 0.2s;}buelete-btn:over {band: #b52a26}.fl {diplay: flex;ap: 2rem;}.flex-row > lx: 1;}#productImage{max-width: 22p;mei 20px;margin:m uto 0 autodsplay: block;border:15px solid #bfc9a;bordius: 12px;bx-shadow: 2px 8px rgba(44,620.ackground: #f8fffroductName {text-alinter;fnt-weiold;margn-top: 0font-size: 1.15rem;c#2978b;letter-spac.5px;display: }#imprtSection {n-op: 2.5rem;pdding-top: 1mder-top: 2px solid e3e8f0grond: #f8faff;bordd 0 0 12px12x-shadow: 0 2pxgba(44,62,804xt-align: center;portSectinpype=file] {dispnline-block;widh;margin: 0.7rem aurem auto;}#importSection pt-align: center;n-left: auto;margin-right a#importedFilesist {syle-type: none;padding: 0;agin-top: 1ext-align: left;}#iteesList li {martom: 0.5rem;}#cmonSection {margin-.5rem;padding: 1.5rder-top: 2x solid 0;background: #;borde-adius: 12px;ado: 0 2px 8pxa(2,8,.04);}#coionSecion h3 {marg:0;margin-bottom: 1color: #2d3a5a;}#ompetition{marin-top: 1.5rem;mpetiionTable a {color: #297ext-dcration: none;}#cometitionTble a {text-decoation: rl}#sortsBtn, #sortDesBtn {marght: 0.5rem;padding: 01rem;fot-si9rem;}.loading {display: inlncidth: 20px;height:boder: 3px solid #;bodr-top: 3px s2978b5;border-rdiu0%imation: sin 1s lineinitemargin-right .}@keyframs{0% { transform: (0deg); }00% { torotate(360deg); }}.eaatus {margin-top: 1remd 1rem;background: #f0border-radius: 8px;left: 4px solid 5display: noe;}@media (max: 900px) {.lex-row -direction: column;2em;}.calcuator {padding: 1.2rem 0.re#uctImage {max-width: 100-height 180px;/style></hea><body>class="aculato<hlkulator Wielpaków<<div class="flex-rowyllgn-itesflex-start;re;"><div stylx:2; min-idth:260px;>ber="basePrice">Cena bazowa brzł za1 sztuk):</lael><intynumber" id="basePrice" valuetep="0.01" min="0" />l for="vatRate">Stawka%):</labe><input ="er" id="vatvalue="8" step="0.1" midisabled/><for="commissiowizja sklepu (%):/label><type="number" id="commission" v13" step="0.1" in="</div><div style="flex:3; minwidth;"><h3>EAN produktu:<<class="flex-row" salign-its:center;"><div style:3;"><input type="txt" id="eanInpulalder="np. 5905341" /></div><div sflex:1;"<butt"setProductBtn">Ustawkt</buton></div><<img id"productImage" alt="Zdjęoduktu" src="" eplay:none;" /><iv id="prode" style="te:center; font-weight:boln-top:.5rem;y"></div></div<3>Wyniki kalkulacji<tathead><tr><th>pczki</h><th>R</th><th>Cena jedna toth><th>Cena całkowita bt + prowizja/th><th>Wagaduktu (kg)</th><th>Cena net/net><th>Marża (%)</th></tr></thed><tby id="packTable"></tbodyble><h>Proi wielopaków (rzmiar i r/3><table id="discounTable"><thead><tr><th>Rozmiar paczki szt<th>Rabat (%)</th><t>Usuń</th></tr></head><tbody></tbod></tatddThresoldBtn">Dodaj próg</button><h3>Po finansowe</h3><table><thead><tr><th>ozmiar paczki</th><th>Cenacałkuowzja<th><th>Kwota</th><th>Zysk</th><th>Marża (/tr></thead><body d="summayTable"></tbody</table><div id="imption">portuj dane proktów z pliku CSV</h3>yp="file"id="csvileIept=".csv,text/csv" />< style="font-size: 0.9rem55; margin-top0.5reml id="iportedFilesList"style="lst-style-type:none; padding:0; maem; texf"></ul></div><diitonSection" style="display: none;"><h3>yszukiwrencji<v classow" style="align-items:ceter; margin-bottom:1rem;"><div style="flex:2;"><la"competitionEan">EAN produktu do wyszukania:</label><input type="text" id="competitinEan" aceholder="np3412345/></didtyle="flex:1;><button id=rchCompeBtn">Wysutton/div><div style="margirem;"><label for="nName>Nazwa produktu:</labl><i"text" id="competitionName" placeholder="Nktu" /><lbel for="competi>Cena (zł):</label><inputber" id="competitioste="min="0" plder="0.0" /><label for="comtionShop"elabel><iut type="text" id="comptitionShop"ceholder="Nazwa sklepu" />l for"conUrlsklepu:</label><input type="tempetitionUrl" acetps://..." /> id="aetitionBtaj do prównaniatton></d<div style="margin-bottom:1rem;"><labeSortuj uny:</label><button id="ortAscBRosnąco<n><buttortDeejąco</button></div><archStatus" class"search-status"><div idssage>Wyszukiwktów...</div>able id="competitio<thea>th>Nazduktu</th><th>Cena (zł)</th><t>p<th><tnh><th>Akcje</th></tr><><tboy></tbody></table></div></iv><scconst basePriceInput = dcument.getElementById('bsePric);cotRateInput = document.getElemenById('vatRate');const commissi=ent.entById('commission);cons discountTableBod = document.querySlector('#dscounTable tbody');onst ace = doctElementyId('pacTable';constummaryTable = documeElementById('summaryTable');constresholdBtn= document.getElementById('addThresholdBtn');consteanInpument.getElementById('econst setProdtBtn = document.getElementById('setProductBtn');ductImage = document.getEementById('productImage');const productName= dgetElem'productName');constput = document.getElementyId('csvFileInput');constdFilesLdocumenlyId('importedFilesLis);// Elemcji konkurencjiconst compeitionEanInput = document.ntById('competitionEan');const competitionNameInput = document.getElemeconName');const cometitionPriceInput = document.ntById('competionPrice');const competitionShopInput = document.geElementById('comSonst competitionUrlInput = dument.getElemecompetitonUrl');const searchCompetitionBtn = document.getElementByIdCoBtn');const addCompetitionBtn = document.getElemeaddComptitionBtn');const sortAscBtn = document.getElementById('sor)sortDecBtn = document.getElementById('sortDescBtn');const itionTadt.getElementById('competitionTablet competitionTableBody = ompetitiouerySelector('tbody');const searchStatusent.getElemetById('searchStatus');const sessage mtElementById('searchMessag');// Mapa produkAN: { ean: { price: Number, image: String } }let produc {};/iych konkurencjilet comptitio[];// rogi et packConfigs = [{ siznt: 0 },{ size: 6,.03 },{ size: : 0.07 },{ siount: 0.10 },{ disco5},{ sidiscount: 0.18 }unction ceThreshdRow(pa,ndex) { tr = document.createElement('tr');// RozmiarpakowaniacosizeTd = document.createElement('td');const sizenput = ment.crteElement('input';sizeInput.type =number';sizeInpi1;sizeInpu.value = packsize;sizeInput.syle.width = '80px;sizeInput.ventLitener('chane', () => {const val = parseInt(sizet.value);if (va >= 1) {packConfigs[index].size = vapackCgs = sortPackConigs(packConfigs);renderDiscountTable();rnderTabl;sizeTd.appendChild(sizeInput);// Raba %const distTd = document.reateElemet('td');const discountInput documereateElement('input');dicountInput.type = 'number';discountt.min= 0;discountInput.max = 100;discoutInput.step = 00intInput.value = (pack.discout * 100).toFixed(2);discountInpuyle.widh = '80px';discountInpt.addEventitener('change', () => {let rseoat(discoutInput.value);val < 0) val = 0;if (val > 100 val = 100;packConfigs[index].discountal / 100;disountInput.value = val.toFixed();renderTable();})discou.appendChild(discontInput);// Usuńconst delTd = document.createElement();const delBtn = docuent.createElement('button');deltn.textContent = ń';delBtn.className = 'delete-bn';delBtn.addEventListener('click' ( {packConfigs.slice(index, 1);renderDiscuntTable();renderTable();});delppendhild(delBtn);tr.appendChild(sizeTd);t.appendChild(dscontTd);appendChild(delTd);return trfunctio renderDiscounte() {discountTableBody.inerHTML = '';packConfigs = sortPanfigs(packConfis);packConfigs.forEach((ack, index) => {discountTabdy.apendChild(createThresholdRow(pack, inex));});}function sortPonfigsarr) {return arr.sort(a,b) => a.size- bsize);}fuon renderTabl) {const baseBrutto = prseFloat(basePriceInput.e 0;const vatRate = paeFloat(vataInput.value) || 0;const commnRate = (parseFloat(csInput.value || 0) / 100;conatFactor = 1 + (vatRate/ ;st baseNetto = baseBrutto / vctor;pckTable.innML = '';packConfigs.forEpack => {const netUnitPrce etto * (1 - pack.discount);s netUnitWithCommission = netPrice * (1 + cmssionRate);t bruttoUitPrice = netUnitPr* vc // Cena jednostkowa brutt bez prowizjicotalBruttoWithCommission = bruttoUnitPice+ssionRate) * pack.sizeOblicz net/net/net: najpierw sprdź CSV, poticz jako 65% ceny nettolet netNetNetPrice;const cEan eanInput.value.trim()e.log(productMap[tEan])neNetNetPrice = producurrentEan].netNetNetPrice * pac;// Calculate marginconst margin = ((totalthCommission - netNetNetPrice *0.65)) ruttoWithCommiss;const row = document.createE');le totalWeight = '-';if (currentEan &&p[currentEan] && producntEan].weight !{eighroductMap[currentEan].weight * iFixed(3) + }row.innerHTL = `<td>${pack.siz} szt.</td><pack.discount * 100).toFixed(2)}%</td><td>${brutoUnittoFixed(2)} zł</td><td>${totalWithCommission.toFid(</td><td>${totalWight}<td>${netNetNetPrice.toFixedł</td><td>${margin.toFixed(2)}%</td>`;packTab.appen(row);if (pack.size > 50){const ow = document.ceateElement('tr');extraRow.inner<td colspan="7">Dodatkow wiersz dla ilośc ${pack.size}</td>`;e.appendChl(extraRow);}rgin 18) {const marginCell = dcument.ement('td');const marginInput = ocumteElement('inpuargi.type = 'numer';marginInput.value = mt(2);marut.stye.width = '80px';marginInput.addEvenner('change', ()=> {const newMargin = parseFloinnput.value);if (!isNanen)) {const newProfi = totalBrutCommission * (newMargin / 10);const newtPrce = totalBruttoWithCommisewProfit;// Update throw with new ao.cn[5].textContent = `${ewNettoFixed()} zł`;row.chi6].textContent = `${newMarixed(2)}%`;// PrzerendaenderSummarle}marginCell.appendChild(mrginInpow.appendChild(margnCell);}});nderuj tabelę posumowania finansowegorenderyTable();}funtion renderSummaryTabonst baseBrutto = pareFloat(basePriceInut.value) || 0;const v = pFlvteInput.value) || 0;const comiate = (parseFloat(commissionInput.value) ||/ statFactor = 1 + (vatRa00);const baseNetto = baseBrutto / vatFactor;summaryTaerHTML = '';packConfigs.forEach(pack => {consnetice = baseNetto * (1 - ack.discount);const bruttoUnitPrice = baseBrutnalBruttoWithomision = bruttoUnitPric + commissionRate)* pack.size;// bcze najpierw sprawdź CSV, potio 65% ceny nettolet netNetNetonst currentEan = eanIput.value.trim();if (currentEaductap[currentEan] && productMap[currentEan].netNetNetPrice == nu Użyj wartości z CSV (cea jednostkowa * rozmiar paczki)netNetNetPce = productMap[curreeetNetPrice * pack.size;} else{// Oblicz jako 65% ceny nettonetNetNetPrice = ete* pack.size;}const profit = totalBruttoWithCommission - netNetNetPrice;conn = ((profit) / totaithComssion) * 100;const row = documenElement('tr');row.innrHTML = `<tdsize} szt.</td><td>${totalBruttoWithCommision.toFixed(2)} zł</td>tice.toFixed(2)} zł</${profit.toFixed(2)} zł</td><td>${margin.toFixed(2)}%</td>`;summaryTable.appendChild(row);};}addTBentListener('click', () => {// Znajdź max paczkilet axSize =1ckConfigs.length > 0) {maxSie = Math.max(...packConfigs.map(p=p.size));}// Dodóg z rozmiarem max+1 i rabatem 0%packConfis.push({ size: maxSize + 1, discou;scountTab();ren;});// Aktualizuj tabelęrzyzmianie bazowych anychbasePriceInpu.addner('input', renderTable);vatRateInput.adener('nput', renderTable);commissionInput.addEventinput, renderTable);/CSVcsvFileInpu.adEventListener('change{const fles=rray.from(e.taes)iles.length) retrn;files.( {const reder = new ();reader.onload = function(vent) {const textarget.result;parseCSVdaj plik do lit zaimportowanych plikówconst listItem =document.cr'liem.textContent = file.name;liste.t.5rem';// Ddaj uwaaconst deleteButton = document.createElemen);deeteButton.textContent = 'Usuń';deleteButton.l'delete-btn';deleteButton.stLeft = '1rem'deleteButton.addEventLisck', () => {listItem.move);})m.appendChild(deleteButton);importdFilesList.listItem);};reader.readAsText(file);});})eProductDta(existingDataor (cont [key, value] of Object.entries(ewData)) {if (!existineistingData[key] = value;} else {// Sprawdź, czy ierwsze 4 inadzająconst existing = existingData[key]s = (eisting.price ==value.price &&exsting.margin === value.mang.image === value.mage &&existing.name === value.nam &geight);if (matches) brakjące daneexisti= xist|| valueprice,margin: existing.marge.margin,mage: existing.imaue,nx.name || value.name,weigh: existing.weightlue.weight,;}}} Zano funkcję parseCSVuncionarcsvText) {const lines = csvText.split(/r?n/).filter((l)trim() !== '');if (ines.length < 2) {alert('Plik Ct pustylub niepprawny.');return;}// Wykryj separatr: jeśli w nagjęcej przecinków niż średików, użyj prz, w przeciwnym razi średnikconst headerLl];let separator = ';';if ((Ltch(//g) || []).length > (heamatch(/;/g) | ).length) {separator = ',';}/ Fu bezpiecznego splitowana linii CSV łowamifunction spltCSVLine(line, sep) {cost result = [];let curent = '';let inQfr (let i = 0; i <lielnth; i++) {const char = ine[i];if (char === '") es = !inQuotes;} elhar === sep && !inQuotes) {result.push(cuurrent = '';} else {curent += char;}}result.push(current);retur result} Nagłówky opcjonalnych kolumnconst headers = splitCSVLine(lines[0]r).map((h) => h.trim().toLowerCase));const eanIdx headers.findIn=> h == an');const priceIdx = headers((h) => h === 'cena srp');const arginIdx = headedarża');let imageIdx = headers.findIndex((h) => === 'zdjif (imageIdx === -1) imagIdx = headers.findndex((h) => h ==it nameIdx = headers.findIndex((h) => h ==kt_nzwa');const = headersfindIndex((h) => a');const gramatraIdx = headers.findIndex((h) = hse().includes('gramatura');// Kolmny encjiconst cmeitionNameIdx = ndInde((h) => h === kkurenc');ompetitionPriceIdx = haders.fix((h h kurencja_cena');const competitionShopIdx = headerIndex((h) = h === 'konkurencep');const compeUrlIdx = haders.findIndex((h)= 'konkurencja_url');// Kolumnadla ceny net/net/nezugłówka zawierjącego 3x 'net' (niezależnie od wielliter, spacji, ukośników itp.)console.log('Headers:',rs);const netNetNetPri= headers.findI((h)h 'cena net/net/net');console.log(ntNetNetPriceIdx)consrcodeHeader = heaers.findIndex((h) => h === 'kod kresk);if (barcodeHeader === -1) {headers.ph('kod kres');}fr (let i = 1; i < lines.length; i++) {const rosCSVLine(lines[ieparator);const ean = row[eanIdx]?.trim();if (erow[bacodeHeader] = ean;}}for (let i < lines.length; i++) {if sim()) coninue;const row SVLine(lines[i], separator);letull;if (eanIdx ! -1 && row.lengx) {const rawEAN = row[eanIdx].tr= parseEAN(rawEA ll;if (priceIdx !== -1 && row.length > priceIraPrice = row[priceIdx].trim(;rawPrice = rawace(/[^0-9,.]/g, '').replace(',',ce = parseloat(rawPrice);}let marginfx ! -1 && row.length > malet rawMargin = row[marginIdx].trim();rwMargin = rawlace(/[^0-9,.-]/g, '').replace(',argin=arseFloat(rawMargin);if (isNa {margin = ull; // Jeśli wartość nie j, ustawna null}}let imgUrl = '';if (imae& row.length > imaggUrlg();}ltpdName = '';if (nameId& row.length nameIdx) {prodName= row[.trlet weight = null;if (pe &&ig/sługa ormatów typu "6 szt (60g)", "3x(20 g)", "12 itd.const sztGramMatch = prodName.matc(/(d+)s*(sz-9]*(d+[.,]?d*)s*(kg|gsztGramMatch) {const szt =t(sztGramonst val = sztGramMatch[3].replace(',', '.');const unit = szt4].toLowerCas(const num = parseFif (!isNaN(num) & {cnst singleWeight = unit === ': num / 100;weight = singleWeight // Fallback — np. "250g" bez licif (!weight) {const wagMatch =math(/(d+[.,]?d*)s*(kg|g)/i)MattaMatch[1].repla;contut = wagaMatch[2]);const num = parseFl!isNaN(num)) {wit = unit === 'kg' 000;}}}}if (weight === null && wei& row.length > weigtIdx) {const raweihtIdx].trim();const wagaMatch =ch(/(d+[.,]?d*)s*(kg|g)/i);if (wagat vatpl',);sit = wagaMatch[2].toLowerCase();t num = parseFloat(val);if ((num)) {weight = nit === 'kg' ? num :num / 1000;}}}if (weightull && gramaturaIdx != w.length > gramaturaIdx) {const awGramatura =maturIdm(saturaMatch = rwGramatura.match(/(d+[.,]?d*)s*(kg|g)/i)if (gramaturaMatch) {cst val = gramaturaMatch[1].replac '.');const uit = gramatura2].toLowerCase();con = parseFloat(val);if (!isNaN(num)) weight = unit === 'kg' ? num: num / 1000;}}netNetNetPrice = ieiceIdx !== -1) {console.log'eNttPriceIdx:', netNetNetPricconsole.log('row[netNetNetPrceIdx[netNetNetPriceIdx]awetNetNetPrice =NetNetPriceIdx]?.trim(rawNetNetNePrice = rawNetNetNetPrice.r/g, '').replace(',', 'e(/[d.]/g, '').rep');consol.log('Raw nie:', rawNetNetNetPrice);const parsed (rawNteNPrice);cParsed netNetN parsed);ice = parsed;} =`p{i}`;if (!poductMap[productMap[key]rirmage: imgUrl, name: prodName, weight, netNrice };} else {productap[key] = {price: prouctMap[key].price || price,margin: prap[ke].margin || margin,image: productMap[ke].image || imgUme: prouctMap[key].name | prodName,weight: productMap[key]t || weigtnetNetNetPrice: productMap[key].netNeNetPricetNetNetPrice,};}// Obsługa danych konkuencjiif (compnNameIdx !== -1 && competitionPriceIdx !== -1 && competitionShopIdx !== -1)st copName = row[competitionNameIdx]?.trim();const compPriceRaw[competitionPriceIdx]?.tri();const compShop = row[compethopIdx]?.trim(;const compUrl = competitionUrlId !== -1 ? row[competitionUrlIdx]?.tr (ompName & compPriceRawmpShop) {const compPriceClean = compPriceRaw.replace(/[^0-9,.]/g, '')replace(');const compPrice parseFloat(compPriceClean);if (!isNaNcompPrice)) {// ź czy ten produkt jż istnieje w danych konkurencjiconst existingIndex = competata.findIndex(tem => item.name === compName && itm.shop === compShop);isdex === -1) {competitionData.puame: omNe,price: compPrice,shop: comphop,url: compUrl || ''});}}}}}alert(`Wczytano ct.keys(productMap).length} produkpliku CSV.`);console.log('Załadowane produkty:', roductMap); // Debugif (coionData.length > 0) {rnderComo();alert(`Dodatkowo wczytano ${comptitionData.length} pozycji konkur`);}}// Zamienia np. 5,2 lub 5.90534E+12 na normalny EA function parseEANraw) {if (!raw) retur// Usuń cudzysłowy i spacjeraw = raw.replace(/.trim()// Spróbuj zinterpretowliczbę, jeśnotacji nauwejlet num =raac if (!isaN(num)) {// Zamień na string bjilet str = num.toFixed0);if gth >= 8 && str.length <= 14) return str;}// ciąg znaków EANma długość 8-14, zwróć bez zmiaif/^d{8,(raw)) rturn raw;return null;}knięciu usaw produkt poPr.Lstener('click',const ean = eanInpt.vleti(if (!ean) {az EAN produktu.');return;}const p ap[ean];if (!p) {lert('Nie znaleziono produktu o podanym EAN wwanym pliku.);productImage.splnuctName.tyle.displne'return;}// Ustaw cenę bazową brutto z CSVbput.value = p.price.toFixed2;Pok i nazwę produktu, jeśli sąif (p.image) {let imgrc = p.image;if (///.tst(imgSrc) && !imgSrc.st/')) {imgSrc = './ productImage.src = imgSrc;oductImage.style.display = 'block'pagd 'none';}// yśzwę produktupod zdjęciemif (p.ame) {productNntent = pname;productName.styayke productName.tex = '';productName.style.disply= 'none';}e();});// InicjalizacjarendetTrle();// Funcje oonkurencjifunction rendeTable() {comeitionTableBody.innerHTML = '';competitionData.forE index) => {const row = documnt.createElment('tr');const naeCell =dcument.created')nameCell.extCo.name;row.appendChild(nameCell);cons= document.createEleent('td');priceCell.textCm.price.toFixed2);row.pendChild(priceCelhopCell = documet.createElemhopCell.textContent item.shohild(shopCell);const linkell = documentcreateElementem.url) const link = documemliiink.target = '_blan';link.textContent = linkCell.append} ele {linkCell.textContent = '-';}row.apendChild(likeactionsCell = docement('td');cnst deleteBtn= document.creaton');deeteBtn.textContent = Usuń';deame = 'deletebtn';deleteBtnner('click', () => a.pce(idex1);renderCompetiionTable(CeldendtionsCell);competitonTableBody.appendChild(rw);});}function aionItem() {const na = competitionNameI.trim();constprice = parseFloat(competitionPriceInput.lue);p = competitionShe.trim();consturl = competitionUrlIput.vaif (!name || isNaN(pce) || !shop) {alj wszystkie wymagae pola (naklep).');return;}ata.pus({name: name,price: price,shop: rlycmupameInput.value = '';competitionPriceInput.value = '';competionShopInput';competitionUrlInput.value = '';renerCompee();}function searchCompetitionByEan(){const ean = competitionEanI.trim(;if (!ea) {aEAN produktu.');return;/ Pokaż wskaźnik ładhStatus.style.display = 'block';earchMessage '<div class="loading"></iv> produktów w internZablokuj rzycisk wyszukiwaniasarchCompetab;mptntent = 'Wyszukiwanie...';lacjawyszukiwania w internecieuctsOnline(ean).th(results => {if (results.length =archMssaennerHTML = '❌ Nie znaeziono produktów o podanym EAecie.';stTimeout(() => {searchStatus.style.display = 'none';return;}// Dodaj wszystkie zalezione produkty do tabeli konkurncjilet addedCount = 0;results.forEac(productprawdź czy produkt już istnieje tabelikonkurencjiongProduct competitionDta.find(item => itemproduct.name & item.shop === product.shop);ingroduct) {competition{re product.price,shop: product.sp,oduct.url});adCountrenderCompetitionTable();// Poaż komunikat o sukcesiesearchMessag.innerHTML `✅ Znaleresults.duktów,dano ${addych pozycji do tabeli konkurencji.`;/formulaz danymi z pierwszego produktuif gth > 0) {const firstProduct = results[ionNameInput.value firstProduct.nme;riceInput.value = firstProuct.price.toFixtitionShopInput.alue = firstProduct.shop;competitonUrlInput.valPro;rs po 5 sekundachsetTimeout(()archStatus.style.display = 'none';}, 5000);).catch(error => {onsole.error('Błąd podczaania:', errr);searhssage.innerHTML = '❌ Wystąodczas wyszukiwania produktów.';seTimeout(() => {seasyle.display = 'none';}, 3000);}).finally(() =>okuj przycisk wyszukiwaniaseachCompetitionBtn.disabled = false;searchCometitt'Wyszukaj';});}// Funkcja do pobierania rokcie z API bacodeasync function getProductIfoFromBarcode(ean) {try {//mweg API UPC Dtabaseconst response = awtsemdb.com/prod/trial/looku;cnst data = await response.json();if (data.code === 'OK' && data.items.length > 0) {const item = datitems[0]; item.title || item.brand || `rodukt ${ean},brd |iiption || '',image: item.imges.length > 0 ? item.i} catch (eror)Błąd podczas pobiektu:', error);}// Symulacja wyszuk w ic rcnl {n oresolve) => {/ Symulacja opóźnienia zapytnia do APIsetTimeut(() => {//dź czy mamy dane dla tego EAN w nasej baziecst localPr= productap[ean];const productNaalProduct ? localPoduct.nrodukt EAN: ${ean}`;// Symulowane dane z różnych sklepówcont mockResultsn pcece: 15.99,shop: 'Allegro',rl: `http://allegro.pl/search?string=${ean}`},ame: roductName,price: shop: 'OLX',url: `htww.olx.pl/oferty/q-${ean}/ame oductName,price: 14.25,shopel: `https://www.ceneopl/search;szukaj-${ean}`},{name: productNamee: 6.80,hop: 'Amazon',url: `https//wzon.pl/s?k=${ean}`e: productName,price: 13.99,hik',url: `https://www.empearch?q=$an}`},{name: productName,price: 18.45, '',url: `htps://www.morele.net/search/q=${ean}`},{name: productName,pri.75,shop: 'Euo RTV AGD',url: `htps:/u.pl/search.bhkedan}`}];if (localroduct) {// Użyjczywistej ceny jako bazowej i ddaj losową wariaonst basePrice = localProduct.prce;mockResulth(result => {result.price =b + (Mathm(5) * 8;esult.price = Mathesult.pric; // Nie może być jemnaresult.price = Math.round(esult.price * 100) / Zaokrąglij do 2 miejsc po przecinkuse {/Jeśli nie ma lokalnych danycpodstawoyą losowościąmockesults.forEach(r> {result.price 10 + Math.random() * 20e10 do 30 złresult.prie = Math.round(resut * 100 / 100;esolv(mockResults);} // Symulacja 1 sekuny opóźnienia});}/ Funkcja do reczywzukiwania (wymaga API kec searchProductsOnlinRelenst results = [];try {// rzykład wwania Awymaga API key)// const allegroespoawtch(`http://api.allegr.pl/serch?ean=$);// const ala = await alleroeponse.json();kład yszukiwania w Ceno (wymaga APIub web sc// const ceneoResponse =awihttps://www.ceneo.l/api/sarch?ean=`)oneoData =awaiteoReson();// Dodaj wi do tablc rults}ch (error) {coor('Błąd podczas wyszukiwania:'ror);}return results;}functionompetitionData(ascending = tru) {coi.sort((a, b) => {f (ascending) {return a b.price;}else {return b.price - a.pricmpetitionTable();}// Evet listenery dla sekurencjisearchCompetitionBtn.addEvner('click', searchCompetit)pettiontn.addEventListener('click', addCompeti);sortAscBtn.adEventListener('click', ( => stitionData(true));sortDescBeer('click', () =>sortCompetitionData(false));matyczne wyszukiwaie przy zmianiepetitonEanInput.addEventLii) => {const en =cpetitionEanInput.value.trnsole.log('WyszuN:', ean); // Debugconsole.log('Dostępne ect.keys(oductap));if (ean && productMap[est produc = productMap[ean];og('Znaleziono produkt:', pr// Debugct.name) {ompetitionNameIne t.name;}if (product.priceeceInput.value = product.price.toFixed(2);}// Usślną nazwę sklepuif (!competitionShopInput.value.trcompetitionShopInput.value = 'Na';}} else / Wyczyść pola jeśli został znezionycompetiionNameInput.valuepetitionPriceInput.vau= '';if nsole.lgNie znaleziono dlaean); // Debug}}});// Atomatycukiwanie przynaciśnięciu EnteetitionEanInput.adEventLitener('keypr(e) i.=== 'nter') {searchCometion);}});</script></body></html>