From 85fbc5eb191ed20fe1168a3341c45bc93d8e6157 Mon Sep 17 00:00:00 2001 From: Namu Date: Sat, 20 Dec 2025 21:46:44 +0100 Subject: [PATCH] Fix: Correct the submission of new school subjects --- .../Components/Calendar/Calendar.razor | 315 ++++++++++++++++++ .../SchoolSubject/SchoolSubjectCreation.razor | 43 +-- .../Data/Entities/SchoolSubject.cs | 7 +- WorkManagementTool/work_management.db | Bin 139264 -> 139264 bytes WorkManagementTool/work_management.db-shm | Bin 32768 -> 32768 bytes WorkManagementTool/work_management.db-wal | Bin 123632 -> 111272 bytes 6 files changed, 343 insertions(+), 22 deletions(-) create mode 100644 WorkManagementTool/Components/Calendar/Calendar.razor diff --git a/WorkManagementTool/Components/Calendar/Calendar.razor b/WorkManagementTool/Components/Calendar/Calendar.razor new file mode 100644 index 0000000..3ebe590 --- /dev/null +++ b/WorkManagementTool/Components/Calendar/Calendar.razor @@ -0,0 +1,315 @@ +@page "/calendar" + +@using Microsoft.AspNetCore.Authorization +@using WorkManagementTool.Services +@using WorkManagementTool.Data.Entities + +@attribute [Authorize] + +@inject SchoolSubjectService SchoolSubjectService +@inject HomeworkService HomeworkService + +

Calendar

