InsightsProjects & Engineering Lessons
Projects & Engineering LessonsERPCustom ApplicationsLessons LearnedReal-World Development

Why Employees Hated the First Version of Our Custom ERP

We built every feature executives asked for, but warehouse workers hated using it. How redesigning for keyboard speed saved our custom ERP rollout.

U

Umar Farooq

System Architect & Full-Stack Engineer

September 1, 2026
6 min read
Why Employees Hated the First Version of Our Custom ERP

Direct Answer: Operational employees hated the initial release of our custom ERP because the user interface was designed around relational database schemas rather than real-world factory and warehouse workflows. Forcing data entry operators to click through multi-tab modal forms and wait for slow animations destroyed their daily keyboard velocity, leading to widespread resistance until we redesigned the system around speed and keyboard shortcuts.

Building a custom Enterprise Resource Planning (ERP) platform is one of the most ambitious undertakings in full-stack engineering. When corporate leadership decides to replace expensive legacy software with a bespoke platform, engineering teams celebrate: full technical freedom, modern frameworks, clean PostgreSQL schemas, and beautiful component libraries.

Several years ago, our engineering team delivered a modern custom ERP for an enterprise manufacturing and logistics client in Saudi Arabia. On paper, the system was technically flawless: 100% test coverage, sub-25ms database queries, and clean Next.js App Router architecture. Yet, on launch day, the warehouse staff and procurement clerks absolutely hated it. Here is the painful retrospective on what went wrong and how we fixed it.

The Fatal Disconnect: Schema Purity vs Operational Speed

Software engineers perceive systems through database normalization: tables for customers, addresses, order lines, and tax rates. Consequently, we designed an interface with beautiful modal drawers, confirmation dialogs, and smooth CSS transitions. To create a purchase order, an operator had to click 'New Order', wait for a modal animation, select a supplier from a dropdown, click 'Add Line Item', and repeat.

What we failed to understand was that the warehouse clerk processes 300 invoices every single morning. In their old, ugly legacy green-screen terminal, they never touched a mouse; their fingers flew across numpads and Tab keys, entering line items in four seconds flat. Our modern web application tripled their daily workload.

Comparison: The Failed First Version vs The User-Adopted Redesign

Here is how user interface conventions transformed after spending three days observing factory floor operations:

UX Dimension

Failed Initial Version (Engineer-Centric)

Adopted Final Version (Operator-Centric)

Input Mechanism

Mouse clicks, modal drawers, and dropdown selectors

100% keyboard navigable with Tab, Enter, and hotkeys

Form Layout

Multi-tab wizards requiring 6 distinct steps

High-density spreadsheet grid with inline auto-save

Visual Feedback

Smooth 300ms CSS drawer transition animations

Instantaneous 0ms state updates with optimistic UI

Search & Selection

Paginated modal lists requiring mouse clicks

Type-ahead predictive search matching SKU barcodes

Error Handling

Blocking red alert modals stopping data entry

Inline non-blocking validation highlighting row cells

Production Code: Building High-Density Keyboard Navigation

Below is a production React component demonstrating how we rebuilt ERP data entry around keyboard event listeners, allowing operators to enter line items effortlessly without touching a mouse:

// src/components/erp/KeyboardDataGrid.tsx
"use client";

import React, { useRef, useState } from "react";

interface GridRow {
  sku: string;
  qty: number;
  unitPrice: number;
}

