DEV Community

Cover image for Building a Modular Panel-Based Management Software with React 19, TypeScript, and Dockview 5
Federico
Federico

Posted on

Building a Modular Panel-Based Management Software with React 19, TypeScript, and Dockview 5

Building a Modular Panel-Based Management Software with React 19, TypeScript, and Dockview

Introduction

This article documents my experience designing and developing the frontend of a management software for a manufacturing company in the leather processing industry. The company had been using a legacy application developed over 20 years ago with outdated technologies — a slow, insecure Windows XP-era tool with a dated interface that had become increasingly difficult to maintain.

The goal was clear: replace the old system with a modern, web-based solution without disrupting the established operational workflows that operators had been using for decades.

The Project at a Glance

The client needed a fast, modern web application accessible from any browser. The key requirements were:

  • Modular panel interface: Operators needed to work with multiple modules simultaneously, just like a desktop environment
  • Modern UI: Clean, intuitive, and responsive with dark/light theme support
  • Performance: Low latency for loading and saving data
  • Security: Two-factor authentication and granular permission management
  • i18n: Full support for Italian and English
  • Keyboard shortcuts: Speed up frequent operations like create, edit, save, and cancel

Tech Stack

The frontend was built entirely with modern tools:

Layer Technology
UI Framework React 19
Language TypeScript 5.9 (strict mode)
Build Tool Vite 7
Router react-router v7
Server State TanStack React Query 5
Client State Zustand 5
HTTP Client Axios 1
UI Library Material-UI (MUI) 7
Panels Dockview 5
Forms react-hook-form
Tables material-react-table (MRT)
i18n i18next + react-i18next
Notifications notistack
Dates dayjs

The backend (handled by a colleague) was built with Symfony (PHP), MySQL, and RESTful APIs with JWT cookie-based authentication.

Architecture: The Modular Panel System

The core of the application is a Dockview container that hosts all open panels — think of it as Visual Studio Code's tab system but for a business management app. Each panel corresponds to a specific business entity (contacts, batches, orders, etc.) and is built from three reusable generic components.

The Three Generics

  1. GenericPanel — The container component providing panel context and layout structure (list on top, form on bottom).
  2. GenericList — A wrapper around material-react-table offering selection, sorting, and filtering out of the box.
  3. GenericForm — The most complex component: it orchestrates the full CRUD lifecycle including button states, permissions, keyboard shortcuts, and confirmation dialogs.

This abstraction was a game-changer for productivity. Instead of writing CRUD logic from scratch for each of the 50+ panels, I defined these three generics once, and each specific panel only declares the variable parts: table columns, form fields, API endpoint, and translations.

src/
├── apps/
│   └── default/          # Entry point (main.tsx, provider hierarchy)
├── features/
│   ├── auth/             # Login, OTP, logout
│   ├── authz/            # Permissions (resource-action based)
│   ├── panels/           # Dockview panels (the heart of the app)
│   ├── password-reset/
│   ├── profile/
│   ├── routing/          # Router and guards
│   ├── search/           # Global search
│   ├── settings/
│   └── user/             # User and role management
└── shared/
    ├── api/              # Axios layer, useApi, query keys
    ├── ui/               # Reusable UI components
    ├── themes/           # MUI light/dark themes
    ├── helpers/          # Utilities (language detection)
    └── interfaces/       # Shared TypeScript interfaces
Enter fullscreen mode Exit fullscreen mode

Factory Pattern for API Hooks

One of the most impactful patterns I implemented was a Factory Method for generating CRUD API hooks. The createPanelApiFactory function automatically generates five TanStack Query hooks for any REST endpoint:

export const batchApi = createPanelApi<IBatch>({
  baseEndpoint: "/batch",
  queryKey: "BATCH"
});
Enter fullscreen mode Exit fullscreen mode

This single declaration produces:

  • useGetList()GET /batch
  • useGetDetail(id)GET /batch/:id
  • usePost()POST /batch
  • usePut()PUT /batch/:id
  • useDelete()DELETE /batch/:id

