Why does my debounce function not work correctly inside a React component?

I’m trying to implement a debounce function in a React component to prevent multiple API calls while the user types in a search input field.

Here’s the debounce function I’m using:

function debounce(fn, delay) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

And this is how I’m using it in my React functional component:

import React, { useState } from 'react';

function SearchComponent() {
  const [query, setQuery] = useState('');

  const handleChange = (e) => {
    setQuery(e.target.value);
    debouncedSearch(e.target.value);
  };

  const debouncedSearch = debounce((val) => {
    console.log('Searching for:', val);
    // Simulate API call here
  }, 500);

  return <input type="text" onChange={handleChange} />;
}

Wrapping debouncedSearch with useCallback

Moving the debounce function outside the component scope (which works but doesn’t have access to component state directly).

Using external libraries like lodash.debounce, but still facing similar behavior if declared inside the component.

Why does setTimeout inside a loop print the same value? [duplicate]

I’m trying to understand how setTimeout works inside a loop in JavaScript. I expected it to print numbers from 1 to 5, each printed after one second, but instead, it prints the number 6 five times.

Here is the code I wrote:

for (var i = 1; i <= 5; i++) {
    setTimeout(() => {
        console.log(i);
    }, i * 1000);
}

I expected the output to be:

1
2
3
4
5

But instead, the output is:

6
6
6
6
6

I think this might be related to the way var works in JavaScript and how closures capture variables in asynchronous code. I would like to understand why this behavior occurs, and how I can modify the code so that it correctly prints numbers 1 through 5 with a one-second delay between each.

indesign js: How to apply link to a specific word within a textframe?

Currently I can only apply it to the whole textfield.

I want to be able to apply internal links to different pages to a specific word within a TextField, so I can have multiple links within the same textfield.

How would I get “LINK-A” and “LINK-B” to work to different destinations?


doc = app.activeDocument;
var layers = doc.layers;
var pages = doc.pages;
var firstPage = pages.item(0);

for (var i = 1; i < 6; i++) {
    var anotherPage = pages.add();
}
var tfPage1 = firstPage.textFrames.add();
tfPage1.geometricBounds = [2, 2, 20, 20];
tfPage1.contents = "yaddda yadda LINK-A yadda yaddanAnd a LINK-B on the 2nd line";

var linkA = layers[0].textFrames[0].texts[0]; // what goes here?
var hyperlinkSource1 = doc.hyperlinkTextSources.add(linkA);
hyperlinkDestination1 = doc.hyperlinkPageDestinations.add(pages.item(3));
doc.hyperlinks.add(hyperlinkSource1, hyperlinkDestination1);

var linkB = layers[0].textFrames[0].texts[0];  // what goes here?
var hyperlinkSource2 = doc.hyperlinkTextSources.add(linkB);
hyperlinkDestination2 = doc.hyperlinkPageDestinations.add(pages.item(2));
doc.hyperlinks.add(hyperlinkSource2, hyperlinkDestination2);


StaleElementReferenceError in Selenium when running in Docker but not Locally

For this apps test, doing a series of cucumber steps with the verification is in selenium. In the steps we hover over an element which causes a tooltip to pop-up. Selenium is then supposed to locate the element in the tooltip and check for it being displayed. When running this test locally the test completes successfully but when ran inside docker it results in a StaleElementReferenceError for the pop up.

    When User hovers over tooltip
    Then User sees the tooltip information

Which calls these two functions

    hoverOverTooltip() {
        return this.hover(locators.Tooltip);
    }

    verifyTooltipinfoIsDisplayed() {
        const locatorToVerify = {
            Mode: By.xpath('//span[contains(text(), 'Modes')]'),
            low: By.xpath('//span[contains(text(), 'Low')]'),
            high: By.xpath('//span[contains(text(), 'High')]'),
            medium: By.xpath('//span[contains(text(), 'Medium')]'),

        }
        return Promise.all([
            this.verifyElementIsDisplayed(locatorToVerify.Mode),
            this.verifyElementIsDisplayed(locatorToVerify.low),
            this.verifyElementIsDisplayed(locatorToVerify.high),
            this.verifyElementIsDisplayedAfterHover(locatorToVerify.medium),
        ])
    }

