# Phase 2: UnifiedAssignmentModal Extraction

Status: βœ… Complete
Date: 2025-11-10
Effort: ~2 hours
Files Created: 6
Lines of Code: ~600


🎯 Objective

Extract generic assignment components from the refactored SquadManagement to create reusable UI patterns for all M:N assignment scenarios in TurnFix.


πŸ“Š Summary

Created a generic three-column assignment UI system that can be reused across: - βœ… Squads ← Participants (original use case) - πŸ”„ Groups ← Participants (next: Phase 4) - πŸ”„ Teams ← Participants - πŸ”„ Judges ← Disciplines - πŸ”„ Coaches ← Athletes - πŸ”„ EventParticipants ← Competitions


πŸ“ File Structure

client/src/components/assignment/
β”œβ”€β”€ index.ts                              # Public API exports
β”œβ”€β”€ UnifiedAssignmentModal.tsx            # Main orchestration component (140 lines)
β”œβ”€β”€ UnifiedAssignmentModal.types.ts       # TypeScript definitions (175 lines)
β”œβ”€β”€ MasterList.tsx                        # Column 1: Master items (105 lines)
β”œβ”€β”€ AvailableList.tsx                     # Column 2: Available items (130 lines)
└── DetailPane.tsx                        # Column 3: Detail view (50 lines)

Total: 6 files, ~600 lines


πŸ—οΈ Architecture

Three-Column Layout Pattern

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Column 1     β”‚ Column 2     β”‚ Column 3     β”‚
β”‚ Master List  β”‚ Available    β”‚ Detail Pane  β”‚
β”‚              β”‚ Items        β”‚              β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Squad A │◄─┼─│ John D. β”‚  β”‚ β”‚ Squad A  β”‚ β”‚
β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  β”‚ β”‚          β”‚ β”‚
β”‚ β”‚ Squad B β”‚  β”‚ β”‚ Mary S. β”‚  β”‚ β”‚ Members: β”‚ β”‚
β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  β”‚ β”‚ β€’ John   β”‚ β”‚
β”‚ β”‚ Squad C β”‚  β”‚ β”‚ Tom H.  β”‚  β”‚ β”‚ β€’ Sarah  β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    Select        Assign β†’      View/Unassign

Generic Type System

// Base interfaces for all assignment types
BaseMasterItem   β†’ Squad, Group, Team, Judge, Coach
BaseAvailableItem β†’ Participant, Athlete, Discipline
BaseAssignment   β†’ SquadParticipant, GroupMember, etc.

// Configuration pattern
AssignmentConfig<TMaster, TAvailable> {
  entityNames: { master, available, masterPlural, availablePlural }
  getMasterMetadata: (item) => { itemCount, subtitle, tags, isVirtual }
  getAvailableMetadata: (item) => { subtitle, tags: [{ label, color, isHighlighted }] }
  onAssign, onUnassign, onCreateMaster, onDeleteMaster, onExportPDF
  features: { allowCreate, allowDelete, allowExport, showFilters, ... }
}

πŸ”§ Component Details

1. UnifiedAssignmentModal.tsx (Main Component)

Purpose: Orchestrate the three-column layout with generic configuration

Key Features: - βœ… Generic type parameters <TMaster, TAvailable, TAssignment> - βœ… Configuration-driven UI (no hardcoded entity names) - βœ… Optional filtering support - βœ… Conditional feature flags (create, delete, export) - βœ… Loading state management

Usage Pattern:

<UnifiedAssignmentModal
  masterItems={squads}
  availableItems={participants}
  assignments={squadAssignments}
  config={{
    entityNames: { master: "Squad", available: "Participant", ... },
    getMasterMetadata: (squad) => ({ itemCount: squad.participantCount, ... }),
    onAssign: assignParticipantToSquad,
    onUnassign: removeParticipantFromSquad,
    features: { allowCreate: true, allowDelete: true, ... }
  }}
/>


2. MasterList.tsx (Column 1)

Purpose: Display master entities (Squads, Groups, Teams, etc.)

Features: - βœ… Selectable items with highlight state - βœ… Virtual entity badge (orange border + badge) - βœ… Item count display - βœ… Tag/badge support (competitions, roles, etc.) - βœ… Delete button (optional) - βœ… Metadata-driven rendering

Metadata Structure:

{
  itemCount: number;         // "5 Participants"
  subtitle?: string;         // Optional description
  tags?: Array<{             // Competition badges, etc.
    label: string;
    color?: string;
  }>;
  isVirtual?: boolean;       // Show virtual badge
}


3. AvailableList.tsx (Column 2)

Purpose: Display available items for assignment

Features: - βœ… Assign button (arrow icon) when master selected - βœ… Tag highlighting (for competition filtering) - βœ… Virtual assignment info box - βœ… Scrollable list (max-height: 600px) - βœ… Flexible naming (firstname + lastname OR displayName OR name) - βœ… Tag-based filtering support

Metadata Structure:

{
  subtitle?: string;              // "Club ABC β€’ male β€’ 15 years"
  tags?: Array<{
    label: string;
    color?: string;
    isHighlighted?: boolean;      // Blue highlight for filtered items
  }>;
}


4. DetailPane.tsx (Column 3)

Purpose: Show selected master item's details

Features: - βœ… Empty state (no selection) - βœ… Custom render function support - βœ… Generic entity name display

Custom Rendering:

renderContent: (squad) => (
  <>
    <Participants list={squad.participants} onRemove={handleRemove} />
    <Competitions list={squad.competitions} onClick={handleClick} />
  </>
)


