Skip to main content

Overview

The Kanban board provides a visual, column-based interface for managing work orders and requests. It enables intuitive drag-and-drop operations to move tickets through their lifecycle stages.
Both Work Requests and Work Orders use Kanban-style boards, but with different column configurations and behaviors.

Board Types

Work Orders Board

Manages accepted work orders through three main status columns:

Pendiente

Orders waiting to be started. Newly accepted work requests appear here.

En Ejecución

Orders currently being worked on by assigned technicians.

Finalizadas

Completed orders ready for review or archiving.
Optional Column:
  • Archivadas - Archived orders (toggled on/off)

Work Requests Board

Displays unaccepted maintenance requests in a single-column layout:
  • All work requests shown together
  • No drag-and-drop (requests must be formally accepted)
  • Focus on review and assignment workflow

Drag-and-Drop Mechanics

How It Works

1

Initiate drag

Click and hold on any ticket card. The card will lift and follow your cursor.
2

Move to target

Drag the card over the target column. The column highlights to show it’s ready to receive.
3

Drop to commit

Release the mouse button. The ticket moves to the new column and status updates.
4

Confirmation

The change saves to the database immediately. All connected users see the update.

Visual Feedback

During Drag:
  • Source card becomes semi-transparent
  • Target column highlights
  • Drop zones become visible
  • Cursor changes to indicate drag state
After Drop:
  • Smooth animation to final position
  • Status badge updates
  • Timestamp refreshes
  • Toast notification confirms save

Drag Constraints

Not all tickets can be dragged:
Tickets cannot be moved if:
  • Not yet accepted (is_accepted = false)
  • Already archived (is_archived = true)
  • User lacks edit permissions
  • Ticket locked by another process

Ordering Modes

The Work Orders board supports three ordering strategies:

Manual Ordering

Behavior:
  • Drag tickets vertically within columns to reorder
  • Custom order persists across sessions
  • Stored in browser localStorage
  • Independent order per status column
Storage Key: work_orders_manual_order_v1 Data Structure:
type ManualOrderMap = {
  Pendiente: number[]; // Array of ticket IDs
  'En Ejecución': number[];
  Finalizadas: number[];
};
Implementation: See ~/workspace/source/src/components/dashboard/workOrder/WorkOrdersBoard.tsx:34

Date Ascending

Behavior:
  • Sorts by incident_date (oldest first)
  • Tickets without dates appear last
  • Tie-breaker: ticket ID descending
Use Case: Prioritize older issues that need attention

Date Descending

Behavior:
  • Sorts by incident_date (newest first)
  • Tickets without dates appear last
  • Tie-breaker: ticket ID descending
Use Case: Focus on recent issues

Column Components

Column Header

Displays:
  • Column title (status name)
  • Ticket count in column
  • Optional loading indicator
  • Collapse/expand control (future)

Column Body

Contains:
  • Scrollable list of ticket cards
  • Drop zone for drag operations
  • Empty state message
  • Pagination controls

Ticket Cards

