Skip to main content

Overview

The Kanban component is the core order management interface that provides a visual, interactive kanban board for tracking orders through their lifecycle. It implements drag-and-drop functionality using @dnd-kit/core, allowing users to move orders between status columns and automatically update their state.

Key Features

Drag & Drop

Intuitive drag-and-drop interface for moving orders between statuses

Real-time Updates

Automatically updates order status when dropped in valid columns

Rider View

Special rendering for picked-up orders showing rider information

Order Details

Click any order card to open a detailed modal view

Architecture

State Management

The Kanban component integrates with the Orders context to manage order data:
const { ordersByStatus, updateOrderStatus } = useOrders();
  • ordersByStatus: Object mapping each order status to an array of orders
  • updateOrderStatus: Function to transition orders between statuses

Drag-and-Drop Flow

1

Drag Start

When a user starts dragging an order card, the onDragStart handler stores the active order in state for rendering in the drag overlay
2

Drag Over

The order card follows the cursor, rendered in a DragOverlay component
3

Drop Validation

On drop, the component validates:
  • The target column represents a valid status
  • The transition is allowed by business rules (canTransition)
  • The transition is permitted from the UI (canTransitionFromUi)
4

Status Update

If valid, updateOrderStatus is called to persist the change

Component Structure

export default function Kanban() {
  const { ordersByStatus, updateOrderStatus } = useOrders();
  
  const [activeOrder, setActiveOrder] = useState<OrderListDto | null>(null);
  const [selectedOrderId, setSelectedOrderId] = useState<string | null>(null);

  const sensors = useSensors(
    useSensor(PointerSensor, {
      activationConstraint: { distance: 6 },
    }),
  );

  // ... handlers and render logic
}

Key State Variables

activeOrder
OrderListDto | null
The order currently being dragged. Used to render the drag overlay.
selectedOrderId
string | null
ID of the order selected for detailed view. Opens the OrderDetailModal when set.

Computed Data

Riders List

The component dynamically computes a list of active riders from picked-up orders:
const riders: RiderDto[] = useMemo(() => {
  return Object.values(ordersByStatus)
    .flat()
    .filter((order) => order.status === "PICKED_UP" && order.courierName)
    .map(
      (order): RiderDto => ({
        id: order.id,
        name: order.courierName!,
        orderId: order.id,
        displayNumber: order.displayNumber,
        riderNumber: Number(order.id.slice(-3)) || 100,
        ...(order.partnerName && {
          partnerName: order.partnerName,
        }),
      }),
    );
}, [ordersByStatus]);
The riders list is memoized for performance. It filters all orders with PICKED_UP status and a courier name, transforming them into rider view models.

Orders Map

A Map is maintained for efficient order lookup during drag operations:
const ordersMap = useMemo(() => {
  const map = new Map<string, OrderListDto>();
  
  Object.values(ordersByStatus)
    .flat()
    .forEach((order) => {
      map.set(order.id, order);
    });

  return map;
}, [ordersByStatus]);

Column Rendering

The kanban board renders columns based on the ORDER_COLUMNS configuration:
{ORDER_COLUMNS.map((status) => {
  const columnOrders = ordersByStatus[status] ?? [];

  return (
    <Column
      key={status}
      status={status}
      title={ORDER_STATUS_LABELS[status]}
      count={columnOrders.length}
    >
      {status === "PICKED_UP"
        ? riders.map((rider) => (
            <DraggableRiderCard
              key={rider.id}
              rider={rider}
              onClick={setSelectedOrderId}
            />
          ))
        : columnOrders.map((order) => (
            <DraggableOrderCard
              key={order.id}
              order={order}
              onClick={setSelectedOrderId}
            />
          ))}
    </Column>
  );
})}
The PICKED_UP column receives special treatment, rendering DraggableRiderCard components instead of regular order cards.

Drag Validation

The component implements a two-layer validation system:
if (!canTransition(current.status, targetStatus)) return;
Validates that the transition is allowed by business domain rules (e.g., can’t go from RECEIVED directly to DELIVERED).

Event Handlers

onDragStart

const onDragStart = (event: DragStartEvent) => {
  const id = String(event.active.id);
  setActiveOrder(findOrder(id));
};
Captures the dragged order and stores it in state for overlay rendering.

onDragEnd

const onDragEnd = (event: DragEndEvent) => {
  const { active, over } = event;
  
  setActiveOrder(null);
  
  if (!over) return;
  
  const orderId = String(active.id);
  const targetStatus = String(over.id) as OrderStatus;
  
  const current = findOrder(orderId);
  if (!current) return;
  
  if (current.status === targetStatus) return;
  
  if (!canTransition(current.status, targetStatus)) return;
  
  if (!canTransitionFromUi(current.status, targetStatus)) return;
  
  updateOrderStatus(orderId, targetStatus);
};
Handles the drop event with comprehensive validation before updating the order status.

Drag Overlay

Provides visual feedback during dragging:
<DragOverlay>
  {activeOrder ? (
    activeOrder.status === "PICKED_UP" ? (
      <Riders rider={mapOrderToRiderViewModel(activeOrder)} />
    ) : (
      <OrderCard order={activeOrder} isOverlay />
    )
  ) : null}
</DragOverlay>
The overlay renders either a Riders component or an OrderCard depending on the order’s status, maintaining visual consistency.

Integration Points

Context Dependencies

  • OrdersContext: Provides order data and status update functionality
  • ThemeContext: (Implicit) Styling adapts to the current theme

Child Components

  • Column: Container for orders in each status
  • DraggableOrderCard: Draggable wrapper for OrderCard
  • DraggableRiderCard: Draggable wrapper for Riders
  • OrderDetailModal: Modal for viewing order details

Usage Example

import Kanban from "@/components/Kanban/Kanban";
import { OrdersProvider } from "@/contexts/Orders.context";

function OrdersPage() {
  return (
    <OrdersProvider>
      <Kanban />
    </OrdersProvider>
  );
}
The Kanban component must be wrapped in an OrdersProvider to function correctly. It will throw an error if the Orders context is not available.

Performance Optimizations

  1. Memoized Computations: Riders list and orders map are memoized to avoid unnecessary recalculations
  2. Pointer Sensor Constraint: 6px activation distance prevents accidental drags
  3. Efficient Lookups: Orders Map enables O(1) lookup during drag operations
  • OrderCard: Individual order card display
  • Riders: Rider card for picked-up orders
  • Column: Status column container (internal component)

Source Location

components/Kanban/Kanban.tsx:1

Build docs developers (and LLMs) love