And this is the selenium checks

    async verifyElementIsDisplayed(locator) {
        const element = await this.app.page.findElement(locator);
        return element.isDisplayed();
    }

    async hover(locator) {
        const element = await this.app.page.findElement(locator);
        const actions = this.page.actions({async: true});
        await actions.move({origin: element}).perform();
    }

Running in Docker results in the error

   ✖ Then User sees the tooltip information # file:steps.js:60
       StaleElementReferenceError: stale element reference: stale element not found in the current frame
         (Session info: chrome=124.0.6367.243)
           at Object.throwDecodedError (/tests/node_modules/selenium-webdriver/lib/error.js:523:15)
           at parseHttpResponse (/tests/node_modules/selenium-webdriver/lib/http.js:524:13)
           at Executor.execute (/tests/node_modules/selenium-webdriver/lib/http.js:456:28)
           at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
           at async Driver.execute (/tests/node_modules/selenium-webdriver/lib/webdriver.js:745:17)
           at async SeleniumEditConnectionPage.verifyElementIsDisplayedAfterHover (file
           at async CustomWorld.<anonymous> (file)

I tried writing this monster of a function to try to re-find the element and re-hover over the tooltip, but it fails at trying to perform the action.move to re-hover over the element again and exits the function.

    async verifyElementIsDisplayedAfterHover(locator, hoverLocator) {
        try {
            await this.verifyElementIsDisplayed(locator);
        }
        catch (error) {
            if (error.name === 'StaleElementReferenceError') {
                console.log("StaleElementReferenceError caught");

                // Re-locate the hover element
                console.log("Re-locating hover element...");
                const hoverElement = await this.app.page.findElement(hoverLocator);

                // Re-hover
                console.log("Re-hovering...");
                const actions = this.app.page.actions({ async: true });
                console.log("Hover acction made");
                await actions.move({ origin: hoverElement, x: 1, y: 1 }).perform();
                console.log("Hover action completed");

                // Optional: small delay to allow DOM update
                await this.app.page.sleep(300); // or use setTimeout in a wrapper

                // Wait for the element to appear and be visible
                console.log("Waiting for element to be visible...");
                await this.app.page.wait(until.elementLocated(locator), 5000);
                const element = await this.app.page.findElement(locator);
                await this.app.page.wait(until.elementIsVisible(element), 5000);

                return element.isDisplayed();
            } else
                throw new Error(`Unable to find the '${locator}' on the window.n Returned error: '${error}'`);
        }
    }

Clip a canvas using a polygon and a blured circle

I try to clip an canvas using to different shape : a polygon (saying a star) and a blured circle (its edges fade-out from transparent to partially opaque).

Here is a sample of the code to make this clip but I’m totally unable to create the “blur effect” around the circle.

      const canvas = document.getElementById("canvas");
      const ctx = canvas.getContext("2d");

      canvas.width = window.innerWidth;
      canvas.height = window.innerHeight;

      const img = new Image();
      img.src = "https://picsum.photos/800/600";
      img.onload = function () {
        let mouseX = canvas.width / 2;
        let mouseY = canvas.height / 2;
        const radius = 70;

        function draw() {
          ctx.save();

          ctx.fillStyle = "rgba(0, 0, 0, 0.7)";
          ctx.fillRect(0, 0, canvas.width, canvas.height);

          const starPoints = createStar(5, mouseX, mouseY, 100, 50);
          ctx.beginPath();
          ctx.moveTo(starPoints[0].x, starPoints[0].y);
          for (let i = 1; i < starPoints.length; i++) {
          ctx.lineTo(starPoints[i].x, starPoints[i].y);
          }
          ctx.closePath();
          ctx.clip();

          const gradient = ctx.createRadialGradient(
            mouseX,
            mouseY,
            radius * 0.8,
            mouseX,
            mouseY,
            radius
          );
          gradient.addColorStop(0, "rgba(0, 0, 0, 0)");
          gradient.addColorStop(1, "rgba(0, 0, 0, 0.7)");

          ctx.fillStyle = gradient;
          ctx.beginPath();
          ctx.arc(mouseX, mouseY, radius, 0, Math.PI * 2);
          ctx.fill();

          ctx.drawImage(img, 0, 0, canvas.width, canvas.height);

          ctx.restore();
        }

        function createStar(points, cx, cy, outerRadius, innerRadius) {
          const result = [];
          const angleStep = Math.PI / points;
          for (let i = 0; i < 2 * points; i++) {
            const angle = i * angleStep - Math.PI / 2;
            const radius = i % 2 === 0 ? outerRadius : innerRadius;
            result.push({
              x: cx + radius * Math.cos(angle),
              y: cy + radius * Math.sin(angle),
            });
          }
          return result;
        }

        canvas.addEventListener("mousemove", function (event) {
          mouseX = event.clientX;
          mouseY = event.clientY;
          draw();
        });

        draw();
      };
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <style>
      body {
        margin: 0;
        overflow: hidden;
      }
      canvas {
        display: block;
      }
    </style>
  </head>
  <body>
    <canvas id="canvas"></canvas>
  </body>
</html>

I need to keep star edges sharp, but the circle edge fading-out

jquery – adding a div to a list of divs

So I have a structure like this on my page (greatly simplified):

<div id='full-list'>
    <div id='test-1'> STUFF ONE</div>
    <div id='test-2'> STUFF TWO</div>
    <div id='test-3'> STUFF THREE</div>
</div>

I have a handler function inside an ajax call that gets a new block of HTML as a string:

json.new_div_html = "<div id='test-4'> STUFF FOUR</div>";

I want to add it to the list above, so that I get:

<div id='full-list'>
    <div id='test-4'> STUFF FOUR</div>
    <div id='test-1'> STUFF ONE</div>
    <div id='test-2'> STUFF TWO</div>
    <div id='test-3'> STUFF THREE</div>
</div>

I tried using:

$('#full-list').prepend( json.new_div_html );

But that just does nothing – no error, no visible change to my page. Not sure what I’m doing wrong?

Demo…

const json = {};
json.new_div_html = "<div id='test-4'> STUFF FOUR</div>";

$('#full-list').prepend( json.new_div_html );
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.slim.min.js"></script>
<div id='full-list'>
    <div id='test-1'> STUFF ONE</div>
    <div id='test-2'> STUFF TWO</div>
    <div id='test-3'> STUFF THREE</div>
</div>

Error uploading image to Firebase Storage with React Native Expo – (storage/unknown)

I’m trying to upload an image to Firebase Storage using React Native with Expo. The image is picked using expo-image-picker, and I’m getting the following error:

FirebaseError: Firebase Storage: An unknown error occurred, please check the error payload for server response. (storage/unknown)

What I’ve already done:
Firebase project is properly configured.
My Storage rules are set as follows:

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if request.auth != null;
    }
  }
}

