@ -10,7 +10,7 @@ import LeStorage
import CoreData
struct AddTeamView : View {
@ Environment ( \ . dismiss ) var dismiss
private var fetchRequest : FetchRequest < ImportedPlayer >
@ -45,7 +45,7 @@ struct AddTeamView: View {
@ State private var displayWarningNotEnoughCharacter : Bool = false
@ State private var testMessageIndex : Int = 0
@ State private var presentLocalMultiplayerSearch : Bool = false
var tournamentStore : TournamentStore {
return self . tournament . tournamentStore
}
@ -61,11 +61,11 @@ struct AddTeamView: View {
createdPlayers . insert ( player )
createdPlayerIds . insert ( player . id )
}
_createdPlayers = . init ( wrappedValue : createdPlayers )
_createdPlayerIds = . init ( wrappedValue : createdPlayerIds )
}
let request : NSFetchRequest < ImportedPlayer > = ImportedPlayer . fetchRequest ( )
request . sortDescriptors = [ NSSortDescriptor ( keyPath : \ ImportedPlayer . rank , ascending : true ) ]
request . fetchLimit = 1000
@ -77,10 +77,10 @@ struct AddTeamView: View {
_textHeight = . init ( wrappedValue : Self . _calculateHeight ( text : pasteString ) )
cancelShouldDismiss = true
}
fetchRequest = FetchRequest ( fetchRequest : request , animation : . default )
}
var body : some View {
if let pasteString , pasteString . isEmpty = = false , fetchPlayers . isEmpty = = false {
computedBody
@ -89,7 +89,7 @@ struct AddTeamView: View {
computedBody
}
}
var computedBody : some View {
List ( selection : $ createdPlayerIds ) {
_buildingTeamView ( )
@ -119,7 +119,7 @@ struct AddTeamView: View {
Button ( " Créer l'équipe quand même " ) {
_createTeam ( checkDuplicates : false , checkHomonym : false )
}
Button ( " Annuler " , role : . cancel ) {
confirmHomonym = false
}
@ -132,7 +132,7 @@ struct AddTeamView: View {
Button ( " Créer l'équipe quand même " ) {
_createTeam ( checkDuplicates : false , checkHomonym : true )
}
Button ( " Annuler " , role : . cancel ) {
confirmDuplicate = false
}
@ -213,11 +213,11 @@ struct AddTeamView: View {
. buttonBorderShape ( . capsule )
}
}
ToolbarItem ( placement : . topBarTrailing ) {
Button {
let generalString = UIPasteboard . general . string ? ? " "
#if targetEnvironment ( simulator )
let s = testMessages [ testMessageIndex % testMessages . count ]
handlePasteString ( s )
@ -241,15 +241,15 @@ struct AddTeamView: View {
. navigationTitle ( editedTeam = = nil ? " Ajouter une équipe " : " Modifier l'équipe " )
. environment ( \ . editMode , Binding . constant ( EditMode . active ) )
}
private func _isEditingTeam ( ) -> Bool {
createdPlayerIds . isEmpty = = false || editedTeam != nil || pasteString != nil
}
var unsortedPlayers : [ PlayerRegistration ] {
tournament . unsortedPlayers ( )
}
@ ViewBuilder
private func _managementView ( ) -> some View {
Section {
@ -261,7 +261,7 @@ struct AddTeamView: View {
Text ( " Cherchez dans la base fédérale de \( rankSourceDate . monthYearFormatted ) , vous y trouverez tous les joueurs ayant participé à au moins un tournoi dans les 12 derniers mois. " )
}
}
if tournament . isAnimation ( ) , createdPlayers . isEmpty = = true {
Section {
RowButtonView ( " Ajouter plusieurs joueurs du club " ) {
@ -271,7 +271,7 @@ struct AddTeamView: View {
Text ( " Crée une équipe par joueur sélectionné " )
}
}
Section {
RowButtonView ( " Créer un non classé / non licencié " ) {
if let pasteString , pasteString . isEmpty = = false {
@ -284,7 +284,7 @@ struct AddTeamView: View {
Text ( " Si le joueur n'a pas encore de licence ou n'a pas encore participé à une compétition, vous pouvez le créer vous-même. " )
}
}
private func _addPlayerSex ( ) -> Int {
switch tournament . tournamentCategory {
case . men , . unlisted :
@ -296,11 +296,11 @@ struct AddTeamView: View {
}
}
private func _filterOption ( ) -> PlayerFilterOption {
return tournament . tournamentCategory . playerFilterOption
}
private func _currentSelection ( ) -> Set < PlayerRegistration > {
var currentSelection = Set < PlayerRegistration > ( )
createdPlayerIds . compactMap { id in
@ -310,7 +310,7 @@ struct AddTeamView: View {
player . setComputedRank ( in : tournament )
currentSelection . insert ( player )
}
createdPlayerIds . compactMap { id in
createdPlayers . first ( where : { id = = $0 . id } )
} . forEach {
@ -326,7 +326,7 @@ struct AddTeamView: View {
} . forEach { player in
currentSelection . append ( player . license )
}
createdPlayerIds . compactMap { id in
createdPlayers . first ( where : { id = = $0 . id } )
} . forEach {
@ -334,7 +334,7 @@ struct AddTeamView: View {
}
return currentSelection
}
private func _isDuplicate ( ) -> Bool {
if tournament . isAnimation ( ) { return false }
let ids : [ String ? ] = _currentSelectionIds ( )
@ -343,15 +343,15 @@ struct AddTeamView: View {
}
return false
}
private func _createTeam ( checkDuplicates : Bool , checkHomonym : Bool ) {
private func _createTeam ( checkDuplicates : Bool , checkHomonym : Bool ) {
if checkDuplicates && _isDuplicate ( ) {
confirmDuplicate = true
return
}
let players = _currentSelection ( )
if checkHomonym {
homonyms = players . filter ( { $0 . hasHomonym ( ) } )
if homonyms . isEmpty = = false {
@ -359,31 +359,23 @@ struct AddTeamView: View {
return
}
}
let team = tournament . addTeam ( players )
do {
try self . tournamentStore . teamRegistrations . addOrUpdate ( instance : team )
} catch {
Logger . error ( error )
}
do {
try self . tournamentStore . playerRegistrations . addOrUpdate ( contentOfs : players )
} catch {
Logger . error ( error )
}
pasteString = nil
editableTextField = " "
if team . players ( ) . count > 1 {
createdPlayers . removeAll ( )
createdPlayerIds . removeAll ( )
dismiss ( )
} else {
editedTeam = team
}
self . tournamentStore . teamRegistrations . addOrUpdate ( instance : team )
self . tournamentStore . playerRegistrations . addOrUpdate ( contentOfs : players )
pasteString = nil
editableTextField = " "
if team . players ( ) . count > 1 {
createdPlayers . removeAll ( )
createdPlayerIds . removeAll ( )
dismiss ( )
} else {
editedTeam = team
}
}
private func _updateTeam ( checkDuplicates : Bool ) {
guard let editedTeam else { return }
if checkDuplicates && _isDuplicate ( ) {
@ -393,17 +385,9 @@ struct AddTeamView: View {
let players = _currentSelection ( )
editedTeam . updatePlayers ( players , inTournamentCategory : tournament . tournamentCategory )
do {
try self . tournamentStore . teamRegistrations . addOrUpdate ( instance : editedTeam )
} catch {
Logger . error ( error )
}
do {
try self . tournamentStore . playerRegistrations . addOrUpdate ( contentOfs : players )
} catch {
Logger . error ( error )
}
self . tournamentStore . teamRegistrations . addOrUpdate ( instance : editedTeam )
self . tournamentStore . playerRegistrations . addOrUpdate ( contentOfs : players )
pasteString = nil
editableTextField = " "
@ -411,7 +395,7 @@ struct AddTeamView: View {
dismiss ( )
}
}
// C a l c u l a t i n g t h e h e i g h t b a s e d o n t h e c o n t e n t o f t h e T e x t E d i t o r
static private func _calculateHeight ( text : String ) -> CGFloat {
let size = CGSize ( width : UIScreen . main . bounds . width - 32 , height : . infinity )
@ -424,15 +408,23 @@ struct AddTeamView: View {
)
return max ( boundingRect . height + 20 , 40 ) // A d d s o m e p a d d i n g a n d s e t a m i n i m u m h e i g h t
}
@ ViewBuilder
private func _buildingTeamView ( ) -> some View {
struct PasteStringSection : View {
let pasteString : String ?
@ Binding var editableTextField : String
@ Binding var textHeight : CGFloat
@ FocusState var focusedField : AddTeamView . FocusField ?
var handlePasteString : ( String ) -> Void
@ Binding var displayWarningNotEnoughCharacter : Bool
var body : some View {
if let pasteString {
Section {
TextEditor ( text : $ editableTextField )
. frame ( height : textHeight )
. onChange ( of : editableTextField ) {
textHeight = Self . _calculateHeight ( text : pasteString )
textHeight = AddTeamView . _calculateHeight ( text : pasteString )
}
. focused ( $ focusedField , equals : . pasteField )
. toolbar {
@ -464,128 +456,111 @@ struct AddTeamView: View {
FooterButtonView ( " effacer le texte " ) {
self . focusedField = nil
self . editableTextField = " "
self . pasteString = nil
self . handlePasteString ( " " )
}
}
}
}
Section {
ForEach ( createdPlayerIds . sorted ( ) , id : \ . self ) { id in
if let p = createdPlayers . first ( where : { $0 . id = = id } ) {
VStack ( alignment : . leading , spacing : 0 ) {
if let player = unsortedPlayers . first ( where : { ( $0 . licenceId = = p . licenceId && $0 . licenceId != nil ) } ) , editedTeam ? . includes ( player : player ) = = false {
Text ( " Déjà inscrit ! " ) . foregroundStyle ( . logoRed ) . bold ( )
}
if tournament . isPlayerAgeInadequate ( player : p ) {
Text ( " Âge invalide ! " ) . foregroundStyle ( . logoRed ) . bold ( )
}
if tournament . isPlayerRankInadequate ( player : p ) {
Text ( " Trop bien classé ! " ) . foregroundStyle ( . logoRed ) . bold ( )
}
PlayerView ( player : p ) . tag ( p . id )
. environment ( tournament )
}
}
if let p = fetchPlayers . first ( where : { $0 . license = = id } ) {
VStack ( alignment : . leading , spacing : 0 ) {
if let pasteString , pasteString . isEmpty = = false , unsortedPlayers . first ( where : { $0 . licenceId = = p . license } ) != nil {
Text ( " Déjà inscrit ! " ) . foregroundStyle ( . logoRed ) . bold ( )
}
if tournament . isPlayerAgeInadequate ( player : p ) {
Text ( " Âge invalide ! " ) . foregroundStyle ( . logoRed ) . bold ( )
}
if tournament . isPlayerRankInadequate ( player : p ) {
Text ( " Trop bien classé ! " ) . foregroundStyle ( . logoRed ) . bold ( )
}
ImportedPlayerView ( player : p ) . tag ( p . license ! )
}
}
}
if editedTeam = = nil {
if createdPlayerIds . isEmpty {
RowButtonView ( " Bloquer une place " ) {
_createTeam ( checkDuplicates : false , checkHomonym : false )
}
} else {
RowButtonView ( " Ajouter l'équipe " ) {
_createTeam ( checkDuplicates : true , checkHomonym : true )
}
}
} else {
RowButtonView ( " Confirmer " ) {
_updateTeam ( checkDuplicates : false )
dismiss ( )
}
}
} header : {
let _currentSelection = _currentSelection ( )
let selectedSortedTeams = tournament . selectedSortedTeams ( )
let rank = _currentSelection . map {
$0 . computedRank
} . reduce ( 0 , + )
let teamIndex = selectedSortedTeams . firstIndex ( where : { $0 . weight >= rank } ) ? ? selectedSortedTeams . count
if _currentSelection . isEmpty = = false , tournament . hideWeight ( ) = = false , rank > 0 {
HStack ( spacing : 16.0 ) {
VStack ( alignment : . leading , spacing : 0 ) {
Text ( " Rang " ) . font ( . caption )
Text ( " # " + ( teamIndex + 1 ) . formatted ( ) )
}
}
}
VStack ( alignment : . leading , spacing : 0 ) {
Text ( " Poids " ) . font ( . caption )
Text ( rank . formatted ( ) )
}
Spacer ( )
VStack ( alignment : . trailing , spacing : 0 ) {
Text ( " " ) . font ( . caption )
Text ( tournament . cutLabel ( index : teamIndex , teamCount : selectedSortedTeams . count ) )
}
}
// } e l s e {
// T e x t ( " P r é p a r a t i o n d e l ' é q u i p e " )
}
}
@ ViewBuilder
private func _buildingTeamView ( ) -> some View {
PasteStringSection (
pasteString : pasteString ,
editableTextField : $ editableTextField ,
textHeight : $ textHeight ,
focusedField : _focusedField ,
handlePasteString : handlePasteString ,
displayWarningNotEnoughCharacter : $ displayWarningNotEnoughCharacter
)
TeamSelectionSection (
createdPlayerIds : createdPlayerIds ,
createdPlayers : createdPlayers ,
unsortedPlayers : unsortedPlayers ,
fetchPlayers : fetchPlayers ,
editedTeam : editedTeam ,
pasteString : pasteString ,
tournament : tournament ,
_createTeam : _createTeam ,
_updateTeam : _updateTeam ,
dismiss : dismiss ,
_currentSelection : _currentSelection
)
if let pasteString , pasteString . isEmpty = = false {
let sortedPlayers = _searchFilteredPlayers ( )
if sortedPlayers . isEmpty {
ContentUnavailableView {
Label ( " Aucun résultat " , systemImage : " person.2.slash " )
} description : {
Text ( " Aucun joueur classé n'a été trouvé dans ce message. Attention, si un joueur n'a pas joué de tournoi dans les 12 derniers, Padel Club ne pourra pas le trouver. " )
} actions : {
RowButtonView ( " Créer un joueur non classé " ) {
selectionSearchField = pasteString
}
RowButtonView ( " Chercher dans la base " ) {
presentPlayerSearch = true
}
let sortedPlayers = _searchFilteredPlayers ( )
if sortedPlayers . isEmpty {
ContentUnavailableView {
Label ( " Aucun résultat " , systemImage : " person.2.slash " )
} description : {
Text ( " Aucun joueur classé n'a été trouvé dans ce message. Attention, si un joueur n'a pas joué de tournoi dans les 12 derniers, Padel Club ne pourra pas le trouver. " )
} actions : {
RowButtonView ( " Créer un joueur non classé " ) {
selectionSearchField = pasteString
}
RowButtonView ( " Effacer cette recherche " ) {
self . pasteString = nil
self . editableTextField = " "
}
RowButtonView ( " Chercher dans la base " ) {
presentPlayerSearch = true
}
} else {
_listOfPlayers ( searchFilteredPlayers : sortedPlayers , pasteString : pasteString )
RowButtonView ( " Effacer cette recherche " ) {
self . pasteString = nil
self . editableTextField = " "
}
}
} else {
_managementView ( )
_listOfPlayers ( searchFilteredPlayers : sortedPlayers , pasteString : pasteString )
}
} else {
_managementView ( )
}
}
//
// i f l e t p a s t e S t r i n g , p a s t e S t r i n g . i s E m p t y = = f a l s e {
// l e t s o r t e d P l a y e r s = _ s e a r c h F i l t e r e d P l a y e r s ( )
//
// i f s o r t e d P l a y e r s . i s E m p t y {
// C o n t e n t U n a v a i l a b l e V i e w {
// L a b e l ( " A u c u n r é s u l t a t " , s y s t e m I m a g e : " p e r s o n . 2 . s l a s h " )
// } d e s c r i p t i o n : {
// T e x t ( " A u c u n j o u e u r c l a s s é n ' a é t é t r o u v é d a n s c e m e s s a g e . A t t e n t i o n , s i u n j o u e u r n ' a p a s j o u é d e t o u r n o i d a n s l e s 1 2 d e r n i e r s , P a d e l C l u b n e p o u r r a p a s l e t r o u v e r . " )
// } a c t i o n s : {
// R o w B u t t o n V i e w ( " C r é e r u n j o u e u r n o n c l a s s é " ) {
// s e l e c t i o n S e a r c h F i e l d = p a s t e S t r i n g
// }
//
// R o w B u t t o n V i e w ( " C h e r c h e r d a n s l a b a s e " ) {
// p r e s e n t P l a y e r S e a r c h = t r u e
// }
//
// R o w B u t t o n V i e w ( " E f f a c e r c e t t e r e c h e r c h e " ) {
// s e l f . p a s t e S t r i n g = n i l
// s e l f . e d i t a b l e T e x t F i e l d = " "
// }
// }
//
// } e l s e {
// _ l i s t O f P l a y e r s ( s e a r c h F i l t e r e d P l a y e r s : s o r t e d P l a y e r s , p a s t e S t r i n g : p a s t e S t r i n g )
// }
// } e l s e {
// _ m a n a g e m e n t V i e w ( )
// }
// }
@ MainActor
func hitForSearch ( _ ip : ImportedPlayer , _ pasteString : String ? ) -> Int {
guard let pasteString else { return 0 }
let _searchForHit = pasteString . hashValue
if searchForHit != _searchForHit {
DispatchQueue . main . async {
DispatchQueue . main . async {
searchForHit = _searchForHit
hitsForSearch = [ : ]
}
@ -615,7 +590,7 @@ struct AddTeamView: View {
}
return 1
}
@ MainActor
private func handlePasteString ( _ first : String ) {
if first . isEmpty = = false {
@ -633,7 +608,7 @@ struct AddTeamView: View {
@ ViewBuilder
private func _listOfPlayers ( searchFilteredPlayers : [ ImportedPlayer ] , pasteString : String ) -> some View {
let sortedPlayers = _sortedPlayers ( searchFilteredPlayers : searchFilteredPlayers , pasteString : pasteString )
Section {
ForEach ( sortedPlayers ) { player in
ImportedPlayerView ( player : player ) . tag ( player . license ! )
@ -644,7 +619,7 @@ struct AddTeamView: View {
}
}
private func _searchFilteredPlayers ( ) -> [ ImportedPlayer ] {
if searchField . isEmpty {
return Array ( fetchPlayers )
@ -652,12 +627,171 @@ struct AddTeamView: View {
return fetchPlayers . filter ( { $0 . contains ( searchField ) } )
}
}
private func _sortedPlayers ( searchFilteredPlayers : [ ImportedPlayer ] , pasteString : String ) -> [ ImportedPlayer ] {
return searchFilteredPlayers . sorted ( by : { hitForSearch ( $0 , pasteString ) > hitForSearch ( $1 , pasteString ) } )
}
}
struct TeamSelectionSection : View {
let createdPlayerIds : Set < String >
let createdPlayers : Set < PlayerRegistration >
let unsortedPlayers : [ PlayerRegistration ]
let fetchPlayers : FetchedResults < ImportedPlayer >
let editedTeam : TeamRegistration ?
let pasteString : String ?
let tournament : Tournament
let _createTeam : ( Bool , Bool ) -> Void
let _updateTeam : ( Bool ) -> Void
let dismiss : DismissAction
let _currentSelection : ( ) -> Set < PlayerRegistration >
var body : some View {
Section {
PlayerList ( createdPlayerIds : createdPlayerIds ,
createdPlayers : createdPlayers ,
unsortedPlayers : unsortedPlayers ,
fetchPlayers : fetchPlayers ,
editedTeam : editedTeam ,
pasteString : pasteString ,
tournament : tournament )
ActionButton ( editedTeam : editedTeam ,
createdPlayerIds : createdPlayerIds ,
_createTeam : _createTeam ,
_updateTeam : _updateTeam ,
dismiss : dismiss )
} header : {
TeamHeader ( tournament : tournament ,
_currentSelection : _currentSelection )
}
}
}
struct PlayerList : View {
let createdPlayerIds : Set < String >
let createdPlayers : Set < PlayerRegistration >
let unsortedPlayers : [ PlayerRegistration ]
let fetchPlayers : FetchedResults < ImportedPlayer >
let editedTeam : TeamRegistration ?
let pasteString : String ?
let tournament : Tournament
var body : some View {
ForEach ( createdPlayerIds . sorted ( ) , id : \ . self ) { id in
if let p = createdPlayers . first ( where : { $0 . id = = id } ) {
CreatedPlayerView ( player : p , unsortedPlayers : unsortedPlayers , editedTeam : editedTeam , tournament : tournament )
}
if let p = fetchPlayers . first ( where : { $0 . license = = id } ) {
FetchedPlayerView ( player : p , unsortedPlayers : unsortedPlayers , pasteString : pasteString , tournament : tournament )
}
}
}
}
struct CreatedPlayerView : View {
let player : PlayerRegistration
let unsortedPlayers : [ PlayerRegistration ]
let editedTeam : TeamRegistration ?
let tournament : Tournament
var body : some View {
VStack ( alignment : . leading , spacing : 0 ) {
if let existingPlayer = unsortedPlayers . first ( where : { ( $0 . licenceId = = player . licenceId && $0 . licenceId != nil ) } ) , editedTeam ? . includes ( player : existingPlayer ) = = false {
Text ( " Déjà inscrit ! " ) . foregroundStyle ( . logoRed ) . bold ( )
}
if tournament . isPlayerAgeInadequate ( player : player ) {
Text ( " Âge invalide ! " ) . foregroundStyle ( . logoRed ) . bold ( )
}
if tournament . isPlayerRankInadequate ( player : player ) {
Text ( " Trop bien classé ! " ) . foregroundStyle ( . logoRed ) . bold ( )
}
PlayerView ( player : player ) . tag ( player . id )
. environment ( tournament )
}
}
}
struct FetchedPlayerView : View {
let player : ImportedPlayer
let unsortedPlayers : [ PlayerRegistration ]
let pasteString : String ?
let tournament : Tournament
var body : some View {
VStack ( alignment : . leading , spacing : 0 ) {
if let pasteString , pasteString . isEmpty = = false , unsortedPlayers . first ( where : { $0 . licenceId = = player . license } ) != nil {
Text ( " Déjà inscrit ! " ) . foregroundStyle ( . logoRed ) . bold ( )
}
if tournament . isPlayerAgeInadequate ( player : player ) {
Text ( " Âge invalide ! " ) . foregroundStyle ( . logoRed ) . bold ( )
}
if tournament . isPlayerRankInadequate ( player : player ) {
Text ( " Trop bien classé ! " ) . foregroundStyle ( . logoRed ) . bold ( )
}
ImportedPlayerView ( player : player ) . tag ( player . license ! )
}
}
}
struct ActionButton : View {
let editedTeam : TeamRegistration ?
let createdPlayerIds : Set < String >
let _createTeam : ( Bool , Bool ) -> Void
let _updateTeam : ( Bool ) -> Void
let dismiss : DismissAction
var body : some View {
if editedTeam = = nil {
if createdPlayerIds . isEmpty {
RowButtonView ( " Bloquer une place " ) {
_createTeam ( false , false )
}
} else {
RowButtonView ( " Ajouter l'équipe " ) {
_createTeam ( true , true )
}
}
} else {
RowButtonView ( " Confirmer " ) {
_updateTeam ( false )
dismiss ( )
}
}
}
}
struct TeamHeader : View {
let tournament : Tournament
let _currentSelection : ( ) -> Set < PlayerRegistration >
var body : some View {
let currentSelection = _currentSelection ( )
let selectedSortedTeams = tournament . selectedSortedTeams ( )
let rank = currentSelection . map { $0 . computedRank } . reduce ( 0 , + )
let teamIndex = selectedSortedTeams . firstIndex ( where : { $0 . weight >= rank } ) ? ? selectedSortedTeams . count
if ! currentSelection . isEmpty , ! tournament . hideWeight ( ) , rank > 0 {
HStack ( spacing : 16.0 ) {
VStack ( alignment : . leading , spacing : 0 ) {
Text ( " Rang " ) . font ( . caption )
Text ( " # " + ( teamIndex + 1 ) . formatted ( ) )
}
VStack ( alignment : . leading , spacing : 0 ) {
Text ( " Poids " ) . font ( . caption )
Text ( rank . formatted ( ) )
}
Spacer ( )
VStack ( alignment : . trailing , spacing : 0 ) {
Text ( " " ) . font ( . caption )
Text ( tournament . cutLabel ( index : teamIndex , teamCount : selectedSortedTeams . count ) )
}
}
}
}
}
let testMessages = [
" Anthony dovetta ( 3620578 K )et christophe capeau ( 4666443v) " ,
" " "
@ -684,6 +818,6 @@ Tullou Benjamin 8990867f
" " " ,
" " "
Sms Julien La Croix + 33622886688
Salut Raz , c ' est ! Ju Lacroix J ' espère que tu vas bien depuis le temps ! Est - ce que tu peux nous inscrire au 1000 de Bandol avec Derek Gerson stp ?
Salut Raz , c ' est ! Ju Lacroix J ' espère que tu vas bien depuis le temps ! Est - ce que tu peux nous inscrire au 1000 de Bandol avec Derek Gerson stp ?
" " "
]