parent
230181fe31
commit
c31062fc70
@ -0,0 +1,110 @@ |
||||
// |
||||
// MatchDateView.swift |
||||
// Padel Tournament |
||||
// |
||||
// Created by Razmig Sarkissian on 25/11/2023. |
||||
// |
||||
|
||||
import SwiftUI |
||||
|
||||
struct MatchDateView: View { |
||||
var match: Match |
||||
var showPrefix: Bool = false |
||||
|
||||
var body: some View { |
||||
Menu { |
||||
if match.startDate == nil { |
||||
Button("Commencer") { |
||||
match.startDate = Date() |
||||
save() |
||||
} |
||||
Button("Échauffement") { |
||||
match.startDate = Calendar.current.date(byAdding: .minute, value: 5, to: Date()) |
||||
save() |
||||
} |
||||
} else { |
||||
Button("Recommencer") { |
||||
match.startDate = Date() |
||||
match.endDate = nil |
||||
save() |
||||
} |
||||
Button("Remise à zéro") { |
||||
match.startDate = nil |
||||
match.endDate = nil |
||||
save() |
||||
} |
||||
} |
||||
} label: { |
||||
label |
||||
} |
||||
.buttonStyle(.plain) |
||||
} |
||||
|
||||
@ViewBuilder |
||||
var label: some View { |
||||
HStack { |
||||
VStack(alignment: .trailing) { |
||||
if match.hasWalkoutTeam() == false { |
||||
if let startDate = match.startDate, match.endDate == nil { |
||||
if startDate.timeIntervalSinceNow < 0 { |
||||
if showPrefix { |
||||
Text("en cours").font(.footnote).foregroundStyle(.secondary) |
||||
} |
||||
Text(startDate, style: .timer) |
||||
.monospacedDigit() |
||||
} else if startDate.timeIntervalSinceNow <= 7200 && showPrefix { |
||||
if showPrefix { |
||||
Text("démarre dans") |
||||
.font(.footnote).foregroundStyle(.secondary) |
||||
} |
||||
Text(startDate, style: .timer) |
||||
.monospacedDigit() |
||||
} else { |
||||
if showPrefix { |
||||
Text("le " + startDate.formatted(date: .abbreviated, time: .omitted)) |
||||
.font(.footnote).foregroundStyle(.secondary) |
||||
Text("à " + startDate.formatted(date: .omitted, time: .shortened)) |
||||
.monospacedDigit() |
||||
} else { |
||||
Text(startDate.formatted(date: .abbreviated, time: .shortened)) |
||||
.monospacedDigit() |
||||
} |
||||
} |
||||
} |
||||
if let startDate = match.startDate, let endDate = match.endDate { |
||||
let duration = Duration( |
||||
secondsComponent: Int64(endDate.timeIntervalSince(startDate)), |
||||
attosecondsComponent: 0 |
||||
).formatted(.units(allowed: [.hours, .minutes], width: .narrow)) |
||||
if showPrefix { |
||||
Text("durée").font(.footnote).foregroundStyle(.secondary) |
||||
} |
||||
Text(duration) |
||||
.monospacedDigit() |
||||
} |
||||
|
||||
if match.startDate == nil { |
||||
Text("démarrage").font(.footnote).foregroundStyle(.secondary) |
||||
Text("non défini") |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
func save() { |
||||
do { |
||||
// match.currentTournament?.objectWillChange.send() |
||||
// match.objectWillChange.send() |
||||
// try viewContext.save() |
||||
} 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. |
||||
let nsError = error as NSError |
||||
fatalError("Unresolved error \(nsError), \(nsError.userInfo)") |
||||
} |
||||
} |
||||
|
||||
|
||||
} |
||||
|
||||
@ -0,0 +1,35 @@ |
||||
// |
||||
// MatchSetupView.swift |
||||
// PadelClub |
||||
// |
||||
// Created by Razmig Sarkissian on 23/03/2024. |
||||
// |
||||
|
||||
import SwiftUI |
||||
|
||||
struct MatchSetupView: View { |
||||
var match: Match |
||||
|
||||
var body: some View { |
||||
HStack { |
||||
VStack(alignment: .leading) { |
||||
_teamView(match.team(.one), index: 0) |
||||
_teamView(match.team(.two), index: 1) |
||||
} |
||||
} |
||||
} |
||||
|
||||
@ViewBuilder |
||||
func _teamView(_ team: TeamRegistration?, index: Int) -> some View { |
||||
if let team { |
||||
TeamDetailView(team: team) |
||||
} else { |
||||
TeamPickerView(match: match, index: match.index*2 + 1 + index) |
||||
.disabled(match.groupStage != nil) |
||||
} |
||||
} |
||||
} |
||||
|
||||
#Preview { |
||||
MatchSetupView(match: Match.mock()) |
||||
} |
||||
@ -0,0 +1,87 @@ |
||||
// |
||||
// PlayerBlockView.swift |
||||
// Padel Tournament |
||||
// |
||||
// Created by Razmig Sarkissian on 25/11/2023. |
||||
// |
||||
|
||||
import SwiftUI |
||||
|
||||
struct PlayerBlockView: View { |
||||
var match: Match |
||||
|
||||
let team: TeamData |
||||
let color: Color |
||||
let width: CGFloat |
||||
|
||||
var names: [String]? { |
||||
match.teamNames(team) |
||||
} |
||||
|
||||
var hasWon: Bool { |
||||
match.teamWon(team) |
||||
} |
||||
|
||||
var hideScore: Bool { |
||||
match.hasWalkoutTeam() |
||||
} |
||||
|
||||
var isWalkOut: Bool { |
||||
match.teamWalkOut(team) |
||||
} |
||||
|
||||
var scores: [String] { |
||||
match.teamScore(team)?.score?.components(separatedBy: ",") ?? [] |
||||
} |
||||
|
||||
private func _defaultLabel() -> String { |
||||
team.localizedLabel() |
||||
} |
||||
|
||||
var body: some View { |
||||
HStack { |
||||
VStack(alignment: .leading) { |
||||
if let names { |
||||
ForEach(names, id: \.self) { name in |
||||
Text(name).lineLimit(1) |
||||
} |
||||
} else { |
||||
ZStack(alignment: .leading) { |
||||
VStack { |
||||
Text("longLabelPlayerOne").lineLimit(1) |
||||
Text("longLabelPlayerTwo").lineLimit(1) |
||||
} |
||||
.opacity(0) |
||||
Text(_defaultLabel()).foregroundStyle(.secondary).lineLimit(1) |
||||
} |
||||
} |
||||
} |
||||
.bold(hasWon) |
||||
Spacer() |
||||
if hasWon { |
||||
Image(systemName: "trophy") |
||||
} else if isWalkOut { |
||||
Text("WO") |
||||
} |
||||
|
||||
if hideScore == false { |
||||
ForEach(scores.indices, id: \.self) { index in |
||||
let string = scores[index] |
||||
if string.isEmpty == false { |
||||
if width == 1 { |
||||
Divider() |
||||
} else { |
||||
Divider().frame(width: width).overlay(color) |
||||
} |
||||
Text(string) |
||||
.font(.title3) |
||||
.frame(maxWidth: 20) |
||||
.scaledToFill() |
||||
.minimumScaleFactor(0.5) |
||||
.lineLimit(1) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,229 @@ |
||||
// |
||||
// PlayerPopoverView.swift |
||||
// PadelClub |
||||
// |
||||
// Created by Razmig Sarkissian on 24/03/2024. |
||||
// |
||||
|
||||
import SwiftUI |
||||
|
||||
struct PlayerPopoverView: View { |
||||
enum PlayerCreationField { |
||||
case firstName, lastName, license, rank |
||||
} |
||||
|
||||
@Environment(\.dismiss) var dismiss |
||||
|
||||
@State private var displayWrongLicenceError: Bool = false |
||||
@State private var firstName: String = "" |
||||
@State private var lastName: String = "" |
||||
@State private var license: String = "" |
||||
@State private var rank: Int? |
||||
@State var sex: Int = 1 |
||||
|
||||
let requiredField: [PlayerCreationField] |
||||
let creationCompletionHandler: ((PlayerRegistration) -> Void) |
||||
|
||||
@FocusState private var firstNameIsFocused: Bool |
||||
@FocusState private var lastNameIsFocused: Bool |
||||
@FocusState private var licenseIsFocused: Bool |
||||
@FocusState private var amountIsFocused: Bool |
||||
|
||||
static var source: String? |
||||
|
||||
init(sex: Int, requiredField: [PlayerCreationField] = [.firstName, .lastName], creationCompletionHandler: @escaping (PlayerRegistration) -> Void) { |
||||
let source = PlayerPopoverView.source |
||||
if let source { |
||||
let words = source.components(separatedBy: .whitespaces) |
||||
if words.isEmpty == false { |
||||
_firstName = State(wrappedValue: words.first?.capitalized ?? "") |
||||
|
||||
if words.count > 1 { |
||||
_lastName = State(wrappedValue: words.last?.capitalized ?? "") |
||||
} |
||||
} else { |
||||
_firstName = State(wrappedValue: source) |
||||
} |
||||
} |
||||
|
||||
_sex = State(wrappedValue: sex) |
||||
_rank = State(wrappedValue: nil) |
||||
|
||||
self.requiredField = requiredField |
||||
self.creationCompletionHandler = creationCompletionHandler |
||||
self.pasteBoard = UIPasteboard.general.string |
||||
} |
||||
|
||||
@State private var pasteBoard: String? |
||||
|
||||
var body: some View { |
||||
NavigationStack { |
||||
List { |
||||
if let pasteBoard { |
||||
Section { |
||||
Text(pasteBoard).foregroundColor(.clear).padding(8) |
||||
.frame(maxWidth: .infinity) |
||||
.overlay( |
||||
TextEditor(text: .constant(pasteBoard)) |
||||
) |
||||
.frame(minHeight: 20.0) |
||||
} header: { |
||||
HStack { |
||||
Spacer() |
||||
Button { |
||||
self.pasteBoard = "" |
||||
} label: { |
||||
Text("effacer le contenu du presse-papier") |
||||
} |
||||
.buttonStyle(.borderless) |
||||
} |
||||
} |
||||
.textCase(nil) |
||||
} |
||||
Section { |
||||
Picker(selection: $sex) { |
||||
Text("Homme").tag(1 as Int) |
||||
Text("Femme").tag(0 as Int) |
||||
} label: { |
||||
|
||||
} |
||||
.labelsHidden() |
||||
.pickerStyle(.segmented) |
||||
|
||||
HStack { |
||||
Text("Prénom").foregroundStyle(.secondary) |
||||
Spacer() |
||||
TextField("Prénom", text: $firstName) |
||||
.submitLabel(.next) |
||||
.focused($firstNameIsFocused) |
||||
.onSubmit { |
||||
firstName = firstName.trimmed |
||||
lastNameIsFocused = true |
||||
} |
||||
.fixedSize() |
||||
} |
||||
HStack { |
||||
Text("Nom").foregroundStyle(.secondary) |
||||
Spacer() |
||||
TextField("Nom", text: $lastName) |
||||
.focused($lastNameIsFocused) |
||||
.submitLabel(.next) |
||||
.onSubmit { |
||||
lastName = lastName.trimmed |
||||
licenseIsFocused = true |
||||
} |
||||
.fixedSize() |
||||
} |
||||
HStack { |
||||
Text("Licence").foregroundStyle(.secondary) |
||||
Spacer() |
||||
TextField("Licence", text: $license) |
||||
.focused($licenseIsFocused) |
||||
.keyboardType(.namePhonePad) |
||||
.submitLabel(.next) |
||||
.onSubmit { |
||||
license = license.trimmed |
||||
if requiredField.contains(.license) { |
||||
if license.isLicenseNumber { |
||||
amountIsFocused = true |
||||
} else { |
||||
displayWrongLicenceError = true |
||||
} |
||||
} else { |
||||
amountIsFocused = true |
||||
} |
||||
} |
||||
.fixedSize() |
||||
} |
||||
HStack { |
||||
Text("Rang").foregroundStyle(.secondary) |
||||
Spacer() |
||||
TextField("Non classé", value: $rank, format: .number) |
||||
.focused($amountIsFocused) |
||||
.keyboardType(.asciiCapable) |
||||
.submitLabel(.done) |
||||
.fixedSize() |
||||
} |
||||
} header: { |
||||
HStack { |
||||
Spacer() |
||||
Button { |
||||
let last = firstName |
||||
firstName = lastName |
||||
lastName = last |
||||
} label: { |
||||
Text("inverser nom & prénom") |
||||
}.buttonStyle(.borderless) |
||||
} |
||||
.textCase(nil) |
||||
} |
||||
.multilineTextAlignment(.trailing) |
||||
|
||||
Section { |
||||
RowButtonView(title: "Valider et ajouter un autre") { |
||||
createManualPlayer() |
||||
lastName = "" |
||||
firstName = "" |
||||
license = "" |
||||
rank = nil |
||||
firstNameIsFocused = true |
||||
} |
||||
} |
||||
} |
||||
.onAppear { |
||||
firstNameIsFocused = true |
||||
} |
||||
.autocorrectionDisabled() |
||||
.navigationTitle(sex == 1 ? "Nouveau joueur" : "Nouvelle joueuse") |
||||
.navigationBarTitleDisplayMode(.inline) |
||||
.toolbarBackground(.visible, for: .navigationBar) |
||||
.toolbar { |
||||
ToolbarItem(placement: .confirmationAction) { |
||||
Button("Valider") { |
||||
createManualPlayer() |
||||
dismiss() |
||||
} |
||||
.clipShape(Capsule()) |
||||
.buttonStyle(.bordered) |
||||
|
||||
} |
||||
ToolbarItem(placement: .cancellationAction) { |
||||
Button("Annuler", role : .cancel) { |
||||
dismiss() |
||||
} |
||||
} |
||||
} |
||||
.alert("Attention", isPresented: $displayWrongLicenceError) { |
||||
Button("OK") { |
||||
|
||||
} |
||||
} message: { |
||||
Text("La licence n'est pas valide") |
||||
} |
||||
} |
||||
} |
||||
|
||||
func createManualPlayer() { |
||||
guard (lastName.isEmpty == false && requiredField.contains(.lastName)) || requiredField.contains(.lastName) == false else { |
||||
return |
||||
} |
||||
|
||||
guard (license.isEmpty == false && license.isLicenseNumber && requiredField.contains(.license)) || requiredField.contains(.license) == false else { |
||||
return |
||||
} |
||||
|
||||
guard (firstName.isEmpty == false && requiredField.contains(.firstName)) || requiredField.contains(.firstName) == false else { |
||||
return |
||||
} |
||||
|
||||
let playerRegistration = PlayerRegistration(firstName: firstName, lastName: lastName, licenceId: license.trimmed.isEmpty ? nil : license, rank: rank, sex: sex) |
||||
self.creationCompletionHandler(playerRegistration) |
||||
} |
||||
|
||||
} |
||||
|
||||
#Preview { |
||||
PlayerPopoverView(sex: 1) { player in |
||||
|
||||
} |
||||
} |
||||
@ -0,0 +1,48 @@ |
||||
// |
||||
// PlayerSexPickerView.swift |
||||
// PadelClub |
||||
// |
||||
// Created by Razmig Sarkissian on 24/03/2024. |
||||
// |
||||
|
||||
import SwiftUI |
||||
|
||||
struct PlayerSexPickerView: View { |
||||
@Bindable var player: PlayerRegistration |
||||
|
||||
var body: some View { |
||||
HStack { |
||||
Text(player.playerLabel()) |
||||
Spacer() |
||||
Picker(selection: $player.sex) { |
||||
Text("Homme").tag(1 as Int64) |
||||
Text("Femme").tag(0 as Int64) |
||||
} label: { |
||||
|
||||
} |
||||
.pickerStyle(.segmented) |
||||
.fixedSize() |
||||
.onChange(of: player.sex) { |
||||
save() |
||||
} |
||||
} |
||||
} |
||||
|
||||
func save() { |
||||
do { |
||||
// player.objectWillChange.send() |
||||
// player.team?.entrant?.tournament?.objectWillChange.send() |
||||
// try viewContext.save() |
||||
} 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. |
||||
let nsError = error as NSError |
||||
fatalError("Unresolved error \(nsError), \(nsError.userInfo)") |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
#Preview { |
||||
PlayerSexPickerView(player: PlayerRegistration.mock()) |
||||
} |
||||
@ -0,0 +1,28 @@ |
||||
// |
||||
// PlayerView.swift |
||||
// PadelClub |
||||
// |
||||
// Created by Razmig Sarkissian on 24/03/2024. |
||||
// |
||||
|
||||
import SwiftUI |
||||
|
||||
struct PlayerView: View { |
||||
@EnvironmentObject var dataStore: DataStore |
||||
let player: PlayerRegistration |
||||
|
||||
var body: some View { |
||||
ImportedPlayerView(player: player) |
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) { |
||||
Button(role: .destructive) { |
||||
try? dataStore.playerRegistrations.delete(instance: player) |
||||
} label: { |
||||
LabelDelete() |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
#Preview { |
||||
PlayerView(player: PlayerRegistration.mock()) |
||||
} |
||||
@ -0,0 +1,27 @@ |
||||
// |
||||
// TeamDetailView.swift |
||||
// PadelClub |
||||
// |
||||
// Created by Razmig Sarkissian on 23/03/2024. |
||||
// |
||||
|
||||
import SwiftUI |
||||
|
||||
struct TeamDetailView: View { |
||||
@EnvironmentObject var dataStore: DataStore |
||||
var team: TeamRegistration |
||||
|
||||
var body: some View { |
||||
if team.players().isEmpty { |
||||
Text("Aucun joueur, espace réservé") |
||||
} else { |
||||
ForEach(team.players()) { player in |
||||
PlayerView(player: player) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
#Preview { |
||||
TeamDetailView(team: TeamRegistration.mock()) |
||||
} |
||||
@ -0,0 +1,22 @@ |
||||
// |
||||
// TeamPickerView.swift |
||||
// PadelClub |
||||
// |
||||
// Created by Razmig Sarkissian on 23/03/2024. |
||||
// |
||||
|
||||
import SwiftUI |
||||
|
||||
struct TeamPickerView: View { |
||||
var match: Match |
||||
var index: Int |
||||
|
||||
var body: some View { |
||||
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/) |
||||
} |
||||
} |
||||
|
||||
#Preview { |
||||
TeamPickerView(match: Match.mock(), index: 0) |
||||
|
||||
} |
||||
@ -0,0 +1,21 @@ |
||||
// |
||||
// TeamRowView.swift |
||||
// PadelClub |
||||
// |
||||
// Created by Razmig Sarkissian on 24/03/2024. |
||||
// |
||||
|
||||
import SwiftUI |
||||
|
||||
struct TeamRowView: View { |
||||
@EnvironmentObject var dataStore: DataStore |
||||
var team: TeamRegistration |
||||
|
||||
var body: some View { |
||||
TeamDetailView(team: team) |
||||
} |
||||
} |
||||
|
||||
#Preview { |
||||
TeamRowView(team: TeamRegistration.mock()) |
||||
} |
||||
@ -0,0 +1,88 @@ |
||||
// |
||||
// InscriptionTipsView.swift |
||||
// PadelClub |
||||
// |
||||
// Created by Razmig Sarkissian on 24/03/2024. |
||||
// |
||||
|
||||
import SwiftUI |
||||
import TipKit |
||||
|
||||
struct InscriptionTipsView: View { |
||||
@Environment(Tournament.self) private var tournament: Tournament |
||||
|
||||
var body: some View { |
||||
List { |
||||
|
||||
Section { |
||||
|
||||
let fileTip = InscriptionManagerFileInputTip() |
||||
TipView(fileTip) { action in |
||||
if action.id == "website" { |
||||
} else if action.id == "add-team-file" { |
||||
} |
||||
} |
||||
.tipStyle(tint: nil) |
||||
} |
||||
|
||||
Section { |
||||
|
||||
let pasteTip = InscriptionManagerPasteInputTip() |
||||
TipView(pasteTip) { action in |
||||
if let paste = UIPasteboard.general.string { |
||||
//self.pasteField = paste |
||||
} |
||||
} |
||||
.tipStyle(tint: nil) |
||||
} |
||||
|
||||
Section { |
||||
|
||||
let searchTip = InscriptionManagerSearchInputTip() |
||||
TipView(searchTip) { action in |
||||
//presentPlayerCreation = true |
||||
} |
||||
.tipStyle(tint: nil) |
||||
} |
||||
|
||||
Section { |
||||
|
||||
let createTip = InscriptionManagerCreateInputTip() |
||||
TipView(createTip) { action in |
||||
//presentPlayerSelection = true |
||||
} |
||||
.tipStyle(tint: nil) |
||||
} |
||||
|
||||
Section { |
||||
ContentUnavailableView("Aucune équipe", systemImage: "person.2.slash", description: |
||||
Text("Vous n'avez encore aucune équipe dans votre liste d'attente.") |
||||
) |
||||
} |
||||
|
||||
// if let mostRecentDate, let currentRankSourceDate, currentRankSourceDate < mostRecentDate, tournament.isOver == false { |
||||
// |
||||
// if #available(iOS 17.0, *) { |
||||
// Section { |
||||
// let tip = InscriptionManagerRankUpdateTip() |
||||
// TipView(tip) { action in |
||||
// self.currentRankSourceDate = mostRecentDate |
||||
// } |
||||
// .tipStyle(tint: nil) |
||||
// } |
||||
// } |
||||
// |
||||
// rankingDateSourcePickerView(showDateInLabel: false) |
||||
// } else if tournament.currentRankSourceDate == nil { |
||||
// rankingDateSourcePickerView(showDateInLabel: false) |
||||
// } |
||||
// |
||||
|
||||
} |
||||
} |
||||
} |
||||
|
||||
#Preview { |
||||
InscriptionTipsView() |
||||
.environment(Tournament.mock()) |
||||
} |
||||
Loading…
Reference in new issue