+ +
+ + @currentMonth.ToString("MMMM yyyy") + + +
+ + +
+
+ +@if (Subjects == null || Subjects.Count == 0) +{ +

Chargement des matières...

+} +else +{ +
+ @foreach (var s in Subjects) + { +
+ + @s.Name +
+ } +
+ +
+ @foreach (var d in WeekDayHeaders) + { +
@d
+ } + + @foreach (var day in DaysForCurrentMonth) + { + var isOtherMonth = day.Month != currentMonth.Month; + var hwForDay = GetHomeworksForDate(day); +
+
@day.Day
+ @if (hwForDay != null && hwForDay.Count > 0) + { +
+ @foreach (var hw in hwForDay) + { + var color = GetSubjectColor(hw.SchoolSubject); +
+ @Shorten(hw.Title, 28) +
+ } +
+ } +
+ } +
+} + + + +@code { + [Parameter] + public List Subjects { get; set; } = new(); + + private DateTime currentMonth = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1); + private bool showCompleted { get; set; } = false; + + // mapping subjectId -> color + private readonly Dictionary subjectColors = new(); + + // mapping date -> list of homeworks + private readonly Dictionary> homeworksByDate = new(); + + private static readonly string[] WeekDayHeaders = new[] { "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim" }; + + private IEnumerable DaysForCurrentMonth + { + get + { + var first = StartOfCalendar(currentMonth); + var last = EndOfCalendar(currentMonth); + for (var d = first; d <= last; d = d.AddDays(1)) + yield return d; + } + } + + protected override async Task OnInitializedAsync() + { + await LoadDataAsync(); + } + + private async Task LoadDataAsync() + { + // Load subjects if not provided by parent + if (Subjects == null || Subjects.Count == 0) + { + Subjects = await SchoolSubjectService.GetAllSchoolSubjectsAsync(); + } + + // Assign colors deterministically + foreach (var s in Subjects) + { + if (!subjectColors.ContainsKey(s.Id)) + subjectColors[s.Id] = GenerateColorFromId(s.Id); + } + + // Clear and load homeworks grouped by date + homeworksByDate.Clear(); + + foreach (var s in Subjects) + { + // services used elsewhere: GetUncompletedHomeworkAsync and GetCompletedHomeworksAsync + var list = new List(); + try + { + var uncompleted = await HomeworkService.GetUncompletedHomeworkAsync(s.Id); + if (uncompleted != null) + list.AddRange(uncompleted); + + if (showCompleted) + { + var completed = await HomeworkService.GetCompletedHomeworksAsync(s.Id); + if (completed != null) + list.AddRange(completed); + } + } + catch + { + // ignore per-subject errors to keep calendar robust + } + + foreach (var hw in list) + { + var date = hw.DueDate.Date; + if (!homeworksByDate.ContainsKey(date)) + homeworksByDate[date] = new List(); + homeworksByDate[date].Add(hw); + } + } + + StateHasChanged(); + } + + private List? GetHomeworksForDate(DateTime date) + { + return homeworksByDate.TryGetValue(date.Date, out var list) ? list : null; + } + + private string GetSubjectColor(SchoolSubject? subject) + { + if (subject is null) return "#6c757d"; // fallback + if (subjectColors.TryGetValue(subject.Id, out var c)) return c; + var gen = GenerateColorFromId(subject.Id); + subjectColors[subject.Id] = gen; + return gen; + } + + private static string GenerateColorFromId(int id) + { + // deterministic pastel-ish color from id using HSL -> RGB + var hue = (id * 47) % 360; // disperse hues + var s = 65; // saturation % + var l = 50; // lightness % + var (r, g, b) = HslToRgb(hue, s, l); + return $"rgb({r},{g},{b})"; + } + + private static (int r, int g, int b) HslToRgb(int h, int s, int l) + { + double hh = h / 360.0; + double ss = s / 100.0; + double ll = l / 100.0; + + double q = ll < 0.5 ? ll * (1 + ss) : ll + ss - ll * ss; + double p = 2 * ll - q; + + double[] t = new double[3] { hh + 1.0 / 3.0, hh, hh - 1.0 / 3.0 }; + for (int i = 0; i < 3; i++) + { + if (t[i] < 0) t[i] += 1; + if (t[i] > 1) t[i] -= 1; + } + + double[] c = new double[3]; + for (int i = 0; i < 3; i++) + { + if (t[i] < 1.0 / 6.0) c[i] = p + ((q - p) * 6 * t[i]); + else if (t[i] < 1.0 / 2.0) c[i] = q; + else if (t[i] < 2.0 / 3.0) c[i] = p + ((q - p) * (2.0 / 3.0 - t[i]) * 6); + else c[i] = p; + } + + return ((int)(c[0] * 255), (int)(c[1] * 255), (int)(c[2] * 255)); + } + + private static DateTime StartOfCalendar(DateTime month) + { + // Start on Monday + var firstOfMonth = new DateTime(month.Year, month.Month, 1); + var diff = ((int)firstOfMonth.DayOfWeek + 6) % 7; // convert Sun=0.. to Mon=0.. + return firstOfMonth.AddDays(-diff); + } + + private static DateTime EndOfCalendar(DateTime month) + { + var last = new DateTime(month.Year, month.Month, DateTime.DaysInMonth(month.Year, month.Month)); + var diff = (7 - (((int)last.DayOfWeek + 6) % 7) - 1); + return last.AddDays(diff); + } + + private void ChangeMonth(int offset) + { + currentMonth = currentMonth.AddMonths(offset); + StateHasChanged(); + } + + private static string Shorten(string? text, int max) + { + if (string.IsNullOrEmpty(text)) return string.Empty; + if (text.Length <= max) return text; + return text.Substring(0, max - 1) + "…"; + } +} \ No newline at end of file diff --git a/WorkManagementTool/Components/SchoolSubject/SchoolSubjectCreation.razor b/WorkManagementTool/Components/SchoolSubject/SchoolSubjectCreation.razor index 78f67c0..cdef475 100644 --- a/WorkManagementTool/Components/SchoolSubject/SchoolSubjectCreation.razor +++ b/WorkManagementTool/Components/SchoolSubject/SchoolSubjectCreation.razor @@ -1,48 +1,53 @@ @page "/school-subjects/creation" +@rendermode InteractiveServer + @attribute [Authorize] @using Microsoft.AspNetCore.Authorization @using WorkManagementTool.Services @using WorkManagementTool.Data.Entities; @inject SchoolSubjectService SchoolSubjectService @inject NavigationManager NavigationManager +@inject ILogger Logger