The user is authenticated with Firebase before uploading.
Image URI is correctly returned by expo-image-picker (confirmed via console.log).
I’m converting the URI to a blob using fetch() and response.blob().

const handlePickImage = async () => {
  const result = await ImagePicker.launchImageLibraryAsync({
    mediaTypes: ImagePicker.MediaTypeOptions.Images,
    quality: 0.7,
  });

  if (!result.canceled) {
    const response = await fetch(result.assets[0].uri);
    const blob = await response.blob();

    const storageRef = ref(storage, `avatars/${userId}.jpg`);

    try {
      await uploadBytes(storageRef, blob);
      console.log('Upload successful!');
    } catch (err) {
      console.error('Error uploading image:', err);
    }
  }
};

Passing parameter to route function from ajax

I am trying to call a controller method using ajax. But I do not know how to do it? I have tried several ways but can not find the right one.

My controller method::

use AppHttpControllersController;
use AppModelsAdminServicesPost;

public function edit(string $id)
    {
        $post = Post::find($id);
        return Response()->json($post);
    }

Route method::

Route::get('admin_post_edt/{id}', [PostController::class, 'edit'])->name('admin_post.edit');

Blade page::

{{-- removed other code --}}
<td>
    <button type="button" id="edit_btn" value="{{ $item->id }}" class=" btn btn-primary btn-sm"><i
            class="fa fa-edit"></i></button>   
