PetroStock

PetroStock
4 min read

The Project

PetroStock Admin Dashboard is a B2B SaaS administration platform designed to centralize and simplify the operational and financial management of service stations. The initial problem was the dispersion of sales data, difficulty tracking invoice payments, and lack of visibility on station performance. Main users are system administrators, network managers, and accounting teams who need effective tools to drive operations. The business objective is to optimize tax collection, reduce payment delays, and provide real-time analytics for strategic decision-making.

Technical Stack & Tools

  • Frontend: Next.js 16 with React 19 and TypeScript for a robust and typed architecture. Using App Router for optimized routing and Server Components when possible to improve performance.
  • UI Components: Radix UI and Shadcn UI for accessible and customizable components. Ant Design for complex tables and data grids requiring advanced features.
  • State Management: TanStack React Query for server cache management and API data synchronization. Zustand for global client state (authentication, themes).
  • Forms & Validation: React Hook Form combined with Zod for type-safe and performant form validation.
  • Charts & Visualization: ApexCharts and Recharts for analytics charts (sales evolution, invoice distribution, station status).
  • Styling: Tailwind CSS 4 with a custom design system architecture using clsx and tailwind-merge for class composition.
  • Internationalization: next-intl to support French and English with centralized translation management.
  • Package Manager: pnpm for efficient and fast dependency management.

Key Features

  • Dashboard Analytics: Real-time visualization of key metrics (fuel volume sold, amounts generated, taxes collected) with interactive charts filterable by period, station, and zone.
  • Invoice Management: Complete billing system with status tracking (paid, unpaid, pending), deadline management, overdue alerts, and data export.
  • Financial Reconciliation: Automated process for reconciling recorded sales with received payments with detailed operation tracking.
  • Resource Management: Administration of sellers/merchants, service stations, SIM cards, and configuration of taxes and fees.

Technical Challenges & Solutions

Challenge 1: Generic API query architecture with automatic invalidation, centralized error management, and intelligent cache

Situation: The application makes dozens of different API calls (CRUD on merchants, stations, invoices, reconciliations, etc.) with common needs: intelligent cache invalidation after mutations, toast notifications for success/errors, and uniform error management. The code risked becoming repetitive and inconsistent without a solid abstraction.

Task: Create a generic abstraction layer over TanStack Query that standardizes query, mutation, and infinite query patterns while remaining flexible for specific use cases.

Action: I developed three generic hooks that encapsulate all common logic. Mutations automatically invalidate active queries sharing the same key prefix, ensuring data remains synchronized after each modification. I integrated Sonner for toast notifications with default but overridable messages. Hooks are fully typed with TypeScript generics to guarantee type-safety throughout the call chain. Each service (auth, dashboard, invoices, etc.) exposes specific functions using these generic hooks.

Result: The code is now DRY (Don’t Repeat Yourself) with a 60% reduction in API state management code. Automatic cache invalidation eliminates stale data bugs. Errors are handled consistently with user-friendly messages. The architecture allows adding new endpoints in a few lines while benefiting from all advanced features (caching, retry, optimistic updates) of TanStack Query.

Challenge 2: Real-time SSE (Server-Sent Events) architecture with distributed state and multi-component management

Situation: The application needed a real-time system to track service station activity (ongoing sales, connections/disconnections, fuel volumes) with multiple components needing to receive and display this data simultaneously. The technical challenge was maintaining a single active SSE connection, efficiently distributing events to interested components, and managing different event types (real_time, connection, disconnection, selling) without creating memory leaks or connection duplications.

Task: Implement a robust SSE architecture with a centralized provider that manages a single connection, distributes events to components via a listener system, and updates state in real-time efficiently.

Action: I created a layered architecture: a service manages the EventSource connection with token authentication and error handling, an abstraction service to subscribe to events, and a provider implementing a pub/sub pattern with a Set of listeners. The provider maintains global state (connectionStats, lastSellingEvents) and broadcasts each event to all registered hooks. Each component can selectively subscribe to event types of interest via a dedicated hook. The architecture uses refs to avoid function recreations and guarantees automatic listener cleanup on component unmount.

Result: A single active SSE connection for the entire application, reducing server load and network consumption. Components receive real-time updates without perceptible latency. The listener system avoids memory leaks and ensures disabled components no longer receive events. The architecture is extensible: new event types can be added easily without modifying existing components.

Share article


Comments
Background