🎨 UI Design Patterns

Visual Hierarchy

Color Coding: - πŸ”΅ Blue: Selected master, highlighted tags (primary action) - 🟠 Orange: Virtual entities, warnings - πŸ”΄ Red: Delete actions - βšͺ Gray: Default state, inactive items

Borders: - Thick blue border: Selected master item - Orange left border: Virtual entities - Blue double border + shadow: Highlighted available items (filtered)

Responsive Design

  • Desktop (lg+): Three-column grid
  • Mobile: Stacked single-column layout
  • Scrollable areas: max-height constraints with overflow-y-auto

πŸ”„ Reusability Pattern

Step 1: Define Your Types

// For Squad Management
interface Squad extends BaseMasterItem {
  participantCount: number;
  competitions: Array<{ id: number; name: string; number: string }>;
  participants: Participant[];
}

interface Participant extends BaseAvailableItem {
  firstname: string;
  lastname: string;
  club: string;
  gender: string;
  birthYear: number;
  competitions: Array<{ id: number; name: string; number: string }>;
}

interface SquadAssignment extends BaseAssignment {
  squadId: number;
  participantId: number;
}

Step 2: Create Configuration

const squadConfig: AssignmentConfig<Squad, Participant> = {
  entityNames: {
    master: "Squad",
    available: "Participant",
    masterPlural: "Squads",
    availablePlural: "Participants"
  },

  getMasterMetadata: (squad) => ({
    itemCount: squad.participantCount,
    subtitle: `${squad.participantCount} participants`,
    tags: squad.competitions.map(c => ({
      label: `${c.name} (Nr. ${c.number})`,
      color: 'bg-gray-100 text-gray-700'
    })),
    isVirtual: squad.isVirtual
  }),

  getAvailableMetadata: (participant) => ({
    subtitle: `${participant.club} β€’ ${participant.gender} β€’ ${age} years`,
    tags: participant.competitions.map(c => ({
      label: `${c.name} (Nr. ${c.number})`,
      color: 'bg-blue-100 text-blue-800',
      isHighlighted: c.id === selectedCompetitionId
    }))
  }),

  onAssign: assignParticipantToSquad,
  onUnassign: removeParticipantFromSquad,
  onCreateMaster: () => setShowCreateModal(true),
  onDeleteMaster: deleteSquad,

  features: {
    allowCreate: true,
    allowDelete: true,
    allowExport: true,
    showFilters: true,
    showSearch: true,
    supportsVirtual: true
  }
};

Step 3: Use the Component

<UnifiedAssignmentModal
  masterItems={squads}
  availableItems={participants}
  assignments={squadAssignments}
  config={squadConfig}
  isLoading={isLoading}
  eventId={eventId}
/>

βœ… Benefits

  1. Code Reuse: Write once, use for 6 different assignment UIs
  2. Consistency: Same UX across all assignment scenarios
  3. Maintainability: Fix bugs in one place, all UIs benefit
  4. Type Safety: Full TypeScript support with generics
  5. Flexibility: Configuration-driven, not hardcoded
  6. Testability: Easier to unit test generic components
  7. Documentation: Self-documenting through types

πŸ“Š Metrics

Before (Monolithic): - SquadManagement: 1070 lines - Groups: ~800 lines (estimate) - Teams: ~700 lines (estimate) - Judges: ~600 lines (estimate) - Coaches: ~500 lines (estimate) - EventParticipants: ~1900 lines - Total: ~5,570 lines

After (Unified Components): - Generic components: ~600 lines - Squad configuration: ~150 lines (estimate) - Group configuration: ~120 lines (estimate) - Team configuration: ~100 lines (estimate) - Judge configuration: ~80 lines (estimate) - Coach configuration: ~80 lines (estimate) - EventParticipant configuration: ~180 lines (estimate) - Total: ~1,310 lines

Reduction: ~76% less code (5,570 β†’ 1,310 lines)


πŸ§ͺ Testing Checklist

Phase 3 will validate these patterns by migrating SquadManagement to use UnifiedAssignmentModal:

  • Squad selection works
  • Participant assignment works
  • Participant unassignment works
  • Virtual squad support works
  • Competition filtering works
  • Create squad modal works
  • Delete squad works
  • PDF export works
  • Empty states display correctly
  • Mobile responsive layout works

πŸš€ Next Steps

Phase 3: Migrate SquadManagement to use UnifiedAssignmentModal - Replace old three-column components - Wire up configuration - Validate all functionality preserved - Document migration pattern

Phase 4: Groups proof of concept - Create Group types (extend base types) - Create group configuration - Test with real group assignment scenario - Document lessons learned

Phase 5: Migrate remaining 4 pages - Teams - Judges - Coaches - EventParticipants


πŸ“ Notes

Virtual Entity Support

The system natively supports virtual entities (items that can be assigned multiple times): - Visual indicator: Orange left border + badge - Storage hint: Shows where virtual data is stored (e.g., int_riegen_virtual) - Info box: Explains virtual assignment behavior

Tag Highlighting

Tags can be highlighted to show filtering context: - isHighlighted: true β†’ Blue background with ring - Used for competition filtering in SquadManagement - Reusable for any filtering scenario

Flexible Naming

Components support multiple naming conventions: - displayName (highest priority) - firstname + lastname (people) - name (entities) - id (fallback)

This allows reuse across different entity types without modification.


Phase 2 Complete! βœ…
Ready for Phase 3: Migration validation.