A sophisticated casino shift management application built with SwiftData that enables floor managers to coordinate dealer schedules across multiple tables with real-time timeline visualization. The app features a comprehensive rota planning system with 20-minute time slot granularity, color-coded fatigue indicators, break management, and historical shift archiving. Designed for high-volume casino operations requiring precise dealer rotation tracking and workload distribution across 8-hour shifts.
- Shift Management: Three shift types (Graveyard 6am-2pm, Afternoon 2pm-10pm, Night 10pm-6am)
- 20-Minute Time Slots: Precise dealer assignment tracking with 24 slots per 8-hour shift
- Real-Time Playhead: Red vertical line showing current time within active shift
- Fatigue Indicators: Color-coded cells (green/yellow/red) warning of excessive consecutive table time
- Break Tracking: Special "BREAK" placeholder table for dealer rest periods
- Multi-Table Support: Assign dealers across multiple table locations (Roulette, Blackjack, Poker, Baccarat)
- Employee Management: Track dealers with ranks (Trainee, Junior, Senior) and roles
- Timeline View: Horizontal scrolling grid showing entire shift schedule at a glance
- On-Duty Roster: Dynamic dealer list management with add/remove functionality
- Shift History: Archive view for reviewing past schedules
- Auto-Centering: Jump to current time slot with animated scrolling
- SwiftData Persistence: Robust relational database with Assignment, Employee, Table, Shift, and Rank entities
- Language: Swift 5.9+
- Framework: SwiftUI
- Platform: iOS 17.0+
- Data Persistence: SwiftData with complex relationships
- Architecture: MVVM with @Query dynamic fetching
- State Management: @AppStorage, @State, @Environment
- UI Components: LazyVStack, ScrollViewReader, Menu, Picker
- One-to-many relationships (Shift → Assignments, Employee → Assignments, Table → Assignments)
- Cascade delete rules for maintaining data integrity
- Inverse relationship configuration
- @Relationship property wrapper
- Bidirectional navigation through relationships
- @Query with dynamic sort descriptors
- FetchDescriptor with predicates
- Direct context fetching for real-time data
- Filtering assignments by shift date and code
- Grouping query results by employee ID
- Horizontal/vertical ScrollView coordination
- LazyVStack for performance with large datasets
- ScrollViewReader for programmatic scrolling
- Overlay positioning for playhead indicator
- Dynamic cell background coloring based on state
- Calendar-based slot generation (20-minute intervals)
- Shift start time computation across day boundaries
- Night shift date anchor handling (crosses midnight)
- Real-time playhead position calculation
- Time-based filtering and comparisons
- Timer.publish for clock updates (60-second intervals)
- .onReceive for reactive time-based UI updates
- Live playhead rendering synchronized with system clock
- Auto-refresh on shift date/code changes
- @AppStorage for shift selection persistence
- TimeInterval storage for Date values
- User preference synchronization across app launches
CasinoRotaScheduler/
├── VicRotaManager/
│ ├── VicRotaManagerApp.swift # Main app entry point
│ ├── ContentView.swift # Tab view container
│ ├── ShiftSheetView.swift # Main timeline grid interface
│ ├── ManagerDashboardView.swift # Statistics and overview
│ ├── EmployeeCreationView.swift # Add new employees
│ ├── ShiftArchiveView.swift # Historical shift browser
│ ├── DealerShiftModels.swift # SwiftData models (Employee, Table, Shift, Assignment, Rank)
│ ├── DataSeeder.swift # Demo data generator
│ ├── AssignmentBlock.swift # Assignment cell component
│ └── TimeRulerView.swift # Timeline header component
Complex entity relationships for shift scheduling:
@Model
final class Assignment: Identifiable {
@Attribute(.unique) var id: UUID
@Relationship var employee: Employee
@Relationship var table: Table
@Relationship var shift: Shift
var position: Int // 1 = current, 2 = next
var startAt: Date
var endAt: Date
var confirmedAt: Date?
}
@Model
final class Employee: Identifiable {
@Attribute(.unique) var id: UUID
var firstName: String
var lastName: String
var role: Role
var rank: Rank?
var isActive: Bool
@Relationship(deleteRule: .cascade, inverse: \Assignment.employee)
var assignments: [Assignment] = []
}
@Model
final class Shift: Identifiable {
@Attribute(.unique) var id: UUID
var date: Date
var shiftCode: String // "DAY", "SWING", "NIGHT"
@Relationship(deleteRule: .cascade, inverse: \Assignment.shift)
var assignments: [Assignment] = []
}Color-coded warnings for dealer workload:
private var consecutiveTables: Int {
let assigns = (try? context.fetch(FetchDescriptor<Assignment>())) ?? []
var n = 0
var cursor = bounds.start
// Count backwards from current slot
while assigns.contains(where: {
$0.employee.id == dealer.id &&
$0.startAt == cursor &&
$0.table.id != breakTable.id // Skip breaks in count
}) {
n += 1
cursor = Calendar.current.date(byAdding: .minute, value: -20, to: cursor)!
}
return n
}
private var colorForConsecutive: Color {
switch consecutiveTables {
case danger...: return .red // 6+ consecutive slots (2 hours)
case warn...: return .yellow // 4-5 consecutive slots (80-100 min)
default: return .green // < 4 slots
}
}Live timeline indicator synchronized with system clock:
private var playheadX: CGFloat? {
let startHour = ["GRAVEYARD":6, "AFTERNOON":14, "NIGHT":22][shiftCode]!
let cal = Calendar.current
let shiftStart = cal.date(bySettingHour: startHour,
minute: 0, second: 0,
of: shiftAnchorDate())!
let shiftEnd = shiftStart.addingTimeInterval(Double(shiftHours * 60 * 60))
guard now >= shiftStart && now < shiftEnd else { return nil }
let minutes = now.timeIntervalSince(shiftStart) / 60
return nameColWidth + CGFloat(minutes) / CGFloat(slotMinutes) * cellWidth
}
.overlay(alignment: .topLeading) {
if showPlayhead, let x = playheadX {
Rectangle()
.fill(.red)
.frame(width: 2)
.frame(maxHeight: .infinity)
.offset(x: x)
}
}
.onReceive(clockTimer) { now = $0 } // Update every 60 secondsCalculating 20-minute intervals for shift duration:
private var timeSlots: [Date] {
let cal = Calendar.current
let startHour = ["GRAVEYARD":6, "AFTERNOON":14, "NIGHT":22][shiftCode]!
// Handle night shift crossing midnight
let anchor = (shiftCode == "NIGHT")
? cal.date(byAdding: .day, value: -1, to: shiftDate)!
: shiftDate
let shiftStart = cal.date(bySettingHour: startHour,
minute: 0, second: 0, of: anchor)!
let count = (shiftHours * 60) / slotMinutes // 24 slots
return (0..<count).map {
shiftStart.addingTimeInterval(Double($0 * slotMinutes * 60))
}
}- SwiftData Mastery: Complex multi-entity relationships with cascade delete rules
- Advanced Querying: Dynamic fetches with predicates and sort descriptors
- Timeline Visualization: Custom scrollable grid with overlay indicators
- Date/Time Programming: Calendar arithmetic and time zone handling
- Real-Time UI: Live updates synchronized with system clock
- State Persistence: AppStorage for user preferences across sessions
- Performance Optimization: LazyVStack for efficient rendering of large datasets
- UI/UX Design: Intuitive shift scheduling interface with visual workload indicators
- Data Modeling: Comprehensive entity design for casino operations
Casino floor managers create and adjust dealer rotations across all tables for the current shift with drag-and-drop simplicity.
Ensure compliance with labor regulations by tracking dealer breaks and preventing excessive consecutive table time.
Monitor live dealer assignments with playhead indicator, making on-the-fly changes as needed during active shifts.
Analyze past shift assignments to identify patterns, optimize rotations, and resolve disputes about work assignments.
Visual fatigue indicators help managers distribute workload fairly and prevent dealer burnout from long consecutive assignments.
- Drag & Drop: Touch-based drag-and-drop dealer assignment interface
- Automatic Scheduling: AI-powered shift planning based on dealer preferences and skills
- Conflict Detection: Real-time warnings for double-bookings or schedule conflicts
- Break Alerts: Automated reminders when dealers exceed maximum consecutive time
- Export Functionality: PDF/Excel export of shift schedules for printing
- Mobile App: Companion app for dealers to view their schedules
- Push Notifications: Alert dealers of shift changes or assignments
- Analytics Dashboard: Charts showing dealer utilization and table coverage
- Skill Matching: Assign dealers based on game type expertise and certifications
- Overtime Tracking: Calculate and display overtime hours for payroll
- Multi-Casino Support: Manage schedules across multiple casino locations
- Integration: Connect with payroll and HR systems for seamless operations
Author: Martynas Prascevicius Contact: mpcode@icloud.com Project Type: Business Application - Casino Operations Management Frameworks: SwiftUI, SwiftData, Combine