diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index 7debfe4..ac7b61c 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -35,6 +35,10 @@ C4A47DAD2B85FCCD00ADC637 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DAC2B85FCCD00ADC637 /* User.swift */; }; C4A47DB12B86375E00ADC637 /* MainUserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DB02B86375E00ADC637 /* MainUserView.swift */; }; C4A47DB32B86387500ADC637 /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47DB22B86387500ADC637 /* AccountView.swift */; }; + FF025AD82BD0C10F00A86CF8 /* TeamHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025AD72BD0C10F00A86CF8 /* TeamHeaderView.swift */; }; + FF025ADB2BD0C2D000A86CF8 /* MatchTeamDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025ADA2BD0C2D000A86CF8 /* MatchTeamDetailView.swift */; }; + FF025ADD2BD0C94300A86CF8 /* FooterButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025ADC2BD0C94300A86CF8 /* FooterButtonView.swift */; }; + FF025ADF2BD0CE0A00A86CF8 /* TeamWeightView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF025ADE2BD0CE0A00A86CF8 /* TeamWeightView.swift */; }; FF089EB42BB0020000F0AEC7 /* PlayerSexPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EB32BB0020000F0AEC7 /* PlayerSexPickerView.swift */; }; FF089EB62BB00A3800F0AEC7 /* TeamRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EB52BB00A3800F0AEC7 /* TeamRowView.swift */; }; FF089EBB2BB0120700F0AEC7 /* PlayerPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF089EBA2BB0120700F0AEC7 /* PlayerPopoverView.swift */; }; @@ -313,6 +317,10 @@ C4A47DAC2B85FCCD00ADC637 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; C4A47DB02B86375E00ADC637 /* MainUserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainUserView.swift; sourceTree = ""; }; C4A47DB22B86387500ADC637 /* AccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountView.swift; sourceTree = ""; }; + FF025AD72BD0C10F00A86CF8 /* TeamHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamHeaderView.swift; sourceTree = ""; }; + FF025ADA2BD0C2D000A86CF8 /* MatchTeamDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatchTeamDetailView.swift; sourceTree = ""; }; + FF025ADC2BD0C94300A86CF8 /* FooterButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FooterButtonView.swift; sourceTree = ""; }; + FF025ADE2BD0CE0A00A86CF8 /* TeamWeightView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamWeightView.swift; sourceTree = ""; }; FF089EB32BB0020000F0AEC7 /* PlayerSexPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerSexPickerView.swift; sourceTree = ""; }; FF089EB52BB00A3800F0AEC7 /* TeamRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TeamRowView.swift; sourceTree = ""; }; FF089EBA2BB0120700F0AEC7 /* PlayerPopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerPopoverView.swift; sourceTree = ""; }; @@ -711,12 +719,32 @@ C4A47D9E2B7D0BCE00ADC637 /* StepperView.swift */, FF5DA1942BB927E800A33061 /* GenericDestinationPickerView.swift */, FF6EC8F62B94773100EA7F5A /* RowButtonView.swift */, + FF025ADC2BD0C94300A86CF8 /* FooterButtonView.swift */, FFBF065D2BBD8040009D6715 /* MatchListView.swift */, FF967CF72BAEDF0000A9A3BD /* Labels.swift */, ); path = Components; sourceTree = ""; }; + FF025AD62BD0C0FB00A86CF8 /* Components */ = { + isa = PBXGroup; + children = ( + FF025AD72BD0C10F00A86CF8 /* TeamHeaderView.swift */, + FF025ADE2BD0CE0A00A86CF8 /* TeamWeightView.swift */, + ); + path = Components; + sourceTree = ""; + }; + FF025AD92BD0C2BD00A86CF8 /* Components */ = { + isa = PBXGroup; + children = ( + FF967D0C2BAF3EB200A9A3BD /* MatchDateView.swift */, + FF967D0E2BAF63B000A9A3BD /* PlayerBlockView.swift */, + FF025ADA2BD0C2D000A86CF8 /* MatchTeamDetailView.swift */, + ); + path = Components; + sourceTree = ""; + }; FF089EB02BB001EA00F0AEC7 /* Components */ = { isa = PBXGroup; children = ( @@ -1012,8 +1040,7 @@ FF967D052BAF3C4200A9A3BD /* MatchSetupView.swift */, FF967D002BAEF0B400A9A3BD /* MatchSummaryView.swift */, FF967D022BAEF0C000A9A3BD /* MatchDetailView.swift */, - FF967D0C2BAF3EB200A9A3BD /* MatchDateView.swift */, - FF967D0E2BAF63B000A9A3BD /* PlayerBlockView.swift */, + FF025AD92BD0C2BD00A86CF8 /* Components */, ); path = Match; sourceTree = ""; @@ -1025,6 +1052,7 @@ FF967D0A2BAF3D4C00A9A3BD /* TeamPickerView.swift */, FF089EB52BB00A3800F0AEC7 /* TeamRowView.swift */, FF1162862BD004AD000C4809 /* EditingTeamView.swift */, + FF025AD62BD0C0FB00A86CF8 /* Components */, ); path = Team; sourceTree = ""; @@ -1348,6 +1376,7 @@ FFB9C8752BBADDF700A0EF4F /* SeedInterval.swift in Sources */, FFBF065C2BBD2657009D6715 /* GroupStageTeamView.swift in Sources */, FF5DA1932BB9279B00A33061 /* RoundSettingsView.swift in Sources */, + FF025ADF2BD0CE0A00A86CF8 /* TeamWeightView.swift in Sources */, FF9268012BCE94920080F940 /* SeedsCallingView.swift in Sources */, FF9268092BCEDC2C0080F940 /* CallView.swift in Sources */, FF5D0D742BB41DF8005CB568 /* Color+Extensions.swift in Sources */, @@ -1404,6 +1433,7 @@ FF1CBC222BB53E590036DAAB /* FederalTournamentHolder.swift in Sources */, C4A47D5E2B6D38EC00ADC637 /* DataStore.swift in Sources */, FFCFC01C2BBC5AAA00B82851 /* SetDescriptor.swift in Sources */, + FF025AD82BD0C10F00A86CF8 /* TeamHeaderView.swift in Sources */, FF82CFC52B911F5B00B0CAF2 /* OrganizedTournamentView.swift in Sources */, FFF964572BC26B3400EEF017 /* RoundScheduleEditorView.swift in Sources */, FF59FFB32B90EFAC0061EFF9 /* EventListView.swift in Sources */, @@ -1412,6 +1442,7 @@ FFC1E10C2BAC7FB0008D6F59 /* ClubImportView.swift in Sources */, FF3B60A32BC49BBC008C2E66 /* MatchScheduler.swift in Sources */, FF11627A2BCF8109000C4809 /* CallMessageCustomizationView.swift in Sources */, + FF025ADB2BD0C2D000A86CF8 /* MatchTeamDetailView.swift in Sources */, FF5DA1952BB927E800A33061 /* GenericDestinationPickerView.swift in Sources */, FF8F26542BAE1E4400650388 /* TableStructureView.swift in Sources */, C45BAE442BCA753E002EEC8A /* Purchase.swift in Sources */, @@ -1499,6 +1530,7 @@ FF967CF82BAEDF0000A9A3BD /* Labels.swift in Sources */, FF089EB42BB0020000F0AEC7 /* PlayerSexPickerView.swift in Sources */, FF9267FF2BCE94830080F940 /* CallSettingsView.swift in Sources */, + FF025ADD2BD0C94300A86CF8 /* FooterButtonView.swift in Sources */, FF5D0D852BB48997005CB568 /* RankCalculatorView.swift in Sources */, FF70916A2B90F95E00AB08DA /* DateBoxView.swift in Sources */, FF5D0D722BB3EFA5005CB568 /* LearnMoreSheetView.swift in Sources */, diff --git a/PadelClub/Data/GroupStage.swift b/PadelClub/Data/GroupStage.swift index 723cc98..1f3e144 100644 --- a/PadelClub/Data/GroupStage.swift +++ b/PadelClub/Data/GroupStage.swift @@ -136,7 +136,8 @@ class GroupStage: ModelObject, Storable { } func availableToStart() -> [Match] { - playedMatches().filter({ $0.canBeStarted() && $0.isRunning() == false }) + let runningMatches = runningMatches() + return playedMatches().filter({ $0.canBeStarted(inMatches: runningMatches) && $0.isRunning() == false }) } func runningMatches() -> [Match] { diff --git a/PadelClub/Data/Match.swift b/PadelClub/Data/Match.swift index ae76ae3..ccd4c18 100644 --- a/PadelClub/Data/Match.swift +++ b/PadelClub/Data/Match.swift @@ -349,24 +349,26 @@ class Match: ModelObject, Storable { court = String(courtIndex) } - func canBeStarted() -> Bool { + func canBeStarted(inMatches matches: [Match]) -> Bool { let teams = teams() guard teams.count == 2 else { return false } guard hasEnded() == false else { return false } guard hasStarted() == false else { return false } - return teams.allSatisfy({ $0.canPlay() && isTeamPlaying($0) == false }) + return teams.allSatisfy({ $0.canPlay() && isTeamPlaying($0, inMatches: matches) == false }) } - func isTeamPlaying(_ team: TeamRegistration) -> Bool { - if isGroupStage() { - let isPlaying = groupStageObject?.runningMatches().filter({ $0.teams().contains(team) }).isEmpty == false - return isPlaying - } else { - //todo - return false - } + func isTeamPlaying(_ team: TeamRegistration, inMatches matches: [Match]) -> Bool { + matches.filter({ $0.teams().contains(team) }).isEmpty == false } - + + var computedStartDateForSorting: Date { + startDate ?? .distantFuture + } + + var computedEndDateForSorting: Date { + endDate ?? .distantFuture + } + func isReady() -> Bool { teams().count == 2 } diff --git a/PadelClub/Data/TeamRegistration.swift b/PadelClub/Data/TeamRegistration.swift index 65bc7ed..706508c 100644 --- a/PadelClub/Data/TeamRegistration.swift +++ b/PadelClub/Data/TeamRegistration.swift @@ -128,9 +128,9 @@ class TeamRegistration: ModelObject, Storable { func teamLabel(_ displayStyle: DisplayStyle = .wide) -> String { switch displayStyle { case .wide: - unsortedPlayers().map { $0.playerLabel(displayStyle) }.joined(separator: " & ") + players().map { $0.playerLabel(displayStyle) }.joined(separator: " & ") case .short: - unsortedPlayers().map { $0.playerLabel(.wide) }.joined(separator: "\n") + players().map { $0.playerLabel(.wide) }.joined(separator: "\n") } } diff --git a/PadelClub/Data/Tournament.swift b/PadelClub/Data/Tournament.swift index c57a020..95791bc 100644 --- a/PadelClub/Data/Tournament.swift +++ b/PadelClub/Data/Tournament.swift @@ -562,6 +562,25 @@ class Tournament : ModelObject, Storable { return false } + func availableToStart(_ allMatches: [Match]) -> [Match] { + let runningMatches = allMatches.filter({ $0.isRunning() && $0.isReady() }) + return allMatches.filter({ $0.canBeStarted(inMatches: runningMatches) && $0.isRunning() == false }).sorted(by: \.computedStartDateForSorting) + } + + func runningMatches(_ allMatches: [Match]) -> [Match] { + allMatches.filter({ $0.isRunning() && $0.isReady() }).sorted(by: \.computedStartDateForSorting) + } + + func readyMatches(_ allMatches: [Match]) -> [Match] { + return allMatches.filter({ $0.isReady() && $0.isRunning() == false && $0.hasEnded() == false }).sorted(by: \.computedStartDateForSorting) + } + + func finishedMatches(_ allMatches: [Match], limit: Int? = nil) -> [Match] { + let _limit = limit ?? courtCount + return Array(allMatches.filter({ $0.hasEnded() }).sorted(by: \.computedEndDateForSorting).reversed().prefix(_limit)) + } + + func lockRegistration() { closedRegistrationDate = Date() let count = selectedSortedTeams().count @@ -880,6 +899,10 @@ class Tournament : ModelObject, Storable { entryFee == nil || entryFee == 0 } + func indexOf(team: TeamRegistration) -> Int? { + selectedSortedTeams().firstIndex(where: { $0.id == team.id }) + } + func addTeam(_ players: Set) -> TeamRegistration { let team = TeamRegistration(tournament: id, registrationDate: Date()) team.tournamentCategory = tournamentCategory diff --git a/PadelClub/Extensions/String+Extensions.swift b/PadelClub/Extensions/String+Extensions.swift index 1b233af..b515c10 100644 --- a/PadelClub/Extensions/String+Extensions.swift +++ b/PadelClub/Extensions/String+Extensions.swift @@ -159,3 +159,8 @@ extension String { } } } + +extension StringProtocol { + var firstUppercased: String { prefix(1).uppercased() + dropFirst() } + var firstCapitalized: String { prefix(1).capitalized + dropFirst() } +} diff --git a/PadelClub/ViewModel/MatchDescriptor.swift b/PadelClub/ViewModel/MatchDescriptor.swift index 1d2d86a..0cd3a14 100644 --- a/PadelClub/ViewModel/MatchDescriptor.swift +++ b/PadelClub/ViewModel/MatchDescriptor.swift @@ -29,8 +29,8 @@ class MatchDescriptor: ObservableObject { } let teamOne = match?.team(.one) let teamTwo = match?.team(.two) - self.teamLabelOne = teamOne?.teamLabel() ?? "" - self.teamLabelTwo = teamTwo?.teamLabel() ?? "" + self.teamLabelOne = teamOne?.teamLabel(.short) ?? "" + self.teamLabelTwo = teamTwo?.teamLabel(.short) ?? "" if let match, let scoresTeamOne = match.teamScore(ofTeam: teamOne)?.score, let scoresTeamTwo = match.teamScore(ofTeam: teamTwo)?.score { diff --git a/PadelClub/Views/Components/FooterButtonView.swift b/PadelClub/Views/Components/FooterButtonView.swift new file mode 100644 index 0000000..2d7f99b --- /dev/null +++ b/PadelClub/Views/Components/FooterButtonView.swift @@ -0,0 +1,18 @@ +// +// FooterButtonView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 18/04/2024. +// + +import SwiftUI + +struct FooterButtonView: View { + var body: some View { + Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) + } +} + +#Preview { + FooterButtonView() +} diff --git a/PadelClub/Views/Components/MatchListView.swift b/PadelClub/Views/Components/MatchListView.swift index 4f36123..1445dcb 100644 --- a/PadelClub/Views/Components/MatchListView.swift +++ b/PadelClub/Views/Components/MatchListView.swift @@ -19,27 +19,20 @@ struct MatchListView: View { var body: some View { if matches.isEmpty == false { Section { - if isExpanded { + DisclosureGroup(isExpanded: $isExpanded) { ForEach(matches) { match in MatchRowView(match: match, matchViewStyle: matchViewStyle) + .listRowInsets(EdgeInsets()) } - } - } header: { - Button { - isExpanded.toggle() } label: { - HStack { - Text(section.capitalized) - Spacer() - Text(matches.count.formatted()) - Image(systemName: isExpanded ? "chevron.down.circle" : "chevron.right.circle") + LabeledContent { + Text(matches.count.formatted() + " match" + matches.count.pluralSuffix) + .foregroundStyle(.master) + } label: { + Text(section.firstCapitalized) } - .contentShape(Rectangle()) } - .buttonStyle(.plain) - .frame(maxWidth: .infinity) } - .headerProminence(.increased) } } } diff --git a/PadelClub/Views/GroupStage/GroupStageView.swift b/PadelClub/Views/GroupStage/GroupStageView.swift index 2789618..f276a95 100644 --- a/PadelClub/Views/GroupStage/GroupStageView.swift +++ b/PadelClub/Views/GroupStage/GroupStageView.swift @@ -34,26 +34,33 @@ struct GroupStageView: View { Section { _groupStageView() } header: { + if let startDate = groupStage.startDate { + Text(startDate.formatted(Date.FormatStyle().weekday(.wide)).capitalized + " à partir de " + startDate.formatted(.dateTime.hour().minute())) + } + } footer: { HStack { - if let startDate = groupStage.startDate { - Text(startDate.formatted(Date.FormatStyle().weekday(.wide)).capitalized + " à partir de " + startDate.formatted(.dateTime.hour().minute())) - } - Spacer() - Button { - if sortingMode == .weight { - sortingMode = .score + if sortingMode == .auto { + if groupStage.hasEnded() { + sortingMode = .weight + } else { + sortingMode = .score + } + } else if sortingMode == .weight { + sortingMode = .weight } else { sortingMode = .weight } } label: { Label(sortByScore ? "tri par score" : "tri par poids", systemImage: "arrow.up.arrow.down").labelStyle(.titleOnly) + .underline() } + .buttonStyle(.borderless) } - .buttonStyle(.plain) } - + .headerProminence(.increased) + MatchListView(section: "disponible", matches: groupStage.availableToStart()).id(UUID()) MatchListView(section: "en cours", matches: groupStage.runningMatches()).id(UUID()) MatchListView(section: "à lancer", matches: groupStage.readyMatches()).id(UUID()) diff --git a/PadelClub/Views/Match/MatchDateView.swift b/PadelClub/Views/Match/Components/MatchDateView.swift similarity index 66% rename from PadelClub/Views/Match/MatchDateView.swift rename to PadelClub/Views/Match/Components/MatchDateView.swift index 9dd35c5..4665e0b 100644 --- a/PadelClub/Views/Match/MatchDateView.swift +++ b/PadelClub/Views/Match/Components/MatchDateView.swift @@ -8,13 +8,14 @@ import SwiftUI struct MatchDateView: View { + @EnvironmentObject var dataStore: DataStore var match: Match var showPrefix: Bool = false var body: some View { Menu { - if match.startDate == nil { - Button("Commencer") { + if match.startDate == nil && match.isReady() { + Button("Démarrer") { match.startDate = Date() save() } @@ -23,12 +24,20 @@ struct MatchDateView: View { save() } } else { - Button("Recommencer") { - match.startDate = Date() - match.endDate = nil - save() + if match.isReady() { + Button("Démarrer maintenant") { + match.startDate = Date() + match.endDate = nil + save() + } + } else { + Button("Décaler de \(match.matchFormat.estimatedDuration) minutes") { + match.startDate = match.startDate?.addingTimeInterval(Double(match.matchFormat.estimatedDuration) * 60.0) + match.endDate = nil + save() + } } - Button("Remise à zéro") { + Button("Retirer l'horaire") { match.startDate = nil match.endDate = nil save() @@ -50,8 +59,16 @@ struct MatchDateView: View { if showPrefix { Text("en cours").font(.footnote).foregroundStyle(.secondary) } - Text(startDate, style: .timer) - .monospacedDigit() + if match.isReady() { + Text(startDate, style: .timer) + .monospacedDigit() + .foregroundStyle(Color.master) + .underline() + } else { + Text("en retard") + .foregroundStyle(Color.master) + .underline() + } } else if startDate.timeIntervalSinceNow <= 7200 && showPrefix { if showPrefix { Text("démarre dans") @@ -59,15 +76,21 @@ struct MatchDateView: View { } Text(startDate, style: .timer) .monospacedDigit() + .foregroundStyle(Color.master) + .underline() } else { if showPrefix { Text("le " + startDate.formatted(date: .abbreviated, time: .omitted)) .font(.footnote).foregroundStyle(.secondary) Text("à " + startDate.formatted(date: .omitted, time: .shortened)) .monospacedDigit() + .foregroundStyle(Color.master) + .underline() } else { Text(startDate.formatted(date: .abbreviated, time: .shortened)) .monospacedDigit() + .foregroundStyle(Color.master) + .underline() } } } @@ -81,11 +104,15 @@ struct MatchDateView: View { } Text(duration) .monospacedDigit() + .foregroundStyle(Color.master) + .underline() } if match.startDate == nil && match.hasEnded() == false { Text("démarrage").font(.footnote).foregroundStyle(.secondary) Text("non défini") + .foregroundStyle(Color.master) + .underline() } } } @@ -94,9 +121,7 @@ struct MatchDateView: View { func save() { do { -// match.currentTournament?.objectWillChange.send() -// match.objectWillChange.send() -// try viewContext.save() + try dataStore.matches.addOrUpdate(instance: match) } catch { // Replace this implementation with code to handle the error appropriately. // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. diff --git a/PadelClub/Views/Match/Components/MatchTeamDetailView.swift b/PadelClub/Views/Match/Components/MatchTeamDetailView.swift new file mode 100644 index 0000000..b09a46e --- /dev/null +++ b/PadelClub/Views/Match/Components/MatchTeamDetailView.swift @@ -0,0 +1,45 @@ +// +// MatchTeamDetailView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 18/04/2024. +// + +import SwiftUI + +struct MatchTeamDetailView: View { + let match: Match + + var body: some View { + NavigationStack { + let tournament = match.currentTournament() + List { + if let teamOne = match.team(.one) { + _teamDetailView(teamOne, inTournament: tournament) + } + if let teamTwo = match.team(.two) { + _teamDetailView(teamTwo, inTournament: tournament) + } + } + .headerProminence(.increased) + .tint(.master) + } + .presentationDetents([.fraction(0.66)]) + } + + @ViewBuilder + private func _teamDetailView(_ team: TeamRegistration, inTournament tournament: Tournament?) -> some View { + Section { + ForEach(team.players()) { player in + EditablePlayerView(player: player, editingOptions: [.licenceId, .payment]) + } + } header: { + TeamHeaderView(team: team, teamIndex: tournament?.indexOf(team: team), tournament: nil) + } + } + +} + +#Preview { + MatchTeamDetailView(match: Match.mock()) +} diff --git a/PadelClub/Views/Match/PlayerBlockView.swift b/PadelClub/Views/Match/Components/PlayerBlockView.swift similarity index 94% rename from PadelClub/Views/Match/PlayerBlockView.swift rename to PadelClub/Views/Match/Components/PlayerBlockView.swift index 68ee799..29b9127 100644 --- a/PadelClub/Views/Match/PlayerBlockView.swift +++ b/PadelClub/Views/Match/Components/PlayerBlockView.swift @@ -79,7 +79,7 @@ struct PlayerBlockView: View { Text("WO") } - if hideScore == false { + if hideScore == false && scores.isEmpty == false { ForEach(scores.indices, id: \.self) { index in let string = scores[index] if string.isEmpty == false { @@ -96,6 +96,8 @@ struct PlayerBlockView: View { .lineLimit(1) } } + } else if let team { + TeamWeightView(team: team, teamPosition: teamPosition) } } } diff --git a/PadelClub/Views/Match/MatchDetailView.swift b/PadelClub/Views/Match/MatchDetailView.swift index 8504db5..d12d91e 100644 --- a/PadelClub/Views/Match/MatchDetailView.swift +++ b/PadelClub/Views/Match/MatchDetailView.swift @@ -48,86 +48,40 @@ struct MatchDetailView: View { _fieldSetup = State(wrappedValue: .field(court)) } } - -// @ViewBuilder -// func entrantView(_ entrant: Entrant) -> some View { -// Section { -// ForEach(entrant.orderedPlayers) { player in -// if player.isPlaying(in: match) { -// playerView(player) -// } -// } -// } header: { -// LabeledContent { -// if let tournament = match.currentTournament, let index = tournament.indexOfEntrant(entrant) { -// Text("#\(index + 1)") -// } -// } label: { -// if let title = entrant.brand?.title { -// Text(title) -// } -// } -// } footer: { -// LabeledContent { -// let weight = entrant.orderedPlayers.filter { $0.isPlaying(in: match) }.map { $0.tournamentRank }.reduce(0, +) -// Text(weight.formatted()) -// } label: { -// Text("Poids de la paire") -// } -// } -// .headerProminence(.increased) -// } - -// @ViewBuilder -// func playerView(_ player: Player) -> some View { -// VStack(alignment: .leading) { -// HStack { -// Text(player.longLabel) -// Text(player.localizedAge) -// Spacer() -// Text(player.formattedRank) -// } -// -// if let computedClubName = player.computedClubName { -// Text(computedClubName).foregroundStyle(.secondary).font(.caption) -// } -// if let computedLicense = player.computedLicense { -// Text(computedLicense).foregroundStyle(.secondary).font(.caption) -// } -// } -// } var quickLookHeader: some View { Section { HStack { - if match.hasEnded() == false { - Menu { - Button("Non défini") { - match.removeCourt() + Menu { + Button("Non défini") { + match.removeCourt() + save() + } + ForEach(1...match.courtCount(), id: \.self) { courtIndex in + Button("Terrain #\(courtIndex.formatted())") { + match.setCourt(courtIndex) save() } - ForEach(1...match.courtCount(), id: \.self) { courtIndex in - Button("Terrain #\(courtIndex.formatted())") { - match.setCourt(courtIndex) - save() - } - } - } label: { - VStack(alignment: .leading) { - Text("terrain").font(.footnote).foregroundStyle(.secondary) - if let court = match.court { - Text("#" + court) - } else { - Text("Choisir") - } + } + } label: { + VStack(alignment: .leading) { + Text("terrain").font(.footnote).foregroundStyle(.secondary) + if let court = match.court { + Text("#" + court) + .foregroundStyle(Color.master) + .underline() + } else { + Text("Choisir") + .foregroundStyle(Color.master) + .underline() } } - .buttonStyle(.plain) } Spacer() MatchDateView(match: match, showPrefix: true) } .font(.title) + .buttonStyle(.plain) } footer: { // if match.hasWalkoutTeam() == false { // if let weatherData = match.weatherData { @@ -151,7 +105,6 @@ struct MatchDetailView: View { Section { MatchSummaryView(match: match, matchViewStyle: .plainStyle) - } header: { } footer: { if match.isEmpty() == false { HStack { @@ -171,34 +124,34 @@ struct MatchDetailView: View { } } - Section { - ForEach(match.teams()) { team in - ForEach(team.players().filter({ $0.hasPaid() == false })) { player in - HStack { - Text(player.playerLabel()) - Spacer() - //PlayerPayView(player: player) + let players = match.teams().flatMap { $0.players() } + let unpaid = players.filter({ $0.hasPaid() == false }) + + if unpaid.isEmpty == false { + Section { + DisclosureGroup { + ForEach(unpaid) { player in + LabeledContent { + PlayerPayView(player: player) + } label: { + Text(player.playerLabel()) + } + } + } label: { + LabeledContent { + Text(unpaid.count.formatted() + " / " + players.count.formatted()) + } label: { + Text("Encaissement manquant") } } } } - menuView } -// .sheet(isPresented: $showDetails) { -// NavigationStack { -// List { -// if let entrantOne = match.entrantOne() { -// entrantView(entrantOne) -// } -// if let entrantTwo = match.entrantTwo() { -// entrantView(entrantTwo) -// } -// } -// } -// .presentationDetents([.fraction(0.66)]) -// } + .sheet(isPresented: $showDetails) { + MatchTeamDetailView(match: match) + } .sheet(item: $scoreType, onDismiss: { if match.hasEnded() { dismiss() @@ -206,6 +159,7 @@ struct MatchDetailView: View { }) { scoreType in let matchDescriptor = MatchDescriptor(match: match) EditScoreView(matchDescriptor: matchDescriptor) + .tint(.master) // switch scoreType { // case .edition: @@ -305,7 +259,8 @@ struct MatchDetailView: View { // } // } .navigationTitle(match.matchTitle()) - .navigationBarTitleDisplayMode(.large) + .navigationBarTitleDisplayMode(.inline) + .toolbarBackground(.visible, for: .navigationBar) } enum ScoreType: Int, Identifiable, Hashable { diff --git a/PadelClub/Views/Score/EditScoreView.swift b/PadelClub/Views/Score/EditScoreView.swift index 3c0cd33..a1bba95 100644 --- a/PadelClub/Views/Score/EditScoreView.swift +++ b/PadelClub/Views/Score/EditScoreView.swift @@ -22,7 +22,10 @@ struct EditScoreView: View { Form { Section { Text(matchDescriptor.teamLabelOne) - Text(matchDescriptor.teamLabelTwo) + HStack { + Spacer() + Text(matchDescriptor.teamLabelTwo).multilineTextAlignment(.trailing) + } } footer: { HStack { Menu { @@ -37,7 +40,8 @@ struct EditScoreView: View { Text(matchDescriptor.teamLabelTwo) } } label: { - Text("Forfait") + Text("Forfait d'une équipe ?") + .underline() } Spacer() diff --git a/PadelClub/Views/Score/PointSelectionView.swift b/PadelClub/Views/Score/PointSelectionView.swift index 40d0579..1b70405 100644 --- a/PadelClub/Views/Score/PointSelectionView.swift +++ b/PadelClub/Views/Score/PointSelectionView.swift @@ -13,8 +13,8 @@ struct PointSelectionView: View { var possibleValues: [Int] var disableValues: [Int] = [] var deleteAction: () -> () - let gridItems: [GridItem] = [GridItem(.adaptive(minimum: 65), spacing: 20)] - + let columns = Array(repeating: GridItem(.flexible()), count: 3) + init(valueSelected: Binding, values: [Int], possibleValues: [Int], disableValues: [Int], deleteAction: @escaping () -> Void) { _valueSelected = valueSelected @@ -26,12 +26,13 @@ struct PointSelectionView: View { var body: some View { - LazyVGrid(columns: gridItems, alignment: .center, spacing: 20) { + LazyVGrid(columns: columns, alignment: .center, spacing: 8) { ForEach(possibleValues, id: \.self) { value in Button { valueSelected = value } label: { PointView(value: "\(value).circle.fill") + .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) .controlSize(.large) @@ -41,10 +42,11 @@ struct PointSelectionView: View { deleteAction() } label: { PointView(value: "delete.left.fill") + .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) .controlSize(.large) } - .padding() + .padding(8) } } diff --git a/PadelClub/Views/Score/PointView.swift b/PadelClub/Views/Score/PointView.swift index 471c749..c72fb1d 100644 --- a/PadelClub/Views/Score/PointView.swift +++ b/PadelClub/Views/Score/PointView.swift @@ -15,7 +15,7 @@ struct PointView: View { .resizable() .aspectRatio(contentMode: .fit) .font(.largeTitle) - .frame(height: 40) + .frame(height: 36) } } diff --git a/PadelClub/Views/Score/SetInputView.swift b/PadelClub/Views/Score/SetInputView.swift index ebcea2f..d69919b 100644 --- a/PadelClub/Views/Score/SetInputView.swift +++ b/PadelClub/Views/Score/SetInputView.swift @@ -124,12 +124,14 @@ struct SetInputView: View { Section { DisclosureGroup(isExpanded: $showSetInputView) { PointSelectionView(valueSelected: currentValue, values: possibleValues(), possibleValues: setFormat.possibleValues, disableValues: disableValues, deleteAction: deleteLastValue) + .listRowInsets(EdgeInsets(top: -8, leading: -20, bottom: 0, trailing: 0)) } label: { SetLabelView(initialValueLeft: $setDescriptor.valueTeamOne, initialValueRight: $setDescriptor.valueTeamTwo, shouldDisplaySteppers: isMainViewTieBreakView) } if showTieBreakView { DisclosureGroup(isExpanded: $showTieBreakInputView) { PointSelectionView(valueSelected: currentTiebreakValue, values: tieBreakPossibleValues(), possibleValues: SetFormat.six.possibleValues, disableValues: disableTieBreakValues, deleteAction: deleteLastTiebreakValue) + .listRowInsets(EdgeInsets(top: -8, leading: -20, bottom: 0, trailing: 0)) } label: { SetLabelView(initialValueLeft: $setDescriptor.tieBreakValueTeamOne, initialValueRight: $setDescriptor.tieBreakValueTeamTwo, shouldDisplaySteppers: showTieBreakInputView, isTieBreak: true) } diff --git a/PadelClub/Views/Team/Components/TeamHeaderView.swift b/PadelClub/Views/Team/Components/TeamHeaderView.swift new file mode 100644 index 0000000..76818a6 --- /dev/null +++ b/PadelClub/Views/Team/Components/TeamHeaderView.swift @@ -0,0 +1,43 @@ +// +// TeamHeaderView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 18/04/2024. +// + +import SwiftUI + +struct TeamHeaderView: View { + var team: TeamRegistration + var teamIndex: Int? + var tournament: Tournament? + + var body: some View { + _teamHeaderView(team, teamIndex: teamIndex) + } + + private func _teamHeaderView(_ team: TeamRegistration, teamIndex: Int?) -> some View { + HStack { + if let teamIndex { + Text("#" + (teamIndex + 1).formatted()) + } + + if team.unsortedPlayers().isEmpty == false { + Text(team.weight.formatted()) + } + if team.isWildCard() { + Text("wildcard").italic().font(.caption) + } + Spacer() + if team.walkOut { + Text("WO") + } else if let teamIndex, let tournament { + Text(tournament.cutLabel(index: teamIndex)) + } + } + } +} + +#Preview { + TeamHeaderView(team: TeamRegistration.mock(), teamIndex: 1, tournament: nil) +} diff --git a/PadelClub/Views/Team/Components/TeamWeightView.swift b/PadelClub/Views/Team/Components/TeamWeightView.swift new file mode 100644 index 0000000..fc48af5 --- /dev/null +++ b/PadelClub/Views/Team/Components/TeamWeightView.swift @@ -0,0 +1,38 @@ +// +// TeamWeightView.swift +// PadelClub +// +// Created by Razmig Sarkissian on 18/04/2024. +// + +import SwiftUI + +struct TeamWeightView: View { + var team: TeamRegistration + var teamPosition: TeamPosition? = nil + + var body: some View { + VStack(alignment: .trailing, spacing: 0) { + if teamPosition == .one || teamPosition == nil { + Text(team.weight.formatted()) + .monospacedDigit() + .font(.caption) + } + if let teams = team.tournamentObject()?.selectedSortedTeams(), let index = team.index(in: teams) { + Text("#" + (index + 1).formatted(.number.precision(.integerLength(2...3)))) + .monospacedDigit() + .font(.title) + } + if teamPosition == .two { + Text(team.weight.formatted()) + .monospacedDigit() + .font(.caption) + + } + } + } +} + +#Preview { + TeamWeightView(team: TeamRegistration.mock(), teamPosition: .one) +} diff --git a/PadelClub/Views/Team/TeamRowView.swift b/PadelClub/Views/Team/TeamRowView.swift index 4eebb7e..a6972c5 100644 --- a/PadelClub/Views/Team/TeamRowView.swift +++ b/PadelClub/Views/Team/TeamRowView.swift @@ -14,21 +14,7 @@ struct TeamRowView: View { var body: some View { LabeledContent { - VStack(alignment: .trailing, spacing: 0) { - if teamPosition == .one || teamPosition == nil { - Text(team.weight.formatted()) - .font(.caption) - } - if let teams = team.tournamentObject()?.selectedSortedTeams(), let index = team.index(in: teams) { - Text("#" + (index + 1).formatted()) - .font(.title) - } - if teamPosition == .two { - Text(team.weight.formatted()) - .font(.caption) - - } - } + TeamWeightView(team: team, teamPosition: teamPosition) } label: { Text(team.teamLabel(.short)) if let callDate = team.callDate, displayCallDate { diff --git a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift index dd6a40b..dee982e 100644 --- a/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift +++ b/PadelClub/Views/Tournament/Screen/InscriptionManagerView.swift @@ -239,7 +239,7 @@ struct InscriptionManagerView: View { Section { TeamDetailView(team: team) } header: { - _teamHeaderView(team, teamIndex: teamIndex) + TeamHeaderView(team: team, teamIndex: teamIndex, tournament: tournament) } footer: { _teamFooterView(team) } @@ -717,27 +717,6 @@ struct InscriptionManagerView: View { } } - private func _teamHeaderView(_ team: TeamRegistration, teamIndex: Int?) -> some View { - HStack { - if let teamIndex { - Text("#" + (teamIndex + 1).formatted()) - } - - if team.unsortedPlayers().isEmpty == false { - Text(team.weight.formatted()) - } - if team.isWildCard() { - Text("wildcard").italic().font(.caption) - } - Spacer() - if team.walkOut { - Text("WO") - } else if let teamIndex { - Text(tournament.cutLabel(index: teamIndex)) - } - } - } - private func _teamFooterView(_ team: TeamRegistration) -> some View { HStack { if let formattedRegistrationDate = team.formattedInscriptionDate() { diff --git a/PadelClub/Views/Tournament/TournamentRunningView.swift b/PadelClub/Views/Tournament/TournamentRunningView.swift index 2bd172b..da4909c 100644 --- a/PadelClub/Views/Tournament/TournamentRunningView.swift +++ b/PadelClub/Views/Tournament/TournamentRunningView.swift @@ -44,8 +44,8 @@ struct TournamentRunningView: View { } } - if tournament.groupStages().isEmpty == false { - Section { + Section { + if tournament.groupStages().isEmpty == false { NavigationLink(value: Screen.groupStage) { LabeledContent { Text(tournament.groupStageStatus()) @@ -55,19 +55,24 @@ struct TournamentRunningView: View { } } } - } - - Section { - NavigationLink(value: Screen.round) { - LabeledContent { - Text(tournament.bracketStatus()) - .foregroundStyle(.master) - } label: { - Text("Tableau") + + if tournament.rounds().isEmpty == false { + NavigationLink(value: Screen.round) { + LabeledContent { + Text(tournament.bracketStatus()) + .foregroundStyle(.master) + } label: { + Text("Tableau") + } } } } - + + let allMatches = tournament.allMatches() + MatchListView(section: "en cours", matches: tournament.runningMatches(allMatches)).id(UUID()) + MatchListView(section: "disponible", matches: tournament.availableToStart(allMatches)).id(UUID()) + MatchListView(section: "à lancer", matches: tournament.readyMatches(allMatches)).id(UUID()) + MatchListView(section: "terminés", matches: tournament.finishedMatches(allMatches)).id(UUID()) } }