Skip to main content

Overview

This example demonstrates how to build a logistics operations console for managing shipments, tracking status, and coordinating deliveries. It showcases advanced table interactions, sidebar navigation, and action-driven workflows.
Time to integrate: Less than 1 dayCreate a professional operations console with shipment tracking, status management, and detailed views.

Key Features

  • Left sidebar navigation for operation modules
  • Shipment table with filtering and sorting
  • Status indicators with color coding
  • Priority flags for urgent shipments
  • Sidebar detail panel for selected shipments
  • Action dialogs for status updates
  • Toast notifications for operation results
  • Real-time ETA tracking

Components Used

MagarySidebar

Module navigation and context

MagaryTable

Shipment data display

MagarySelect

Status and filter controls

MagaryOverlayPanel

Quick action menus

MagaryDialog

Action confirmations

MagaryTag

Status and priority badges

MagaryToast

Operation feedback

MagaryButton

Action triggers

Project Structure

Organize your logistics module with this structure:
src/app/logistics/
  logistics.page.ts
  logistics.page.html
  logistics.page.scss
  logistics.models.ts
  logistics.facade.ts

Data Models

Define your shipment and action models:
logistics.models.ts
export interface Shipment {
  id: string;
  trackingNumber: string;
  origin: string;
  destination: string;
  status: 'PENDING' | 'IN_TRANSIT' | 'DELIVERED' | 'BLOCKED';
  eta: string;
  priority: 'LOW' | 'MEDIUM' | 'HIGH';
  carrier: string;
  weight: number;
  customer: string;
  notes?: string;
}

export interface ShipmentAction {
  type: 'DISPATCH' | 'HOLD' | 'RELEASE' | 'MARK_DELIVERED';
  shipmentId: string;
  reason?: string;
  timestamp: string;
}

export interface OperationModule {
  id: string;
  label: string;
  icon: string;
  route: string;
}

Component Implementation

import { ChangeDetectionStrategy, Component, signal, computed } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import {
  MagarySidebar,
  MagaryTable,
  MagarySelect,
  MagaryOverlayPanel,
  MagaryDialog,
  MagaryTag,
  MagaryToast,
  MagaryButton,
  MessageService,
} from 'ng-magary';
import { Shipment, ShipmentAction, OperationModule } from './logistics.models';

