From 24fcc9a0481b8fd00785f62e6bd0374f423c2ea6 Mon Sep 17 00:00:00 2001 From: cedrick2711 Date: Thu, 16 Jul 2026 20:34:56 -0400 Subject: [PATCH] =?UTF-8?q?Ajout=20de=20fonctionnalit=C3=A9s:=20Ajout=20de?= =?UTF-8?q?=20notes=20personnels=20et=20de=20notes=20d'=C3=A9quipe=20avec?= =?UTF-8?q?=20historique.=20Ajout=20de=20prise=20de=20rendez-vous=20avec?= =?UTF-8?q?=20un=20coach=20selon=20ses=20disponibilit=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __pycache__/app.cpython-313.pyc | Bin 3022 -> 4160 bytes __pycache__/models.cpython-313.pyc | Bin 34610 -> 42213 bytes __pycache__/seed.cpython-313.pyc | Bin 20376 -> 27427 bytes app.py | 18 + instance/team_tryouts.db | Bin 139264 -> 163840 bytes models.py | 126 +++- requirements.txt | Bin 1050 -> 1086 bytes routes/__pycache__/teams.cpython-313.pyc | Bin 11432 -> 14704 bytes routes/__pycache__/users.cpython-313.pyc | Bin 34137 -> 68187 bytes routes/teams.py | 75 ++- routes/users.py | 696 ++++++++++++++++++++- seed.py | 137 +++- templates/layouts/base.html | 46 +- templates/pages/add_note_from_match.html | 63 ++ templates/pages/add_note_from_tryout.html | 63 ++ templates/pages/add_personal_note.html | 80 +++ templates/pages/coach_availability.html | 260 ++++++++ templates/pages/create_match.html | 121 +++- templates/pages/edit_match.html | 615 ++++++++++++------ templates/pages/one_on_one.html | 250 ++++++++ templates/pages/personal_notes.html | 64 ++ templates/pages/player_personal_notes.html | 85 +++ templates/pages/team_notes.html | 58 ++ templates/pages/view_tryout.html | 132 ++-- 24 files changed, 2625 insertions(+), 264 deletions(-) create mode 100644 templates/pages/add_note_from_match.html create mode 100644 templates/pages/add_note_from_tryout.html create mode 100644 templates/pages/add_personal_note.html create mode 100644 templates/pages/coach_availability.html create mode 100644 templates/pages/one_on_one.html create mode 100644 templates/pages/personal_notes.html create mode 100644 templates/pages/player_personal_notes.html create mode 100644 templates/pages/team_notes.html diff --git a/__pycache__/app.cpython-313.pyc b/__pycache__/app.cpython-313.pyc index c56d9c3ac77bc9c318fc9b52d7152e5835994181..cfa6192675352cdb747f9daa32c77de794db4969 100644 GIT binary patch delta 1962 zcmaJ?Uu+ab7@xh}y+3>Z;Cd8FX*)%(cYBwa zy<+=vB|cyzF(E6)7ZZ6hMiMb3CcgP#L}FsZ#uQeGFFyE)l=$R>-^||05fi7mnQwmc z{hQg}_w|R}U*p(zG^!yO(bAjC--oZo_B20$-F2iRigZc$S>D(8GznaY@BN>0hD zRL%OSKO3NdY>)=CAsP~AYEH|BX;{d0v!`3<8&ZWnU_~eq)9hRXju2Y7Lv=@p`>t77g31S$G&v zR1CKA3^rjD?HXMh(X^Q*li_Nu)Tj_#r?pkHL>RW02sS!gEQ0zt=;Os2#YIZMu~o)~ zRl>!ZwP=)dBEXu#^Nu6{4 zmCl_f>pO=)0R3amSMp$YBG+dHfxTV1zHGl0$_`i>M*YqyWx$uu@UI&_+xDR+ol&LF zIjj!snGNO7x8bL2{(|$iGBmm?m&(3mL1oQ`pH>dkS%UwK`8<6%MF-Fznmgw_Pp&*& zoPTDIJi}NgP&hVTAzl?R!WgekMv729_L^W1XkC~xAz-O#*v5jv$iD?9YPqG9(eo3$ zSZz||72ZOpYGu>HRl_pMgtoBpN(EEs2ta5cZykthGjXJygF96q(Na5*+0I@>Dv3KY zLQ0(Lg09S{)}cvo18gp0Qmxy(fjmQaaqTpQ+7exXl>WCN+`g&Gl3&pDyz{xANC%x= zf!CXR;gh&Akv1E#y^cnEIhbgHgmYfx$>=_Q5NRDW7@>^zaVXq^s)lLN{!S$uIwhk6 z9BN0Y8g_AsFp2?meh(zaL{e^O*|e680xa%sDvS<- z9o_FFgBP2;U#B9j3~H3$L}V_q6nTkML~s#|@;22n=MuMOoRhrcvua0hHoMh;a?k9fq!oiKqmFdgVTT0J8rRT$;d&++2@6gTB zbaXNye;a)DwY2Y!dJyD0>G8y5T)i9d!RBsE;&NQ(azdLNbhfm^k%Sv46iT&Xp+KK^ zwD3feCn#P>Btj4H?MawinKrCX5lwPHBpp2N{ckxzbAa(C#V)|~NR}k&PoE+w+Yuyb z57F-5(Dct}`XTE51?_z74@j8Byn7%-4qlzv(#RzH-uJ$F?|pA}W;6da9ovsab>M6E z^}^$C>UymA;R_zU0uxM91{7W@6GK8NFR)ye4Fwe=fB_?jK|@8=2w}+IQ_7m5qt0<4 z0~lV4n8C^00M=1}rSJ(T3K_mIx)iPCn5Co=su?mhQ$Hu?VTG)ap)jMQa-^Nswn6S> z{_Vfq*p%N&$~;siSV791mVoPMCX_R_=#eOAjI6C0$84_O2aAzTlgcE!rKXNgb>jbi z@j?fiNtRG^?7Ny`H$o9*UaOQu5&NxX*_s*+q{^p^0k*ED(&86DQEC_FVIEFGKP=9$ z4{Db9LS$A<$NJR*`=a*oc2iRK^^ILkChV_H$soHMzPT3X^{p5-J(oHoE=sl9poip%%w9$m$n?Y6?!>gsZ}X}k2P+X^X*w95q&)sDE7{GrtaMi zrf7@&Ap(wb(~{A%-@!hIfD?iUo#!X^>U0{#tKib$%y6?O6GDD~wgc&3Ft-hJJCOaO RjF9-=J;Hjr29#-@^bc;aylemf diff --git a/__pycache__/models.cpython-313.pyc b/__pycache__/models.cpython-313.pyc index 737a8e093700fb94b0373b59029f33114f2ad90d..ecf4b092f7d7f32f72cb691ab14ca538a920d625 100644 GIT binary patch delta 3212 zcmbtWTTmP471m0CK+;M=;vz1F#g~kPDPRX1kiq6|7uP^762pW6>4BWy2k?-m(2O_>t8SupGBU zF4S5gJtS>ybT&}G7yOIVZ>0Vu@cXFGQNJJj0rZQT5`&?kJ)iBrHYT>`v%j{$e7yI4 zrOszkasuS*zCtp4& z^RfBZ&qagg*xz|)7vL4%u9e@%Kuo*-i<%E;@To{~!4GoHOdB9a<#x!1>6$G1lI zTLyI3;O$|h1?TFc=eGu}WWz3!2WP9un*&v3VrrATFWSjycN+;!SHnU~xRLBU83M1y zW>fs+SK*p6SHu=!p<x|m1^s)kkO=46G_3iH{`3CSBh ztwpkuh>{Q3dLmy8kUQta7_11J33HQXybvMY*q6xq z*b}{Z2d)&Q8<|wvv2+)}j40=ExQC_(&EL?-Xnv076qt5#tK|kX!IH zZ#NewZ>Jhd82py>vo;Lk5DnH&{Vk+^!}1TSv$*s@z*5;n8l>t*U&UtVUTACJ{^<7T zuJuj!$81h|Cpf%6@s#d+I(wCTyR)X&K)`!$@4C1uZdvc!x9z*9ba#99GFg{8Lr!-6 z((0wH?%kyBypSDwuYi(VFjuR)dI~TZG+?r0djuv^02MF!#Z=p;;8J!3F4e`3ZUhWJ zb(uV0<|-})uY!8+jDsm6YvE3WTqS!Bj*7oXQ3P|UW=gKNlCPImmb+)1fGite%ccYh zQIDJA%?o&|IgFLhR6r)iY7b++nMyNOSBS0nXR6FteWXG;mZ%I>5_ z6}Y4*EeLU3K}`!WaZZdGcwExdFqh)B*nA5GehY^TP?qII4Pj3{UTQ`e`M=S=f}Ei1 zM8$ZR8&4zOK(~N!f|!uF1z{OCha{p|h20rsG9mu>_b6{E@|QI8HF^%ZVEAz>8s~ku zWZs=NzZ|lYw5J+6f=HcWG#>$=3=(?K-AC7fqd(y2T{JI&84ER#bgIYoke12_X;S@9 zZeqqKXhQhbJskZ7&G*na(O@4_eE5`-S4B;(yMjI%1d=)j10=5p$jg!RnObrw;U|Be zaFMmnz~dQuzW70j#p@$?)T&b*x@RJLMdLd$}!uecvu1t4^ zK^M%argGD{t2*0vd&FoTR}5X4{UTkcH(LsaOkeDvDlu`>P>UZtTThZV{WMk-t|u=J z))B{Zb)?qeC%+7`W3Y>>mF?dM|@914jQbXo$!*+6DF(o;LPR z_jh3VqyltSImMXIU@;|p`{#FfmCK0QJTN{5w}hmuLFZ2jF{G7A+>_42WS|V>)pI92 zbPonK4%=DZvVSGcYeJYaHx4R+zR?PSj^+)-cum!KMT_E^NAqcv?!?Fvr1)oYAa*1p zEsGK=&y9Iro0ZD&5XXAOMK!jlf`FlLzyjJLq!(&y_*^gzv%=w8I6h6=f4l{b zd=si5z(|E_QhYHf#6#F}2NXd~%Tip3M|teZa0Hkom4*&3Naoi$OlfuG{jwxPWeNU- zsQFFEnUuc`TeQ-NrVR;6J2Y*{57mJ7YC{8>Py-}G1BNmGPx$8hXll@{MN@}{HtrIm zF3Pts;m>HkgQf({>u69s@~*>u!ciWT4TfGw8?NGFYNl0y6-9SYT_hWl)yJ4|C#M)avi$AEBlp4^kYV6pZ#3@DBK}(S986(v;X#xp$&MTvCwgH z_vPRB&?`5+sM2c$d4SyE(!@advzRN@oEK>{gu|$C-ZS?%B<*jjjU=jt@qeCT4)SJG(i* MG5>E1j_H#B1AV3KPXGV_ delta 125 zcmaEQl4(;P6W?cEUM>b8_-h=IbvbV$p9Ir0ri~iUm{`)SHElNUV*by}=)8FsduKLd z)n=DsX#sWvMh1o=W5&r7^TgS-L2T{K&CTl=*+FbSO}WX>7hYx5o_ukUESEb_1c(@l W=WhP7Xci;)SB3~iM$IA}pb!ACw2dvmX-?u9S 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'.join(str(value).splitlines())) + return '' def create_app(): @@ -51,6 +66,9 @@ def create_app(): app.register_blueprint(teams_bp) app.register_blueprint(matches_bp) + # Register custom Jinja filters + app.jinja_env.filters['nl2br'] = nl2br + with app.app_context(): import models from models import User, MatchParticipant diff --git a/instance/team_tryouts.db b/instance/team_tryouts.db index bec2e211a7766b8f7144bcdb6589b4a1e9ab5b0e..bbc885b31b5c2441ad3ed56c252d478c26d97026 100644 GIT binary patch literal 163840 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 G|NjGxGm}IB diff --git a/models.py b/models.py index 4f2a64a..f458d0c 100644 --- a/models.py +++ b/models.py @@ -665,4 +665,128 @@ class Contract(db.Model): bool: True if user is the player associated with the contract. """ # Only the player can upload their signed contract - return user.id == self.player_id \ No newline at end of file + return user.id == self.player_id + + +class CoachAvailability(db.Model): + """Coach availability in 30-minute time blocks for One on One sessions. + + Allows coaches to specify when they're available for individual coaching sessions. + + Attributes: + id: Unique identifier. + coach_id: Foreign key to the coach. + day_of_week: Day of week (0=Monday, 6=Sunday). + start_time: Start time of availability block. + end_time: End time of availability block (always 30 min after start). + created_at: Timestamp of creation. + updated_at: Timestamp of last update. + """ + __tablename__ = 'coach_availabilities' + id = db.Column(db.Integer, primary_key=True) + coach_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + day_of_week = db.Column(db.Integer, nullable=False) # 0=Monday, 6=Sunday + start_time = db.Column(db.Time, nullable=False) + end_time = db.Column(db.Time, nullable=False) # Always 30 minutes after start_time + created_at = db.Column(db.DateTime, default=datetime.utcnow) + updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + coach = db.relationship('User', backref='coach_availabilities') + + +class TeamNote(db.Model): + """Team improvement notes from coach. + + Contains coaching notes and suggestions for team improvement. + + Attributes: + id: Unique identifier. + org_team_id: Foreign key to the organization team. + coach_id: Foreign key to the coach who wrote the notes. + content: The note content. + created_at: Timestamp of creation. + updated_at: Timestamp of last update. + """ + __tablename__ = 'team_notes' + id = db.Column(db.Integer, primary_key=True) + org_team_id = db.Column(db.Integer, db.ForeignKey('org_teams.id'), nullable=False) + coach_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + content = db.Column(db.Text, nullable=False) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + team = db.relationship('OrgTeam', backref='team_notes') + coach = db.relationship('User', foreign_keys=[coach_id]) + + +class PersonalNote(db.Model): + """Personal notes from coach to individual player. + + Contains individual feedback and coaching tips for players. + Can be linked to specific contexts: match, team, or tryout. + + Attributes: + id: Unique identifier. + player_id: Foreign key to the player. + coach_id: Foreign key to the coach who wrote the notes. + content: The note content. + created_at: Timestamp of creation. + updated_at: Timestamp of last update. + match_id: Optional foreign key to the match context. + team_id: Optional foreign key to the team context. + tryout_id: Optional foreign key to the tryout context. + """ + __tablename__ = 'personal_notes' + id = db.Column(db.Integer, primary_key=True) + player_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + coach_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + content = db.Column(db.Text, nullable=False) + created_at = db.Column(db.DateTime, default=datetime.utcnow) + updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + # Optional context linking + match_id = db.Column(db.Integer, db.ForeignKey('matches.id'), nullable=True) + team_id = db.Column(db.Integer, db.ForeignKey('teams.id'), nullable=True) + tryout_id = db.Column(db.Integer, db.ForeignKey('tryouts.id'), nullable=True) + + player = db.relationship('User', foreign_keys=[player_id], backref='personal_notes') + coach = db.relationship('User', foreign_keys=[coach_id]) + match = db.relationship('Match', foreign_keys=[match_id]) + team = db.relationship('Team', foreign_keys=[team_id]) + tryout = db.relationship('Tryout', foreign_keys=[tryout_id]) + + +class OneOnOneRequest(db.Model): + """Request from player to coach for a One on One session. + + Tracks requests for individual coaching sessions with time slot selection. + + Attributes: + id: Unique identifier. + player_id: Foreign key to the player requesting. + coach_id: Foreign key to the coach. + org_team_id: Foreign key to the player's team. + date: Requested date for the session. + start_time: Requested start time. + end_time: Requested end time. + points: What the player wants to discuss. + status: Request status (pending, approved, rejected, scheduled). + created_at: Timestamp of creation. + responded_at: Timestamp when coach responded. + """ + __tablename__ = 'one_on_one_requests' + id = db.Column(db.Integer, primary_key=True) + player_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + coach_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + org_team_id = db.Column(db.Integer, db.ForeignKey('org_teams.id'), nullable=True) + date = db.Column(db.Date, nullable=False) + start_time = db.Column(db.Time, nullable=False) + end_time = db.Column(db.Time, nullable=False) + points = db.Column(db.Text, nullable=True) + status = db.Column(db.String(20), default='pending') # pending, approved, rejected, scheduled + created_at = db.Column(db.DateTime, default=datetime.utcnow) + responded_at = db.Column(db.DateTime, nullable=True) + + player = db.relationship('User', foreign_keys=[player_id], backref='one_on_one_requests') + coach = db.relationship('User', foreign_keys=[coach_id]) + team = db.relationship('OrgTeam', foreign_keys=[org_team_id]) diff --git a/requirements.txt b/requirements.txt index 8c8f4a9d8e7a13fb8bcdb8381fea2f657d6bdc2f..5c75244fe189a88b336c1d5c871d53a4ca0297c7 100644 GIT binary patch delta 42 pcmbQmv5#Xyf_xD}DnlVdDUd8?C;?(yAT(moV=#u08>9C#0|49P31R>M delta 10 RcmdnTF^gkD!p4?e%m5e71WNz_ diff --git a/routes/__pycache__/teams.cpython-313.pyc b/routes/__pycache__/teams.cpython-313.pyc index 9457852ef21199c3f7cf5cfd6d5a57073879b773..beb6f71a5f61b8bc09499d1035c3870526b2a3b6 100644 GIT binary patch delta 3537 zcmb_eT}&I<6`rxjp25argKaQi>;VF?Nn#*g(m)9bo9q&?T1afzHigP@@BpriP4Ael zAgWBBwsfT?^j6({*oUa?L-wI66;hS{tSYrqm8TYDRL}^uszmo;rK&{9OZK7aId^RS zl5UaOEBW}`Ip^Ga&OPTl_ddF{N91P?hmC_FzI*E%e>}Y+`%D*2E1_8Xkc+s6sz}vP zHK`tQ6ZepZcoc5J)Xc@L6W+KOmwL>kW}+t6vGO2l@iFE})KZ7c_xY%$9vONOxCX|} z0q1AjBR*PaL=Lw-I^wnQ$__s6=rK)H#C`Q#cS&6SCxQQ9S9i#+{uOq?>4va>abbi^ zC$s6rB>jy4%j=>p!hBRs&g#OLrV?FXybbBw>g*l(Qh7rqS}vQ+4CnHyE}?{R)={#M zo6oOpn|>njb@bb!k9X4_ikEpm{j->C4}#melq1uLJXjDnOi3e>01(XrM8TnVPtv3` zz@J(Bv2@+i*Z|)@ccFzA$)vSBGuB8O8v3Xt2lzI6T5h(MK8(mcyh4}dkhcjnnvt9W zqMOs%JZYhSkekY?|BxHq?I^NO5;d7u4Y&H&8l7D{e|Bxe^}Z0q-AN}BV}0bhkeo*n zr7t`I2c{wHA(i;0iJtOaEnGnMOF(q%R63JaiDBJ>pV1j|5s43p9Z3(8F9Qi%bP+>M zq*LS)emvN<52go)M400hYAS6U2)PF0o2U}uxwTKdoftuF?Jvxcg9uC@4)dg%{pr>D zB$-z8iE=(*%VBb!{;R&Rzyhi!2mqoW5s_emh!u$uDRNhkppUwBHkVQ}8e82s_0uSO z6UosSbt@>Uc{OY$S5bNpCwd0)hgcP9I-l2YJ605m5wP7bH8S-U{awRn5av()yB1|X zOub?*lUz^<#G^}dIW3(}=d#30f8N{(HTkFJ86&)MzR-g!NLaK3$Q_GT!f*Q-B@Udj z{P~e%A%u{_V>lM-kr0PbKEiPkj?@7QNXVp&Vm=47_u3D&w5R?;JPix z`QEQ`T(Y0z#`u_xHdVXn*904#x4AF$z6%0{KDuV+JY${apP8+vH7dVOmVTtoHdVQ zV`77D1U>YH&r8#ll@&+6nqsZ6T74(@ap0d6ulTS2-E4ASwk`g8AeB;*3LcV@o}D9b zuCr=3k4aUPDUzF2@-wO-o=Z-vN@yxal*wFjaz@p{^mZuZR`NLo&Y4+g3Wy^%`bp@G zLM=G{dHYBMHcD;^CQvhMzsY3wz%p`BR=PNu%jUrg;)ONBX5D_MM;Kk}76=vt zqv$nys^L0nO3~&gCsj?Gn$Khk3r&R8qWhJ#$gsm2U<)ku#gNl2TArlm2EdroV6c`>&p3} zFIsfIwl2N4Tadl?Gs~GRIj|uIK3sUzT$Imk$BvumJ zo`$EQ;Ie&3cy8k=?DuWUw)^t3{2;$6`FA86&bOqx4XJKZYGC_DRx388-d(%<2ZQVW zNYT?#w0EuxorK{q($VLC35Z5RVNj&MZ*4O@N8}CAvu(k{=rjG_(8qh|)7CC3*HzHJ zwpr+T!F@4qc?FoQCj@g1#ntQmpT++`LM=-yUTfiZQGbR7NP@+x78js$16{Q^J|X9T?6NH zrH4}q-+<+$V&vr{V81Dsfqo3<1bXM8dx70Ai@~da?;PU4Y{l9`X+JGO5Go~z6V~t< z9vQTq!`Oeo4nU0^hs02q;n{l>&()=y^AMo%nkgK+HOJB3h2-Edun%B@_h%$6lG_ zE#yX#yn_TADZ#V24sjVZH4B}1jN`D7u(=sYu`KL;!}Hq zkMG*+<4m^q0`~&zuE^t>$I?<@ogdidoli~meAhEGXL2EVo!=8+zSq02eC5f7Cm~dh QGiBwF@+kjQfH`yM-!{G^m;e9( delta 1334 zcmZ`(OHUI~6z=q0un(Z6<9?Vv~M3_ndR@{l5F1_6Iy&4E*x@ zYi#)W6u5TtO=33?v%i#fduGG3ZOPtln{zLP<~(!Wj05VH>Ud}{yt|ilG?c?{Nk`ZU zd6$%QM9T3Ej2p}GLyV*4_??t=#d6V9jA!j%LUz+L4Y_%y6inxW&Y<+b@x<92Mk?E( z4JF6)69(4w{&k~}PdOpLC%t1*l;8I%z0KHUdNs%`Qws!e2{D2u1l@%o)5knL&ZE9_ zQdeowcfl2J#QdL-b!cAI4Y65=ByDWquLB7w$v+0#-Q|QUn2}oeSgh3w)h!q?o zs2m2zY1v26&!0yU{s9bAVK_pWR&GYdchVG|L@?cJYTjUA?M_gdNWvgN13@jpDS{z{ zl*{xG!DTfIXDE4a>o6`49?v4~V_DUDC>X=?6>901Y^BGI1Ek<>bkC7KP+-S$N;2K^ zu%&Gp7}XmJ zMXBlA$SYe6P><=|&~(*MwIYP@pzY{Po+&z$b-aJokQLeL?1wPF+p*qSWhF5a>clII z;IKufp<*rgsVQ$c_khI diff --git a/routes/__pycache__/users.cpython-313.pyc b/routes/__pycache__/users.cpython-313.pyc index b9cca18573621df9e24ad19a275b17c2f046157e..5fd218c1df299490f617bcaca60ce0422159c26b 100644 GIT binary patch literal 68187 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 delta 4444 zcma)93v5%@8TPe(9VaG9Av_Fm66XQrIt0S2yjv)wKmsWcU}S^dn)nh7e(~R%A}Ga_ zSG#E)We3r%gYi&T2{fi^-b%+{tSYKDjWN)qt-WZ1O`_GbX&YlhQ`JrTzjG5iSe-Nk zALoDEbN=@^zJ1L5(O29R56a8ST>R&J<%VsS`;Jxw-G{wLoA=HRySkO`lFo4*)l@Cl z{?1TGh(aATR1^fb7-OE=bbmvsl`c&M&>*fiE3uRH9T=xW}jy@|*?khN_8ET-~A73N?4v@F`kecE}x8BYEly zQ!RPwpG-~4Q>~t`+M1^frY7g9lbN_T{JZp-s-v&PgsRpo6^r{$@Y-mp47PaR@v zMxMID)XYA&HZym&5^7PSm3o;2)IV9ZEZ z(cHb#pL&&g@m59H*T6ZUMsdAjL!chZ0eVVhT%E>rRqkYEL{VyUKYOgtt&9_GRXz3N zm~Sc!VVcTlmnGt+rT3c#nY4fzGjr#wPM0V%L}zt#<4k7IGXxTA3{_hk{LWm|>Uo8J3eegg#)@kP6l){nB@%Np??tC z5xjvBi#xHUFR{wf+{2on%Au`njQE_;iYFU2-)e01j7T*Gv#WTiRnRbSU;~u^RDciA z45$PkbhH+L(+SQ}3!n!8cQkjwKhw>O@IcA~Tt|jw$fVOI7VPJL<~s~RZMvvDk-O4l zIcA1LSEOS4qg7!0Q7kA>Cl@jnGJG;C((0w+NTk+3i+9u%(!~!WVP&!SO{D&5omsR2 zAoYr({tOFt4-5B_!DSx{N6oOU)DvPth>GVjzvB(LS!QvCSf4AIJI8rxSJ?<>NvP*l@ zDT}yVOf4`Zf1_CD~sBXv`egN+svlq+7|h=h0HAOlR|UULb?L*oT!;qt;`g2X4Q>``2($q z)k=mg>Jq70jj|kfm&@TWdC-70EZ4kF*DS*a%K_*EtwHuL6=h~UVI`wqC}A0v zUI=8V%M1<&Inw3I4*0SJ^cMgHU!)rta#R73HC5;Y*#w2QX@W{o_m>vOsTE3(^!T)) zOhCC<;E%Za=<|AHsf$Beby!Lm)-nfO#4zijUKmFHAy7T;SqU708Tu&^sT zhb1IKh@x^lLK)d!jCJ}ln6l;$fmym_QIFoWXk~jhy$ssVDqCO>y~3oX+9jDpbQj+Q zwvP<6KVcEfkh|DQ%tSn81e~s@>NCbzdMs{c(y937cp`4a4U@jkN_Dvp79I3tx8mai zfP)M?Zbx8UFjwHAPEW3PY4t5o#X{f^Bf^ENZ|+ZQ zr}Hd6obz|Ld6sU)N2?gtYGwbsa^;m<@FH*EITt;F4W^3h`fBw8FRzMI>sNCX-d|tu z?$`&}OMv|V=a|5dW8}>)6HurcS!0E>oZDV(>aym+Z+occC%u8Y$>Sw1oR z#s9eZ)ls*pX}ugLqj6**CCwmZHsn;6HCD;c}YnSNqjta6;JlM4AZ z&R^{G9xJN3y)iYQToMPCAxMrl*@ob246rg?g$PU{zWO2aq#2QqO14>coPB0 z7PlL}C`{Ww_>04Kr%l_NiCSCbZEO2@j`r@1+5Tj7J70DD3qh78l0{=&V|fR!d!iEBeyf)~wo3>0 zk6J?>;ur&bodyO?); z4_R~xhaMh0blY$wOMBcd(e+rmSJ&y*>sQ>$k*r;@)1d9UjsCvL{Z>3-IvGH)ou`vu z*HLY{?)(DbiJc)l0Bx1h{oLaC^(ud7$=@_&fJ_3?fL8(XM#2k)jswmDF!Jew%eCA^ zZ)5o`0G&>E0Z0iUbOcxEy8vgh^UC(mPo`u23F8TJDK7kVne#laf9O@hm3O#YT=Z)f Z{mvD+<7&RxpelY(;J&v?NhorM{{m-q6AJ(U diff --git a/routes/teams.py b/routes/teams.py index 3b01ddb..5fdb422 100644 --- a/routes/teams.py +++ b/routes/teams.py @@ -6,7 +6,7 @@ This module handles CRUD operations for organization teams and player assignment 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 OrgTeam, User +from models import OrgTeam, User, Team, TeamMember, PersonalNote, TeamNote, Tryout teams_bp = Blueprint('teams', __name__, url_prefix='/teams') @@ -220,4 +220,75 @@ def remove_player(team_id, player_id): player.team_id = None db.session.commit() flash(f'{player.full_name} removed from {team.name}.', 'success') - return redirect(url_for('teams.list_teams')) \ No newline at end of file + return redirect(url_for('teams.list_teams')) + + +# Note actions from team page + + +@teams_bp.route('//add-team-note', methods=['POST']) +@login_required +def add_team_note(team_id): + """Add a team improvement note from the team page (for coaches). + + Args: + team_id: The ID of the team to add notes for. + + Returns: + Response: Redirect to teams list with status message. + """ + team = OrgTeam.query.get_or_404(team_id) + if current_user.role == 'coach' and team.coach_id != current_user.id: + flash('Only the coach of this team can add notes.', 'danger') + return redirect(url_for('teams.list_teams')) + + content = request.form.get('content', '').strip() + + if content: + note = TeamNote( + org_team_id=team_id, + coach_id=current_user.id, + content=content + ) + db.session.add(note) + db.session.commit() + flash('Team notes added successfully!', 'success') + + return redirect(url_for('teams.list_teams')) + + +@teams_bp.route('//add-player-note/', methods=['POST']) +@login_required +def add_player_note(team_id, player_id): + """Add a personal note for a player from the team page (for coaches). + + Args: + team_id: The ID of the team. + player_id: The ID of the player to add note for. + + Returns: + Response: Redirect to teams list with status message. + """ + team = OrgTeam.query.get_or_404(team_id) + if current_user.role == 'coach' and team.coach_id != current_user.id: + flash('Only the coach of this team can add notes.', 'danger') + return redirect(url_for('teams.list_teams')) + + player = User.query.get_or_404(player_id) + if player.role != 'player': + flash('Can only add notes for players.', 'danger') + return redirect(url_for('teams.list_teams')) + + content = request.form.get('content', '').strip() + + if content: + note = PersonalNote( + player_id=player_id, + coach_id=current_user.id, + content=content + ) + db.session.add(note) + db.session.commit() + flash(f'Note added for {player.full_name}!', 'success') + + return redirect(url_for('teams.list_teams')) diff --git a/routes/users.py b/routes/users.py index 48fa505..1bdeb0f 100644 --- a/routes/users.py +++ b/routes/users.py @@ -8,9 +8,10 @@ import os from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify, send_file from flask_login import login_required, current_user from extensions import db, hash_password -from models import User, ROLES, ESPORT_GAMES, PlayerDisponibility, UserGamertag, GAME_PLATFORMS, Contract, OrgTeam +from models import User, ROLES, ESPORT_GAMES, PlayerDisponibility, UserGamertag, GAME_PLATFORMS, Contract, OrgTeam, CoachAvailability, TeamNote, PersonalNote, OneOnOneRequest, Match, Team, TeamMember, MatchParticipant, Tryout from werkzeug.utils import secure_filename -from datetime import datetime, timedelta +from datetime import datetime, timedelta, date as date_type +import requests users_bp = Blueprint('users', __name__, url_prefix='/users') @@ -716,4 +717,693 @@ def download_signed_contract(contract_id): flash('No signed contract available.', 'danger') return redirect(url_for('users.list_contracts')) - return send_file(contract.signed_file_path, as_attachment=True, download_name=contract.signed_filename) \ No newline at end of file + return send_file(contract.signed_file_path, as_attachment=True, download_name=contract.signed_filename) + + +# One on One Functions + + +DISCORD_WEBHOOK_URL = os.environ.get('DISCORD_WEBHOOK_URL', '') + + +def send_discord_notification(player_name, points, date_str, start_time_str, end_time_str, team_name, coach_name, coach_discord): + """Send a Discord webhook notification for a One on One request. + + Args: + player_name: Name of the player making the request. + points: Discussion points from the player. + date_str: Date of the requested session. + start_time_str: Start time of the requested session. + end_time_str: End time of the requested session. + team_name: Name of the player's team. + coach_name: Name of the coach. + coach_discord: Coach's Discord username. + """ + if not DISCORD_WEBHOOK_URL: + return # No webhook configured, skip notification + + embed = { + "embeds": [{ + "title": "One on One Request", + "color": 3447003, # Blue color + "fields": [ + {"name": "Player", "value": player_name, "inline": True}, + {"name": "Team", "value": team_name or "Unknown Team", "inline": True}, + {"name": "Date", "value": date_str, "inline": True}, + {"name": "Time", "value": f"{start_time_str} - {end_time_str}", "inline": True}, + {"name": "Discussion Points", "value": points or "No specific points provided", "inline": False} + ], + "footer": { + "text": f"Coach: {coach_name}" + (f" (Discord: {coach_discord})" if coach_discord else "") + } + }] + } + + try: + requests.post(DISCORD_WEBHOOK_URL, json=embed, timeout=5) + except Exception: + pass # Silently fail if webhook doesn't work + + +@users_bp.route('/one-on-one', methods=['GET', 'POST']) +@login_required +def one_on_one(): + """One on One request page for players. + + Players can view team notes, personal notes, and request One on One sessions. + + Returns: + Response: Rendered One on One page. + """ + if current_user.role != 'player': + flash('Only players can request One on One sessions.', 'danger') + return redirect(url_for('main.dashboard')) + + # Get player's team and coach + org_team = OrgTeam.query.get(current_user.team_id) if current_user.team_id else None + coach = User.query.get(org_team.coach_id) if org_team and org_team.coach_id else None + + if not coach: + flash('You do not have a coach assigned to your team.', 'info') + + # Get team notes for this player's team + team_notes = [] + if org_team: + team_notes = TeamNote.query.filter_by(org_team_id=org_team.id).order_by(TeamNote.created_at.desc()).all() + + # Get personal notes for this player + personal_notes = PersonalNote.query.filter_by(player_id=current_user.id).order_by(PersonalNote.created_at.desc()).all() + + # Get coach's availability for the next 7 days + coach_availability = [] + if coach: + coach_availability = CoachAvailability.query.filter_by(coach_id=coach.id).all() + + if request.method == 'POST': + # Handle One on One request submission + date_str = request.form.get('date') + start_time_str = request.form.get('start_time') + end_time_str = request.form.get('end_time') + points = request.form.get('points', '').strip() + + if not coach: + flash('Cannot request One on One - no coach assigned.', 'danger') + return redirect(url_for('users.one_on_one')) + + # Validate date and time + try: + date_obj = datetime.strptime(date_str, '%Y-%m-%d').date() + start_time = datetime.strptime(start_time_str, '%H:%M').time() + end_time = datetime.strptime(end_time_str, '%H:%M').time() + except (ValueError, TypeError): + flash('Invalid date or time format.', 'danger') + return redirect(url_for('users.one_on_one')) + + # Check if the requested slot is within coach's availability + date_parts = date_str.split('-') + check_date = datetime(int(date_parts[0]), int(date_parts[1]), int(date_parts[2])) + # Python weekday: Monday=0, Sunday=6 + day_of_week = check_date.weekday() + + is_available = any( + av.day_of_week == day_of_week and + av.start_time <= start_time and + av.end_time >= end_time + for av in coach_availability + ) + + if not is_available: + flash('The requested time is not within the coach\'s availability.', 'danger') + return redirect(url_for('users.one_on_one')) + + # Create the request + request_obj = OneOnOneRequest( + player_id=current_user.id, + coach_id=coach.id, + org_team_id=org_team.id if org_team else None, + date=date_obj, + start_time=start_time, + end_time=end_time, + points=points if points else None + ) + db.session.add(request_obj) + db.session.commit() + + # Send Discord notification + send_discord_notification( + player_name=current_user.full_name, + points=points, + date_str=date_str, + start_time_str=start_time_str, + end_time_str=end_time_str, + team_name=org_team.name if org_team else None, + coach_name=coach.full_name, + coach_discord=coach.discord_username + ) + + flash('One on One request sent to your coach!', 'success') + return redirect(url_for('users.one_on_one')) + + # Calculate dates for the next week (starting from today) + dates = [] + for i in range(7): + d = date_type.today() + timedelta(days=i) + dates.append({ + 'value': d.strftime('%Y-%m-%d'), + 'display': d.strftime('%A, %b %d'), + 'day_of_week': d.weekday() + }) + + # Serialize coach availability for JavaScript + coach_availability_serialized = [ + { + 'id': av.id, + 'day_of_week': av.day_of_week, + 'start_time': av.start_time.strftime('%H:%M'), + 'end_time': av.end_time.strftime('%H:%M') + } + for av in coach_availability + ] + + return render_template('pages/one_on_one.html', + org_team=org_team, + coach=coach, + team_notes=team_notes, + personal_notes=personal_notes, + coach_availability=coach_availability_serialized, + dates=dates) + + +@users_bp.route('/coach-availability', methods=['GET', 'POST']) +@login_required +def manage_coach_availability(): + """Manage coach availability (for coaches). + + GET: Render the availability management page. + POST: Add availability slots via bulk. + + Returns: + Response: Rendered management page or redirect. + """ + if current_user.role != 'coach': + flash('Only coaches can manage availability.', 'danger') + return redirect(url_for('main.dashboard')) + + if request.method == 'POST': + data = request.get_json() + slots = data.get('slots', []) + + created = [] + for slot in slots: + day_of_week = slot.get('day_of_week') + start_time_str = slot.get('start_time') + + if day_of_week is None or day_of_week < 0 or day_of_week > 6: + continue + + try: + start_time = datetime.strptime(start_time_str, '%H:%M').time() + except (ValueError, TypeError): + continue + + end_time = add_30_minutes(start_time) + + # Check if this slot already exists for this coach + existing = CoachAvailability.query.filter_by( + coach_id=current_user.id, + day_of_week=day_of_week, + start_time=start_time + ).first() + + if not existing: + availability = CoachAvailability( + coach_id=current_user.id, + day_of_week=day_of_week, + start_time=start_time, + end_time=end_time + ) + db.session.add(availability) + db.session.flush() + created.append({ + 'id': availability.id, + 'day_of_week': availability.day_of_week, + 'day_name': DAY_NAMES[availability.day_of_week], + 'start_time': availability.start_time.strftime('%H:%M'), + 'end_time': availability.end_time.strftime('%H:%M') + }) + + db.session.commit() + return jsonify({'success': True, 'created': created}) + + # Get existing availability + existing_availability = CoachAvailability.query.filter_by(coach_id=current_user.id).all() + + return render_template('pages/coach_availability.html', + existing_availability=existing_availability) + + +@users_bp.route('/coach-availability/clear', methods=['POST']) +@login_required +def clear_coach_availability(): + """Clear all coach availability slots. + + Returns: + Response: JSON with success status. + """ + if current_user.role != 'coach': + return jsonify({'error': 'Unauthorized'}), 403 + + CoachAvailability.query.filter_by(coach_id=current_user.id).delete() + db.session.commit() + return jsonify({'success': True}) + + +@users_bp.route('/coach-availability//delete', methods=['POST']) +@login_required +def delete_coach_availability(availability_id): + """Delete a coach availability slot. + + Args: + availability_id: The ID of the availability slot to delete. + + Returns: + Response: JSON with success status or error. + """ + if current_user.role != 'coach': + return jsonify({'error': 'Unauthorized'}), 403 + + availability = CoachAvailability.query.get_or_404(availability_id) + + if availability.coach_id != current_user.id: + return jsonify({'error': 'Unauthorized'}), 403 + + db.session.delete(availability) + db.session.commit() + return jsonify({'success': True}) + + +@users_bp.route('/api/coach-availability/') +@login_required +def api_get_coach_availability(coach_id): + """API endpoint to get coach availability. + + Args: + coach_id: The ID of the coach. + + Returns: + Response: JSON with availability data. + """ + if current_user.role != 'player': + return jsonify({'error': 'Unauthorized'}), 403 + + availability = CoachAvailability.query.filter_by(coach_id=coach_id).all() + + result = {} + for av in availability: + if av.day_of_week not in result: + result[av.day_of_week] = [] + result[av.day_of_week].append({ + 'id': av.id, + 'start_time': av.start_time.strftime('%H:%M'), + 'end_time': av.end_time.strftime('%H:%M') + }) + + return jsonify(result) + + +@users_bp.route('/team-notes', methods=['GET', 'POST']) +@login_required +def manage_team_notes(): + """Manage team improvement notes (for coaches). + + GET: Render the team notes management page. + POST: Create or update team notes. + + Returns: + Response: Rendered management page or redirect. + """ + if current_user.role != 'coach': + flash('Only coaches can manage team notes.', 'danger') + return redirect(url_for('main.dashboard')) + + # Get the coach's team + org_team = OrgTeam.query.filter_by(coach_id=current_user.id).first() + if not org_team: + flash('You are not assigned to coach any team.', 'danger') + return redirect(url_for('main.dashboard')) + + # Get existing team notes + team_notes = TeamNote.query.filter_by(org_team_id=org_team.id).order_by(TeamNote.created_at.desc()).all() + + if request.method == 'POST': + content = request.form.get('content', '').strip() + + if content: + # Create new team note entry (keeps history) + note = TeamNote( + org_team_id=org_team.id, + coach_id=current_user.id, + content=content + ) + db.session.add(note) + db.session.commit() + flash('Team notes added successfully!', 'success') + return redirect(url_for('users.manage_team_notes')) + + return render_template('pages/team_notes.html', + org_team=org_team, + team_notes=team_notes) + + +@users_bp.route('/personal-notes', methods=['GET', 'POST']) +@login_required +def manage_personal_notes(): + """Manage personal notes for players (for coaches). + + GET: Render the personal notes management page. + POST: Create a personal note for a player. + + Returns: + Response: Rendered management page or redirect. + """ + if current_user.role != 'coach': + flash('Only coaches can manage personal notes.', 'danger') + return redirect(url_for('main.dashboard')) + + # Get players on the coach's team + org_team = OrgTeam.query.filter_by(coach_id=current_user.id).first() + players = [] + if org_team: + players = User.query.filter_by(role='player', team_id=org_team.id).order_by(User.full_name).all() + + if request.method == 'POST': + player_id = request.form.get('player_id', type=int) + content = request.form.get('content', '').strip() + + if not player_id or not content: + flash('Please select a player and enter note content.', 'danger') + return redirect(url_for('users.manage_personal_notes')) + + # Verify player is on coach's team + if org_team and player_id not in [p.id for p in players]: + flash('You can only add notes for players on your team.', 'danger') + return redirect(url_for('users.manage_personal_notes')) + + note = PersonalNote( + player_id=player_id, + coach_id=current_user.id, + content=content + ) + db.session.add(note) + db.session.commit() + flash('Personal note added successfully!', 'success') + return redirect(url_for('users.manage_personal_notes')) + + # Get all personal notes for players on this team + personal_notes = [] + if org_team: + personal_notes = PersonalNote.query.filter( + PersonalNote.player_id.in_([p.id for p in players]) + ).order_by(PersonalNote.created_at.desc()).all() + + return render_template('pages/personal_notes.html', + players=players, + personal_notes=personal_notes, + org_team=org_team) + + +# Note source types for filtering +NOTE_SOURCE_MATCH = 'match' +NOTE_SOURCE_TRYOUT = 'tryout' +NOTE_SOURCE_TEAM = 'team' + + +@users_bp.route('/my-notes') +@login_required +def my_notes(): + """View all notes for the current player. + + Shows both personal notes and team notes that the player has received. + Personal notes are grouped by source (match, tryout, team). + + Returns: + Response: Rendered my notes template. + """ + if current_user.role != 'player': + flash('Only players can view their notes.', 'danger') + return redirect(url_for('main.dashboard')) + + # Get player's team and coach + org_team = OrgTeam.query.get(current_user.team_id) if current_user.team_id else None + + # Get all personal notes for this player with eager loading for relationships + personal_notes = PersonalNote.query.filter_by(player_id=current_user.id).order_by(PersonalNote.created_at.desc()).all() + + # Get team notes for this player's team + team_notes = [] + if org_team: + team_notes = TeamNote.query.filter_by(org_team_id=org_team.id).order_by(TeamNote.created_at.desc()).all() + + return render_template('pages/player_personal_notes.html', + org_team=org_team, + personal_notes=personal_notes, + team_notes=team_notes) + + +@users_bp.route('/notes/add', methods=['GET', 'POST']) +@login_required +def add_personal_note(): + """Add a personal note with optional context (for coaches and managers). + + GET: Render the note creation form. + POST: Create a personal note, optionally linked to match/tryout/team. + + Returns: + Response: Create form or redirect to notes view. + """ + if current_user.role not in ['coach', 'manager', 'president']: + flash('Only coaches and managers can add notes.', 'danger') + return redirect(url_for('main.dashboard')) + + # Get players this user can manage + players = [] + org_team = None + if current_user.role == 'coach': + org_team = OrgTeam.query.filter_by(coach_id=current_user.id).first() + if org_team: + players = User.query.filter_by(role='player', team_id=org_team.id).order_by(User.full_name).all() + elif current_user.role in ['president', 'manager']: + players = User.query.filter_by(role='player').order_by(User.full_name).all() + + # Get available matches and tryouts for context + matches = [] + tryouts = [] + teams = [] + if current_user.role == 'coach' and org_team: + tryouts = Tryout.query.filter_by(target_org_team_id=org_team.id).order_by(Tryout.date.desc()).all() + matches = Match.query.join(Tryout).filter(Tryout.target_org_team_id == org_team.id).order_by(Match.date.desc()).all() + elif current_user.role in ['president', 'manager']: + tryouts = Tryout.query.order_by(Tryout.date.desc()).all() + matches = Match.query.order_by(Match.date.desc()).all() + teams = Team.query.order_by(Team.name).all() + + if request.method == 'POST': + player_id = request.form.get('player_id', type=int) + content = request.form.get('content', '').strip() + match_id = request.form.get('match_id', type=int) + tryout_id = request.form.get('tryout_id', type=int) + team_id = request.form.get('team_id', type=int) + + if not player_id or not content: + flash('Please select a player and enter note content.', 'danger') + return redirect(url_for('users.add_personal_note')) + + # Verify player is on coach's team (if coach) + if current_user.role == 'coach' and org_team and player_id not in [p.id for p in players]: + flash('You can only add notes for players on your team.', 'danger') + return redirect(url_for('users.add_personal_note')) + + # Validate context - ensure coach can access the match/tryout/team + if match_id: + match = Match.query.get(match_id) + match_tryout = None + if match: + match_tryout = Tryout.query.get(match.tryout_id) + if match_tryout and match_tryout.target_org_team_id and match_tryout.target_org_team_id != org_team.id: + flash('You can only add notes for matches in your team\'s tryouts.', 'danger') + return redirect(url_for('users.add_personal_note')) + if tryout_id and org_team: + tryout = Tryout.query.get(tryout_id) + if tryout and tryout.target_org_team_id and tryout.target_org_team_id != org_team.id: + flash('You can only add notes for your team\'s tryouts.', 'danger') + return redirect(url_for('users.add_personal_note')) + if team_id and org_team: + team = Team.query.get(team_id) + if team: + tryout = Tryout.query.get(team.tryout_id) + if tryout and tryout.target_org_team_id and tryout.target_org_team_id != org_team.id: + flash('You can only add notes for your team\'s tryouts.', 'danger') + return redirect(url_for('users.add_personal_note')) + + note = PersonalNote( + player_id=player_id, + coach_id=current_user.id, + content=content, + match_id=match_id if match_id else None, + team_id=team_id if team_id else None, + tryout_id=tryout_id if tryout_id else None + ) + db.session.add(note) + db.session.commit() + flash('Personal note added successfully!', 'success') + return redirect(url_for('users.my_notes')) + + return render_template('pages/add_personal_note.html', + players=players, + matches=matches, + tryouts=tryouts, + teams=teams, + org_team=org_team) + + +@users_bp.route('/match//add-note', methods=['GET', 'POST']) +@login_required +def add_note_from_match(match_id): + """Add a personal note from a match view context (for coaches). + + GET: Render the note creation form pre-filled with match info. + POST: Create a personal note linked to the match. + + Args: + match_id: The ID of the match to create note for. + + Returns: + Response: Create form or redirect. + """ + if current_user.role not in ['coach', 'manager', 'president']: + flash('Only coaches can add notes from matches.', 'danger') + return redirect(url_for('main.dashboard')) + + match = Match.query.get_or_404(match_id) + tryout = Tryout.query.get(match.tryout_id) + + # Check permissions + if current_user.role == 'coach': + org_team = OrgTeam.query.filter_by(coach_id=current_user.id).first() + if not org_team or (tryout.target_org_team_id and tryout.target_org_team_id != org_team.id): + flash('You do not have permission for this match.', 'danger') + return redirect(url_for('matches.calendar')) + + # Get all participants for this match + participant_ids = [] + if match.match_type == 'team_vs_team': + if match.team1_id: + team_members = TeamMember.query.filter_by(team_id=match.team1_id).all() + participant_ids.extend([m.player_id for m in team_members]) + if match.team2_id: + team_members = TeamMember.query.filter_by(team_id=match.team2_id).all() + participant_ids.extend([m.player_id for m in team_members]) + else: + participants = MatchParticipant.query.filter_by(match_id=match_id).all() + participant_ids = [p.player_id for p in participants] + + players = User.query.filter(User.id.in_(participant_ids)).order_by(User.full_name).all() if participant_ids else [] + + # Get team notes for context + team_notes = [] + if tryout and tryout.target_org_team_id: + team_notes = TeamNote.query.filter_by(org_team_id=tryout.target_org_team_id).order_by(TeamNote.created_at.desc()).all() + + if request.method == 'POST': + player_id = request.form.get('player_id', type=int) + content = request.form.get('content', '').strip() + + if not player_id or not content: + flash('Please select a player and enter note content.', 'danger') + elif player_id not in participant_ids: + flash('Selected player is not in this match.', 'danger') + else: + note = PersonalNote( + player_id=player_id, + coach_id=current_user.id, + content=content, + match_id=match_id + ) + db.session.add(note) + db.session.commit() + flash('Personal note added successfully!', 'success') + return redirect(url_for('tryouts.view_tryout', tryout_id=tryout.id)) + + return render_template('pages/add_note_from_match.html', + match=match, + tryout=tryout, + players=players, + team_notes=team_notes) + + +@users_bp.route('/tryout//add-note', methods=['GET', 'POST']) +@login_required +def add_note_from_tryout(tryout_id): + """Add a personal note from a tryout view context (for coaches). + + GET: Render the note creation form pre-filled with tryout info. + POST: Create a personal note linked to the tryout. + + Args: + tryout_id: The ID of the tryout to create note for. + + Returns: + Response: Create form or redirect. + """ + if current_user.role not in ['coach', 'manager', 'president']: + flash('Only coaches can add notes from tryouts.', 'danger') + return redirect(url_for('main.dashboard')) + + tryout = Tryout.query.get_or_404(tryout_id) + + # Check permissions + if current_user.role == 'coach': + org_team = OrgTeam.query.filter_by(coach_id=current_user.id).first() + if not org_team or (tryout.target_org_team_id and tryout.target_org_team_id != org_team.id): + flash('You do not have permission for this tryout.', 'danger') + return redirect(url_for('tryouts.list_tryouts')) + + # Get all registered players in this tryout + registrations = TryoutRegistration.query.filter_by(tryout_id=tryout_id).all() + player_ids = [r.player_id for r in registrations] + + # If coach, filter to only their team players + if current_user.role == 'coach' and org_team: + players = User.query.filter(User.id.in_(player_ids), User.team_id == org_team.id).order_by(User.full_name).all() + else: + players = User.query.filter(User.id.in_(player_ids)).order_by(User.full_name).all() + + # Get team notes for context + team_notes = [] + if tryout.target_org_team_id: + team_notes = TeamNote.query.filter_by(org_team_id=tryout.target_org_team_id).order_by(TeamNote.created_at.desc()).all() + + if request.method == 'POST': + player_id = request.form.get('player_id', type=int) + content = request.form.get('content', '').strip() + + if not player_id or not content: + flash('Please select a player and enter note content.', 'danger') + elif player_id not in [p.id for p in players]: + flash('Selected player is not registered for this tryout.', 'danger') + else: + note = PersonalNote( + player_id=player_id, + coach_id=current_user.id, + content=content, + tryout_id=tryout_id + ) + db.session.add(note) + db.session.commit() + flash('Personal note added successfully!', 'success') + return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id)) + + return render_template('pages/add_note_from_tryout.html', + tryout=tryout, + players=players, + team_notes=team_notes) diff --git a/seed.py b/seed.py index 18b004b..09af244 100644 --- a/seed.py +++ b/seed.py @@ -6,7 +6,7 @@ users, tryouts, teams, evaluations, and player disponibilities. from sqlalchemy import text from extensions import db, hash_password -from models import User, Tryout, TryoutRegistration, Evaluation, Team, TeamMember, OrgTeam, PlayerDisponibility, UserGamertag +from models import User, Tryout, TryoutRegistration, Evaluation, Team, TeamMember, OrgTeam, PlayerDisponibility, UserGamertag, CoachAvailability, TeamNote, PersonalNote, Match, MatchParticipant from datetime import datetime, timedelta, time import random @@ -363,8 +363,141 @@ def seed_database(): db.session.add(d) disponibilities.append(d) + # Create sample coach availabilities + coach_availabilities = [] + coach_avail_time_slots = [(16, 0), (16, 30), (17, 0), (17, 30), (18, 0), (18, 30), (19, 0), (19, 30), (20, 0), (20, 30), (21, 0), (21, 30)] + + # Only coaches with teams get availabilities + for coach, org_team in zip(coaches[:3], org_teams[:3]): + if coach.id == coaches[2].id: # Skip coach3 as they don't have all days + days_available = [0, 1, 2, 3, 4] # Mon-Fri + else: + days_available = [0, 1, 2, 3, 4, 5] # Mon-Sat + + for day in days_available: + num_slots = random.randint(3, 5) + chosen_slots = random.sample(coach_avail_time_slots, min(num_slots, len(coach_avail_time_slots))) + for hour, minute in chosen_slots: + start_time = time(hour, minute) + end_minute = minute + 30 + end_hour = hour + if end_minute >= 60: + end_minute -= 60 + end_hour += 1 + end_time = time(end_hour, end_minute) + + ca = CoachAvailability( + coach_id=coach.id, + day_of_week=day, + start_time=start_time, + end_time=end_time + ) + db.session.add(ca) + coach_availabilities.append(ca) + db.session.commit() - print(f"[OK] Created {len(disponibilities)} player disponibilities") + print(f"[OK] Created {len(coach_availabilities)} coach availabilities") + + # Create team notes for each org team + team_notes_data = [ + { + 'team': org_teams[0], + 'coach': coaches[0], + 'content': 'Team, focus on rotation and positioning during scrims. We need to improve our mechanical consistency and work on post-platoon transitions. Remember to communicate clearly and stay positive!' + }, + { + 'team': org_teams[1], + 'coach': coaches[1], + 'content': 'Great progress this week! Keep working on your smoke lineups and utility usage. Individual practice on aim trainers is paying off. Next week we focus on map control and trading.' + }, + { + 'team': org_teams[2], + 'coach': coaches[2], + 'content': 'Agent comp needs work. Make sure to stick to your roles and trust your teammates. Work on your crosshair placement and pre-aim common angles. Team chemistry is key!' + }, + ] + + for note_data in team_notes_data: + note = TeamNote( + org_team_id=note_data['team'].id, + coach_id=note_data['coach'].id, + content=note_data['content'] + ) + db.session.add(note) + + db.session.commit() + print(f"[OK] Created {len(team_notes_data)} team notes") + + # Create personal notes for players + personal_notes_data = [ + {'player': players[0], 'coach': coaches[0], 'content': 'Your mechanics are improving! Focus on staying calm during high-pressure situations. Keep practicing those flip resets.'}, + {'player': players[0], 'coach': coaches[0], 'content': 'Good positioning in last scrim. Work on your kickoffs - consistency will help the team.'}, + {'player': players[1], 'coach': coaches[0], 'content': 'Your aerial game is strong. Try to be more aggressive on the ball when you have space.'}, + {'player': players[3], 'coach': coaches[1], 'content': 'Need to work on your smoke grenade placement. Practice pre-aiming and strafe stopping.'}, + {'player': players[4], 'coach': coaches[1], 'content': 'Good clutch performance! Keep your utility management consistent throughout rounds.'}, + {'player': players[6], 'coach': coaches[2], 'content': 'Your aim trainer routine is paying off. Work on your agent abilities usage timing.'}, + {'player': players[7], 'coach': coaches[2], 'content': 'Focus on communication in matches. Call out enemy positions clearly and ask for help when needed.'}, + ] + + for note_data in personal_notes_data: + note = PersonalNote( + player_id=note_data['player'].id, + coach_id=note_data['coach'].id, + content=note_data['content'] + ) + db.session.add(note) + + db.session.commit() + print(f"[OK] Created {len(personal_notes_data)} personal notes") + + # Create sample matches for tryouts (for calendar testing) + matches_data = [ + {'tryout': tryouts[0], 'title': 'Alpha vs Bravo', 'date': tryouts[0].date, 'start_time': time(18, 0), 'end_time': time(18, 30), 'match_type': 'team_vs_team', 'team1_id': team1.id if 'team1' in dir() else None}, + {'tryout': tryouts[0], 'title': 'Bravo vs Alpha', 'date': tryouts[0].date, 'start_time': time(19, 0), 'end_time': time(19, 30), 'match_type': 'team_vs_team'}, + {'tryout': tryouts[1], 'title': 'Scrimmage', 'date': tryouts[1].date, 'start_time': time(17, 0), 'end_time': time(17, 30), 'match_type': 'player_scrim'}, + {'tryout': tryouts[2], 'title': 'Team Alpha Scrim', 'date': tryouts[2].date, 'start_time': time(18, 30), 'end_time': time(19, 0), 'match_type': 'player_vs_player'}, + ] + + # Re-fetch teams after commit + team1 = Team.query.filter_by(name='Alpha Team').first() + team2 = Team.query.filter_by(name='Bravo Team').first() + + matches = [] + for i, m_data in enumerate(matches_data): + m = Match( + tryout_id=m_data['tryout'].id, + title=m_data['title'], + date=m_data['date'], + start_time=m_data['start_time'], + end_time=m_data['end_time'], + match_type=m_data['match_type'], + created_by=president.id, + team1_id=m_data.get('team1_id') or (team1.id if i < 2 else None), + team2_id=team2.id if i < 2 else None + ) + db.session.add(m) + matches.append(m) + db.session.commit() + print(f"[OK] Created {len(matches)} matches") + + # Add match participants for scrim matches + player_scrim_match = matches[2] if len(matches) > 2 else None + if player_scrim_match: + for player in players[3:5]: + mp = MatchParticipant(match_id=player_scrim_match.id, player_id=player.id) + db.session.add(mp) + + pvp_match = matches[3] if len(matches) > 3 else None + if pvp_match and team1: + for player in players[:2]: + mp = MatchParticipant(match_id=pvp_match.id, player_id=player.id, team_side=1) + db.session.add(mp) + for player in players[2:4]: + mp = MatchParticipant(match_id=pvp_match.id, player_id=player.id, team_side=2) + db.session.add(mp) + + db.session.commit() + print("[OK] Created match participants") print("\n[SUCCESS] Database seeded successfully!") print("\n=== Login Credentials ===") diff --git a/templates/layouts/base.html b/templates/layouts/base.html index 1106c27..f972f01 100644 --- a/templates/layouts/base.html +++ b/templates/layouts/base.html @@ -67,18 +67,52 @@ {% endif %} -

  • - - - Contracts - -
  • My Profile
  • + {% if current_user.role == 'player' %} +
  • + + + One on One + +
  • +
  • + + + My Notes + +
  • + {% endif %} + {% if current_user.role == 'coach' %} +
  • + + + Availability + +
  • +
  • + + + Team Notes + +
  • +
  • + + + Personal Notes + +
  • + {% endif %} +
  • + + + Contracts + +
  • diff --git a/templates/pages/add_note_from_match.html b/templates/pages/add_note_from_match.html new file mode 100644 index 0000000..f2eaedb --- /dev/null +++ b/templates/pages/add_note_from_match.html @@ -0,0 +1,63 @@ +{% extends "layouts/base.html" %} +{% block title %}Add Note from Match - TryoutPro{% endblock %} +{% block page_title %}Add Note from Match{% endblock %} +{% block breadcrumb %}Home / Tryouts / {{ tryout.title }} / Add Note{% endblock %} + +{% block content %} +
    +
    +

    Add Note for Match: {{ match.title }}

    + {{ match.date.strftime('%m/%d/%Y') }} +
    +
    +
    + + +
    + + +
    + +
    + + +

    This note will be linked to this match and visible to the selected player.

    +
    + +
    + + + Back to Tryout + +
    +
    +
    +
    + +{% if team_notes %} +
    +
    +

    Team Notes Reference

    +
    +
    +
    + {% for note in team_notes %} +
    + + {{ note.coach.full_name if note.coach else 'Unknown Coach' }} + + {{ note.content | nl2br }} +
    + {% endfor %} +
    +
    +
    +{% endif %} +{% endblock %} \ No newline at end of file diff --git a/templates/pages/add_note_from_tryout.html b/templates/pages/add_note_from_tryout.html new file mode 100644 index 0000000..6591e6b --- /dev/null +++ b/templates/pages/add_note_from_tryout.html @@ -0,0 +1,63 @@ +{% extends "layouts/base.html" %} +{% block title %}Add Note from Tryout - TryoutPro{% endblock %} +{% block page_title %}Add Note from Tryout{% endblock %} +{% block breadcrumb %}Home / Tryouts / {{ tryout.title }} / Add Note{% endblock %} + +{% block content %} +
    +
    +

    Add Note for Tryout: {{ tryout.title }}

    + {{ tryout.date.strftime('%m/%d/%Y') }} +
    +
    +
    + + +
    + + +
    + +
    + + +

    This note will be linked to this tryout and visible to the selected player.

    +
    + +
    + + + Back to Tryout + +
    +
    +
    +
    + +{% if team_notes %} +
    +
    +

    Team Notes Reference

    +
    +
    +
    + {% for note in team_notes %} +
    + + {{ note.coach.full_name if note.coach else 'Unknown Coach' }} + + {{ note.content | nl2br }} +
    + {% endfor %} +
    +
    +
    +{% endif %} +{% endblock %} \ No newline at end of file diff --git a/templates/pages/add_personal_note.html b/templates/pages/add_personal_note.html new file mode 100644 index 0000000..36084c2 --- /dev/null +++ b/templates/pages/add_personal_note.html @@ -0,0 +1,80 @@ +{% extends "layouts/base.html" %} +{% block title %}Add Note - TryoutPro{% endblock %} +{% block page_title %}Add Personal Note{% endblock %} +{% block breadcrumb %}Home / My Notes / Add Note{% endblock %} + +{% block content %} +
    +
    +

    Add Personal Note

    + {% if org_team %} + {{ org_team.name }} + {% endif %} +
    +
    +
    + + +
    + + +
    + +
    + + +

    These notes will only be visible to the selected player.

    +
    + +
    + +

    Link this note to a specific match, tryout, or team for better organization.

    + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    + +
    + + + Back to Notes + +
    +
    +
    +
    +{% endblock %} \ No newline at end of file diff --git a/templates/pages/coach_availability.html b/templates/pages/coach_availability.html new file mode 100644 index 0000000..e154bd5 --- /dev/null +++ b/templates/pages/coach_availability.html @@ -0,0 +1,260 @@ +{% extends "layouts/base.html" %} +{% block title %}Manage Availability - TryoutPro{% endblock %} +{% block page_title %}Manage Availability{% endblock %} +{% block breadcrumb %}Home / Coach Availability{% endblock %} + +{% block content %} +
    +
    +

    Set Your Weekly Availability

    +

    Select time slots when you're available for One on One sessions

    +
    +
    +
    +

    Loading availability grid...

    +
    + +
    + +
    +
    +
    + +
    +
    +

    Current Availability

    +
    +
    + {% if existing_availability %} + + + + + + + + + + {% for av in existing_availability %} + + + + + + {% endfor %} + +
    DayTimeAction
    {{ ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'][av.day_of_week] }}{{ av.start_time.strftime('%I:%M %p') }} - {{ av.end_time.strftime('%I:%M %p') }} + +
    + {% else %} +

    No availability slots set. Use the grid above to add your available times.

    + {% endif %} +
    +
    +{% endblock %} + +{% block scripts %} + + +{% endblock %} \ No newline at end of file diff --git a/templates/pages/create_match.html b/templates/pages/create_match.html index acaed23..b9aca6d 100644 --- a/templates/pages/create_match.html +++ b/templates/pages/create_match.html @@ -114,7 +114,19 @@

    Select Players

    -

    Click on players to assign them to Team 1 or Team 2. Players available in all selected time blocks are shown below.

    + +
    +
    + + + vs + 0 + +
    +

    Select players then click Randomize to split them into teams.

    +
    @@ -203,7 +215,7 @@ font-size: 14px; } .team-selection .player-item:hover { - background: var(--accent-color); + background: var(--primary); color: white; } .player-pool { @@ -277,6 +289,41 @@ .team-selection .player-item:hover .remove-btn { opacity: 1; } + +.randomize-section { + margin: 15px 0; + padding: 10px; + background: var(--bg-secondary); + border-radius: 8px; +} +.randomize-controls { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; +} +.randomize-controls label { + margin: 0; + font-weight: 500; +} +.randomize-input { + width: 60px; + padding: 4px 8px; + border: 1px solid var(--border-color); + border-radius: 4px; +} +.randomize-preview { + font-weight: bold; + min-width: 20px; + text-align: center; +} +.randomize-btn { + background: var(--primary); + color: white; +} +.randomize-btn:hover { + background: var(--primary-dark); +} {% endblock %} \ No newline at end of file diff --git a/templates/pages/edit_match.html b/templates/pages/edit_match.html index 256cdeb..34ddcf0 100644 --- a/templates/pages/edit_match.html +++ b/templates/pages/edit_match.html @@ -7,6 +7,7 @@

    Edit Match

    +

    Match Type: {{ match.match_type.replace('_', ' ').title() }}

    @@ -36,7 +37,7 @@ {% if current_user.can_manage_teams() or current_user.can_schedule_matches() %}

    Select Match Time

    -

    Click time slots consecutively to set match duration. Available players will be auto-selected below.

    +

    Click time slots consecutively to set match duration. Players available in all selected time blocks are shown below.

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

    Teams

    - -
    -
    - - +
    +
    +

    Teams

    + +
    +
    + + +
    +
    + + +
    -
    - - + + +
    + {% if match.team1 %} +
    +
    {{ match.team1.name }}
    +
      + {% for member in match.team1.members %} +
    • {{ member.player.full_name if member.player else 'Unknown Player' }}{% if member.position %} {{ member.position }}{% endif %}
    • + {% else %} +
    • No players assigned
    • + {% endfor %} +
    +
    + {% endif %} + {% if match.team2 %} +
    +
    {{ match.team2.name }}
    +
      + {% for member in match.team2.members %} +
    • {{ member.player.full_name if member.player else 'Unknown Player' }}{% if member.position %} {{ member.position }}{% endif %}
    • + {% else %} +
    • No players assigned
    • + {% endfor %} +
    +
    + {% endif %}
    - -
    - {% if match.team1 %} -
    -
    {{ match.team1.name }}
    -
      - {% for member in match.team1.members %} -
    • {{ member.player.full_name if member.player else 'Unknown Player' }}{% if member.position %} {{ member.position }}{% endif %}
    • - {% else %} -
    • No players assigned
    • - {% endfor %} -
    -
    - {% endif %} - {% if match.team2 %} -
    -
    {{ match.team2.name }}
    -
      - {% for member in match.team2.members %} -
    • {{ member.player.full_name if member.player else 'Unknown Player' }}{% if member.position %} {{ member.position }}{% endif %}
    • - {% else %} -
    • No players assigned
    • - {% endfor %} -
    -
    - {% endif %} -
    - - {% elif match.match_type == 'player_vs_player' %} -
    -

    Select Players

    - - -
    -
    - - - vs - 0 - +
    +
    +

    Select Players

    + + +
    +
    + + + vs + 0 + +
    +

    Select players then click Randomize to split them into teams.

    -

    Select players then click Randomize to split them into teams.

    -
    - - {% if current_user.can_manage_teams() or current_user.can_schedule_matches() %} -

    Green indicators show player availability for the match date/time

    - {% endif %} - -
    -
    - -
    - {% for player in all_players %} - - {% endfor %} + +
    +
    +
    Team 1
    +
    -
    -
    - -
    - {% for player in all_players %} - - {% endfor %} + +
    +
    Available Players
    +
    +

    All registered players are shown. Click time slots to filter available players.

    +
    +
    + +
    +
    Team 2
    +
    + + +
    - {% else %} -
    -

    Select Players

    - - {% if current_user.can_manage_teams() or current_user.can_schedule_matches() %} -

    Green indicators show player availability for the match date/time

    - {% endif %} - -
    - +
    +
    +

    Select Players

    + + {% if current_user.can_manage_teams() or current_user.can_schedule_matches() %} +

    Green indicators show player availability for the match date/time

    + {% endif %} +
    {% for player in all_players %}
    - {% endif %}
    +
    + + {% else %} +

    You need to be assigned to a team with a coach to request a One on One session.

    + {% endif %} +
    +
    +
    + +{% if coach %} + + +{% endif %} +{% endblock %} + +{% block scripts %} + +{% endblock %} \ No newline at end of file diff --git a/templates/pages/personal_notes.html b/templates/pages/personal_notes.html new file mode 100644 index 0000000..35efcd0 --- /dev/null +++ b/templates/pages/personal_notes.html @@ -0,0 +1,64 @@ +{% extends "layouts/base.html" %} +{% block title %}Personal Notes - TryoutPro{% endblock %} +{% block page_title %}Personal Notes{% endblock %} +{% block breadcrumb %}Home / Personal Notes{% endblock %} + +{% block content %} +
    +
    +

    Add Personal Note

    + {% if org_team %} + {{ org_team.name }} + {% endif %} +
    +
    +
    + + +
    + + +
    + +
    + + +

    These notes will only be visible to the selected player.

    +
    + +
    + +
    +
    +
    +
    + +{% if personal_notes %} +
    +
    +

    Recent Notes

    +
    +
    +
    + {% for note in personal_notes %} +
    + + {{ note.player.full_name if note.player else 'Unknown Player' }} - + {{ note.created_at.strftime('%B %d, %Y') if note.created_at else 'Unknown date' }} + + {{ note.content | nl2br if note.content else '' }} + From: {{ note.coach.full_name if note.coach else 'Unknown Coach' }} +
    + {% endfor %} +
    +
    +
    +{% endif %} +{% endblock %} \ No newline at end of file diff --git a/templates/pages/player_personal_notes.html b/templates/pages/player_personal_notes.html new file mode 100644 index 0000000..c9a69ae --- /dev/null +++ b/templates/pages/player_personal_notes.html @@ -0,0 +1,85 @@ +{% extends "layouts/base.html" %} +{% block title %}My Notes - TryoutPro{% endblock %} +{% block page_title %}My Notes{% endblock %} +{% block breadcrumb %}Home / One on One / My Notes{% endblock %} + +{% block content %} +
    + +
    +
    +

    Personal Notes

    + {% if org_team %} + Team: {{ org_team.name }} + {% endif %} +
    +
    + {% if personal_notes %} + {% for note in personal_notes %} +
    +
    + + Note from {{ note.coach.full_name if note.coach else 'Unknown Coach' }} + + {{ note.content | nl2br }} +
    + {% if note.match_id and note.match %} + + Match: {{ note.match.title }} + + {% endif %} + {% if note.team_id and note.team %} + + Team: {{ note.team.name }} + + {% endif %} + {% if note.tryout_id and note.tryout %} + + Tryout: {{ note.tryout.title }} + + {% endif %} +
    +
    +
    +

    Added: {{ note.created_at.strftime('%B %d, %Y at %I:%M %p') }}

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

    No personal notes have been added yet. Your coach may provide individual feedback here.

    + {% endif %} +
    +
    + + +
    +
    +

    Team Notes

    + {% if org_team %} + {{ org_team.name }} + {% endif %} +
    +
    + {% if team_notes %} + {% for note in team_notes %} +
    +
    + Coach: {{ note.coach.full_name if note.coach else 'Unknown Coach' }} + {{ note.content | nl2br }} +
    +
    +

    Updated: {{ note.updated_at.strftime('%B %d, %Y at %I:%M %p') }}

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

    No team notes have been added yet. Your coach will post improvement suggestions here.

    + {% endif %} +
    +
    +
    + + +{% endblock %} \ No newline at end of file diff --git a/templates/pages/team_notes.html b/templates/pages/team_notes.html new file mode 100644 index 0000000..9e85fcd --- /dev/null +++ b/templates/pages/team_notes.html @@ -0,0 +1,58 @@ +{% extends "layouts/base.html" %} +{% block title %}Team Notes - TryoutPro{% endblock %} +{% block page_title %}Team Notes{% endblock %} +{% block breadcrumb %}Home / Team Notes{% endblock %} + +{% block content %} +
    +
    +

    Team Improvement Notes

    + {% if org_team %} + {{ org_team.name }} + {% endif %} +
    +
    +
    + + +
    + + +

    These notes will be visible to all players on your team.

    +
    + +
    + +
    +
    +
    +
    + +{% if team_notes %} +
    +
    +

    Note History

    +
    +
    + + + + + + + + + {% for note in team_notes %} + + + + + {% endfor %} + +
    Last UpdatedContent Preview
    {{ note.updated_at.strftime('%B %d, %Y at %I:%M %p') if note.updated_at else 'Unknown date' }}{{ note.content[:100] if note.content else '' }}{% if note.content and note.content|length > 100 %}...{% endif %}
    +
    +
    +{% endif %} +{% endblock %} \ No newline at end of file diff --git a/templates/pages/view_tryout.html b/templates/pages/view_tryout.html index e1a124b..9f0b80f 100644 --- a/templates/pages/view_tryout.html +++ b/templates/pages/view_tryout.html @@ -6,7 +6,7 @@ {% block content %}
    -
    +

    Tryout Details

    {% if can_edit %} @@ -32,7 +32,7 @@
    -
    +
    Game {{ tryout.game }} @@ -249,9 +249,14 @@

    Schedule

    {% if can_edit %} - - Schedule Match - + {% endif %}
    @@ -266,7 +271,7 @@
    {% endif %} -{% if matches %} + {% if matches %}
    @@ -301,59 +306,59 @@ - + {% endif %} @@ -433,8 +441,9 @@ + - -{% endblock %} +{% endblock %} \ No newline at end of file
    {{ m.date.strftime('%m/%d/%Y') }} - {% if m.match_type == 'team_vs_team' %} -
    -
    - {{ m.team1.name if m.team1 else 'Team 1' }} - {% if participants.team1_players %} -
      - {% for pl in participants.team1_players %} -
    • {{ pl.name }}{% if pl.position %} {{ pl.position }}{% endif %}
    • - {% endfor %} -
    - {% endif %} -
    -
    vs
    -
    - {{ m.team2.name if m.team2 else 'Team 2' }} - {% if participants.team2_players %} -
      - {% for pl in participants.team2_players %} -
    • {{ pl.name }}{% if pl.position %} {{ pl.position }}{% endif %}
    • - {% endfor %} -
    - {% endif %} -
    -
    - {% elif m.match_type == 'player_vs_player' %} -
    -
    - Team 1 - {% if participants.team1_players %} -
      - {% for pl in participants.team1_players %} -
    • {{ pl.name }}{% if pl.position %} {{ pl.position }}{% endif %}
    • - {% endfor %} -
    - {% endif %} -
    -
    vs
    -
    - Team 2 - {% if participants.team2_players %} -
      - {% for pl in participants.team2_players %} -
    • {{ pl.name }}{% if pl.position %} {{ pl.position }}{% endif %}
    • - {% endfor %} -
    - {% endif %} -
    -
    - {% else %} - {{ participants | join(', ') }} - {% endif %} -
    + {% if m.match_type == 'team_vs_team' %} +
    +
    + {{ m.team1.name if m.team1 else 'Team 1' }} + {% if participants.team1_players %} +
      + {% for pl in participants.team1_players %} +
    • {{ pl.name }}{% if pl.position %} {{ pl.position }}{% endif %}
    • + {% endfor %} +
    + {% endif %} +
    +
    vs
    +
    + {{ m.team2.name if m.team2 else 'Team 2' }} + {% if participants.team2_players %} +
      + {% for pl in participants.team2_players %} +
    • {{ pl.name }}{% if pl.position %} {{ pl.position }}{% endif %}
    • + {% endfor %} +
    + {% endif %} +
    +
    + {% elif m.match_type == 'player_vs_player' %} +
    +
    + Team 1 + {% if participants.team1_players %} +
      + {% for pl in participants.team1_players %} +
    • {{ pl.name }}{% if pl.position %} {{ pl.position }}{% endif %}
    • + {% endfor %} +
    + {% endif %} +
    +
    vs
    +
    + Team 2 + {% if participants.team2_players %} +
      + {% for pl in participants.team2_players %} +
    • {{ pl.name }}{% if pl.position %} {{ pl.position }}{% endif %}
    • + {% endfor %} +
    + {% endif %} +
    +
    + {% else %} + {{ participants | join(', ') }} + {% endif %} +
    {% if m.start_time and m.end_time %} {{ m.start_time.strftime('%H:%M') }} - {{ m.end_time.strftime('%H:%M') }} @@ -369,6 +374,9 @@ Edit + + Note +