# 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¶
- Code Reuse: Write once, use for 6 different assignment UIs
- Consistency: Same UX across all assignment scenarios
- Maintainability: Fix bugs in one place, all UIs benefit
- Type Safety: Full TypeScript support with generics
- Flexibility: Configuration-driven, not hardcoded
- Testability: Easier to unit test generic components
- 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.