SchoolSubjectCreation

- +@if (!string.IsNullOrEmpty(errorMessage)) +{ +
@errorMessage
+} + +
- + +
- + +
@code { - [SupplyParameterFromForm] - private SchoolSubject? Model { get; set; } + private SchoolSubject Model { get; set; } = new(); - protected override void OnInitialized() - { - if (Model is null) - { - Model = new SchoolSubject - { - Name = null!, - Description = null, - }; - } - } + private string? errorMessage; private async Task Submit() { - await SchoolSubjectService.AddSchoolSubjectAsync(Model!); - NavigationManager.NavigateTo("/school-subjects"); + if (Model is not null) + { + await SchoolSubjectService.AddSchoolSubjectAsync(Model); + Logger.LogInformation("SchoolSubject created: {Name}", Model.Name); + NavigationManager.NavigateTo("/school-subjects"); + } } -} +} \ No newline at end of file diff --git a/WorkManagementTool/Data/Entities/SchoolSubject.cs b/WorkManagementTool/Data/Entities/SchoolSubject.cs index 2353464..ef53e8b 100644 --- a/WorkManagementTool/Data/Entities/SchoolSubject.cs +++ b/WorkManagementTool/Data/Entities/SchoolSubject.cs @@ -5,9 +5,10 @@ namespace WorkManagementTool.Data.Entities public class SchoolSubject { public int Id { get; set; } - [Required] - [MaxLength(100, ErrorMessage = "Name must be less than 100 caracters")] - public required string Name { get; set; } = null!; + //[Required] + //[MaxLength(100, ErrorMessage = "Name must be less than 100 caracters")] + //[MinLength(1, ErrorMessage = "Name must containes more than 1 caracters")] + public string Name { get; set; } = null!; [MaxLength(500, ErrorMessage = "Description must be less than 500 caracters")] public string? Description { get; set; } public DateTime CreatedAt { get; set; } = DateTime.UtcNow; diff --git a/WorkManagementTool/work_management.db b/WorkManagementTool/work_management.db index 2d14ccd8dadb442f9a6c1edae16eece883d0d05d..b4c2b836d38bd793aad21713a9d83e0c55d4d0fa 100644 GIT binary patch delta 305 zcmZoTz|nAkW5etDjNF^w%$L}}#UQ}Iz<-N>1%KLRL4gSV>1pwd^CZB6C-@{_koPU` z3Eq{wr9dT-ywgi_8UL~Hy0LzYB-g`7iRH=06J5zLJ0X vQGO;lZT|NRZ0xM8%#4ieAOi&1nZax@fiUAY|2O`(n*|;2^G|;(&m;^0z`ah* delta 326 zcmX}mu}cDB7zXg~z3&e14(=W$f{P&CBEmQ?57J3!Y6u!_3JHxOEh=~agtl)}Xv$4E zXm$~V1`C2nu!a^R4)gZEKh-#UBr4>bt%d{viE0mWjTw`KXp}_`eY}wdU zX`XGB3kSu->DkFqasR@x9XFYBl6ER#+gZoXx|y|%>#Vz(bY#15c&vMro5rtpqW^bu zM{>f_(PQ|*H$LEFh*v!02@mK6k9ZKhDJ`UB&PWN7LwJb9In#8;n2rv^1fLk=EhrE0 thbJHxsiMgCv;2&&*yH415fN3?dBT3{niT8$Z5g5@u##W8h@qVc=&FVi0AJV31~z+xSsm F769IqM&SSe diff --git a/WorkManagementTool/work_management.db-wal b/WorkManagementTool/work_management.db-wal index 14a41c926d873347c56164d61c03241b673e879d..b138805ef7684f4053b4fe4c866fc6e2e2bf2941 100644 GIT binary patch literal 111272 zcmeI*du&@*9l-J9#__e|IHxIOX&I$ASr#c`Huvdk`;U#1sq3_9RP#1bL!mV`vi+0;%QoN7soxP$&KXse%DxFj};(NUNX-wLB)Rg9T}vbB#^i zq-vLD)Na4m_R;m#@j2&y?oIwUI=|ly?u&g|q+ebu#A>0psT-{awnndxeqivX-EV)# zHJ?z2dd@ul{G%uD-*)|VZ%e6ztK>^Uo>T=J0tg_000IagfB*srAbU z$mZ(DDm$yQ({sr+k*={wu{>F=&J^eGzPD1Y&BfJ8U8PT~F<&rPd(gbkKk%ZS7l_DX zLLQU*OF9KQ$f~)6J<9)PD~ez)(a#KTHD@uHTb4pFQ7vYSTE4omz>cf zfB*srAbFzrRS{!3*%E z3IYfqfB*srAbA@WbDJW6hd>?)@=&2j46%^$|b-0R#|0009ILKmY**5NI=j^XDB*su8#3 z9o)OSfBx{nk3U(@J9tFMBXXadXtVQTI0zts00IagfB*srAbNd*ZGgJAa}V5k%yIkPGs( zJk?Ry3>Ew|Lim!JCqaRf^Natr|k z5I_I{1Q0*~0R#|00D%q?XpJN2T@&dVixkU~)#^-f{_cA#<=UK{7Z}uMG#I>)^hd?|GsgM{r2ULw#!-uhgm3q11O$^Bp9>P!T`?0R#|0009ILKmdXE6mWWy zB9(IEk(6-bjYZy;ZK@~Bhi}8OEIS>(o5$8C5{=(1^^0rb39+u;kskP(H zX2*<#7)Xules!frm3q&fT48Qyf^6Wqo@Z>0B}97Tcp@?+Zm*QelV)wQVopqLFU`!2 zm$sX`s`KXTS;x&AS{roo1uviRtb8t)%NkqeXDjC1^wi8u-=D?1&p8V9Bgf6S`J9vU zZDVsZA;!{b6oX>ROlg-MM12frk7-fQmX&d{xxlrvMp1XFR>iN)m({7S7n@7v>F{)J z+gRLa?wqPkn&ZXd_+)AOM=Rm+a&@LUJ7-oW!f$h>T4iQREyr(GJ*%@^!OmrT-|_?B zH*UUt+*D1~wy#Qvx2V6M;5nIGU^%wuHv7x=Ce@|=?=965JMh2TD!IgMrT#!n{YTQ6 z7ckyw#vjdR-+x9N!ExDm9h1M8zm>nz_1O?W009ILKmY**5I_I{1Q0-=?F4kBK#wjG zx`^w-&_ztXBD%ZvUQ~^_uPfYB-+I-zA@vz<%nS5B`{e%8mtWpgpBH#t$k*jd&DUZ1 zYx#5e)3!Tn27~|t2q1s}0tg_000IagfWVb3(A^b@7(HQ`2+Md_8etiee-kmIJKWck z0{uo;xUY+(o)>6|BlzrVJMMbqySrv}9D!IZh$GMrYzQEL00IagfB*srAb`M13lK*@ z9KrJA7T(f0f`dbqTc7^=?{X0n3gf zxJnPGkze4eM?ZM|yS}zzLo<%xh} zdJu$|o~}R4z)m~9>DUF=QSXcw1a{!<-<9Agom`l}pW8Php_ysqg$>p4^@8?yY!}`;h7w})Xwsp|4)@ literal 123632 zcmeI*ZEPE70l@L|>*WmRr(IPVM5|pol{Bn2=d&+<(Iga1wPd1Q+wiiD3So@XxN+8RFL+MBq?!A%xO z_7a;V_-{3jl#Bh`-S4^j!^wX+exLsVCG@9OMOmT9$EppRr#h}4-2aPB8{e;d_sWaT zrZuy-r#|_K`~UvHk#FnHMok}8#Bp&)a3XjpSPr)8llpOeR3DMg=YaqM2q1s}0tg_0 z00Iagum}Mw@GeEyqb)97iMCj#WrreG$cz}KnX3L%P1B4x8)k1?r)jm<{I^%!)S@Zx zou2YzxpKvb)ShISGt+jcnzC9?%l*nH)t)lF=a`v_MZ)pUcq|%=sdxG{CDfJHTy4sZ zTqfIZRQhv9U%r?rl+&4_v9~m04A0qaeCYKPTFF!_8IGCBL?Y3tZW|fS8Rda|q0r%5 z;3%9IS_(`Ar+s0cV>gPAlRSaZOwn zlVU=Qi*w?PI4w@f=kq`S0R#|0009ILKmY**5I_KdB`4tZD6W7UG&!`$L6w7F4n8?} z<=~Nn+v8C*=M37MpY^r-0^c+L@Yh$1?W5E81x_g9WpPDZ5*MAj0%ygTI3-SqV@vL= zIUocOKmY**5I_I{1Q0*~0R);^z~^zf)IfFAs$)xaRI8)EI{K=kw>o;Nquc9oX{y8a zx{jdzgZ^8_wh#VBb_6Z@aXNx#zOFcT1Q0*~0R#|0009ILXq*5Y!MrsC9l;xR3k~fE z4!rb_n_rr0yGMRrphcXaBWT>a05c0R#|0009ILKmY**5I~^b0@W^{J{>{Z_+ZzU)rW7D9f7J( z(h<~qt(Xr11Q0*~0R#|0009ILkOFiBbOdkz`wtr05j+?={Hc3y`$$=SUO*M+=m>bn zLI42-5I_I{1Q0*~0R#|0VEG6vx+D0~D}mV1wyTd$-xnC8BUnCb&yga400IagfB*sr zAb5I_I{1Q0*~fyN5Z5zrAV z(T*VdzPtWBdH98&$j=M-#W)>7W8Vpw9RUOoKmY**5I_I{1Q0*~fqDuox+B>5*^bt| z*~fQI-xoMbM^MkzVJ-v^KmY**5I_I{1Q0*~0R$Q$P{)qo$lspX*S&q`GkUPB_-1zm zt-)4(Qa`Sb>LZOf1?EHm0R#|0009ILKmY**no6L#I|65^Z>A$yq8-6e{lKxGeesXm z17bq)&F%=s#ke>p&WO|EWK;cLVw zc;w*rcofaq-sb$QU$tTLRL9kW`v+!OGLSslSyaaN3pQ{sd;wtP4^ zQUnk{009ILKmY**5I_I{1e!o#;f}xo@T9=I#iB6YoGr;H=wFGMjF3 zty8))*#V={pEI_0^>!J1@|Aug-P`;2Ki8i~MG|4#Hto1=t35l?hBH;A=+=}5?Ju5+ zS>Z(7v?8(S_5F3aG^Jg4&TqBSGnm@ zGNx|H7exz-Ulhwq!eBN*HFtKtVfeb)uq5qN7I!9}Mbpd(ny>&~$ufB*srAbz^+5I_I{1Q0*~0R*G~9l<-$5x83E2;TVn2u|JGzV)?%JMWeqfm?P2GZg_H0q4JA&H}7w`Ypvp0NP4^Akq;2}DKX1%63Zv+rP009IL zKmY**5NNCb9RVFdx<6Cgl`|^+IU`#tl!nVjsjvE5IaA3M@|9fK*psjH8|mI&%e3rJ z#0r@a!!%P?JQcITiMVM+Vo|kcN7`_vs&u+ErCm?AxK=AYgBfSyq2bc*T(&ajwPwmr zhLg4xwUaS*OD11%E_6d)?5j3xp6a-IaQ|PPh?dg7{NdLF;J#_oaKR(|#rAyBh3LQZ+Uss$v0tg_000IagfB*srG){nyfR12^b_6&6 z_Krt?wDY;=1M=#3?hEjJ1dV$eU}gjmKmY**5I_I{1Q0*~0R-wSu;`B9t80I^@?`Yc m@$2^m_&$OurfB*srAb