DEV Community

Antonio
Antonio

Posted on

I replaced our warehouse paper inventory with one HTML file

inventory

At work, inventory day looks the same every time. We walk around the warehouse with printed sheets and pens: wood panels, aluminium profiles, glass and laminates, hundreds of entries. We write numbers in the margins and cross things out. A few weeks later at least one sheet has gone missing.

I'm not a professional app developer. I build a 2D game engine in JavaScript as a hobby. One evening I thought: I can't build an inventory system for the company, but I can build one for myself.

This post covers what I built, how it works, and what I'd do differently. Spoiler: there are better tools out there. This one exists because it solved my problem in one evening.

The goal

  • It opens on my phone, even with no signal in the warehouse.
  • Finding an item takes two letters, not scrolling through 300 rows.
  • Big + and − buttons I can use with one hand.
  • Nothing to install, no server, no database, no login.
  • At the end I can export everything to Excel.
  • It's private: nobody needs to know it exists.

The architecture

It's one index.html file with inline CSS and JavaScript. That's the whole thing.

  • The catalogue (item codes and descriptions) is a JavaScript array embedded in the file.
  • The quantities are saved in localStorage on the phone.
  • Hosting is a folder with a random name on a site I already had, with a noindex meta tag.
<meta name="robots" content="noindex, nofollow">
Enter fullscreen mode Exit fullscreen mode

Even if someone found the URL, they would only see an empty app. The counts live on my phone, not on the server.

Getting the data in

The most boring part was the catalogue itself: several pages of printed tables, some photographed upside down, plus a scanned PDF. I gave the photos to an AI assistant, which transcribed them into plain text, one line per item:

2|Line A|AL-001|White Matt
2|Line A|AL-002|Light Grey
3|Line B|AL-101|Bronze Brushed
Enter fullscreen mode Exit fullscreen mode

A tiny script turns that into the array that goes inside the HTML:

const CATALOGUE = [
  { c: 2, l: 'Line A', k: 'AL-001', d: 'White Matt' },
  { c: 2, l: 'Line A', k: 'AL-002', d: 'Light Grey' },
  // ...
];
Enter fullscreen mode Exit fullscreen mode

(The codes above are made up. The real ones belong to our suppliers.)

The core: search plus counters

Search matches every word you type against the code, description and line, ignoring accents:

const norm = (s) => String(s).toLowerCase()
  .normalize('NFD').replace(/[\u0300-\u036f]/g, '');

const words = norm(query).split(/\s+/).filter(Boolean);
const visible = items.filter((i) =>
  words.every((w) => norm(`${i.k} ${i.d} ${i.l}`).includes(w))
);
Enter fullscreen mode Exit fullscreen mode

Typing grey matt finds "Light Grey Matt" whatever order the words are in.

Each row has a minus button, a number field and a plus button. One click listener on the list handles all of them:

list.addEventListener('click', (e) => {
  const btn = e.target.closest('[data-a]');
  if (!btn) return;
  const key = btn.closest('.card').dataset.k;
  qty[key] = Math.max(0, (qty[key] || 0) + Number(btn.dataset.a));
  localStorage.setItem('inv-qty', JSON.stringify(qty));
  navigator.vibrate?.(15); // a tiny buzz so you know it counted
});
Enter fullscreen mode Exit fullscreen mode

Tapping the number lets you type a value directly, which is useful when you count 40 panels in one go.

Panels have sizes, and they're never the same

Aluminium is easy: one code, one count. Wood and laminate panels aren't. The same "Oak Natural" might be in stock as 200×200 and as a leftover 270×120, and the sizes are different every time.

So every panel can have its own list of sizes, each with its own counter:

// sizes['P|Laminate|Oak Natural'] = [{ m: '200×200 sp.18', n: 3 }, { m: '270×120', n: 1 }]
const total = (k) => (qty[k] || 0) + (sizes[k] || []).reduce((s, x) => s + x.n, 0);
Enter fullscreen mode Exit fullscreen mode

I type the size once (200x200 becomes 200×200), and after that it's just + and −.

Export and backup

localStorage has one big weakness: lose the phone or clear the browser data, and everything is gone. So there are two buttons.

CSV for Excel. I use a semicolon separator and a UTF-8 BOM, otherwise Excel set to Italian puts everything in one column and mangles the accents:

const csv = '\ufeff' + rows
  .map((r) => r.map((v) => `"${String(v).replace(/"/g, '""')}"`).join(';'))
  .join('\r\n');
Enter fullscreen mode Exit fullscreen mode

JSON backup. It saves the quantities and sizes to a file, and the same file can be restored on another phone. I send it to myself on WhatsApp after every count.

Installing it like an app

There's no app store involved. I open the page and choose Add to Home screen. With apple-mobile-web-app-capable and a theme-color, it opens full screen like a native app.

What I'd do better

I know this isn't how you'd build a real inventory system:

  • No sync. The data lives on one phone, so two people can't count at the same time.
  • The catalogue is hardcoded. Adding items from the UI works, but those extras stay on that phone.
  • No service worker. It works offline once loaded, but a proper PWA would cache it properly.
  • No history. It records the current count, not who changed what and when.

A small backend (or even a shared Google Sheet) would fix most of this. But that would be a project. This was an evening.

Takeaway

You don't need permission, a budget or a framework to fix a small, real problem at work. One HTML file, localStorage and a phone took us from "where's sheet number 3?" to typing two letters and tapping +.

If you've built a tiny tool like this for your own job, I'd love to hear about it in the comments.

Top comments (0)