317 lines
9.0 KiB
Plaintext
317 lines
9.0 KiB
Plaintext
@page "/calendar"
|
|
|
|
@rendermode InteractiveServer
|
|
|
|
@using Microsoft.AspNetCore.Authorization
|
|
@using WorkManagementTool.Services
|
|
@using WorkManagementTool.Data.Entities
|
|
|
|
@attribute [Authorize]
|
|
|
|
@inject SchoolSubjectService SchoolSubjectService
|
|
@inject HomeworkService HomeworkService
|
|
|
|
<h3>Calendar</h3>
|
|
|
|
<div class="calendar-controls mb-3">
|
|
<button class="btn btn-sm btn-outline-primary" @onclick="() => ChangeMonth(-1)">«</button>
|
|
<span class="mx-2 fw-bold">@currentMonth.ToString("MMMM yyyy")</span>
|
|
<button class="btn btn-sm btn-outline-primary" @onclick="() => ChangeMonth(1)">»</button>
|
|
|
|
<div class="form-check ms-4 d-inline-block">
|
|
<input class="form-check-input" type="checkbox" id="showCompleted" @bind="showCompleted" @bind:after="LoadDataAsync" />
|
|
<label class="form-check-label" for="showCompleted">Afficher les rendus complétés</label>
|
|
</div>
|
|
</div>
|
|
|
|
@if (Subjects == null || Subjects.Count == 0)
|
|
{
|
|
<p>Chargement des matières...</p>
|
|
}
|
|
else
|
|
{
|
|
<div class="calendar-legend mb-3">
|
|
@foreach (var s in Subjects)
|
|
{
|
|
<div class="legend-item">
|
|
<span class="legend-color" style="background:@GetSubjectColor(s)"></span>
|
|
<span class="legend-name">@s.Name</span>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<div class="calendar-grid">
|
|
@foreach (var d in WeekDayHeaders)
|
|
{
|
|
<div class="calendar-weekday">@d</div>
|
|
}
|
|
|
|
@foreach (var day in DaysForCurrentMonth)
|
|
{
|
|
var isOtherMonth = day.Month != currentMonth.Month;
|
|
var hwForDay = GetHomeworksForDate(day);
|
|
<div class="calendar-day @(isOtherMonth ? "other-month" : "")">
|
|
<div class="date-number">@day.Day</div>
|
|
@if (hwForDay != null && hwForDay.Count > 0)
|
|
{
|
|
<div class="homeworks-list">
|
|
@foreach (var hw in hwForDay)
|
|
{
|
|
var color = GetSubjectColor(hw.SchoolSubject);
|
|
<div class="hw-pill" title="@($"{hw.Title} - {hw.SchoolSubject?.Name}")" style="background:@color">
|
|
@Shorten(hw.Title, 28)
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
|
|
<style>
|
|
.calendar-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.calendar-legend {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
align-items: center;
|
|
}
|
|
|
|
.legend-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.legend-color {
|
|
width: 18px;
|
|
height: 18px;
|
|
border-radius: 4px;
|
|
display: inline-block;
|
|
border: 1px solid rgba(0,0,0,0.12);
|
|
}
|
|
|
|
.legend-name {
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.calendar-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(7,1fr);
|
|
gap: 6px;
|
|
}
|
|
|
|
.calendar-weekday {
|
|
font-weight: 700;
|
|
text-align: center;
|
|
padding: 6px 0;
|
|
background: #f5f5f5;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.calendar-day {
|
|
min-height: 80px;
|
|
padding: 6px;
|
|
border: 1px solid #e9e9e9;
|
|
border-radius: 6px;
|
|
background: white;
|
|
position: relative;
|
|
}
|
|
|
|
.calendar-day.other-month {
|
|
background: #fbfbfb;
|
|
color: #9a9a9a;
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.date-number {
|
|
font-size: 0.9rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.homeworks-list {
|
|
margin-top: 6px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
}
|
|
|
|
.hw-pill {
|
|
color: #fff;
|
|
padding: 3px 6px;
|
|
border-radius: 6px;
|
|
font-size: 0.8rem;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
</style>
|
|
|
|
@code {
|
|
[Parameter]
|
|
public List<SchoolSubject> 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<int, string> subjectColors = new();
|
|
|
|
// mapping date -> list of homeworks
|
|
private readonly Dictionary<DateTime, List<Homework>> homeworksByDate = new();
|
|
|
|
private static readonly string[] WeekDayHeaders = new[] { "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim" };
|
|
|
|
private IEnumerable<DateTime> 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<Homework>();
|
|
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<Homework>();
|
|
homeworksByDate[date].Add(hw);
|
|
}
|
|
}
|
|
|
|
StateHasChanged();
|
|
}
|
|
|
|
private List<Homework>? 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) + "…";
|
|
}
|
|
} |