Information Displayed:
  • Ticket ID (e.g., #1234)
  • Title (truncated if long)
  • Description preview
  • Priority badge (color-coded)
  • Assignee name
  • Location name
  • Incident date
  • Thumbnail of first attached image
  • Special incident indicator (if applicable)
Card Styling:
/* Priority Colors */
.priority-alta    { border-color: #ef4444; } /* Red */
.priority-media   { border-color: #f59e0b; } /* Orange */
.priority-baja    { border-color: #10b981; } /* Green */

/* Status Colors */
.status-pendiente    { background: #fef3c7; }
.status-ejecucion    { background: #dbeafe; }
.status-finalizadas  { background: #d1fae5; }

Real-time Synchronization

The board stays synchronized across all users:

Update Triggers

  1. Ticket Created - New work requests appear instantly
  2. Status Changed - Cards move between columns
  3. Fields Updated - Card content refreshes
  4. Assignments Changed - Assignee name updates
  5. Comments Added - Notification badge appears
  6. Archived/Unarchived - Cards appear/disappear

Sync Implementation

import { onDataInvalidated } from '@/lib/dataInvalidation';
import { invalidateData } from '@/lib/dataInvalidation';

// Subscribe to changes
useEffect(() => {
  return onDataInvalidated('tickets', () => {
    reloadBoard();
  });
}, []);

// Trigger sync after changes
await moveWorkOrderStatus(ticketId, newStatus);
invalidateData('tickets'); // Notifies all subscribers
See ~/workspace/source/src/lib/dataInvalidation.ts for event system details.

Performance Optimization

Pagination Strategy

Work Orders Board:
  • Loads 200 tickets per page
  • Maximum 8 pages (1600 tickets total)
  • Server-side pagination via Supabase
  • Uses .range(from, to) for efficient loading
Implementation:
const BOARD_PAGE_SIZE = 200;
const MAX_BOARD_PAGES = 8;

const from = page * BOARD_PAGE_SIZE;
const to = from + BOARD_PAGE_SIZE - 1;

const { data, count } = await getTicketsByWorkOrdersFiltersPaginated(
  filters,
  page,
  BOARD_PAGE_SIZE
);

Virtual Scrolling

Not yet implemented, but recommended for columns with 100+ tickets:
  • Render only visible cards
  • Recycle DOM elements
  • Smooth scroll performance
  • Libraries: react-window, react-virtual

Reduced Motion Support

Respects user’s motion preferences:
import { useReducedMotion } from 'framer-motion';

const prefersReducedMotion = useReducedMotion();

<motion.div
  initial={prefersReducedMotion ? false : { opacity: 0, y: 10 }}
  animate={prefersReducedMotion ? undefined : { opacity: 1, y: 0 }}
  transition={prefersReducedMotion ? { duration: 0 } : { duration: 0.3 }}
>

Accessibility

Keyboard Navigation

Board supports keyboard-only operation:
  • Tab - Navigate between cards and columns
  • Enter - Open selected card
  • Space - Start drag operation (with keyboard)
  • Arrow Keys - Move card between columns (when dragging)
  • Escape - Cancel drag operation

Screen Reader Support

  • Column headers announce count: “Pendiente, 5 tickets”
  • Card content read in logical order
  • Status changes announced: “Ticket moved to En Ejecución”
  • Drop zones labeled: “Drop here to move to Finalizadas”

ARIA Attributes

<!-- Column -->
<div role="region" aria-label="Pendiente column">
  
  <!-- Ticket Card -->
  <article 
    role="button" 
    tabindex="0"
    aria-label="Ticket 1234: Fix broken door"
    draggable="true"
  >
    ...
  </article>
  
</div>

Mobile Responsiveness

Layout Adaptations

Desktop (≥1024px):
  • Horizontal columns side-by-side
  • Drag horizontally between columns
  • All columns visible simultaneously
Tablet (768px - 1023px):
  • Horizontal scroll for columns
  • Narrow columns with wrapped content
  • Sticky column headers
Mobile (<768px):
  • Vertical stacking of columns
  • Accordion-style expansion
  • Tap to expand/collapse columns
  • Simplified card layout

Touch Gestures

Long Press: Initiate drag on touch devices Swipe: Scroll within column Tap: Open ticket detail Pinch: Not used (reserved for zoom)

Error Handling

Common Errors

Error: “No se pudo mover la orden (no existe, está archivada o no tienes permiso).”Causes:
  • User lacks tickets:manage permission
  • Ticket is archived
  • RLS policy blocks access
Solution:
  • Check user permissions
  • Verify ticket is not archived
  • Review RLS policies

Error Recovery

Optimistic Updates:
  • UI updates immediately
  • Reverts on server error
  • Toast notification shows error
  • User can retry
Example:
try {
  // Optimistic update
  setBoardTickets(updatedTickets);
  
  // Server update
  await moveWorkOrderStatus(ticketId, newStatus);
} catch (error) {
  // Revert on error
  setBoardTickets(previousTickets);
  showToastError(error.message);
}

Customization

Column Configuration

Define custom columns:
const STATUSES: Ticket['status'][] = [
  'Pendiente',
  'En Ejecución',
  'Finalizadas',
];

// Add custom column
type ColumnStatus = Ticket['status'] | 'Archivadas';

Card Templates

Customize card appearance:
function TicketCard({ ticket }: { ticket: WorkOrder }) {
  return (
    <div className="ticket-card">
      <h3>{ticket.title}</h3>
      <PriorityBadge priority={ticket.priority} />
      <p>{ticket.description}</p>
      {/* Add custom fields */}
    </div>
  );
}

Styling

Override default styles:
/* Custom column styling */
.wo-column {
  background: var(--custom-bg);
  border: 2px solid var(--custom-border);
}

/* Custom card styling */
.ticket-card {
  box-shadow: var(--custom-shadow);
  border-radius: var(--custom-radius);
}

Best Practices

Board Management

  1. Keep Columns Balanced - Distribute work evenly
  2. Limit WIP - Don’t overload “En Ejecución”
  3. Clear Finalizadas - Archive completed work regularly
  4. Use Manual Order - Prioritize important tickets at top
  5. Monitor Flow - Track tickets moving through stages

Performance

  1. Filter Aggressively - Reduce loaded tickets
  2. Archive Old Tickets - Keep active board small
  3. Limit Date Range - Use specific date filters
  4. Enable Virtual Scrolling - For large columns (future)
  5. Clear Local Storage - If manual order gets too large

User Experience

  1. Provide Feedback - Show loading states
  2. Confirm Actions - Toast for successful moves
  3. Handle Errors - Clear error messages
  4. Support Keyboard - Don’t rely only on mouse
  5. Test Touch - Verify mobile drag works

Troubleshooting

Cards not draggable

  • Verify browser supports drag API
  • Check ticket is accepted
  • Ensure ticket is not archived
  • Confirm user has permissions

Manual order not persisting

  • Check localStorage enabled
  • Verify storage quota
  • Clear old order: localStorage.removeItem('work_orders_manual_order_v1')
  • Check for JSON serialization errors

Columns not loading

  • Check network tab for API errors
  • Verify Supabase connection
  • Check RLS policies
  • Review filter values

Real-time updates not working

  • Verify Supabase Realtime enabled
  • Check subscription channel
  • Confirm data invalidation events fire
  • Review browser console for errors