feat(wizard): add mode-specific trip wizard inputs
- Add GamePickerStep with sheet-based Sport → Team → Game selection - Add TeamPickerStep with sheet-based Sport → Team selection - Add LocationsStep for start/end location selection with round trip toggle - Update TripWizardViewModel with mode-specific fields and validation - Update TripWizardView with conditional step rendering per mode - Update ReviewStep with mode-aware validation display - Fix gameFirst mode to derive date range from selected games Each planning mode now shows only relevant steps: - By Dates: Dates → Sports → Regions → Route → Repeat → Must Stops - By Games: Game Picker → Route → Repeat → Must Stops - By Route: Locations → Dates → Sports → Route → Repeat → Must Stops - Follow Team: Team Picker → Dates → Route → Repeat → Must Stops Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,22 @@ final class TripWizardViewModel {
|
||||
|
||||
var mustStopLocations: [LocationInput] = []
|
||||
|
||||
// MARK: - Mode-Specific: gameFirst (cascading selection)
|
||||
|
||||
var gamePickerSports: Set<Sport> = []
|
||||
var gamePickerTeamIds: Set<String> = []
|
||||
var selectedGameIds: Set<String> = []
|
||||
|
||||
// MARK: - Mode-Specific: followTeam
|
||||
|
||||
var selectedTeamId: String? = nil
|
||||
var teamPickerSport: Sport? = nil
|
||||
|
||||
// MARK: - Mode-Specific: locations
|
||||
|
||||
var startLocation: LocationInput? = nil
|
||||
var endLocation: LocationInput? = nil
|
||||
|
||||
// MARK: - Planning State
|
||||
|
||||
var isPlanning: Bool = false
|
||||
@@ -65,26 +81,65 @@ final class TripWizardViewModel {
|
||||
planningMode != nil
|
||||
}
|
||||
|
||||
/// Mode-specific step visibility
|
||||
var showDatesStep: Bool {
|
||||
planningMode == .dateRange || planningMode == .followTeam || planningMode == .locations
|
||||
}
|
||||
|
||||
var showSportsStep: Bool {
|
||||
planningMode == .dateRange || planningMode == .locations
|
||||
}
|
||||
|
||||
var showRegionsStep: Bool {
|
||||
planningMode == .dateRange
|
||||
}
|
||||
|
||||
var showGamePickerStep: Bool {
|
||||
planningMode == .gameFirst
|
||||
}
|
||||
|
||||
var showTeamPickerStep: Bool {
|
||||
planningMode == .followTeam
|
||||
}
|
||||
|
||||
var showLocationsStep: Bool {
|
||||
planningMode == .locations
|
||||
}
|
||||
|
||||
// MARK: - Validation
|
||||
|
||||
/// All required fields must be set before planning
|
||||
var canPlanTrip: Bool {
|
||||
planningMode != nil &&
|
||||
hasSetDates &&
|
||||
!selectedSports.isEmpty &&
|
||||
!selectedRegions.isEmpty &&
|
||||
hasSetRoutePreference &&
|
||||
hasSetRepeatCities
|
||||
guard let mode = planningMode else { return false }
|
||||
|
||||
// Common requirements for all modes
|
||||
guard hasSetRoutePreference && hasSetRepeatCities else { return false }
|
||||
|
||||
switch mode {
|
||||
case .dateRange:
|
||||
return hasSetDates && !selectedSports.isEmpty && !selectedRegions.isEmpty
|
||||
case .gameFirst:
|
||||
return !selectedGameIds.isEmpty
|
||||
case .locations:
|
||||
return startLocation != nil && endLocation != nil && hasSetDates && !selectedSports.isEmpty
|
||||
case .followTeam:
|
||||
return selectedTeamId != nil && hasSetDates
|
||||
}
|
||||
}
|
||||
|
||||
/// Field validation for the review step - shows which fields are missing
|
||||
var fieldValidation: FieldValidation {
|
||||
FieldValidation(
|
||||
planningMode: planningMode,
|
||||
sports: selectedSports.isEmpty ? .missing : .valid,
|
||||
dates: hasSetDates ? .valid : .missing,
|
||||
regions: selectedRegions.isEmpty ? .missing : .valid,
|
||||
routePreference: hasSetRoutePreference ? .valid : .missing,
|
||||
repeatCities: hasSetRepeatCities ? .valid : .missing
|
||||
repeatCities: hasSetRepeatCities ? .valid : .missing,
|
||||
selectedGames: selectedGameIds.isEmpty ? .missing : .valid,
|
||||
selectedTeam: selectedTeamId == nil ? .missing : .valid,
|
||||
startLocation: startLocation == nil ? .missing : .valid,
|
||||
endLocation: endLocation == nil ? .missing : .valid
|
||||
)
|
||||
}
|
||||
|
||||
@@ -123,12 +178,26 @@ final class TripWizardViewModel {
|
||||
// MARK: - Reset Logic
|
||||
|
||||
private func resetAllSelections() {
|
||||
// Common fields
|
||||
selectedSports = []
|
||||
hasSetDates = false
|
||||
selectedRegions = []
|
||||
hasSetRoutePreference = false
|
||||
hasSetRepeatCities = false
|
||||
mustStopLocations = []
|
||||
|
||||
// gameFirst mode fields
|
||||
gamePickerSports = []
|
||||
gamePickerTeamIds = []
|
||||
selectedGameIds = []
|
||||
|
||||
// followTeam mode fields
|
||||
selectedTeamId = nil
|
||||
teamPickerSport = nil
|
||||
|
||||
// locations mode fields
|
||||
startLocation = nil
|
||||
endLocation = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,9 +209,70 @@ struct FieldValidation {
|
||||
case missing
|
||||
}
|
||||
|
||||
let planningMode: PlanningMode?
|
||||
|
||||
// Common fields
|
||||
let sports: Status
|
||||
let dates: Status
|
||||
let regions: Status
|
||||
let routePreference: Status
|
||||
let repeatCities: Status
|
||||
|
||||
// Mode-specific fields
|
||||
let selectedGames: Status
|
||||
let selectedTeam: Status
|
||||
let startLocation: Status
|
||||
let endLocation: Status
|
||||
|
||||
/// Returns only the fields that are required for the current planning mode
|
||||
var requiredFields: [(name: String, status: Status)] {
|
||||
var fields: [(String, Status)] = []
|
||||
|
||||
guard let mode = planningMode else { return fields }
|
||||
|
||||
switch mode {
|
||||
case .dateRange:
|
||||
fields = [
|
||||
("Dates", dates),
|
||||
("Sports", sports),
|
||||
("Regions", regions),
|
||||
("Route Preference", routePreference),
|
||||
("Repeat Cities", repeatCities)
|
||||
]
|
||||
case .gameFirst:
|
||||
fields = [
|
||||
("Games", selectedGames),
|
||||
("Route Preference", routePreference),
|
||||
("Repeat Cities", repeatCities)
|
||||
]
|
||||
case .locations:
|
||||
fields = [
|
||||
("Start Location", startLocation),
|
||||
("End Location", endLocation),
|
||||
("Dates", dates),
|
||||
("Sports", sports),
|
||||
("Route Preference", routePreference),
|
||||
("Repeat Cities", repeatCities)
|
||||
]
|
||||
case .followTeam:
|
||||
fields = [
|
||||
("Team", selectedTeam),
|
||||
("Dates", dates),
|
||||
("Route Preference", routePreference),
|
||||
("Repeat Cities", repeatCities)
|
||||
]
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
/// Returns only the missing required fields for the current mode
|
||||
var missingFields: [String] {
|
||||
requiredFields.filter { $0.status == .missing }.map { $0.name }
|
||||
}
|
||||
|
||||
/// Whether all required fields for the current mode are valid
|
||||
var allRequiredFieldsValid: Bool {
|
||||
requiredFields.allSatisfy { $0.status == .valid }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user