From 83ee10ca6425aa552848fb8076b88f48a561036d Mon Sep 17 00:00:00 2001 From: cedrick2711 Date: Tue, 14 Jul 2026 13:35:30 -0400 Subject: [PATCH] ajout d'un calendrier pour sceduler des match --- __pycache__/app.cpython-313.pyc | Bin 0 -> 2965 bytes __pycache__/models.cpython-313.pyc | Bin 13680 -> 17396 bytes app.py | 2 + instance/team_tryouts.db | Bin 57344 -> 69632 bytes models.py | 47 ++- routes/__pycache__/matches.cpython-313.pyc | Bin 0 -> 19632 bytes routes/__pycache__/tryouts.cpython-313.pyc | Bin 19569 -> 20563 bytes routes/matches.py | 340 +++++++++++++++++++++ routes/tryouts.py | 20 +- static/css/style.css | 64 +++- templates/layouts/base.html | 7 + templates/pages/calendar.html | 145 +++++++++ templates/pages/create_match.html | 134 ++++++++ templates/pages/edit_match.html | 108 +++++++ templates/pages/view_tryout.html | 107 ++++++- 15 files changed, 968 insertions(+), 6 deletions(-) create mode 100644 __pycache__/app.cpython-313.pyc create mode 100644 routes/__pycache__/matches.cpython-313.pyc create mode 100644 routes/matches.py create mode 100644 templates/pages/calendar.html create mode 100644 templates/pages/create_match.html create mode 100644 templates/pages/edit_match.html diff --git a/__pycache__/app.cpython-313.pyc b/__pycache__/app.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6b5d54a78b43977049d752a996a752a75e9d361f GIT binary patch literal 2965 zcma)8-EY%Y6u(YxKAbpBTMAuMXwnY~Vf}&%jdg$)(zMiWS}jhup;q$T#7#pJCtN#> zRud{{Qu)%#!zO_=i9NDEgpEf+gD9rakj5VNR;@@(JndfB#;LTbI7;sC-1BkIIrp67 zZ25d1fS+p%UtU~t0q`dw%*Sbft?x1T3GjfYIDp7%<0#gKY$1Z}oSlUTvJT{6oyf_$ zkc)LAx6Xqc&3cdrV@CiW?+n8`hrR|tYyn`#+W}4*2O!^!FLB5`)>?TN@8)UV^Qk=q zGE{~Nd;ccPi7=%(MslLE5O!#`^sMH~6*5^_$cwU=k&p&c3Yyb=^P(~@6h%e(wt&)F zU20xRExf0(38+d})Rc7^03={L{%+lZB;aYvWKP>mbkf+9!8DfHeiCM)%niII=o-R+ zJjL5i8kVL&LQnioDV{olH+cIlT&Qy$yKtR4*R>1RO}NvHd0wKyt?-(LBS|v3?m}HpV`#o^6n!j_bHacAxCfv7c*V z_sdQl*K-HhX4w_OekYEb9$4!XWks`#gl$g=`<@bTPYK7K63#s(TstHNttd@oHj@qL zxdB{2Ce72p1=v=Z4uQ7X+dpM$S!?1m3!7;AjEVS0^L%r~6=GG>SyRqht@g(ELnlTo zj)@XuCbCvD3ZB*ng%6Ng*vB1ayJb8^raZM)@$kVPv!h5Abb)p-{kdt)1w;aObK|jLLb)ih_mkd|@^ zIzXgdilitw?KtEsQVK_b$jeFd;JOi2Nf2{6jgDPONkwuuni~~zxmhu_pt;kiP}JFu zOC<>{YK~N)ginGbyQmr42zxa~p5A%(VTiKIw|B1xG5(=mt}_8ow34Gr3t0nEKVBn?ujNtz~cPn)lEz z4Qt+xDs$`w({g9>Z71-wZgvC4zs&yorWw??Yyn6ge2vw0v%xYGtT64w9CwSm6F2Ps zFu6%%v3GA9uzH0K7#7|sTCQS$!}F$tBl7pZ>|-z^xLtYmT%uTgqr~zM$ZeEEbR&_9Ohq5@?P&^51Z~ z=RLR^B$-sdqW8Ad(a42mk((%Z@Q4(1c+TV(_14g93VlTQ#BI%vO7gE@3qycTkPY`~ z++U@fbQY0OkLwO7=kU*_oua6hfUbh(KS2B$h*v@DKd_JTZ+=4o$lO?3v$wqPG~Mo5 fj^g`W8ND`ox9i@8hsov9r|`t41Hb?xFWmnC0RCi~ literal 0 HcmV?d00001 diff --git a/__pycache__/models.cpython-313.pyc b/__pycache__/models.cpython-313.pyc index 2f075960fdb6ae4a89d2afe133168df474d79eb3..2a51998a0eca546670abfe2f16b69253415aba59 100644 GIT binary patch delta 3709 zcmbtXYj9J?6~3#7EiB2hUX~@lZj2RM_#qJ3)Wi>(fblQ|j752*rizd*wx}hUz1IyU z(~%}^Xdn{|EQt+F3)2n>oirU%(J3=+(@r~WnTAO$Xp4JG+qD1OKc(ROXgi(uoV~Jn z*i0wso$;r$XV0G9vwQYC>pKVLzDi2(S*;}kd_PF-Jn&xNWN8Jt{Z7MCQc@w*31PQS z2$!=Hq}J@c>})O71BsZHuneJxWVER=!)=@v}vdz(3l%6{-UlS_{_S& z&&m>&2cMDRG%>g#9UDtZDMcPsq-e55q2uW>Xyk4#dybHAu{Gvx?L7b9w2s%ry! zdO9~^J+330a{sbjC60bn4FCjmbS27r*w5{KB+N=3J4iE&IX*`QazA!-lilmFmwVWR z(q@D$2+tzy1yJ3Qh^$1FL@W|j6q?vKrbu!mLL+FJXMAp#QCb3i=#2q6qsC zVgPD!Boa@@U=eXt=@BG^{RkL09Rg4dsc2H7No?~S*^bgdgdzlrAR`Q^SRM#VD(TYn&`#*v-DhXvoq6#`U$0a#N6QfEZol*_VAg&1T=y~9~ zaGu9SPhVlX#81^M73*;vhj1|WwiqYmI1jd|?m8)Y3AgZTg3$faQ z)#Nm533@YHgq|hO6R#8H`?VdVhtKN%9ip#5uUedxlKU2Jju+>DfG98k zig1zrG5ACB8qcOHE3XprU6u@aJpVKyOUtc^`24xyfVbGW(Aq*MA2+>ld`HUF?sY@%db6I8D)#9HXR#q{>d;Xe z%X%t_$Th`WW9ORs2W*^+G%nPzS6jq_7k8mu&i{K;JN%Eo#xxabsw}ev!{wm ziczPh6#%UPz4YcF5MjT!>Z)z`X!(szp7?-T4)$={>M|)}piw3&c->cnLTevK2 z2QT7+#u3(Y%@gw)yK}HQNW!kL8+_J1psfLIIoB?q*JeE7im*5A1IzkwB~);KxQaeG zR2`^Mja#tb&Hf11cet6$k{m=*OD3a3k}S>_#<=)=LK#k^#6mf5g|yR3<>D)G5}Z*l zfVxZ@o*99MK@Dn;Ap8sg3z%x-><zqPgsUW2718Duo*RR`BCJ1Ih1iKWe2h^nfLVz4zJs>G#G$oDzv5M*aVxhu`qx0)i z5~(2(_jEWRi#*iUOhB(%77XAKrRt+2Bm62A9v3PZm36QTvG9KJ4oOO6VFbF26edz@ z`F6BE*5DSnky`+=!iWCAEq{C7pJ~74Uzhi<`?bIGrmgFSzw-i_G@mx-{hfJR*R-kY zA2)1WGH%4z+--&en>YObT=zP7t5_l3(C8D5gl2UOl(X(iU zRZ1=4yBAT$MTB46 zfVW5fB_!4n;dh4i8CNyyZfa*YU3T^tG1y>tPI^yyr$Xn~o>_ZoN8TRFZl1B(C&rJB zPdUze&Uh}Z$=gxrT*X6OYcdD0Vs3UBl&LL}{lzsraHG3Q2aHwB<^DVv+ zS_T9A6o^Q>9g^ItI<%8sNO-s+8Ur2HasX@|E@hl|GW@-)m1fr`Ojp&UO$}MYE89T` z;enU!FR-~p7315d-Blhs2Mk|+5dbr$bH>%k-uHP}+O^E>JL9`B@aEoc?7iH1)4%>& zTi&)Ea^uqK0GnOyaon`lf4NsX5ZuPC0T*+x8SF<-{E@41X9G4x1Y}VfK;X%WV;*^1 ztESFm8vpg_rnAtC>?r5i_t(_AHW1=|x#PsX$>Ed3a{{*RY-N94^Fq^{U?N+{TzM7g fe);K%zN39}0*G_nCgMLqCl8%G^br{N`6~Yl96-9m delta 1612 zcmbW1O-xi*6vsL9@tA@`5gccLQ3rIyVMrCMA8idrt#o4Qpe?1bqQfxn1qPjYaNonU zxFD58F@8imi#E|T4W@~jMnj&ua_7p%O}DR2Sd%W)YExIW_kYK>U@?uIWPUmKY4Qz(63d-{XC2X))8_rcr-_z4Zm=%XlIHYymnr65LKt_<69Wa`(L1 zd%^cSBISHnb>5@BFXye}{9$d(Ik1$eW-s=2Nyu;;VE6DJH7VN1AJleJk}uaDq%P-K zZ3k`Mj@6w&C$IDhC|7yQaZIlPO3Gr3Df6z6`2mR3XMNrz9({_*K2T^G9 doeTY64NeU{EyL(hiqpG}SN>KOpuMDW`4eNgYKH&- diff --git a/app.py b/app.py index 61f6a94..736775d 100644 --- a/app.py +++ b/app.py @@ -20,6 +20,7 @@ def create_app(): from routes.users import users_bp from routes.main import main_bp from routes.teams import teams_bp + from routes.matches import matches_bp app.register_blueprint(auth_bp) app.register_blueprint(tryouts_bp) @@ -27,6 +28,7 @@ def create_app(): app.register_blueprint(users_bp) app.register_blueprint(main_bp) app.register_blueprint(teams_bp) + app.register_blueprint(matches_bp) with app.app_context(): import models diff --git a/instance/team_tryouts.db b/instance/team_tryouts.db index 91d116b02b58cb8e2354681f734feb0776fa6b1b..926066b2c81976ed476fb4cf692ee0ecc251f0c4 100644 GIT binary patch delta 711 zcmZoTz}&EaWrDOI7Xt%>AP_@=>->p2#_C)QdR-!{ARz&;xFG)`9zI?lt~icAYziDl zSw1oEXH4P$F>&HO!KNjx5aQ_MnwV=CpP8cI=@;Va?ivKr1rjqt5}RDdqB_~0!*KFCZX*_u`pL^!v=m{g zG!=qe-CToQ{hVEc6+nW;3L2Rynp~Qj_cJ@*VdDk+e6wJ}TKpRRXhOekkXj| delta 75 zcmZozz|wGld4jYc2Ll5G9}q)<>)eSt#>yNFdR-#C{684DnfEjB*7Kj`U&O=5>%$et Z@rO-;<0#7~=KY%u6>OO|?`L+r0{}?J5?ufQ diff --git a/models.py b/models.py index 6eb2296..93228a7 100644 --- a/models.py +++ b/models.py @@ -60,10 +60,13 @@ class User(UserMixin, db.Model): return self.role == 'president' def can_manage_tryouts(self): - return self.role in ['president', 'manager'] + return self.role in ['president', 'manager', 'coach', 'scout'] def can_manage_teams(self): return self.role in ['president', 'manager'] + + def can_schedule_matches(self): + return self.role in ['president', 'manager', 'coach'] def can_manage_this_tryout(self, tryout): """Check if user can manage a specific tryout (president, or manager/coach in charge of it).""" @@ -181,3 +184,45 @@ class TeamMember(db.Model): added_at = db.Column(db.DateTime, default=datetime.utcnow) player = db.relationship('User', overlaps="player_ref,team_assignments") # Many-to-one, no dynamic loader + + +class Match(db.Model): + """Matches/scrimmages scheduled within tryouts.""" + __tablename__ = 'matches' + id = db.Column(db.Integer, primary_key=True) + tryout_id = db.Column(db.Integer, db.ForeignKey('tryouts.id'), nullable=False) + title = db.Column(db.String(200), nullable=False) + description = db.Column(db.Text, nullable=True) + date = db.Column(db.Date, nullable=False) + start_time = db.Column(db.Time, nullable=True) + end_time = db.Column(db.Time, nullable=True) + location = db.Column(db.String(200), nullable=True) + status = db.Column(db.String(20), default='scheduled') # scheduled, completed, cancelled + match_type = db.Column(db.String(20), nullable=False) # team_vs_team, player_scrim + created_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + + # For team vs team matches + team1_id = db.Column(db.Integer, db.ForeignKey('teams.id'), nullable=True) + team2_id = db.Column(db.Integer, db.ForeignKey('teams.id'), nullable=True) + + creator = db.relationship('User', backref='created_matches') + tryout = db.relationship('Tryout', backref='matches') + team1 = db.relationship('Team', foreign_keys=[team1_id], backref='matches_as_team1') + team2 = db.relationship('Team', foreign_keys=[team2_id], backref='matches_as_team2') + participants = db.relationship('MatchParticipant', backref='match', lazy='dynamic') + + def get_participating_players(self): + """Return list of players participating in this match.""" + return [p.player_id for p in self.participants.all()] + + +class MatchParticipant(db.Model): + """Players participating in player scrimmage matches.""" + __tablename__ = 'match_participants' + id = db.Column(db.Integer, primary_key=True) + match_id = db.Column(db.Integer, db.ForeignKey('matches.id'), nullable=False) + player_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + added_at = db.Column(db.DateTime, default=datetime.utcnow) + + player = db.relationship('User') diff --git a/routes/__pycache__/matches.cpython-313.pyc b/routes/__pycache__/matches.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..83accc46c62200f9e81b9a395e7f229d8857a09d GIT binary patch literal 19632 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 Q7tgXt*CP$ag}L;900Y8(00000 literal 0 HcmV?d00001 diff --git a/routes/__pycache__/tryouts.cpython-313.pyc b/routes/__pycache__/tryouts.cpython-313.pyc index 86171b9fd138804e010450c0a0c67f7ea7cae59b..ecbf1a6c0723569ed3cf9d6abfe289115e033f80 100644 GIT binary patch delta 3723 zcmaJ^eN0=|6@SlP*w`5Si9h&w#u#FPjUgnMG@%e@Au%C*IHp@*abMw)I5DDrM992UDd@)6|8~3G20*+DY5LBTYB0>bmWo`}{#7 zvM2m|=bU@)ednBe?m3sox7dw)tno=rO*KQFKc=S6Mfa^59qeyb+h;>jE3!sy$i^`- zCZ-;-NA1YYF*9rn6VuGp$Esr5ekF3mbS?%tXB?Az@tWF#UvTJF->+A@OM=xeTht9D z2KTWo>c%p6f#uXqW$talHJ7|9$maELj&BbZWLAUoklvJ3&pG%FOb)WF{@? z7x>GGY>HnZh9V#Sg=u7WJJIlK(Exd&7Xe@j8Uz>uXaU#*;KS|aF=G7MA&cD%f)0S) z1SD0OUl33b-!nJJ=4!Zu^`M4_R7m z)VrElB!GhKA9^XjZ23^L=K!p65TO65)tAMGm!Cp+jRF~qU(K* zZmcG#NZZE_k~v|}!&6?j*}_ceL`4@PDu_W2hRNK#?Hf)RAW5%FjFDu)@F{8hdb*3Si=o71rvvE~i9oNS7aYei)Zi<`Zmbi5q_LM%8 zgE&-c?cQ5tV|D#Ct;7P3{L;$#5>;2Gbn}egbOH_If_I5+!jr8(^q(ek{j8*uPhlcC zhh8QUQ9*#?=su!ZLZdm%`I$h{QBMrO9>=HKJS%VvP&WXi zD>4y~G<-HIr038%kemXbl}0zem`WvRxv_u{lC@+&ZYe9E3`pq#&H{x1ARzMTq-Phl z&tG3Tb+zackw`2;A}HqYpWFN^i&T`Aw8CXlWRvML=psmH=tOiG6wd>ssNMov0%jTD z8vs`TE&zZ9Vm*QKX?>a~p51M6MmHEn%cNmmM2QbZ-1*gZlVasF4O44<{rrvdn?~=t z(fi}%L!Vm#*Q}15a zyKi0H&<8#a^xU7mKYhz~*L~Z)>GVHx`X2~|orI6o$_C?&<6G5C|G>&-IJzE=ZiY{+ zhfjPE9^Y_JJPMEhy!-Ca+ebGWf*&*l*Tdu2k3V!z6tw0|t#e)LEZAIyhFxS4clAFR zrB46Y#F(9*xfp}{p?gn(Yb$gP6nr7T_Fnk&b{1MXK;qd|XbpbU=)0TvUSgv$_}FPM zg!;D_gF5v2r%9G^eVzUM(|wHN9C@`d4U?=0U39J1Iu=l^wFe0P&OF47^%&kaG>`RZ z^8fMwQpvXA2Z7Fd+CD0^UxA?ippx`W^HYz9NUCH#VEv9cqy4}v|UMid>n-xFwiroq+j=*%hNjH7J?>^KfU-Kv;PEiP7` zDLlBx;iPufEM|*co6jwZ=(}X*BYa`cTbf2fE%(0BQJ>%2Td%OK!jhFhBZ& zV;FdV4@3s=kNU34&TwyZIP;P4&r~NX&Xh9;!OF|^<0*j`1x^%F!fcM?Ik~C_X~kJ~ zMffzANvD>$(&iS&#UMJ14<8I@UL}j5Z{te`8|AcfJ~tSSm5RT#a*o4PDDm5hYb6!0 z!2rE7+ytt!t(9!7+LnzpOtebMc?okMU}6;kxm4HhRA-YF~EVwW7F0uU|5r=a34z|-xmpc+i)Jellk{3FI!^1xFs~}Mp zB_KYBGsA8VEhkpFv2}o$Hbi>kq&0;e!)=>u0v*~ayMdzHywmn+We2GN;G2AZq#H?F zCTygnSr8UpA$NVa#)9AQ6n*FC<`TIKy%wN1Auithk)yS26`bP}ejlDW_RSS!B@Dgq zO4I2=E0Hy&*4`UD%Fj^xJ95R|JG|=cxU>gA7 zd$LK|0}GkC#gs6BenMpAo~C3(7oLdPtHe!4{1YSMboAZ&2+M|^C>cc^K--oI_^qfS MKR6O#Rj}Cq0FsDV$N&HU delta 3048 zcmZ`*S!^3s6rJbsIF6Sjj-4iP>@ZoHWzzp< z0h+Rk@_|TSkt!rMA%yru{$LY`2tGhaEJ6Y?RDAIRLTaHBqT+{ppIwiEU}=uuUHsT;;cHC5>zl+>!Uf>Kvi`pluSZvKno1gqmS@?QR-{Gu9w!h)$AGbSwJ!Wb$p zTAtZdKAB79Q}a4R`K!(@)=~P{IjFc=u>jD<|EMoEhj3}h`N9KQGf-imoj>TgZ(ESm zt_5?JEDjJ}?gGpwO+A$}9B5Tlh>(*nn=#B}<*jgCDd2sB;d|co)hpqx_`Lwt9zt9OEa zR}H{&r&Fe$M9-Qidic}9-WHLue-?CdDCZ1`(rWMr*XWJ3$94kB+TQKo>GY6wj9{F9 z8SZo)qU8j?9PV!2P0Kw5HzQ~&_-WnGH+GCKQWkUbq7KPS8jhf*md}tv1svs1Zn$Q7mb~S16?9S`MtIX;+tRusHl?xYWJZ6W!lR*MW-g00r*d1(pwDOc;r_xh zY(QCSuTTNka2Ej;45&C|I*90eBnl=p;>wZ@Bt0F6<)b;EpG;*JkhbD%VH5c_5d_G? zl`0nXd>W3C=57MfRrPZt3)yT^^p+rzpx7wdwO|c73bX|SxSxKQ<*S;uD<@P%%dJ0% z;~|ruhdgN%F+QM3jv0dE!Y>CWNB{(bUctu(LQ&Cc-kJ)*DKybGM&406F|g4g|Kttx zV}qSLFGSbBoO~hqO?2qD7LRZ9CCQ^~zViECCWQ~PE5BbawH;?--_^!YtjD>!VG!}t z=5Z<3?^|j=yQ95yaquOX_3~$jH@1pCwwuraG@qc4e?2_R=D0ipk=>}k{QacwClDt> zmB!(TvneKqm3=}(WDy0YezTO>!b08zyW?&nJGGsr)D-n0(muv7jzo~VzR`B?6R3C% zAL9r$PDd#=dJ7HOvs*5(a?oP<(7Tbdm7t%zez1?it(hu~YX*Kde>B#1*q$XK1v^9$ zU8NHQK|OgSu3@Ik1;dc>1=#WBUiU8)E7Ox~@C@2M!pC??qM-TEWH*0!yN93KI*qYk z+&b!Lc?!*7PtQ3LhxyhUcmF%`=KqNtOP;D$+w#q2057BhB)4qdA})iP5xG`cm7|~j^q<}qOcDpOY#)ed?kV~y&5xS?x7l@)3}U1|Ml1XDY=YI*5KJW8 zOP*z_(TY+x>JUWMs*H!q)rl7GSycTj&Z>AXky$y`2n8j=aX&nwQHmUqHbbPcA`7Z{dxStb8d*O zpE^w%xuPL8`7O2FCaxPbl}_tv)hwt~kV>AR@)Wk`h!`t21>$zH0oqW^d@)P}8P-bM zT~1qsMH!cGa69aW_^+|{*7A78kdkIW{HufW=o=_S_BXORI@T$zhmRb1WZ7~j#SM^5 zTAs?^yyVd$MA5}k#Gg*Mj^2_Wpxa%{X?TKG)c!zS1H>RvU-23UlSpqVpMrZwu8>~H z>bu|-)G>UFHisl#k{RPSOf}X`e{match.description}" if match.description else '') + else: + # Player scrim - show all participants + player_names = [p.player.full_name for p in match.participants.all()] + participants_str = ', '.join(player_names) if player_names else 'No players' + match_desc = participants_str + (f"
{match.description}" if match.description else '') + + # Include time for calendar display + start_time_str = match.start_time.strftime('%H:%M') if match.start_time else None + end_time_str = match.end_time.strftime('%H:%M') if match.end_time else None + + events.append({ + 'id': f'match_{match.id}', + 'title': match.title + ' (' + participants_str + ')', + 'date': match.date.strftime('%Y-%m-%d'), + 'type': 'match', + 'color': match_color, + 'extendedProps': { + 'location': match.location or tryout.location or 'TBD', + 'status': match.status, + 'description': match_desc, + 'match_type': match.match_type, + 'tryout_id': tryout.id, + 'match_id': match.id, + 'start_time': start_time_str, + 'end_time': end_time_str, + 'participants': participants_str + } + }) + + return jsonify(events) + + +@matches_bp.route('/api/events/') +@login_required +def api_events_for_tryout(tryout_id): + """API endpoint returning calendar events for a specific tryout.""" + tryout = Tryout.query.get_or_404(tryout_id) + + # Check if user can view this tryout + if not current_user.can_manage_this_tryout(tryout): + # For players, check if they're registered or participating in the match + if current_user.role == 'player': + # Check if player is registered for this tryout + is_registered = TryoutRegistration.query.filter_by( + tryout_id=tryout_id, player_id=current_user.id + ).first() is not None + + # Check if player is participating in any matches for this tryout + match_participation = MatchParticipant.query.filter( + MatchParticipant.match_id == MatchParticipant.match_id + ).join(Match).filter(Match.tryout_id == tryout_id).all() + + player_in_match = any(mp.player_id == current_user.id for mp in match_participation) + + if not is_registered and not player_in_match: + return jsonify([]) + + events = [] + + for match in tryout.matches: + match_color = '#10b981' if match.match_type == 'team_vs_team' else '#f59e0b' + + # Build participant string + participants_str = '' + if match.match_type == 'team_vs_team': + teams = [] + if match.team1: + teams.append(match.team1.name) + if match.team2: + teams.append(match.team2.name) + participants_str = f"{' vs '.join(teams)}" + else: + player_names = [p.player.full_name for p in match.participants.all()] + participants_str = ', '.join(player_names) if player_names else 'No players' + + events.append({ + 'id': f'match_{match.id}', + 'title': match.title + ' (' + participants_str + ')', + 'date': match.date.strftime('%Y-%m-%d'), + 'type': 'match', + 'color': match_color, + 'extendedProps': { + 'location': match.location or tryout.location or 'TBD', + 'status': match.status, + 'match_type': match.match_type, + 'tryout_id': tryout.id, + 'match_id': match.id + } + }) + + return jsonify(events) + + +def get_visible_tryouts_for_user(): + """Get tryouts that the current user can see based on their role.""" + if current_user.role == 'president': + return Tryout.query.order_by(Tryout.date).all() + elif current_user.role == 'manager': + return Tryout.query.filter_by(created_by=current_user.id).order_by(Tryout.date).all() + elif current_user.role == 'coach': + org_team = OrgTeam.query.filter_by(coach_id=current_user.id).first() + if org_team: + return Tryout.query.filter_by(target_org_team_id=org_team.id).order_by(Tryout.date).all() + return [] + elif current_user.role == 'player': + # Get tryouts player is registered for + player_tryout_ids = [r.tryout_id for r in current_user.tryout_registrations.all()] + tryouts = Tryout.query.filter(Tryout.id.in_(player_tryout_ids)).order_by(Tryout.date).all() if player_tryout_ids else [] + + # Also include matches where player is participating + player_matches = Match.query.join(MatchParticipant).filter( + MatchParticipant.player_id == current_user.id + ).all() + + player_match_tryout_ids = list(set(m.tryout_id for m in player_matches)) + additional_tryouts = Tryout.query.filter( + Tryout.id.in_(player_match_tryout_ids) + ).order_by(Tryout.date).all() if player_match_tryout_ids else [] + + # Combine and deduplicate + all_tryouts = tryouts + [t for t in additional_tryouts if t.id not in player_tryout_ids] + return all_tryouts + else: # scout + return Tryout.query.order_by(Tryout.date).all() + + +@matches_bp.route('/create/', methods=['GET', 'POST']) +@login_required +def create_match(tryout_id): + """Create a new match/scrimmage within a tryout.""" + tryout = Tryout.query.get_or_404(tryout_id) + + # Check if user can manage this tryout (president, manager, or coach) + if not current_user.can_manage_this_tryout(tryout): + flash('You do not have permission to schedule matches for this tryout.', 'danger') + return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id)) + + teams = Team.query.filter_by(tryout_id=tryout_id).all() + all_players = User.query.filter_by(role='player').order_by(User.full_name).all() + + if request.method == 'POST': + title = request.form.get('title') + description = request.form.get('description') + date_str = request.form.get('date') + start_time_str = request.form.get('start_time') + end_time_str = request.form.get('end_time') + location = request.form.get('location') + match_type = request.form.get('match_type') + + try: + date_obj = datetime.strptime(date_str, '%Y-%m-%d').date() if date_str else tryout.date + except (ValueError, TypeError): + flash('Invalid date format.', 'danger') + return render_template('pages/create_match.html', tryout=tryout, teams=teams, all_players=all_players) + + start_time = None + end_time = None + try: + if start_time_str: + start_time = datetime.strptime(start_time_str, '%H:%M').time() + if end_time_str: + end_time = datetime.strptime(end_time_str, '%H:%M').time() + except ValueError: + flash('Invalid time format.', 'danger') + return render_template('pages/create_match.html', tryout=tryout, teams=teams, all_players=all_players) + + match = Match( + tryout_id=tryout_id, + title=title, + description=description, + date=date_obj, + start_time=start_time, + end_time=end_time, + location=location, + match_type=match_type, + created_by=current_user.id + ) + db.session.add(match) + db.session.flush() # Get match.id before commit + + # Handle team vs team matches + if match_type == 'team_vs_team': + team1_id = request.form.get('team1_id') + team2_id = request.form.get('team2_id') + match.team1_id = int(team1_id) if team1_id else None + match.team2_id = int(team2_id) if team2_id else None + + # Handle player scrim matches + elif match_type == 'player_scrim': + player_ids = request.form.getlist('player_ids') + for pid in player_ids: + participant = MatchParticipant(match_id=match.id, player_id=int(pid)) + db.session.add(participant) + + db.session.commit() + flash('Match scheduled successfully!', 'success') + return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id)) + + return render_template('pages/create_match.html', tryout=tryout, teams=teams, all_players=all_players) + + +@matches_bp.route('//edit', methods=['GET', 'POST']) +@login_required +def edit_match(match_id): + """Edit an existing match.""" + match = Match.query.get_or_404(match_id) + tryout = match.tryout + + if not current_user.can_manage_this_tryout(tryout): + flash('You do not have permission to edit this match.', 'danger') + return redirect(url_for('matches.calendar')) + + teams = Team.query.filter_by(tryout_id=tryout.id).all() + all_players = User.query.filter_by(role='player').order_by(User.full_name).all() + current_player_ids = [p.player_id for p in match.participants.all()] + + if request.method == 'POST': + match.title = request.form.get('title') + match.description = request.form.get('description') + date_str = request.form.get('date') + start_time_str = request.form.get('start_time') + end_time_str = request.form.get('end_time') + location = request.form.get('location') + status = request.form.get('status') + + try: + match.date = datetime.strptime(date_str, '%Y-%m-%d').date() + except (ValueError, TypeError): + flash('Invalid date format.', 'danger') + return render_template('pages/edit_match.html', match=match, tryout=tryout, teams=teams, all_players=all_players, current_player_ids=current_player_ids) + + try: + if start_time_str: + match.start_time = datetime.strptime(start_time_str, '%H:%M').time() + if end_time_str: + match.end_time = datetime.strptime(end_time_str, '%H:%M').time() + except ValueError: + pass + + match.location = location + if status in ['scheduled', 'completed', 'cancelled']: + match.status = status + + # Handle team vs team matches + if match.match_type == 'team_vs_team': + team1_id = request.form.get('team1_id') + team2_id = request.form.get('team2_id') + match.team1_id = int(team1_id) if team1_id else None + match.team2_id = int(team2_id) if team2_id else None + + # Handle player scrim matches - update participants + elif match.match_type == 'player_scrim': + MatchParticipant.query.filter_by(match_id=match.id).delete() + player_ids = request.form.getlist('player_ids') + for pid in player_ids: + participant = MatchParticipant(match_id=match.id, player_id=int(pid)) + db.session.add(participant) + + db.session.commit() + flash('Match updated successfully!', 'success') + return redirect(url_for('tryouts.view_tryout', tryout_id=tryout.id)) + + return render_template('pages/edit_match.html', match=match, tryout=tryout, teams=teams, all_players=all_players, current_player_ids=current_player_ids) + + +@matches_bp.route('//delete', methods=['POST']) +@login_required +def delete_match(match_id): + """Delete a match.""" + match = Match.query.get_or_404(match_id) + tryout = match.tryout + + if not current_user.can_manage_this_tryout(tryout): + flash('You do not have permission to delete this match.', 'danger') + return redirect(url_for('matches.calendar')) + + db.session.delete(match) + db.session.commit() + flash('Match deleted successfully.', 'success') + return redirect(url_for('tryouts.view_tryout', tryout_id=tryout.id)) \ No newline at end of file diff --git a/routes/tryouts.py b/routes/tryouts.py index b7911a9..1142606 100644 --- a/routes/tryouts.py +++ b/routes/tryouts.py @@ -1,7 +1,7 @@ from flask import Blueprint, render_template, redirect, url_for, flash, request from flask_login import login_required, current_user from extensions import db -from models import User, Tryout, TryoutRegistration, Evaluation, Team, TeamMember, OrgTeam +from models import User, Tryout, TryoutRegistration, Evaluation, Team, TeamMember, OrgTeam, Match, MatchParticipant from datetime import datetime tryouts_bp = Blueprint('tryouts', __name__, url_prefix='/tryouts') @@ -148,6 +148,22 @@ def view_tryout(tryout_id): # Get all players (for manager registration dropdown) all_players = User.query.filter_by(role='player').order_by(User.full_name).all() + # Get matches for this tryout with participant info + matches = Match.query.filter_by(tryout_id=tryout_id).order_by(Match.date).all() + match_data = [] + for match in matches: + if match.match_type == 'team_vs_team': + participants = { + 'team1': match.team1.name if match.team1 else 'TBD', + 'team2': match.team2.name if match.team2 else 'TBD' + } + else: + participants = [p.player.full_name for p in match.participants.all()] + match_data.append({ + 'match': match, + 'participants': participants + }) + return render_template('pages/view_tryout.html', tryout=tryout, registered_players=registered_players, @@ -158,6 +174,8 @@ def view_tryout(tryout_id): team_data=team_data, can_edit=can_edit, all_players=all_players, + matches=matches, + match_data=match_data, now=datetime.utcnow()) @tryouts_bp.route('//register', methods=['POST']) diff --git a/static/css/style.css b/static/css/style.css index 089f508..a978463 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -358,6 +358,8 @@ a:hover { color: var(--primary-dark); } .badge-upcoming { background: var(--info-light); color: var(--info); } .badge-in_progress { background: var(--warning-light); color: var(--warning); } .badge-completed { background: var(--success-light); color: var(--success); } +.badge-scheduled { background: var(--info-light); color: var(--info); } +.badge-cancelled { background: var(--danger-light); color: var(--danger); } .badge-president { background: #eef2ff; color: #4338ca; } .badge-manager { background: #ecfdf5; color: #059669; } .badge-coach { background: #fffbeb; color: #d97706; } @@ -888,7 +890,65 @@ a:hover { color: var(--primary-dark); } .mr-2 { margin-right: 8px; } .mx-2 { margin-left: 8px; margin-right: 8px; } -/* Responsive */ +/* Calendar Styles */ +#calendar { + min-height: 600px; +} + +.fc { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; +} + +.fc-toolbar { + margin-bottom: 16px; +} + +.fc-toolbar-title { + font-size: 1.3rem; + font-weight: 600; + color: var(--gray-900); +} + +.fc-button { + background: var(--primary) !important; + border: none !important; + border-radius: var(--radius-sm) !important; + padding: 6px 12px !important; + font-size: 0.85rem !important; +} + +.fc-button:hover { + background: var(--primary-dark) !important; +} + +.fc-button-primary:not(:disabled).fc-button-active, +.fc-button-primary:not(:disabled):active { + background: var(--primary-dark) !important; +} + +.fc-daygrid-day { + transition: var(--transition); +} + +.fc-daygrid-day:hover { + background: var(--gray-50); +} + +.fc-event { + border-radius: var(--radius-sm); + padding: 2px 4px; + font-size: 0.8rem; +} + +.fc-event-title { + font-weight: 600; +} + +.btn-group { + display: flex; + gap: 4px; +} + @media (max-width: 768px) { .sidebar { width: var(--sidebar-collapsed); @@ -910,4 +970,4 @@ a:hover { color: var(--primary-dark); } .content { padding: 16px; } .top-bar { padding: 12px 16px; } .flash-messages { padding: 0 16px; } -} \ No newline at end of file +} diff --git a/templates/layouts/base.html b/templates/layouts/base.html index 50963cd..136598f 100644 --- a/templates/layouts/base.html +++ b/templates/layouts/base.html @@ -39,6 +39,12 @@ Tryouts
+
  • + + + Calendar + +
  • {% if current_user.can_evaluate() %}
  • @@ -130,5 +136,6 @@ {% endif %} + {% block scripts %}{% endblock %} \ No newline at end of file diff --git a/templates/pages/calendar.html b/templates/pages/calendar.html new file mode 100644 index 0000000..289892c --- /dev/null +++ b/templates/pages/calendar.html @@ -0,0 +1,145 @@ +{% extends "layouts/base.html" %} +{% block title %}Calendar - TryoutPro{% endblock %} +{% block page_title %}Calendar{% endblock %} +{% block breadcrumb %}Home / Calendar{% endblock %} + +{% block content %} +
    +
    +

    Schedule

    +
    +
    + + + + +
    +
    +
    +
    +
    +
    +
    + + + +{% endblock %} + +{% block scripts %} + + + +{% endblock %} \ No newline at end of file diff --git a/templates/pages/create_match.html b/templates/pages/create_match.html new file mode 100644 index 0000000..0c6d45d --- /dev/null +++ b/templates/pages/create_match.html @@ -0,0 +1,134 @@ +{% extends "layouts/base.html" %} +{% block title %}Schedule Match - {{ tryout.title }} - TryoutPro{% endblock %} +{% block page_title %}Schedule Match{% endblock %} +{% block breadcrumb %}Home /
    Tryouts / {{ tryout.title }} / Schedule Match{% endblock %} + +{% block content %} +
    +
    +

    Schedule Match for {{ tryout.title }}

    +
    +
    +
    + + +
    + + +
    + +
    +
    + + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + + +
    +
    + +
    + + +
    + +
    + + +
    + + +
    +
    +

    Select Teams

    + +
    +
    + + +
    +
    + + +
    +
    +
    + + + + +
    + + + Cancel + +
    +
    +
    +
    +{% endblock %} + +{% block scripts %} + +{% endblock %} \ No newline at end of file diff --git a/templates/pages/edit_match.html b/templates/pages/edit_match.html new file mode 100644 index 0000000..8fba2a6 --- /dev/null +++ b/templates/pages/edit_match.html @@ -0,0 +1,108 @@ +{% extends "layouts/base.html" %} +{% block title %}Edit Match - TryoutPro{% endblock %} +{% block page_title %}Edit Match{% endblock %} +{% block breadcrumb %}Home / Tryouts / {{ tryout.title }} / Edit Match{% endblock %} + +{% block content %} +
    +
    +

    Edit Match

    +
    +
    +
    + + +
    + + +
    + +
    +
    + + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + + +
    +
    + +
    + + +
    + +
    + + +
    + + {% if match.match_type == 'team_vs_team' %} +
    +

    Teams

    + +
    +
    + + +
    +
    + + +
    +
    + {% else %} +
    +

    Players

    + +
    + +
    + {% for player in all_players %} + + {% endfor %} +
    +
    + {% endif %} + +
    + + + Cancel + +
    +
    +
    +
    +{% endblock %} \ No newline at end of file diff --git a/templates/pages/view_tryout.html b/templates/pages/view_tryout.html index eef9458..abf3e1e 100644 --- a/templates/pages/view_tryout.html +++ b/templates/pages/view_tryout.html @@ -6,9 +6,14 @@ {% block content %}
    -
    +

    Tryout Details

    + {% if can_edit %} + + Schedule Match + + {% endif %} {% if can_edit %}
    @@ -226,6 +231,80 @@
    + {% if matches %} +
    +
    +

    Scheduled Matches

    + {% if can_edit %} + + Schedule Match + + {% endif %} +
    +
    + +
    + + +
    + + + + + + + + + + {% if can_edit %} + + {% endif %} + + + + {% for item in match_data %} + {% set m = item.match %} + + + + + + + + {% if can_edit %} + + {% endif %} + + {% endfor %} + +
    MatchTypeDateParticipantsTimeStatusActions
    {{ m.title }} + + {{ 'Team vs Team' if m.match_type == 'team_vs_team' else 'Player Scrim' }} + + {{ m.date.strftime('%m/%d/%Y') }} + {% if m.match_type == 'team_vs_team' %} + {{ item.participants.team1 }} vs {{ item.participants.team2 }} + {% else %} + {{ item.participants | join(', ') }} + {% endif %} + + {% if m.start_time and m.end_time %} + {{ m.start_time.strftime('%H:%M') }} - {{ m.end_time.strftime('%H:%M') }} + {% else %} + TBD + {% endif %} + + {{ m.status }} + + + Edit + +
    +
    +
    +
    + {% endif %} +

    Evaluation Summary

    @@ -274,5 +353,29 @@ -{% endblock %} \ No newline at end of file +{% endblock %}