</td>
<script>
$(document).on('click', '#edit_btn', function() {
            var post_id = $(this).val();
            $.ajax({
            type:"GET",
            url: "{{ route('admin_post.edit')}}",
            data: { id: post_id },
            dataType: 'json',
            success: function(res){
            $('#editModal').modal('show');
            },
            failure: function (response) {
                        alert(response.responseText);
                    },
                    error: function (response) {
                        alert(response.responseText);
                    }
            });
</script>

How to pass id to the route admin_post.edit?

Why my drag and drop menu is not working?

I’m building an admin menu manager with Laravel Livewire and SortableJS. The drag-and-drop UI works perfectly but I can not reorder menus and nested submenus visually.

I have a recursive function in Livewire to save the menu hierarchy (parent_id and order) in the database based on the nested structure received from the frontend. The updateMenuOrder Livewire method is triggered on drag-end with the serialized menu structure.

The top-level menus do not load ordered by order, and the children relationship is not eager loaded with order by order as well.

Here is my html menu

<div class="w-2/3 p-4 bg-white rounded shadow">
    <h3 class="font-semibold mb-4 text-black">Menu Items (Drag and Drop to reorder)</h3>

    <ul id="menu-list">
    @foreach ($menuItems as $menu)
        <li data-id="{{ $menu->id }}" class="mb-2 border p-2 rounded bg-gray-100">
            <div class="flex justify-between items-center">
                <span class="handle cursor-move text-black">{{ $menu->title }}</span>
                <button wire:click="removeMenu({{ $menu->id }})" class="text-red-500 hover:text-red-700 font-bold">&times;</button>
            </div>

            @if ($menu->children->count())
                <ul>
                    @foreach ($menu->children as $child)
                        <li data-id="{{ $child->id }}" class="ml-6 mt-2 border p-2 rounded bg-gray-50">
                            <div class="flex justify-between items-center">
                                <span class="handle cursor-move text-black">{{ $child->title }}</span>
                                <button wire:click="removeMenu({{ $child->id }})" class="text-red-500 hover:text-red-700 font-bold">&times;</button>
                            </div>
                        </li>
                    @endforeach
                </ul>
            @endif
        </li>
    @endforeach
</ul>



</div>

and here is my js

@push('scripts')
<script src="https://cdn.jsdelivr.net/npm/[email protected]/Sortable.min.js"></script>
<script>
    document.addEventListener('livewire:load', function () {
        let menuList = document.getElementById('menu-list');

        new Sortable(menuList, {
            animation: 150,
            handle: '.handle', // this matches your <span class="handle">
            onEnd: function (evt) {
                let order = serializeMenu(menuList);
                Livewire.emit('updateMenuOrder', order);
            }
        });

        // Initialize sortable on all child ULs (if you want nested drag & drop)
        menuList.querySelectorAll('ul').forEach(function(childUl) {
            new Sortable(childUl, {
                group: 'nested',
                animation: 150,
                handle: '.handle',
                onEnd: function (evt) {
                    let order = serializeMenu(menuList);
                    Livewire.emit('updateMenuOrder', order);
                }
            });
        });
    });

    function serializeMenu(el) {
        let items = [];
        el.querySelectorAll(':scope > li').forEach((li, index) => {
            let item = {
                id: li.getAttribute('data-id'),
                order: index + 1,
                children: []
            };

            let childUl = li.querySelector('ul');
            if (childUl) {
                item.children = serializeMenu(childUl);
            }

            items.push(item);
        });
        return items;
    }
</script>




@endpush

My menu manager component is here

    public function loadMenu()
{
    $this->menuItems = Menu::whereNull('parent_id')->with('children')->orderBy('order')->get();
}
 #[On('updateMenuOrder')]
    public function updateMenuOrder(array $structure)
    {
        Log::info('updateMenuOrder called with:', $structure);

        $this->updateMenuHierarchy($structure);

        $this->loadMenu();

    }


    // Recursive update of order and parent_id
   public function updateMenuHierarchy(array $items, $parentId = null)
    {
        foreach ($items as $index => $item) {
            Menu::where('id', $item['id'])->update([
                'parent_id' => $parentId,
                'order' => $index
            ]);

            if (!empty($item['children'])) {
                $this->updateMenuHierarchy($item['children'], $item['id']);
            }
        }
    }

How to select an element from a dropdown menu?

I have a pop-up modal, in which I am creating a rule from, but when trying to select the elements from the select-menu, my test fails. I have this on the web page

<label class="form-label"> Group Granularity </label>
<select class="form-select"> 
<option selected disabled value>Select granularity</option>
<option value="per-metric">per-metric</option>
<option value="per-hostname">per-hostname</option>
<option value="per-group">per-group</option>
<option value="global">global</option>
</select>

I tried a few code options to handle it, but none of them seem to work i.e

await page.getByText('Group Granularity').click();
await page.getByRole('option', { name: 'per-hostname' }).click();

and the error is as follows, because it seems that the element doesn’t get identified within the timeout threshold:

Error: locator.click: Test timeout of 30000ms exceeded.

title not visible during overlappng scroll effect

I’m currently working on an overlapping scroll effect for a section on my page. The animation itself works fine, but I’m running into a specific issue: the section title scrolls out of view before the full effect is completed.

Ideally, I want the title to remain fixed or “sticky” until the entire overlapping effect finishes, and then scroll away normally.

I’ve tried wrapping the title inside the animated section and adjusting paddings and positioning, but that introduces layout issues. Sometimes the title appears static, but part of it still scrolls away too early, or it causes spacing glitches in the layout.

I’ve been experimenting with combinations of position: sticky, z-index, and conditional padding/margins, but nothing fully solves it.

Also tagging a collaborator here—we both hit the same wall trying multiple fixes with layout structuring, wrapping strategies, and sticky positioning, but none of them managed to keep the title in place correctly throughout the scroll sequence.

How can I keep localStorage in sync with server-side changes from a cron job?

I’m building a web app that has a concept of an accountType (e.g., “free”, “pro”, etc.). The user’s accountType is stored in the backend database and is updated A cron job runs every hour and may change the accountType based on some business logic (e.g., expiration of a free plan).

In the frontend, I cache the accountType in localStorage so that I can avoid unnecessary API calls. However, this creates a problem: the data in localStorage can become stale if the backend changes due to the cron job.

How to fix margin calculation and CSV-based net/net/net price import in JavaScript web app? [closed]

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>

How to host the same folder in IIS using two different PHP versions?

I have a folder running a PHP application configured in IIS. Here’s the current setup:

  • Site Name: Original Website
  • PHP Manager > PHP Version: 7.0
  • Bindings: * on ports 80 and 443
  • Handler Mappings: First handler is PHPv7.0 using FastCGI

Now, I want to run the same application folder using PHP 8, accessible via a different domain (e.g., php8.example.com.local). The goal is to test the app on PHP 8 while keeping the original running on PHP 7.0.

Here’s what I tried:

Created a new IIS website (PHP 8 Website) pointing to the same folder.

Set bindings to only php8.example.com.local.

Changed the PHP version to 8 in PHP Manager for the new site.

When I change the PHP version in the new site, the original site also switches to PHP 8 (and vice versa). It seems the PHP version setting is global, even though they are separate sites.

I also tried modifying Handler Mappings at the site level to switch only the PHP 8 Website to use PHPv8.0, but that also affects the original site.

How can I configure IIS to serve the same application folder using two different PHP versions, based on the domain name?