@Component({
  selector: 'app-logistics-page',
  standalone: true,
  imports: [
    CommonModule,
    FormsModule,
    MagarySidebar,
    MagaryTable,
    MagarySelect,
    MagaryOverlayPanel,
    MagaryDialog,
    MagaryTag,
    MagaryToast,
    MagaryButton,
  ],
  providers: [MessageService],
  templateUrl: './logistics.page.html',
  styleUrls: ['./logistics.page.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LogisticsPage {
  constructor(private messageService: MessageService) {}

  // Sidebar state
  readonly sidebarVisible = signal(true);
  readonly selectedModule = signal('shipments');

  // Shipment data
  readonly allShipments = signal<Shipment[]>([
    {
      id: 'SH-001',
      trackingNumber: 'TRK-2026-001234',
      origin: 'New York, NY',
      destination: 'Los Angeles, CA',
      status: 'IN_TRANSIT',
      eta: '2026-03-06T14:00:00',
      priority: 'HIGH',
      carrier: 'FastShip Express',
      weight: 125.5,
      customer: 'Acme Corporation',
      notes: 'Handle with care - fragile items',
    },
    {
      id: 'SH-002',
      trackingNumber: 'TRK-2026-001235',
      origin: 'Chicago, IL',
      destination: 'Miami, FL',
      status: 'PENDING',
      eta: '2026-03-08T10:00:00',
      priority: 'MEDIUM',
      carrier: 'Global Logistics',
      weight: 85.2,
      customer: 'TechStart Inc',
    },
    {
      id: 'SH-003',
      trackingNumber: 'TRK-2026-001236',
      origin: 'Seattle, WA',
      destination: 'Boston, MA',
      status: 'BLOCKED',
      eta: '2026-03-05T16:00:00',
      priority: 'HIGH',
      carrier: 'FastShip Express',
      weight: 210.8,
      customer: 'MegaMart',
      notes: 'Weather delay - awaiting clearance',
    },
    {
      id: 'SH-004',
      trackingNumber: 'TRK-2026-001237',
      origin: 'Houston, TX',
      destination: 'Denver, CO',
      status: 'DELIVERED',
      eta: '2026-03-03T12:00:00',
      priority: 'LOW',
      carrier: 'Budget Freight',
      weight: 45.0,
      customer: 'Small Business LLC',
    },
    {
      id: 'SH-005',
      trackingNumber: 'TRK-2026-001238',
      origin: 'Phoenix, AZ',
      destination: 'Portland, OR',
      status: 'IN_TRANSIT',
      eta: '2026-03-07T09:00:00',
      priority: 'MEDIUM',
      carrier: 'Global Logistics',
      weight: 152.3,
      customer: 'Northwest Supplies',
    },
  ]);

  // Filters
  readonly statusFilter = signal<Shipment['status'] | null>(null);
  readonly priorityFilter = signal<Shipment['priority'] | null>(null);

  // Filtered shipments
  readonly filteredShipments = computed(() => {
    let shipments = this.allShipments();

    const status = this.statusFilter();
    if (status) {
      shipments = shipments.filter(s => s.status === status);
    }

    const priority = this.priorityFilter();
    if (priority) {
      shipments = shipments.filter(s => s.priority === priority);
    }

    return shipments;
  });

  // Selected shipment
  readonly selectedShipmentId = signal<string | null>(null);
  readonly selectedShipment = computed(() => {
    const id = this.selectedShipmentId();
    return this.allShipments().find(s => s.id === id) || null;
  });

  // Dialog state
  readonly actionDialogVisible = signal(false);
  readonly pendingAction = signal<ShipmentAction | null>(null);

  // Navigation modules
  readonly modules: OperationModule[] = [
    { id: 'shipments', label: 'Shipments', icon: 'pi pi-box', route: '/logistics/shipments' },
    { id: 'tracking', label: 'Live Tracking', icon: 'pi pi-map-marker', route: '/logistics/tracking' },
    { id: 'fleet', label: 'Fleet Management', icon: 'pi pi-car', route: '/logistics/fleet' },
    { id: 'analytics', label: 'Analytics', icon: 'pi pi-chart-line', route: '/logistics/analytics' },
    { id: 'settings', label: 'Settings', icon: 'pi pi-cog', route: '/logistics/settings' },
  ];

  // Filter options
  readonly statusOptions = [
    { label: 'All Statuses', value: null },
    { label: 'Pending', value: 'PENDING' },
    { label: 'In Transit', value: 'IN_TRANSIT' },
    { label: 'Delivered', value: 'DELIVERED' },
    { label: 'Blocked', value: 'BLOCKED' },
  ];

  readonly priorityOptions = [
    { label: 'All Priorities', value: null },
    { label: 'Low', value: 'LOW' },
    { label: 'Medium', value: 'MEDIUM' },
    { label: 'High', value: 'HIGH' },
  ];

  selectShipment(shipment: Shipment): void {
    this.selectedShipmentId.set(shipment.id);
  }

  initiateAction(type: ShipmentAction['type'], shipment: Shipment): void {
    this.pendingAction.set({
      type,
      shipmentId: shipment.id,
      timestamp: new Date().toISOString(),
    });
    this.actionDialogVisible.set(true);
  }

  confirmAction(): void {
    const action = this.pendingAction();
    if (!action) return;

    // Simulate API call
    const shipments = this.allShipments();
    const shipment = shipments.find(s => s.id === action.shipmentId);
    
    if (shipment) {
      // Update shipment status based on action
      switch (action.type) {
        case 'DISPATCH':
          shipment.status = 'IN_TRANSIT';
          break;
        case 'HOLD':
          shipment.status = 'BLOCKED';
          break;
        case 'RELEASE':
          shipment.status = 'IN_TRANSIT';
          break;
        case 'MARK_DELIVERED':
          shipment.status = 'DELIVERED';
          break;
      }

      this.allShipments.set([...shipments]);

      this.messageService.add({
        severity: 'success',
        summary: 'Action Completed',
        detail: `${action.type.replace('_', ' ')} action completed for ${shipment.trackingNumber}`,
      });
    }

    this.actionDialogVisible.set(false);
    this.pendingAction.set(null);
  }

  getStatusSeverity(status: Shipment['status']): string {
    switch (status) {
      case 'PENDING': return 'info';
      case 'IN_TRANSIT': return 'warning';
      case 'DELIVERED': return 'success';
      case 'BLOCKED': return 'danger';
    }
  }

  getStatusLabel(status: Shipment['status']): string {
    return status.replace('_', ' ');
  }

  getPrioritySeverity(priority: Shipment['priority']): string {
    switch (priority) {
      case 'LOW': return 'secondary';
      case 'MEDIUM': return 'info';
      case 'HIGH': return 'danger';
    }
  }

  formatETA(eta: string): string {
    const date = new Date(eta);
    return date.toLocaleString('en-US', {
      month: 'short',
      day: 'numeric',
      hour: '2-digit',
      minute: '2-digit',
    });
  }

  getAvailableActions(shipment: Shipment): ShipmentAction['type'][] {
    switch (shipment.status) {
      case 'PENDING': return ['DISPATCH', 'HOLD'];
      case 'IN_TRANSIT': return ['MARK_DELIVERED', 'HOLD'];
      case 'BLOCKED': return ['RELEASE'];
      case 'DELIVERED': return [];
      default: return [];
    }
  }
}

Layout Diagram

┌──────────┬──────────────────────────────────────────────┐
│          │ ☰ Shipment Management    [12] [8] [2]       │
│Logistics │                                              │
│ Console  ├──────────────────────────────────────────────┤
│          │ [Status ▼] [Priority ▼]                     │
│          ├──────────────────────────────────────────────┤
│📦Shipments│ Track# │Origin│Dest │Status│Pri│ETA│Actions │
│📍Tracking│ ─────────────────────────────────────────── │
│🚗Fleet   │TRK-001│NYC  │LA   │TRANS│H  │3/6│[▶][⏸]  │
│📊Analytics│TRK-002│CHI  │MIA  │PEND │M  │3/8│[▶][⏸]  │
│⚙Settings │TRK-003│SEA  │BOS  │BLOCK│H  │3/5│[▶]     │
│          │TRK-004│HOU  │DEN  │DELIV│L  │3/3│        │
│          │                                              │
└──────────┴──────────────────────────────────────────────┘

Workflow Patterns

1

View Shipments

Users see all active shipments in the main table with status and priority indicators
2

Filter and Search

Users can filter by status or priority to focus on specific shipments
3

Select Shipment

Clicking a row shows detailed information in the sidebar panel
4

Initiate Action

Action buttons appear based on current shipment status (dispatch, hold, release, etc.)
5

Confirm Action

A dialog prompts for confirmation before applying the action
6

Receive Feedback

Toast notification confirms the action and table updates automatically

Accessibility Checklist

  • Sidebar menu items are keyboard accessible
  • Table rows can be selected with arrow keys
  • Action buttons receive focus in logical order
  • Escape key closes dialogs and detail panel
  • Table headers properly associated with cells
  • Status tags have semantic roles
  • Action buttons have descriptive tooltips
  • Dialog announces purpose on open
  • Shipment status changes are announced
  • Table updates announce row count
  • Action confirmations read full context
  • Navigation landmarks identify regions
  • Sidebar becomes modal overlay on small screens
  • Table columns stack or scroll horizontally
  • Detail panel slides over full width
  • Touch targets meet minimum size requirements

Best Practices

Action Safety: Always confirm destructive or status-changing actions with a dialog to prevent accidental operations.
Real-time Updates: In production, implement WebSocket connections or polling to keep shipment data current across all users.
Type Safety: Use strict TypeScript enums for status and priority to ensure only valid values are used throughout the application.
Audit Trail: Log all shipment actions with timestamps and user information for compliance and debugging.

Next Steps

Map Integration

Add real-time tracking maps with delivery routes

Notifications

Implement push notifications for critical status changes

Bulk Actions

Enable multi-select for batch operations

Reports

Generate PDF manifests and delivery reports

Build docs developers (and LLMs) love