Each hook handles caching, automatic cache invalidation after mutations, and error handling. The factory completely abstracts HTTP complexity so the developer can focus on business logic.

Isolated State Management with Zustand

A tricky architectural challenge: users can open multiple panels of the same type simultaneously. Each instance must maintain its own independent state.

The solution combines React Context with a Zustand factory. Every panel gets wrapped in its own React context, inside which a dynamic Zustand store is instantiated:

Panel "Batches" #1 — context #1 → Zustand store #1 — { selectedId: 5, formEnabled: false }
Panel "Batches" #2 — context #2 → Zustand store #2 — { selectedId: 12, formEnabled: true }
Panel "Contacts" — context #3 → Zustand store #3 — { selectedId: 3, formEnabled: false }
Enter fullscreen mode Exit fullscreen mode

This guarantees complete state isolation: modifying a form on one panel never interferes with another.

Cross-Panel Communication

Here's my favorite feature. Imagine an operator is in the Create Order form and needs to select a contact that doesn't exist yet. They press Enter on the contact select field, and the system opens the Create Contact panel automatically. Once they save the new contact, the ID flows back to the original order form — without the user having to re-select anything.

This was implemented using React Query's cache as a communication bus. The creating panel writes the newly created entity ID to a special query key (LAST_CREATED), and the receiving panel subscribes to changes on that key via a useSubscribePanel hook. No direct coupling between panels — the architecture stays modular and decoupled.

Keyboard Shortcuts

To match the operators' muscle memory from the old system, I implemented keyboard shortcuts:

Key Action
F4 Enable editing on selected item
F9 Create new item
F10 Save current form
Esc Cancel operation / close floating panel

Shortcuts only fire on the focused panel to avoid conflicts when multiple panels are open.

Permission System — Three Layers of Security

The authorization system operates on three levels:

  1. Component level — Buttons and sections are conditionally rendered based on permissions.
  2. Hook level — Permissions are verified before executing any operation.
  3. Engine level — A centralized policy checker enforces rules globally.

Users belong to groups (Admin, Staff), and each group has specific permissions on business resources (contacts, production, orders, etc.). The menu and routes are filtered automatically — operators only see what they're allowed to use.

Results

After three months of development, the frontend reached:

  • 50+ functional panels across 10 business areas
  • 35,000+ lines of TypeScript code
  • 3 reusable generic components (GenericPanel, GenericList, GenericForm)
  • Full CRUD on every entity
  • Cross-panel communication via React Query cache
  • Keyboard shortcuts for all CRUD operations
  • Italian/English i18n with real-time switching
  • Light/dark theme persisted in user preferences
  • Granular permission system with three-level verification

Challenges Faced

No project is without its struggles. Here are the main ones:

Unclear requirements. The client provided the legacy software without a structured analysis. Many features were discovered and implemented day by day. I often had to infer expected behavior by observing the old system.

Backend coordination. APIs frequently returned data in different formats than agreed upon, or threw unexpected errors that required last-minute fixes.

Domain complexity. The leather processing industry has highly specific production workflows. Understanding batch management, tanning stages, product composition, and leather logistics was essential to building an interface that truly matches how operators work.

Technical challenges. Implementing isolated panel state, cross-panel data passing, concurrent cache synchronization, and multi-panel keyboard shortcuts — each problem pushed me to find creative solutions.

What's Next

The modular architecture makes it easy to extend. Future possibilities include:

  • Automated tests — Unit, integration, and E2E tests for robustness
  • Performance optimizations — Virtual scrolling, lazy loading, query optimization
  • Custom layouts — Let users save their own panel configurations
  • AI assistant — A chatbot that can perform CRUD operations via natural language

Final Thoughts

Working on this project was an incredible learning experience. Having full autonomy over the frontend architecture meant every decision — from library choices to design patterns — had a direct impact on the final product. The result is a production-ready application used by real operators every day, replacing a two-decade-old system with a modern, performant, and maintainable solution.


This article is based on my graduate thesis project for the Web Developer Full Stack program at ITS Digital Academy Mario Volpato.

Top comments (0)