export function KeyboardDataGrid() {
  const [rows, setRows] = useState<GridRow[]>([{ sku: "", qty: 1, unitPrice: 0 }]);
  const tableRef = useRef<HTMLTableElement>(null);

  const handleKeyDown = (e: React.KeyboardEvent, rowIndex: number, field: keyof GridRow) => {
    // Pressing Enter on the last column creates a new row instantly
    if (e.key === "Enter" && field === "unitPrice") {
      e.preventDefault();
      setRows((prev) => [...prev, { sku: "", qty: 1, unitPrice: 0 }]);
      setTimeout(() => {
        // Focus the first input of the newly created row immediately
        const nextInput = tableRef.current?.querySelector<HTMLInputElement>(`#sku-${rowIndex + 1}`);
        nextInput?.focus();
      }, 10);
    }
  };

  return (
    <table ref={tableRef} className="w-full text-sm font-mono border-collapse">
      <thead>
        <tr className="bg-slate-100 dark:bg-slate-800 text-left">
          <th className="p-2">Item SKU</th>
          <th className="p-2">Quantity</th>
          <th className="p-2">Unit Price (SAR)</th>
        </tr>
      </thead>
      <tbody>
        {rows.map((row, idx) => (
          <tr key={idx} className="border-b">
            <td className="p-1">
              <input
                id={`sku-${idx}`}
                className="w-full p-1 bg-transparent border rounded"
                value={row.sku}
                onChange={(e) => {
                  const val = e.target.value;
                  setRows((prev) => prev.map((r, i) => (i === idx ? { ...r, sku: val } : r)));
                }}
              />
            </td>
            <td className="p-1">
              <input
                type="number"
                className="w-full p-1 bg-transparent border rounded"
                value={row.qty}
                onChange={(e) => {
                  const val = Number(e.target.value);
                  setRows((prev) => prev.map((r, i) => (i === idx ? { ...r, qty: val } : r)));
                }}
              />
            </td>
            <td className="p-1">
              <input
                type="number"
                className="w-full p-1 bg-transparent border rounded"
                value={row.unitPrice}
                onKeyDown={(e) => handleKeyDown(e, idx, "unitPrice")}
                onChange={(e) => {
                  const val = Number(e.target.value);
                  setRows((prev) => prev.map((r, i) => (i === idx ? { ...r, unitPrice: val } : r)));
                }}
              />
            </td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

The Golden Rule of Internal Enterprise Software

The lesson was unforgettable: internal enterprise software is evaluated on input velocity, not visual beauty. A software tool that cuts forty seconds off a repetitive invoice entry workflow saves hundreds of hours of employee frustration every month.

Frequently Asked Questions

Why do operational employees resist new software?

Employees resist new software when it slows down their daily productivity. If an existing system allowed them to finish tasks quickly, a modern web app with slow transitions and mouse-dependent forms feels like an operational hindrance rather than an upgrade.

How should developers test internal software before launch?

Sit beside actual data entry operators for half a day. Watch their hands on the keyboard, count how many times they reach for a mouse, and measure how long it takes to process a real stack of paperwork. Their friction points will immediately guide your backlog.

Is Next.js fast enough for heavy ERP data grids?

Yes, when built properly. Avoid heavy uncontrolled re-renders across large table arrays by leveraging virtualized lists (such as TanStack Virtual) and optimistic local state management.

Summary & Architectural Wisdom

To ensure long-term ergonomic success, enterprise platforms should adhere strictly to the Nielsen Norman Group usability heuristics for enterprise software while leveraging responsive token systems like Tailwind CSS accessibility documentation to maintain visual hierarchy across desktop workstations and warehouse handheld tablets.

Designing successful enterprise software requires humility. By prioritizing keyboard accessibility, high-density layouts, and sub-10ms UI responsiveness, you create tools that operational teams adopt with genuine enthusiasm.

In our enterprise Laravel development practice and Next.js SaaS application practice, user-centric speed and keyboard workflows are built into every system.

To discover more about our enterprise system case studies, explore my About Me page or read our full archive on the Engineering Blog.

Building or modernizing a custom ERP platform for your business operations? Schedule an architecture consultation on my Connect page.

Umar Farooq - Full-Stack & AI Engineer

Umar Farooq

Author & Consultant

Specializes in Laravel, Next.js, and AI products. 5+ years enterprise experience with 80+ delivered platforms and full source code ownership.

Did you find this architecture breakdown useful?