diff --git a/BackEnd/Domain/Domain.csproj b/BackEnd/Domain/Domain.csproj new file mode 100644 index 000000000..fa71b7ae6 --- /dev/null +++ b/BackEnd/Domain/Domain.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/BackEnd/Domain/Models/DashboardSummary.cs b/BackEnd/Domain/Models/DashboardSummary.cs new file mode 100644 index 000000000..e87055ab3 --- /dev/null +++ b/BackEnd/Domain/Models/DashboardSummary.cs @@ -0,0 +1,6 @@ +namespace Domain.Models; + +public class DashboardSummary +{ + public List SubjectAverages { get; set; } = new(); +} \ No newline at end of file diff --git a/BackEnd/Domain/Models/Student.cs b/BackEnd/Domain/Models/Student.cs new file mode 100644 index 000000000..0497b388c --- /dev/null +++ b/BackEnd/Domain/Models/Student.cs @@ -0,0 +1,7 @@ +namespace Domain.Models; + +public class Student +{ + public int UserId { get; set; } + public string Name => $"Student {UserId}"; +} \ No newline at end of file diff --git a/BackEnd/Domain/Models/StudentData.cs b/BackEnd/Domain/Models/StudentData.cs new file mode 100644 index 000000000..3cb684a9c --- /dev/null +++ b/BackEnd/Domain/Models/StudentData.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; + +namespace Domain.Models; + +public class StudentData +{ + public int UserId { get; set; } + public required string Name { get; set; } + public List SubjectAverages { get; set; } = new(); + public IList Exercises { get; set; } = new List(); +} + +public class StudentExerciseResult +{ + public Subject Subject { get; set; } + public required string SubjectName { get; set; } + public long ExerciseId { get; set; } + public required string ExerciseName { get; set; } + public bool Correct { get; set; } + public decimal Difficulty { get; set; } + public decimal Progress { get; set; } + public DateTime SubmitDateTime { get; set; } +} \ No newline at end of file diff --git a/BackEnd/Domain/Models/StudentListItem.cs b/BackEnd/Domain/Models/StudentListItem.cs new file mode 100644 index 000000000..5ad1ed703 --- /dev/null +++ b/BackEnd/Domain/Models/StudentListItem.cs @@ -0,0 +1,14 @@ +namespace Domain.Models; + +public class StudentSubjectAverage +{ + public Subject Subject { get; set; } + public decimal Average { get; set; } +} + +public class StudentListItem +{ + public int UserId { get; set; } + public string Name { get; set; } = string.Empty; + public List SubjectAverages { get; set; } = new(); +} \ No newline at end of file diff --git a/BackEnd/Domain/Models/Subject.cs b/BackEnd/Domain/Models/Subject.cs new file mode 100644 index 000000000..503e57c70 --- /dev/null +++ b/BackEnd/Domain/Models/Subject.cs @@ -0,0 +1,33 @@ +namespace Domain.Models; + +public enum Subject +{ + BegrijpendLezen, + Rekenen, + Spelling +} + +public static class SubjectExtensions +{ + public static string ToDisplayName(this Subject subject) + { + return subject switch + { + Subject.BegrijpendLezen => "Begrijpend Lezen", + Subject.Rekenen => "Rekenen", + Subject.Spelling => "Spelling", + _ => throw new ArgumentException($"Unknown subject: {subject}") + }; + } + + public static Subject FromString(string subjectName) + { + return subjectName switch + { + "Begrijpend Lezen" => Subject.BegrijpendLezen, + "Rekenen" => Subject.Rekenen, + "Spelling" => Subject.Spelling, + _ => throw new ArgumentException($"Unknown subject name: {subjectName}") + }; + } +} \ No newline at end of file diff --git a/BackEnd/Domain/Models/SubjectAverage.cs b/BackEnd/Domain/Models/SubjectAverage.cs new file mode 100644 index 000000000..a85653b6d --- /dev/null +++ b/BackEnd/Domain/Models/SubjectAverage.cs @@ -0,0 +1,10 @@ +namespace Domain.Models; + +public class SubjectAverage +{ + public Subject Subject { get; set; } + public required string SubjectName { get; set; } + public decimal Average { get; set; } + public int TotalExercises { get; set; } + public int StudentCount { get; set; } +} \ No newline at end of file diff --git a/BackEnd/Domain/Models/WorkResult.cs b/BackEnd/Domain/Models/WorkResult.cs new file mode 100644 index 000000000..1b29caf4c --- /dev/null +++ b/BackEnd/Domain/Models/WorkResult.cs @@ -0,0 +1,15 @@ +namespace Domain.Models; + +public class WorkResult +{ + public long SubmittedAnswerId { get; set; } + public DateTime SubmitDateTime { get; set; } + public bool Correct { get; set; } + public decimal Progress { get; set; } + public int UserId { get; set; } + public long ExerciseId { get; set; } + public decimal Difficulty { get; set; } + public Subject Subject { get; set; } + public string Domain { get; set; } = string.Empty; + public string LearningObjective { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/BackEnd/Domain/Queries/GetAllStudentsQuery.cs b/BackEnd/Domain/Queries/GetAllStudentsQuery.cs new file mode 100644 index 000000000..11e4e3b77 --- /dev/null +++ b/BackEnd/Domain/Queries/GetAllStudentsQuery.cs @@ -0,0 +1,40 @@ +using Domain.Models; +using Domain.Repositories; +using Domain.Services; + +namespace Domain.Queries; + +public class GetAllStudentsQuery +{ + private readonly IWorkDataRepository _workDataRepository; + private readonly AverageCalculationService _averageCalculationService; + + public GetAllStudentsQuery( + IWorkDataRepository workDataRepository, + AverageCalculationService averageCalculationService) + { + _workDataRepository = workDataRepository; + _averageCalculationService = averageCalculationService; + } + + public async Task> ExecuteAsync(DateTime cutoffDate) + { + var workResults = await _workDataRepository.GetWorkResultsBeforeDateAsync(cutoffDate); + var students = await _workDataRepository.GetAllStudentsAsync(cutoffDate); + + var studentsWithAverages = students.Select(student => + { + var studentWorkResults = workResults.Where(wr => wr.UserId == student.UserId).ToList(); + var subjectAverages = _averageCalculationService.CalculateStudentSubjectAverages(studentWorkResults); + + return new StudentListItem + { + UserId = student.UserId, + Name = student.Name, + SubjectAverages = subjectAverages + }; + }).ToList(); + + return studentsWithAverages; + } +} \ No newline at end of file diff --git a/BackEnd/Domain/Queries/GetDashboardAveragesQuery.cs b/BackEnd/Domain/Queries/GetDashboardAveragesQuery.cs new file mode 100644 index 000000000..a39dec6d5 --- /dev/null +++ b/BackEnd/Domain/Queries/GetDashboardAveragesQuery.cs @@ -0,0 +1,31 @@ +using Domain.Models; +using Domain.Repositories; +using Domain.Services; + +namespace Domain.Queries; + +public class GetDashboardAveragesQuery +{ + private readonly IWorkDataRepository _workDataRepository; + private readonly AverageCalculationService _averageCalculationService; + + public GetDashboardAveragesQuery( + IWorkDataRepository workDataRepository, + AverageCalculationService averageCalculationService) + { + _workDataRepository = workDataRepository; + _averageCalculationService = averageCalculationService; + } + + public async Task ExecuteAsync(DateTime cutoffDate) + { + var workResults = await _workDataRepository.GetWorkResultsBeforeDateAsync(cutoffDate); + + var subjectAverages = _averageCalculationService.CalculateSubjectAverages(workResults); + + return new DashboardSummary + { + SubjectAverages = subjectAverages + }; + } +} \ No newline at end of file diff --git a/BackEnd/Domain/Queries/GetStudentDataQuery.cs b/BackEnd/Domain/Queries/GetStudentDataQuery.cs new file mode 100644 index 000000000..2534e92af --- /dev/null +++ b/BackEnd/Domain/Queries/GetStudentDataQuery.cs @@ -0,0 +1,55 @@ +using Domain.Models; +using Domain.Repositories; +using Domain.Services; + +namespace Domain.Queries; + +public class GetStudentDataQuery +{ + private readonly IWorkDataRepository _workDataRepository; + private readonly AverageCalculationService _averageCalculationService; + + public GetStudentDataQuery( + IWorkDataRepository workDataRepository, + AverageCalculationService averageCalculationService) + { + _workDataRepository = workDataRepository; + _averageCalculationService = averageCalculationService; + } + + public async Task ExecuteAsync(int studentId, DateTime cutoffDate) + { + var allWorkResults = await _workDataRepository.GetWorkResultsBeforeDateAsync(cutoffDate); + var studentWorkResults = allWorkResults.Where(wr => wr.UserId == studentId).ToList(); + + if (!studentWorkResults.Any()) + { + return null; + } + + var subjectAverages = _averageCalculationService.CalculateStudentSubjectAverages(studentWorkResults); + + var exercises = studentWorkResults + .OrderByDescending(wr => wr.SubmitDateTime) + .Select(wr => new StudentExerciseResult + { + Subject = wr.Subject, + SubjectName = wr.Subject.ToDisplayName(), + ExerciseId = wr.ExerciseId, + ExerciseName = $"Exercise {wr.ExerciseId}", + Correct = wr.Correct, + Difficulty = wr.Difficulty, + Progress = wr.Progress, + SubmitDateTime = wr.SubmitDateTime + }) + .ToList(); + + return new StudentData + { + UserId = studentId, + Name = $"Student {studentId}", + SubjectAverages = subjectAverages, + Exercises = exercises + }; + } +} \ No newline at end of file diff --git a/BackEnd/Domain/Repositories/IWorkDataRepository.cs b/BackEnd/Domain/Repositories/IWorkDataRepository.cs new file mode 100644 index 000000000..a496f65e5 --- /dev/null +++ b/BackEnd/Domain/Repositories/IWorkDataRepository.cs @@ -0,0 +1,10 @@ +using Domain.Models; + +namespace Domain.Repositories; + +public interface IWorkDataRepository +{ + Task> GetWorkResultsBeforeDateAsync(DateTime cutoffDate); + Task> GetUniqueStudentIdsAsync(DateTime cutoffDate); + Task> GetAllStudentsAsync(DateTime cutoffDate); +} \ No newline at end of file diff --git a/BackEnd/Domain/Services/AverageCalculationService.cs b/BackEnd/Domain/Services/AverageCalculationService.cs new file mode 100644 index 000000000..049055fbd --- /dev/null +++ b/BackEnd/Domain/Services/AverageCalculationService.cs @@ -0,0 +1,66 @@ +using Domain.Models; + +namespace Domain.Services; + +public class AverageCalculationService +{ + public List CalculateSubjectAverages(IList workResults) + { + var subjectGroups = workResults.GroupBy(wr => wr.Subject); + var subjectAverages = new List(); + + foreach (var subjectGroup in subjectGroups) + { + var subjectResults = subjectGroup.ToList(); + var correctAnswers = subjectResults.Count(wr => wr.Correct); + var totalAnswers = subjectResults.Count; + var averagePercentage = totalAnswers > 0 ? (decimal)correctAnswers / totalAnswers : 0; + var averageOnTenScale = averagePercentage * 10; + + var uniqueStudents = subjectResults.Select(wr => wr.UserId).ToHashSet().Count; + + subjectAverages.Add(new SubjectAverage + { + Subject = subjectGroup.Key, + SubjectName = subjectGroup.Key.ToDisplayName(), + Average = Math.Round(averageOnTenScale, 1), + TotalExercises = totalAnswers, + StudentCount = uniqueStudents + }); + } + + return subjectAverages.OrderBy(sa => sa.Subject).ToList(); + } + + public List CalculateStudentSubjectAverages(IList studentWorkResults) + { + if (studentWorkResults == null || !studentWorkResults.Any()) + { + return new List(); + } + + var subjectAverages = new List(); + var subjectGroups = studentWorkResults.GroupBy(wr => wr.Subject); + + foreach (var subjectGroup in subjectGroups) + { + var subjectResults = subjectGroup.ToList(); + var correctAnswers = subjectResults.Count(wr => wr.Correct); + var totalAnswers = subjectResults.Count; + + if (totalAnswers > 0) + { + var averagePercentage = (decimal)correctAnswers / totalAnswers; + var averageOnTenScale = averagePercentage * 10; + + subjectAverages.Add(new StudentSubjectAverage + { + Subject = subjectGroup.Key, + Average = Math.Round(averageOnTenScale, 1) + }); + } + } + + return subjectAverages.OrderBy(sa => sa.Subject).ToList(); + } +} \ No newline at end of file diff --git a/BackEnd/Storage/CsvDataReader.cs b/BackEnd/Storage/CsvDataReader.cs new file mode 100644 index 000000000..725b93240 --- /dev/null +++ b/BackEnd/Storage/CsvDataReader.cs @@ -0,0 +1,91 @@ +using System.Globalization; +using Domain.Models; + +namespace Storage; + +public class CsvDataReader +{ + private readonly string _csvFilePath; + + public CsvDataReader(string csvFilePath) + { + _csvFilePath = csvFilePath; + } + + public async Task> ReadWorkResultsAsync(DateTime cutoffDate) + { + var results = new List(); + + using var reader = new StreamReader(_csvFilePath); + var headerLine = await reader.ReadLineAsync(); + + string? line; + while ((line = await reader.ReadLineAsync()) != null) + { + var workResult = ParseCsvLine(line); + if (workResult != null && workResult.SubmitDateTime < cutoffDate) + { + results.Add(workResult); + } + } + + return results; + } + + private WorkResult? ParseCsvLine(string csvLine) + { + try + { + var values = SplitCsvLine(csvLine); + + if (values.Length < 10) return null; + + return new WorkResult + { + SubmittedAnswerId = long.Parse(values[0]), + SubmitDateTime = DateTime.Parse(values[1], null, DateTimeStyles.RoundtripKind), + Correct = values[2] == "1", + Progress = decimal.Parse(values[3], CultureInfo.InvariantCulture), + UserId = int.Parse(values[4]), + ExerciseId = long.Parse(values[5]), + Difficulty = decimal.Parse(values[6], CultureInfo.InvariantCulture), + Subject = SubjectExtensions.FromString(values[7]), + Domain = values[8], + LearningObjective = values[9] + }; + } + catch + { + return null; + } + } + + private string[] SplitCsvLine(string csvLine) + { + var values = new List(); + var currentValue = ""; + var insideQuotes = false; + + for (int i = 0; i < csvLine.Length; i++) + { + var currentChar = csvLine[i]; + + if (currentChar == '"') + { + insideQuotes = !insideQuotes; + } + else if (currentChar == ',' && !insideQuotes) + { + values.Add(currentValue); + currentValue = ""; + } + else + { + currentValue += currentChar; + } + } + + values.Add(currentValue); + return values.ToArray(); + } +} \ No newline at end of file diff --git a/BackEnd/Storage/CsvWorkDataRepository.cs b/BackEnd/Storage/CsvWorkDataRepository.cs new file mode 100644 index 000000000..3884f2517 --- /dev/null +++ b/BackEnd/Storage/CsvWorkDataRepository.cs @@ -0,0 +1,40 @@ +using Domain.Models; +using Domain.Repositories; + +namespace Storage; + +public class CsvWorkDataRepository : IWorkDataRepository +{ + private readonly CsvDataReader _csvDataReader; + + public CsvWorkDataRepository(string csvFilePath) + { + _csvDataReader = new CsvDataReader(csvFilePath); + } + + public async Task> GetWorkResultsBeforeDateAsync(DateTime cutoffDate) + { + return await _csvDataReader.ReadWorkResultsAsync(cutoffDate); + } + + public async Task> GetUniqueStudentIdsAsync(DateTime cutoffDate) + { + var workResults = await GetWorkResultsBeforeDateAsync(cutoffDate); + return workResults.Select(wr => wr.UserId).ToHashSet(); + } + + public async Task> GetAllStudentsAsync(DateTime cutoffDate) + { + var workResults = await GetWorkResultsBeforeDateAsync(cutoffDate); + + return workResults + .GroupBy(wr => wr.UserId) + .Select(studentGroup => new StudentListItem + { + UserId = studentGroup.Key, + Name = $"Student {studentGroup.Key}" + }) + .OrderBy(s => s.UserId) + .ToList(); + } +} \ No newline at end of file diff --git a/BackEnd/Storage/Storage.csproj b/BackEnd/Storage/Storage.csproj new file mode 100644 index 000000000..4f082811e --- /dev/null +++ b/BackEnd/Storage/Storage.csproj @@ -0,0 +1,13 @@ + + + + + + + + net8.0 + enable + enable + + + diff --git a/BackEnd/Tests/Models/SubjectExtensionsTests.cs b/BackEnd/Tests/Models/SubjectExtensionsTests.cs new file mode 100644 index 000000000..55f261e86 --- /dev/null +++ b/BackEnd/Tests/Models/SubjectExtensionsTests.cs @@ -0,0 +1,73 @@ +using Domain.Models; +using FluentAssertions; +using Xunit; + +namespace Tests.Models; + +public class SubjectExtensionsTests +{ + [Theory] + [InlineData(Subject.BegrijpendLezen, "Begrijpend Lezen")] + [InlineData(Subject.Rekenen, "Rekenen")] + [InlineData(Subject.Spelling, "Spelling")] + public void ToDisplayName_WithValidSubject_ShouldReturnCorrectDisplayName(Subject subject, string expectedDisplayName) + { + // Act + var result = subject.ToDisplayName(); + + // Assert + result.Should().Be(expectedDisplayName); + } + + [Fact] + public void ToDisplayName_WithInvalidSubject_ShouldThrowArgumentException() + { + // Arrange + var invalidSubject = (Subject)999; + + // Act + var action = () => invalidSubject.ToDisplayName(); + + // Assert + action.Should().Throw() + .WithMessage("Unknown subject: 999"); + } + + [Theory] + [InlineData("Begrijpend Lezen", Subject.BegrijpendLezen)] + [InlineData("Rekenen", Subject.Rekenen)] + [InlineData("Spelling", Subject.Spelling)] + public void FromString_WithValidSubjectName_ShouldReturnCorrectSubject(string subjectName, Subject expectedSubject) + { + // Act + var result = SubjectExtensions.FromString(subjectName); + + // Assert + result.Should().Be(expectedSubject); + } + + [Theory] + [InlineData("Invalid Subject")] + [InlineData("")] + [InlineData("begrijpend lezen")] + [InlineData("REKENEN")] + public void FromString_WithInvalidSubjectName_ShouldThrowArgumentException(string invalidSubjectName) + { + // Act + var action = () => SubjectExtensions.FromString(invalidSubjectName); + + // Assert + action.Should().Throw() + .WithMessage($"Unknown subject name: {invalidSubjectName}"); + } + + [Fact] + public void FromString_WithNullSubjectName_ShouldThrowArgumentException() + { + // Act + var action = () => SubjectExtensions.FromString(null!); + + // Assert + action.Should().Throw(); + } +} \ No newline at end of file diff --git a/BackEnd/Tests/Queries/GetAllStudentsQueryTests.cs b/BackEnd/Tests/Queries/GetAllStudentsQueryTests.cs new file mode 100644 index 000000000..2e6ac2494 --- /dev/null +++ b/BackEnd/Tests/Queries/GetAllStudentsQueryTests.cs @@ -0,0 +1,139 @@ +using Domain.Models; +using Domain.Queries; +using Domain.Repositories; +using Domain.Services; +using FluentAssertions; +using NSubstitute; +using Xunit; + +namespace Tests.Queries; + +public class GetAllStudentsQueryTests +{ + [Fact] + public async Task ExecuteAsync_ReturnsStudentListWithSubjectAverages() + { + // Arrange + var mockRepository = Substitute.For(); + var calculationService = new AverageCalculationService(); + var cutoffDate = new DateTime(2015, 3, 24, 11, 30, 0, DateTimeKind.Utc); + + var basicStudents = new List + { + new() { UserId = 1, Name = "Student 1" }, + new() { UserId = 2, Name = "Student 2" } + }; + + var workResults = new List + { + new() { UserId = 1, Subject = Subject.Rekenen, Correct = true }, + new() { UserId = 1, Subject = Subject.Rekenen, Correct = true }, + new() { UserId = 1, Subject = Subject.Rekenen, Correct = true }, + new() { UserId = 1, Subject = Subject.Rekenen, Correct = false }, + + new() { UserId = 1, Subject = Subject.Spelling, Correct = true }, + new() { UserId = 1, Subject = Subject.Spelling, Correct = true }, + + new() { UserId = 2, Subject = Subject.BegrijpendLezen, Correct = true }, + new() { UserId = 2, Subject = Subject.BegrijpendLezen, Correct = false } + }; + + mockRepository.GetAllStudentsAsync(cutoffDate).Returns(basicStudents); + mockRepository.GetWorkResultsBeforeDateAsync(cutoffDate).Returns(workResults); + + var query = new GetAllStudentsQuery(mockRepository, calculationService); + + // Act + var result = await query.ExecuteAsync(cutoffDate); + + // Assert + result.Should().HaveCount(2); + + var student1 = result.First(s => s.UserId == 1); + student1.Name.Should().Be("Student 1"); + student1.SubjectAverages.Should().HaveCount(2); + + var rekenenAverage = student1.SubjectAverages.First(sa => sa.Subject == Subject.Rekenen); + rekenenAverage.Average.Should().Be(7.5m); + + var spellingAverage = student1.SubjectAverages.First(sa => sa.Subject == Subject.Spelling); + spellingAverage.Average.Should().Be(10.0m); + + var student2 = result.First(s => s.UserId == 2); + student2.Name.Should().Be("Student 2"); + student2.SubjectAverages.Should().HaveCount(1); + + var begrijpendLezenAverage = student2.SubjectAverages.First(sa => sa.Subject == Subject.BegrijpendLezen); + begrijpendLezenAverage.Average.Should().Be(5.0m); + } + + [Fact] + public async Task ExecuteAsync_WithEmptyData_ReturnsEmptyList() + { + // Arrange + var mockRepository = Substitute.For(); + var calculationService = new AverageCalculationService(); + var cutoffDate = new DateTime(2015, 3, 24, 11, 30, 0, DateTimeKind.Utc); + + mockRepository.GetAllStudentsAsync(cutoffDate).Returns(new List()); + mockRepository.GetWorkResultsBeforeDateAsync(cutoffDate).Returns(new List()); + + var query = new GetAllStudentsQuery(mockRepository, calculationService); + + // Act + var result = await query.ExecuteAsync(cutoffDate); + + // Assert + result.Should().NotBeNull(); + result.Should().BeEmpty(); + } + + [Fact] + public async Task ExecuteAsync_CallsRepositoryWithCorrectCutoffDate() + { + // Arrange + var mockRepository = Substitute.For(); + var calculationService = new AverageCalculationService(); + var cutoffDate = new DateTime(2015, 3, 24, 11, 30, 0, DateTimeKind.Utc); + + mockRepository.GetAllStudentsAsync(cutoffDate).Returns(new List()); + mockRepository.GetWorkResultsBeforeDateAsync(cutoffDate).Returns(new List()); + + var query = new GetAllStudentsQuery(mockRepository, calculationService); + + // Act + await query.ExecuteAsync(cutoffDate); + + // Assert + await mockRepository.Received(1).GetAllStudentsAsync(cutoffDate); + await mockRepository.Received(1).GetWorkResultsBeforeDateAsync(cutoffDate); + } + + [Fact] + public async Task ExecuteAsync_WithStudentHavingNoWorkResults_ReturnsStudentWithEmptyAverages() + { + // Arrange + var mockRepository = Substitute.For(); + var calculationService = new AverageCalculationService(); + var cutoffDate = new DateTime(2015, 3, 24, 11, 30, 0, DateTimeKind.Utc); + + var basicStudents = new List + { + new() { UserId = 1, Name = "Student 1" } + }; + + var workResults = new List(); + + mockRepository.GetAllStudentsAsync(cutoffDate).Returns(basicStudents); + mockRepository.GetWorkResultsBeforeDateAsync(cutoffDate).Returns(workResults); + + var query = new GetAllStudentsQuery(mockRepository, calculationService); + + // Act + var result = await query.ExecuteAsync(cutoffDate); + + // Assert + result.Should().HaveCount(1); + result.First().SubjectAverages.Should().BeEmpty(); + } +} \ No newline at end of file diff --git a/BackEnd/Tests/Queries/GetDashboardAveragesQueryTests.cs b/BackEnd/Tests/Queries/GetDashboardAveragesQueryTests.cs new file mode 100644 index 000000000..a03228954 --- /dev/null +++ b/BackEnd/Tests/Queries/GetDashboardAveragesQueryTests.cs @@ -0,0 +1,269 @@ +using Domain.Models; +using Domain.Queries; +using Domain.Repositories; +using Domain.Services; +using FluentAssertions; +using NSubstitute; +using Xunit; + +namespace Tests.Queries; + +public class GetDashboardAveragesQueryTests +{ + private readonly IWorkDataRepository _mockRepository; + private readonly AverageCalculationService _averageCalculationService; + private readonly GetDashboardAveragesQuery _query; + private readonly DateTime _cutoffDate = new(2015, 3, 24, 11, 30, 0, DateTimeKind.Utc); + + public GetDashboardAveragesQueryTests() + { + _mockRepository = Substitute.For(); + _averageCalculationService = new AverageCalculationService(); + _query = new GetDashboardAveragesQuery(_mockRepository, _averageCalculationService); + } + + [Fact] + public async Task ExecuteAsync_WithValidWorkResults_ShouldReturnCorrectDashboardSummary() + { + // Arrange + var workResults = CreateTestWorkResults(); + _mockRepository.GetWorkResultsBeforeDateAsync(_cutoffDate) + .Returns(workResults); + + // Act + var result = await _query.ExecuteAsync(_cutoffDate); + + // Assert + result.Should().NotBeNull(); + result.SubjectAverages.Should().HaveCount(3); + } + + [Fact] + public async Task ExecuteAsync_WithBegrijpendLezenData_ShouldCalculateCorrectAverage() + { + // Arrange + var workResults = CreateBegrijpendLezenTestData(); + _mockRepository.GetWorkResultsBeforeDateAsync(_cutoffDate) + .Returns(workResults); + + // Act + var result = await _query.ExecuteAsync(_cutoffDate); + + // Assert + var begrijpendLezenAverage = result.SubjectAverages + .First(sa => sa.Subject == Subject.BegrijpendLezen); + + begrijpendLezenAverage.Average.Should().Be(7.0m); + begrijpendLezenAverage.SubjectName.Should().Be("Begrijpend Lezen"); + begrijpendLezenAverage.TotalExercises.Should().Be(10); + begrijpendLezenAverage.StudentCount.Should().Be(2); + } + + [Fact] + public async Task ExecuteAsync_WithRekenenData_ShouldCalculateCorrectAverage() + { + // Arrange + var workResults = CreateRekenenTestData(); + _mockRepository.GetWorkResultsBeforeDateAsync(_cutoffDate) + .Returns(workResults); + + // Act + var result = await _query.ExecuteAsync(_cutoffDate); + + // Assert + var rekenenAverage = result.SubjectAverages + .First(sa => sa.Subject == Subject.Rekenen); + + rekenenAverage.Average.Should().Be(5.0m); + rekenenAverage.SubjectName.Should().Be("Rekenen"); + rekenenAverage.TotalExercises.Should().Be(6); + rekenenAverage.StudentCount.Should().Be(2); + } + + [Fact] + public async Task ExecuteAsync_WithSpellingData_ShouldCalculateCorrectAverage() + { + // Arrange + var workResults = CreateSpellingTestData(); + _mockRepository.GetWorkResultsBeforeDateAsync(_cutoffDate) + .Returns(workResults); + + // Act + var result = await _query.ExecuteAsync(_cutoffDate); + + // Assert + var spellingAverage = result.SubjectAverages + .First(sa => sa.Subject == Subject.Spelling); + + spellingAverage.Average.Should().Be(10.0m); + spellingAverage.SubjectName.Should().Be("Spelling"); + spellingAverage.TotalExercises.Should().Be(4); + spellingAverage.StudentCount.Should().Be(1); + } + + [Fact] + public async Task ExecuteAsync_WithEmptyResults_ShouldReturnEmptyDashboard() + { + // Arrange + var emptyResults = new List(); + _mockRepository.GetWorkResultsBeforeDateAsync(_cutoffDate) + .Returns(emptyResults); + + // Act + var result = await _query.ExecuteAsync(_cutoffDate); + + // Assert + result.SubjectAverages.Should().BeEmpty(); + } + + [Fact] + public async Task ExecuteAsync_WithAllIncorrectAnswers_ShouldReturnZeroAverage() + { + // Arrange + var workResults = CreateAllIncorrectAnswersTestData(); + _mockRepository.GetWorkResultsBeforeDateAsync(_cutoffDate) + .Returns(workResults); + + // Act + var result = await _query.ExecuteAsync(_cutoffDate); + + // Assert + var begrijpendLezenAverage = result.SubjectAverages + .First(sa => sa.Subject == Subject.BegrijpendLezen); + + begrijpendLezenAverage.Average.Should().Be(0.0m); + } + + [Fact] + public async Task ExecuteAsync_WithAllCorrectAnswers_ShouldReturnTenAverage() + { + // Arrange + var workResults = CreateAllCorrectAnswersTestData(); + _mockRepository.GetWorkResultsBeforeDateAsync(_cutoffDate) + .Returns(workResults); + + // Act + var result = await _query.ExecuteAsync(_cutoffDate); + + // Assert + var begrijpendLezenAverage = result.SubjectAverages + .First(sa => sa.Subject == Subject.BegrijpendLezen); + + begrijpendLezenAverage.Average.Should().Be(10.0m); + } + + [Fact] + public async Task ExecuteAsync_ShouldCallRepositoryWithCorrectCutoffDate() + { + // Arrange + var workResults = new List(); + _mockRepository.GetWorkResultsBeforeDateAsync(_cutoffDate) + .Returns(workResults); + + // Act + await _query.ExecuteAsync(_cutoffDate); + + // Assert + await _mockRepository.Received(1).GetWorkResultsBeforeDateAsync(_cutoffDate); + } + + private IList CreateTestWorkResults() + { + return new List + { + new() + { + UserId = 1, Subject = Subject.BegrijpendLezen, Correct = true, + SubmitDateTime = new DateTime(2015, 3, 24, 10, 0, 0, DateTimeKind.Utc) + }, + new() + { + UserId = 1, Subject = Subject.BegrijpendLezen, Correct = false, + SubmitDateTime = new DateTime(2015, 3, 24, 10, 5, 0, DateTimeKind.Utc) + }, + new() + { + UserId = 2, Subject = Subject.Rekenen, Correct = true, + SubmitDateTime = new DateTime(2015, 3, 24, 10, 10, 0, DateTimeKind.Utc) + }, + new() + { + UserId = 3, Subject = Subject.Spelling, Correct = true, + SubmitDateTime = new DateTime(2015, 3, 24, 10, 15, 0, DateTimeKind.Utc) + } + }; + } + + private IList CreateBegrijpendLezenTestData() + { + var results = new List(); + + for (int i = 0; i < 7; i++) + { + results.Add(new WorkResult + { + UserId = 1, + Subject = Subject.BegrijpendLezen, + Correct = true, + SubmitDateTime = new DateTime(2015, 3, 24, 10, i, 0, DateTimeKind.Utc) + }); + } + + for (int i = 0; i < 3; i++) + { + results.Add(new WorkResult + { + UserId = 2, + Subject = Subject.BegrijpendLezen, + Correct = false, + SubmitDateTime = new DateTime(2015, 3, 24, 10, 10 + i, 0, DateTimeKind.Utc) + }); + } + + return results; + } + + private IList CreateRekenenTestData() + { + return new List + { + new() { UserId = 1, Subject = Subject.Rekenen, Correct = true, SubmitDateTime = new DateTime(2015, 3, 24, 9, 0, 0, DateTimeKind.Utc) }, + new() { UserId = 1, Subject = Subject.Rekenen, Correct = true, SubmitDateTime = new DateTime(2015, 3, 24, 9, 5, 0, DateTimeKind.Utc) }, + new() { UserId = 1, Subject = Subject.Rekenen, Correct = true, SubmitDateTime = new DateTime(2015, 3, 24, 9, 10, 0, DateTimeKind.Utc) }, + new() { UserId = 2, Subject = Subject.Rekenen, Correct = false, SubmitDateTime = new DateTime(2015, 3, 24, 9, 15, 0, DateTimeKind.Utc) }, + new() { UserId = 2, Subject = Subject.Rekenen, Correct = false, SubmitDateTime = new DateTime(2015, 3, 24, 9, 20, 0, DateTimeKind.Utc) }, + new() { UserId = 2, Subject = Subject.Rekenen, Correct = false, SubmitDateTime = new DateTime(2015, 3, 24, 9, 25, 0, DateTimeKind.Utc) } + }; + } + + private IList CreateSpellingTestData() + { + return new List + { + new() { UserId = 3, Subject = Subject.Spelling, Correct = true, SubmitDateTime = new DateTime(2015, 3, 24, 8, 0, 0, DateTimeKind.Utc) }, + new() { UserId = 3, Subject = Subject.Spelling, Correct = true, SubmitDateTime = new DateTime(2015, 3, 24, 8, 5, 0, DateTimeKind.Utc) }, + new() { UserId = 3, Subject = Subject.Spelling, Correct = true, SubmitDateTime = new DateTime(2015, 3, 24, 8, 10, 0, DateTimeKind.Utc) }, + new() { UserId = 3, Subject = Subject.Spelling, Correct = true, SubmitDateTime = new DateTime(2015, 3, 24, 8, 15, 0, DateTimeKind.Utc) } + }; + } + + private IList CreateAllIncorrectAnswersTestData() + { + return new List + { + new() { UserId = 1, Subject = Subject.BegrijpendLezen, Correct = false, SubmitDateTime = new DateTime(2015, 3, 24, 10, 0, 0, DateTimeKind.Utc) }, + new() { UserId = 1, Subject = Subject.BegrijpendLezen, Correct = false, SubmitDateTime = new DateTime(2015, 3, 24, 10, 5, 0, DateTimeKind.Utc) }, + new() { UserId = 2, Subject = Subject.BegrijpendLezen, Correct = false, SubmitDateTime = new DateTime(2015, 3, 24, 10, 10, 0, DateTimeKind.Utc) } + }; + } + + private IList CreateAllCorrectAnswersTestData() + { + return new List + { + new() { UserId = 1, Subject = Subject.BegrijpendLezen, Correct = true, SubmitDateTime = new DateTime(2015, 3, 24, 10, 0, 0, DateTimeKind.Utc) }, + new() { UserId = 1, Subject = Subject.BegrijpendLezen, Correct = true, SubmitDateTime = new DateTime(2015, 3, 24, 10, 5, 0, DateTimeKind.Utc) }, + new() { UserId = 2, Subject = Subject.BegrijpendLezen, Correct = true, SubmitDateTime = new DateTime(2015, 3, 24, 10, 10, 0, DateTimeKind.Utc) } + }; + } +} \ No newline at end of file diff --git a/BackEnd/Tests/Queries/GetStudentDataQueryTests.cs b/BackEnd/Tests/Queries/GetStudentDataQueryTests.cs new file mode 100644 index 000000000..d694a8208 --- /dev/null +++ b/BackEnd/Tests/Queries/GetStudentDataQueryTests.cs @@ -0,0 +1,220 @@ +using Domain.Models; +using Domain.Queries; +using Domain.Repositories; +using Domain.Services; +using FluentAssertions; +using NSubstitute; +using Xunit; + +namespace Tests.Queries; + +public class GetStudentDataQueryTests +{ + private readonly IWorkDataRepository _mockRepository; + private readonly AverageCalculationService _averageCalculationService; + private readonly GetStudentDataQuery _query; + private readonly DateTime _cutoffDate = new(2015, 3, 24, 11, 30, 0, DateTimeKind.Utc); + + public GetStudentDataQueryTests() + { + _mockRepository = Substitute.For(); + _averageCalculationService = new AverageCalculationService(); + _query = new GetStudentDataQuery(_mockRepository, _averageCalculationService); + } + + [Fact] + public async Task ExecuteAsync_WithValidStudentId_ShouldReturnStudentDataSuccessfully() + { + // Arrange + var studentId = 1; + var workResults = CreateTestWorkResultsForStudent(studentId); + _mockRepository.GetWorkResultsBeforeDateAsync(_cutoffDate).Returns(workResults); + + // Act + var result = await _query.ExecuteAsync(studentId, _cutoffDate); + + // Assert + result.Should().NotBeNull(); + result!.UserId.Should().Be(studentId); + result.Name.Should().Be("Student 1"); + result.SubjectAverages.Should().HaveCount(2); + result.Exercises.Should().HaveCount(6); + } + + [Fact] + public async Task ExecuteAsync_WithNonExistentStudentId_ShouldReturnNull() + { + // Arrange + var studentId = 999; + var workResults = CreateTestWorkResultsForStudent(1); + _mockRepository.GetWorkResultsBeforeDateAsync(_cutoffDate).Returns(workResults); + + // Act + var result = await _query.ExecuteAsync(studentId, _cutoffDate); + + // Assert + result.Should().BeNull(); + } + + [Fact] + public async Task ExecuteAsync_WithEmptyWorkResults_ShouldReturnNull() + { + // Arrange + var studentId = 1; + var emptyWorkResults = new List(); + _mockRepository.GetWorkResultsBeforeDateAsync(_cutoffDate).Returns(emptyWorkResults); + + // Act + var result = await _query.ExecuteAsync(studentId, _cutoffDate); + + // Assert + result.Should().BeNull(); + } + + [Fact] + public async Task ExecuteAsync_ShouldCalculateCorrectSubjectAverages() + { + // Arrange + var studentId = 1; + var workResults = CreateTestWorkResultsForStudent(studentId); + _mockRepository.GetWorkResultsBeforeDateAsync(_cutoffDate).Returns(workResults); + + // Act + var result = await _query.ExecuteAsync(studentId, _cutoffDate); + + // Assert + result.Should().NotBeNull(); + + var rekenenAverage = result!.SubjectAverages.First(sa => sa.Subject == Subject.Rekenen); + rekenenAverage.Average.Should().Be(7.5m); + + var spellingAverage = result.SubjectAverages.First(sa => sa.Subject == Subject.Spelling); + spellingAverage.Average.Should().Be(10.0m); + } + + [Fact] + public async Task ExecuteAsync_ShouldReturnExercisesOrderedByNewestFirst() + { + // Arrange + var studentId = 1; + var workResults = CreateTestWorkResultsForStudent(studentId); + _mockRepository.GetWorkResultsBeforeDateAsync(_cutoffDate).Returns(workResults); + + // Act + var result = await _query.ExecuteAsync(studentId, _cutoffDate); + + // Assert + result.Should().NotBeNull(); + result!.Exercises.Should().HaveCount(6); + + var exercises = result.Exercises.ToList(); + exercises[0].SubmitDateTime.Should().BeAfter(exercises[1].SubmitDateTime); + exercises[1].SubmitDateTime.Should().BeAfter(exercises[2].SubmitDateTime); + } + + [Fact] + public async Task ExecuteAsync_ShouldIncludeAllExerciseProperties() + { + // Arrange + var studentId = 1; + var workResults = CreateTestWorkResultsForStudent(studentId); + _mockRepository.GetWorkResultsBeforeDateAsync(_cutoffDate).Returns(workResults); + + // Act + var result = await _query.ExecuteAsync(studentId, _cutoffDate); + + // Assert + result.Should().NotBeNull(); + + var exercise = result!.Exercises.First(); + exercise.Subject.Should().NotBe(default(Subject)); + exercise.SubjectName.Should().Be("Rekenen"); + exercise.ExerciseId.Should().BeGreaterThan(0); + exercise.ExerciseName.Should().NotBeNullOrEmpty(); + exercise.Difficulty.Should().BeGreaterThan(0); + exercise.Progress.Should().BeGreaterOrEqualTo(0); + exercise.SubmitDateTime.Should().NotBe(default(DateTime)); + } + + [Fact] + public async Task ExecuteAsync_ShouldCallRepositoryWithCorrectCutoffDate() + { + // Arrange + var studentId = 1; + var workResults = new List(); + _mockRepository.GetWorkResultsBeforeDateAsync(_cutoffDate).Returns(workResults); + + // Act + await _query.ExecuteAsync(studentId, _cutoffDate); + + // Assert + await _mockRepository.Received(1).GetWorkResultsBeforeDateAsync(_cutoffDate); + } + + private IList CreateTestWorkResultsForStudent(int studentId) + { + return new List + { + new() + { + UserId = studentId, + Subject = Subject.Rekenen, + ExerciseId = 101, + Correct = true, + Difficulty = 0.8m, + Progress = 50, + SubmitDateTime = new DateTime(2015, 3, 24, 10, 30, 0, DateTimeKind.Utc) + }, + new() + { + UserId = studentId, + Subject = Subject.Rekenen, + ExerciseId = 102, + Correct = true, + Difficulty = 0.6m, + Progress = 60, + SubmitDateTime = new DateTime(2015, 3, 24, 10, 25, 0, DateTimeKind.Utc) + }, + new() + { + UserId = studentId, + Subject = Subject.Rekenen, + ExerciseId = 103, + Correct = true, + Difficulty = 0.7m, + Progress = 70, + SubmitDateTime = new DateTime(2015, 3, 24, 10, 20, 0, DateTimeKind.Utc) + }, + new() + { + UserId = studentId, + Subject = Subject.Rekenen, + ExerciseId = 104, + Correct = false, + Difficulty = 0.9m, + Progress = 80, + SubmitDateTime = new DateTime(2015, 3, 24, 10, 15, 0, DateTimeKind.Utc) + }, + new() + { + UserId = studentId, + Subject = Subject.Spelling, + ExerciseId = 201, + Correct = true, + Difficulty = 0.5m, + Progress = 90, + SubmitDateTime = new DateTime(2015, 3, 24, 10, 10, 0, DateTimeKind.Utc) + }, + new() + { + UserId = studentId, + Subject = Subject.Spelling, + ExerciseId = 202, + Correct = true, + Difficulty = 0.4m, + Progress = 100, + SubmitDateTime = new DateTime(2015, 3, 24, 10, 5, 0, DateTimeKind.Utc) + } + }; + } +} \ No newline at end of file diff --git a/BackEnd/Tests/Services/AverageCalculationServiceTests.cs b/BackEnd/Tests/Services/AverageCalculationServiceTests.cs new file mode 100644 index 000000000..e651e887c --- /dev/null +++ b/BackEnd/Tests/Services/AverageCalculationServiceTests.cs @@ -0,0 +1,272 @@ +using Domain.Models; +using Domain.Services; +using FluentAssertions; +using Xunit; + +namespace Tests.Services; + +public class AverageCalculationServiceTests +{ + private readonly AverageCalculationService _service; + + public AverageCalculationServiceTests() + { + _service = new AverageCalculationService(); + } + + [Fact] + public void CalculateSubjectAverages_WithMixedResults_ShouldCalculateCorrectAverages() + { + // Arrange + var workResults = CreateMixedTestData(); + + // Act + var result = _service.CalculateSubjectAverages(workResults); + + // Assert + result.Should().HaveCount(2); + + var begrijpendLezen = result.First(r => r.Subject == Subject.BegrijpendLezen); + begrijpendLezen.Average.Should().Be(6.0m); + begrijpendLezen.TotalExercises.Should().Be(5); + begrijpendLezen.StudentCount.Should().Be(2); + + var rekenen = result.First(r => r.Subject == Subject.Rekenen); + rekenen.Average.Should().Be(10.0m); + rekenen.TotalExercises.Should().Be(2); + rekenen.StudentCount.Should().Be(1); + } + + [Fact] + public void CalculateSubjectAverages_WithEmptyList_ShouldReturnEmptyList() + { + // Arrange + var workResults = new List(); + + // Act + var result = _service.CalculateSubjectAverages(workResults); + + // Assert + result.Should().BeEmpty(); + } + + [Fact] + public void CalculateSubjectAverages_WithAllCorrectAnswers_ShouldReturnTenAverage() + { + // Arrange + var workResults = new List + { + new() { UserId = 1, Subject = Subject.Spelling, Correct = true }, + new() { UserId = 1, Subject = Subject.Spelling, Correct = true }, + new() { UserId = 2, Subject = Subject.Spelling, Correct = true } + }; + + // Act + var result = _service.CalculateSubjectAverages(workResults); + + // Assert + result.Should().HaveCount(1); + result[0].Average.Should().Be(10.0m); + result[0].SubjectName.Should().Be("Spelling"); + } + + [Fact] + public void CalculateSubjectAverages_WithAllIncorrectAnswers_ShouldReturnZeroAverage() + { + // Arrange + var workResults = new List + { + new() { UserId = 1, Subject = Subject.Rekenen, Correct = false }, + new() { UserId = 1, Subject = Subject.Rekenen, Correct = false }, + new() { UserId = 2, Subject = Subject.Rekenen, Correct = false } + }; + + // Act + var result = _service.CalculateSubjectAverages(workResults); + + // Assert + result.Should().HaveCount(1); + result[0].Average.Should().Be(0.0m); + result[0].SubjectName.Should().Be("Rekenen"); + } + + [Fact] + public void CalculateSubjectAverages_ShouldRoundToOneDecimalPlace() + { + // Arrange + var workResults = new List + { + new() { UserId = 1, Subject = Subject.BegrijpendLezen, Correct = true }, + new() { UserId = 1, Subject = Subject.BegrijpendLezen, Correct = true }, + new() { UserId = 1, Subject = Subject.BegrijpendLezen, Correct = false } + }; + + // Act + var result = _service.CalculateSubjectAverages(workResults); + + // Assert + result[0].Average.Should().Be(6.7m); + } + + [Fact] + public void CalculateSubjectAverages_ShouldCountUniqueStudentsPerSubject() + { + // Arrange + var workResults = new List + { + new() { UserId = 1, Subject = Subject.BegrijpendLezen, Correct = true }, + new() { UserId = 1, Subject = Subject.BegrijpendLezen, Correct = false }, + new() { UserId = 1, Subject = Subject.BegrijpendLezen, Correct = true }, + new() { UserId = 2, Subject = Subject.BegrijpendLezen, Correct = true }, + new() { UserId = 3, Subject = Subject.BegrijpendLezen, Correct = false } + }; + + // Act + var result = _service.CalculateSubjectAverages(workResults); + + // Assert + result[0].StudentCount.Should().Be(3); + result[0].TotalExercises.Should().Be(5); + } + + [Fact] + public void CalculateSubjectAverages_ShouldOrderBySubject() + { + // Arrange + var workResults = new List + { + new() { UserId = 1, Subject = Subject.Spelling, Correct = true }, + new() { UserId = 1, Subject = Subject.BegrijpendLezen, Correct = true }, + new() { UserId = 1, Subject = Subject.Rekenen, Correct = true } + }; + + // Act + var result = _service.CalculateSubjectAverages(workResults); + + // Assert + result[0].Subject.Should().Be(Subject.BegrijpendLezen); + result[1].Subject.Should().Be(Subject.Rekenen); + result[2].Subject.Should().Be(Subject.Spelling); + } + + [Fact] + public void CalculateStudentSubjectAverages_WithMultipleSubjects_ReturnsCorrectAverages() + { + // Arrange + var service = new AverageCalculationService(); + var workResults = new List + { + new() { UserId = 1, Subject = Subject.Rekenen, Correct = true }, + new() { UserId = 1, Subject = Subject.Rekenen, Correct = true }, + new() { UserId = 1, Subject = Subject.Rekenen, Correct = true }, + new() { UserId = 1, Subject = Subject.Rekenen, Correct = false }, + + new() { UserId = 1, Subject = Subject.Spelling, Correct = true }, + new() { UserId = 1, Subject = Subject.Spelling, Correct = true } + }; + + // Act + var result = service.CalculateStudentSubjectAverages(workResults); + + // Assert + result.Should().HaveCount(2); + + var rekenenAverage = result.First(r => r.Subject == Subject.Rekenen); + rekenenAverage.Average.Should().Be(7.5m); + + var spellingAverage = result.First(r => r.Subject == Subject.Spelling); + spellingAverage.Average.Should().Be(10.0m); + } + + [Fact] + public void CalculateStudentSubjectAverages_WithAllWrongAnswers_ReturnsZeroAverage() + { + // Arrange + var service = new AverageCalculationService(); + var workResults = new List + { + new() { UserId = 1, Subject = Subject.Rekenen, Correct = false }, + new() { UserId = 1, Subject = Subject.Rekenen, Correct = false }, + new() { UserId = 1, Subject = Subject.Rekenen, Correct = false } + }; + + // Act + var result = service.CalculateStudentSubjectAverages(workResults); + + // Assert + result.Should().HaveCount(1); + result.First().Subject.Should().Be(Subject.Rekenen); + result.First().Average.Should().Be(0.0m); + } + + [Fact] + public void CalculateStudentSubjectAverages_WithEmptyResults_ReturnsEmptyList() + { + // Arrange + var service = new AverageCalculationService(); + var workResults = new List(); + + // Act + var result = service.CalculateStudentSubjectAverages(workResults); + + // Assert + result.Should().BeEmpty(); + } + + [Fact] + public void CalculateStudentSubjectAverages_RoundsToOneDecimalPlace() + { + var service = new AverageCalculationService(); + var workResults = new List + { + new() { UserId = 1, Subject = Subject.Rekenen, Correct = true }, + new() { UserId = 1, Subject = Subject.Rekenen, Correct = false }, + new() { UserId = 1, Subject = Subject.Rekenen, Correct = false } + }; + + // Act + var result = service.CalculateStudentSubjectAverages(workResults); + + // Assert + result.Should().HaveCount(1); + result.First().Average.Should().Be(3.3m); + } + + [Fact] + public void CalculateStudentSubjectAverages_OrdersBySubject() + { + // Arrange + var service = new AverageCalculationService(); + var workResults = new List + { + new() { UserId = 1, Subject = Subject.Spelling, Correct = true }, + new() { UserId = 1, Subject = Subject.BegrijpendLezen, Correct = true }, + new() { UserId = 1, Subject = Subject.Rekenen, Correct = true } + }; + + // Act + var result = service.CalculateStudentSubjectAverages(workResults); + + // Assert + result.Should().HaveCount(3); + result[0].Subject.Should().Be(Subject.BegrijpendLezen); + result[1].Subject.Should().Be(Subject.Rekenen); + result[2].Subject.Should().Be(Subject.Spelling); + } + + + + private IList CreateMixedTestData() + { + return new List + { + new() { UserId = 1, Subject = Subject.BegrijpendLezen, Correct = true }, + new() { UserId = 1, Subject = Subject.BegrijpendLezen, Correct = true }, + new() { UserId = 1, Subject = Subject.BegrijpendLezen, Correct = true }, + new() { UserId = 2, Subject = Subject.BegrijpendLezen, Correct = false }, + new() { UserId = 2, Subject = Subject.BegrijpendLezen, Correct = false }, + new() { UserId = 3, Subject = Subject.Rekenen, Correct = true }, + new() { UserId = 3, Subject = Subject.Rekenen, Correct = true } + }; + } +} \ No newline at end of file diff --git a/BackEnd/Tests/Tests.csproj b/BackEnd/Tests/Tests.csproj new file mode 100644 index 000000000..0c701a2a9 --- /dev/null +++ b/BackEnd/Tests/Tests.csproj @@ -0,0 +1,25 @@ + + + + net8.0 + enable + enable + false + true + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BackEnd/WebApi/Controllers/DashboardController.cs b/BackEnd/WebApi/Controllers/DashboardController.cs new file mode 100644 index 000000000..b16c2fef5 --- /dev/null +++ b/BackEnd/WebApi/Controllers/DashboardController.cs @@ -0,0 +1,32 @@ +using Domain.Models; +using Domain.Queries; +using Microsoft.AspNetCore.Mvc; + +namespace WebApi.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class DashboardController : ControllerBase +{ + private readonly GetDashboardAveragesQuery _getDashboardAveragesQuery; + private static readonly DateTime TodaysDate = new(2015, 3, 24, 11, 30, 0, DateTimeKind.Utc); + + public DashboardController(GetDashboardAveragesQuery getDashboardAveragesQuery) + { + _getDashboardAveragesQuery = getDashboardAveragesQuery; + } + + [HttpGet("gemiddelden")] + public async Task> GetGemiddelden() + { + try + { + var dashboardSummary = await _getDashboardAveragesQuery.ExecuteAsync(TodaysDate); + return Ok(dashboardSummary); + } + catch (Exception ex) + { + return StatusCode(500, new { Message = "Er is een fout opgetreden bij het ophalen van de gegevens", Details = ex.Message }); + } + } +} \ No newline at end of file diff --git a/BackEnd/WebApi/Controllers/StudentController.cs b/BackEnd/WebApi/Controllers/StudentController.cs new file mode 100644 index 000000000..b9054a74a --- /dev/null +++ b/BackEnd/WebApi/Controllers/StudentController.cs @@ -0,0 +1,54 @@ +using Domain.Models; +using Domain.Queries; +using Microsoft.AspNetCore.Mvc; + +namespace WebApi.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class StudentController : ControllerBase +{ + private readonly GetAllStudentsQuery _getAllStudentsQuery; + private readonly GetStudentDataQuery _getStudentDataQuery; + private static readonly DateTime TodaysDate = new(2015, 3, 24, 11, 30, 0, DateTimeKind.Utc); + + public StudentController(GetAllStudentsQuery getAllStudentsQuery, GetStudentDataQuery getStudentDataQuery) + { + _getAllStudentsQuery = getAllStudentsQuery; + _getStudentDataQuery = getStudentDataQuery; + } + + [HttpGet] + public async Task>> GetAll() + { + try + { + var students = await _getAllStudentsQuery.ExecuteAsync(TodaysDate); + return Ok(students); + } + catch (Exception ex) + { + return StatusCode(500, new { Message = "Er is een fout opgetreden bij het ophalen van de leerlingen", Details = ex.Message }); + } + } + + [HttpGet("{studentId}")] + public async Task> GetStudentData(int studentId) + { + try + { + var studentData = await _getStudentDataQuery.ExecuteAsync(studentId, TodaysDate); + + if (studentData == null) + { + return NotFound(new { Message = "Leerling niet gevonden" }); + } + + return Ok(studentData); + } + catch (Exception ex) + { + return StatusCode(500, new { Message = "Er is een fout opgetreden bij het ophalen van de leerlinggegevens", Details = ex.Message }); + } + } +} \ No newline at end of file diff --git a/BackEnd/WebApi/Program.cs b/BackEnd/WebApi/Program.cs new file mode 100644 index 000000000..a6a129a8c --- /dev/null +++ b/BackEnd/WebApi/Program.cs @@ -0,0 +1,43 @@ +using Domain.Queries; +using Domain.Repositories; +using Domain.Services; +using Storage; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +builder.Services.AddCors(options => +{ + options.AddPolicy("ReactPolicy", policy => + { + policy.WithOrigins("http://localhost:5173") + .AllowAnyHeader() + .AllowAnyMethod(); + }); +}); + +var csvFilePath = Path.Combine(builder.Environment.ContentRootPath, "..", "..", "Data", "work.csv"); +builder.Services.AddSingleton(provider => new CsvWorkDataRepository(csvFilePath)); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); +app.UseCors("ReactPolicy"); + +app.UseRouting(); +app.MapControllers(); + +app.Run(); diff --git a/BackEnd/WebApi/Properties/launchSettings.json b/BackEnd/WebApi/Properties/launchSettings.json new file mode 100644 index 000000000..c7b94fca7 --- /dev/null +++ b/BackEnd/WebApi/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5050", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7044;http://localhost:5050", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/BackEnd/WebApi/WebApi.csproj b/BackEnd/WebApi/WebApi.csproj new file mode 100644 index 000000000..fbcc51dae --- /dev/null +++ b/BackEnd/WebApi/WebApi.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + diff --git a/BackEnd/WebApi/appsettings.Development.json b/BackEnd/WebApi/appsettings.Development.json new file mode 100644 index 000000000..0c208ae91 --- /dev/null +++ b/BackEnd/WebApi/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/BackEnd/WebApi/appsettings.json b/BackEnd/WebApi/appsettings.json new file mode 100644 index 000000000..10f68b8c8 --- /dev/null +++ b/BackEnd/WebApi/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/README.md b/README.md index 7fc4491cf..1f0efedbd 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,139 @@ -# SnappetChallenge -At [Snappet](http://www.snappet.org), we care about data and we care about code. When we interview for development positions, we want to see code and we want to discuss code. That's why we want candidates to show some work on our challenge. This challenge is not meant to cost you tons of time. A few hours should be enough. The challenge is defined very broadly. You could spend weeks on it, or half an hour. We understand that in 2 hours, you can only do so much. Don't worry about completeness, work on something that works and shows your skills. +# Snappet Challenge -### Language -From the next paragraph on, this challenge is worded in Dutch. Snappet is a Dutch organisation. We are present in several European countries and part of our development team is based in Russia, but still, most of the organisation is Dutch. We all speak English, standups, code and documentation are in English, but being able to operate in a Dutch environment is a required skill. So use whatever tools you can to make sense of the rest of the challenge if you are not a Dutch speaker. It is part of the exercise. :) +## 🚀 Step-by-Step Setup Instructions -### De opdracht -In deze repository vind je een folder Data met daarin work.csv en work.json. Beiden bevatten dezelfde data, je hoeft er maar één te gebruiken (wat jij handig vindt). In dit bestand zitten de werkresultaten van de kinderen in één klas over een maand. +### Step 1: Install Required Software -Maak een rapport of scherm of wat ook dat een leerkracht een overzicht geeft van hoe zijn klas vandaag heeft gewerkt en waaraan. Het is nu dinsdag 2015-03-24 11:30:00 UTC. De antwoorden van na dat tijdstip worden dus nog niet getoond. +**Install .NET 8 (for the backend):** -Maak een pull request aan waarin je in ieder geval een readme hebt opgenomen die uitlegt wat je moet doen om het resultaat te kunnen bekijken. +1. **Go to:** https://dotnet.microsoft.com/en-us/download/dotnet/8.0 +2. **Download the installer** for your system: + - **Windows**: Click "Download .NET 8.0" → Run the .exe file + - **macOS**: Click "Download .NET 8.0" → Run the .pkg file + - **Linux**: Follow your distribution's instructions +3. **Run the installer** (accept all defaults) +4. **Test it works:** + - Open Terminal (macOS/Linux) or PowerShell (Windows) + - Type: `dotnet --version` + - You should see: `8.0.xxx` + - If not, close and reopen your terminal +**Install Node.js (for the frontend):** +1. **Go to:** https://nodejs.org/en/download +2. **Select your system:** + - **Windows**: Windows Installer (.msi) + - **macOS**: macOS Installer (.pkg) + - **Linux**: Choose your distribution's package +3. **Download:** The LTS version (green button) +4. **Run the installer** (accept all defaults) +5. **Test it works:** + - Open a new terminal/command prompt + - Type: `node --version` → should show `v18.xx.x` or higher + - Type: `npm --version` → should show a version number -### Achtergrond informatie -- Alle tijden zijn in UTC -- Er is een attribuut Progress. Dit geeft de verandering in de inschatting van de vaardigheid van de leerling op een leerdoel. Daar zitten psychometrische modellen achter die rekening houden met de moeilijkheid van de opgave, of de opgave al eerder door deze leerling is gemaakt, etc. Er zijn meerdere situaties waarbij de Progress 0 is. Bijvoorbeeld als we nog geen goede calibratie van de moeilijkheid van de opgave hebben. Of als de leerling nog te weinig opgaven in een leerdoel heeft gemaakt om een goede schatting van de vaardigheid te maken. -- Aangezien deze dataset alleen wijzigingen laat zien en geen absolute waarde, kan je aan deze dataset niet zien wat de vaardigheid van iedere leerling is. Dat hoeft ook niet in de resultaten terug te komen. +**Can't find the terminal?** -### Vrijheid -Deze opdracht is expres ruim geformuleerd. Je mag de technieken en tools gebruiken die je het liefst gebruikt. Je mag je tijd besteden aan de aspecten die je zelf het belangrijkst vindt. Er is geen tijd om alles te doen: maak een keuze. Bij Snappet werken we met C#, .NET, Typescript en Angular. Maar we denken dat een goede programmeur op een ander platform zich dat snel genoeg eigen maakt. -Je mag frameworks en libraries gebruiken. Je mag de data in een ander formaat omzetten of importeren in databases. Dan wel in de readme uitleggen hoe een ander het werkend kan krijgen. -De minimale requirement in de opdracht is "waar heeft mijn klas vandaag aan gewerkt". Dat kan in een lijstje, in een grafisch vorm, het kan als getallen of kleuren. Je kan het vergelijken met vorige week of een gemiddelde score. Probeer te bedenken wat voor een leerkracht in de klas het belangrijkst is. +- **Windows**: Type "PowerShell" in the Start menu +- **macOS**: Press Cmd+Space, type "Terminal" +- **Linux**: Usually Ctrl+Alt+T or search for "Terminal" + +### Step 2: Get the Code + +```bash +# If you have git installed: +git clone [repository-url] +cd SnappetChallenge + +# If you downloaded a ZIP file: +# Extract it to a folder and open terminal in that folder and then navigate to the project: +cd SnappetChallenge +``` + +### Step 3: Start the Backend API + +```bash +# Navigate to the backend folder +cd BackEnd/WebApi + +# Download and install all backend dependencies +dotnet restore + +# Start the server +dotnet run +``` + +**IMPORTANT:** Keep this terminal window open! You should see output ending with: + +``` +Now listening on: http://localhost:5050 +``` + +**Common Issues:** + +- If you get "dotnet: command not found" → restart your terminal after installing .NET +- If you get permission errors on Windows → try running terminal as Administrator +- If port 5050 is in use → the app will automatically use a different port (note the number) + +### Step 4: Start the Frontend + +Open a **completely new** terminal window (don't close the first one!) and run: + +```bash +# Navigate to the project folder again +cd SnappetChallenge + +# Go to the UI folder +cd ui + +# Install all frontend dependencies (this takes 2-3 minutes) +npm install + +# Start the web interface +npm run dev +``` + +You should see output ending with: + +``` +Local: http://localhost:5173/ +``` + +**Common Issues:** + +- If you get "npm: command not found" → restart your terminal after installing Node.js +- If port 5173 is in use → the app will show a different port (note the number) +- If installation fails → try deleting `node_modules` folder and run `npm install` again + +### Step 5: Open the Application + +1. Open any web browser (Chrome, Firefox, Safari, Edge) +2. Type in the address bar: `http://localhost:5173` +3. Press Enter +4. You should see a dashboard showing class performance data + +**Success:** You should see subject widgets with colors (green, yellow, red) showing class performance. + +## Troubleshooting + +**"Cannot connect" or blank page:** + +- Make sure both terminal windows are still running +- Check the backend terminal shows "Now listening on: http://localhost:5050" +- Try refreshing the browser page +- Check your firewall isn't blocking the applications + +**On macOS:** + +- You might get a security popup asking to allow the application +- Click "Allow" in System Preferences → Security & Privacy + +**On Windows:** + +- Windows Defender might block the app initially +- Click "Allow access" when prompted + +## Running Tests + +```bash +cd BackEnd/Tests +dotnet test +``` diff --git a/ui/.gitignore b/ui/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/ui/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/ui/eslint.config.js b/ui/eslint.config.js new file mode 100644 index 000000000..d94e7deb7 --- /dev/null +++ b/ui/eslint.config.js @@ -0,0 +1,23 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { globalIgnores } from 'eslint/config' + +export default tseslint.config([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs['recommended-latest'], + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + }, +]) diff --git a/ui/index.html b/ui/index.html new file mode 100644 index 000000000..52b9be68e --- /dev/null +++ b/ui/index.html @@ -0,0 +1,13 @@ + + + + + + + Snappet Challenge + + +
+ + + diff --git a/ui/package-lock.json b/ui/package-lock.json new file mode 100644 index 000000000..5b4178614 --- /dev/null +++ b/ui/package-lock.json @@ -0,0 +1,4379 @@ +{ + "name": "ui", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ui", + "version": "0.0.0", + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/icons-material": "^7.2.0", + "@mui/material": "^7.2.0", + "material-react-table": "^3.2.1", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router-dom": "^7.7.0" + }, + "devDependencies": { + "@eslint/js": "^9.30.1", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@types/react-router-dom": "^5.3.3", + "@vitejs/plugin-react": "^4.6.0", + "eslint": "^9.30.1", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^16.3.0", + "typescript": "~5.8.3", + "typescript-eslint": "^8.35.1", + "vite": "^7.0.4" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", + "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", + "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.0", + "@babel/types": "^7.28.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", + "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz", + "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", + "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz", + "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.2.0.tgz", + "integrity": "sha512-d49s7kEgI5iX40xb2YPazANvo7Bx0BLg/MNRwv+7BVpZUzXj1DaVCKlQTDex3gy/0jsCb4w7AY2uH4t4AJvSog==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.2.0.tgz", + "integrity": "sha512-gRCspp3pfjHQyTmSOmYw7kUQTd9Udpdan4R8EnZvqPeoAtHnPzkvjBrBqzKaoAbbBp5bGF7BcD18zZJh4nwu0A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^7.2.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.2.0.tgz", + "integrity": "sha512-NTuyFNen5Z2QY+I242MDZzXnFIVIR6ERxo7vntFi9K1wCgSwvIl0HcAO2OOydKqqKApE6omRiYhpny1ZhGuH7Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "@mui/core-downloads-tracker": "^7.2.0", + "@mui/system": "^7.2.0", + "@mui/types": "^7.4.4", + "@mui/utils": "^7.2.0", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.1.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^7.2.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.2.0.tgz", + "integrity": "sha512-y6N1Yt3T5RMxVFnCh6+zeSWBuQdNDm5/UlM0EAYZzZR/1u+XKJWYQmbpx4e+F+1EpkYi3Nk8KhPiQDi83M3zIw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "@mui/utils": "^7.2.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.2.0.tgz", + "integrity": "sha512-yq08xynbrNYcB1nBcW9Fn8/h/iniM3ewRguGJXPIAbHvxEF7Pz95kbEEOAAhwzxMX4okhzvHmk0DFuC5ayvgIQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.2.0.tgz", + "integrity": "sha512-PG7cm/WluU6RAs+gNND2R9vDwNh+ERWxPkqTaiXQJGIFAyJ+VxhyKfzpdZNk0z0XdmBxxi9KhFOpgxjehf/O0A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "@mui/private-theming": "^7.2.0", + "@mui/styled-engine": "^7.2.0", + "@mui/types": "^7.4.4", + "@mui/utils": "^7.2.0", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.4.tgz", + "integrity": "sha512-p63yhbX52MO/ajXC7hDHJA5yjzJekvWD3q4YDLl1rSg+OXLczMYPvTuSuviPRCgRX8+E42RXz1D/dz9SxPSlWg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.2.0.tgz", + "integrity": "sha512-O0i1GQL6MDzhKdy9iAu5Yr0Sz1wZjROH1o3aoztuivdCXqEeQYnEjTDiRLGuFxI9zrUbTHBwobMyQH5sNtyacw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "@mui/types": "^7.4.4", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/x-date-pickers": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-8.9.0.tgz", + "integrity": "sha512-MD2/F63Tdsodygp3Z2VtfvvQhAiEVXvleuK9mqXuD6a1cCPOENICCJC98y2AKbOcsbVd37o6HCvWFOQsfsy7TQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.27.6", + "@mui/utils": "^7.2.0", + "@mui/x-internals": "8.8.0", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0", + "@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0", + "date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0", + "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0", + "dayjs": "^1.10.7", + "luxon": "^3.0.2", + "moment": "^2.29.4", + "moment-hijri": "^2.1.2 || ^3.0.0", + "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "date-fns": { + "optional": true + }, + "date-fns-jalali": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + }, + "moment-hijri": { + "optional": true + }, + "moment-jalaali": { + "optional": true + } + } + }, + "node_modules/@mui/x-internals": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.8.0.tgz", + "integrity": "sha512-qTRK5oINkAjZ7sIHpSnESLNq1xtQUmmfmGscYUSEP0uHoYh6pKkNWH9+7yzggRHuTv+4011VBwN9s+efrk+xZg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.27.6", + "@mui/utils": "^7.2.0", + "reselect": "^5.1.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz", + "integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz", + "integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz", + "integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz", + "integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz", + "integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz", + "integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz", + "integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz", + "integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz", + "integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz", + "integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz", + "integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz", + "integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz", + "integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz", + "integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz", + "integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz", + "integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz", + "integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz", + "integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz", + "integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz", + "integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tanstack/match-sorter-utils": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.19.4.tgz", + "integrity": "sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==", + "license": "MIT", + "dependencies": { + "remove-accents": "0.5.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-table": { + "version": "8.20.6", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.6.tgz", + "integrity": "sha512-w0jluT718MrOKthRcr2xsjqzx+oEM7B7s/XXyfs19ll++hlId3fjTm+B2zrR3ijpANpkzBAr15j1XGVOMxpggQ==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.20.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/react-virtual": { + "version": "3.11.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.11.2.tgz", + "integrity": "sha512-OuFzMXPF4+xZgx8UzJha0AieuMihhhaWG0tCqpp6tDzlFwOmNBPYMuLOtMJ1Tr4pXLHmgjcWhG6RlknY2oNTdQ==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.11.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz", + "integrity": "sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.11.2", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.11.2.tgz", + "integrity": "sha512-vTtpNt7mKCiZ1pwU9hfKPhpdVO2sVzFQsxoVBGtOSHxlrRRzYr8iQ2TlwbAcRYCcEiZ9ECAM8kBzH0v2+VzfKw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.1.8", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", + "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", + "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.37.0.tgz", + "integrity": "sha512-jsuVWeIkb6ggzB+wPCsR4e6loj+rM72ohW6IBn2C+5NCvfUVY8s33iFPySSVXqtm5Hu29Ne/9bnA0JmyLmgenA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.37.0", + "@typescript-eslint/type-utils": "8.37.0", + "@typescript-eslint/utils": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.37.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.37.0.tgz", + "integrity": "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.37.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.37.0.tgz", + "integrity": "sha512-BIUXYsbkl5A1aJDdYJCBAo8rCEbAvdquQ8AnLb6z5Lp1u3x5PNgSSx9A/zqYc++Xnr/0DVpls8iQ2cJs/izTXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.37.0", + "@typescript-eslint/types": "^8.37.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.37.0.tgz", + "integrity": "sha512-0vGq0yiU1gbjKob2q691ybTg9JX6ShiVXAAfm2jGf3q0hdP6/BruaFjL/ManAR/lj05AvYCH+5bbVo0VtzmjOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.37.0.tgz", + "integrity": "sha512-1/YHvAVTimMM9mmlPvTec9NP4bobA1RkDbMydxG8omqwJJLEW/Iy2C4adsAESIXU3WGLXFHSZUU+C9EoFWl4Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.37.0.tgz", + "integrity": "sha512-SPkXWIkVZxhgwSwVq9rqj/4VFo7MnWwVaRNznfQDc/xPYHjXnPfLWn+4L6FF1cAz6e7dsqBeMawgl7QjUMj4Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0", + "@typescript-eslint/utils": "8.37.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.37.0.tgz", + "integrity": "sha512-ax0nv7PUF9NOVPs+lmQ7yIE7IQmAf8LGcXbMvHX5Gm+YJUYNAl340XkGnrimxZ0elXyoQJuN5sbg6C4evKA4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.37.0.tgz", + "integrity": "sha512-zuWDMDuzMRbQOM+bHyU4/slw27bAUEcKSKKs3hcv2aNnc/tvE/h7w60dwVw8vnal2Pub6RT1T7BI8tFZ1fE+yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.37.0", + "@typescript-eslint/tsconfig-utils": "8.37.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/visitor-keys": "8.37.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.37.0.tgz", + "integrity": "sha512-TSFvkIW6gGjN2p6zbXo20FzCABbyUAuq6tBvNRGsKdsSQ6a7rnV6ADfZ7f4iI3lIiXc4F4WWvtUfDw9CJ9pO5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.37.0", + "@typescript-eslint/types": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.37.0.tgz", + "integrity": "sha512-YzfhzcTnZVPiLfP/oeKtDp2evwvHLMe0LOy7oe+hb9KKIumLNohYS9Hgp1ifwpu42YWxhZE8yieggz6JpqO/1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.37.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001727", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", + "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.187", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.187.tgz", + "integrity": "sha512-cl5Jc9I0KGUoOoSbxvTywTa40uspGJt/BDBoDLoxJRSBpWh4FFXBsjNRHfQrONsV/OoEjDfHUmZQa2d6Ze4YgA==", + "dev": true, + "license": "ISC" + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/esbuild": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", + "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.15.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.31.0", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.20", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz", + "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", + "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/highlight-words": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/highlight-words/-/highlight-words-2.0.0.tgz", + "integrity": "sha512-If5n+IhSBRXTScE7wl16VPmd+44Vy7kof24EdqhjsZsDuHikpv1OCagVcJFpB4fS4UPUniedlWqrjIO8vWOsIQ==", + "license": "MIT", + "engines": { + "node": ">= 20", + "npm": ">= 9" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/material-react-table": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/material-react-table/-/material-react-table-3.2.1.tgz", + "integrity": "sha512-sQtTf7bETpkPN2Hm5BVtz89wrfXCVQguz6XlwMChSnfKFO5QCKAJJC5aSIKnUc3S0AvTz/k/ILi00FnnY1Gixw==", + "license": "MIT", + "dependencies": { + "@tanstack/match-sorter-utils": "8.19.4", + "@tanstack/react-table": "8.20.6", + "@tanstack/react-virtual": "3.11.2", + "highlight-words": "2.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kevinvandy" + }, + "peerDependencies": { + "@emotion/react": ">=11.13", + "@emotion/styled": ">=11.13", + "@mui/icons-material": ">=6", + "@mui/material": ">=6", + "@mui/x-date-pickers": ">=7.15", + "react": ">=18.0", + "react-dom": ">=18.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/react-is": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz", + "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.7.0.tgz", + "integrity": "sha512-3FUYSwlvB/5wRJVTL/aavqHmfUKe0+Xm9MllkYgGo9eDwNdkvwlJGjpPxono1kCycLt6AnDTgjmXvK3/B4QGuw==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.7.0.tgz", + "integrity": "sha512-wwGS19VkNBkneVh9/YD0pK3IsjWxQUVMDD6drlG7eJpo1rXBtctBqDyBm/k+oKHRAm1x9XWT3JFC82QI9YOXXA==", + "license": "MIT", + "dependencies": { + "react-router": "7.7.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/remove-accents": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", + "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==", + "license": "MIT" + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT", + "peer": true + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.45.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz", + "integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.45.1", + "@rollup/rollup-android-arm64": "4.45.1", + "@rollup/rollup-darwin-arm64": "4.45.1", + "@rollup/rollup-darwin-x64": "4.45.1", + "@rollup/rollup-freebsd-arm64": "4.45.1", + "@rollup/rollup-freebsd-x64": "4.45.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.45.1", + "@rollup/rollup-linux-arm-musleabihf": "4.45.1", + "@rollup/rollup-linux-arm64-gnu": "4.45.1", + "@rollup/rollup-linux-arm64-musl": "4.45.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.45.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.45.1", + "@rollup/rollup-linux-riscv64-gnu": "4.45.1", + "@rollup/rollup-linux-riscv64-musl": "4.45.1", + "@rollup/rollup-linux-s390x-gnu": "4.45.1", + "@rollup/rollup-linux-x64-gnu": "4.45.1", + "@rollup/rollup-linux-x64-musl": "4.45.1", + "@rollup/rollup-win32-arm64-msvc": "4.45.1", + "@rollup/rollup-win32-ia32-msvc": "4.45.1", + "@rollup/rollup-win32-x64-msvc": "4.45.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.37.0.tgz", + "integrity": "sha512-TnbEjzkE9EmcO0Q2zM+GE8NQLItNAJpMmED1BdgoBMYNdqMhzlbqfdSwiRlAzEK2pA9UzVW0gzaaIzXWg2BjfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.37.0", + "@typescript-eslint/parser": "8.37.0", + "@typescript-eslint/typescript-estree": "8.37.0", + "@typescript-eslint/utils": "8.37.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.5.tgz", + "integrity": "sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.6", + "picomatch": "^4.0.2", + "postcss": "^8.5.6", + "rollup": "^4.40.0", + "tinyglobby": "^0.2.14" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/ui/package.json b/ui/package.json new file mode 100644 index 000000000..d6acefa81 --- /dev/null +++ b/ui/package.json @@ -0,0 +1,36 @@ +{ + "name": "ui", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/icons-material": "^7.2.0", + "@mui/material": "^7.2.0", + "material-react-table": "^3.2.1", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router-dom": "^7.7.0" + }, + "devDependencies": { + "@eslint/js": "^9.30.1", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@types/react-router-dom": "^5.3.3", + "@vitejs/plugin-react": "^4.6.0", + "eslint": "^9.30.1", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^16.3.0", + "typescript": "~5.8.3", + "typescript-eslint": "^8.35.1", + "vite": "^7.0.4" + } +} diff --git a/ui/public/vite.svg b/ui/public/vite.svg new file mode 100644 index 000000000..e7b8dfb1b --- /dev/null +++ b/ui/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/src/App.css b/ui/src/App.css new file mode 100644 index 000000000..b6b52a543 --- /dev/null +++ b/ui/src/App.css @@ -0,0 +1,809 @@ +/* Layout Styles */ +.layout { + min-height: 100vh; + background: #f5f7fa; +} + +.main-content { + min-height: 100vh; + padding: 3rem 2rem; + background: #f5f7fa; +} + +.back-button { + background: linear-gradient(135deg, #4fc3f7 0%, #2196f3 100%); + color: white; + border: none; + padding: 0.8rem 1.5rem; + border-radius: 12px; + font-weight: 600; + cursor: pointer; + box-shadow: 0 4px 15px rgba(79, 195, 247, 0.3); +} + +.back-button:hover { + box-shadow: 0 6px 20px rgba(79, 195, 247, 0.4); + background: linear-gradient(135deg, #2196f3 0%, #1976d2 100%); +} + +.primary-button { + background: linear-gradient(135deg, #4fc3f7 0%, #2196f3 100%); + color: white; + border: none; + padding: 1rem 2rem; + border-radius: 12px; + font-weight: 600; + cursor: pointer; + box-shadow: 0 4px 15px rgba(79, 195, 247, 0.3); + margin: 0.5rem; +} + +.primary-button:hover { + box-shadow: 0 6px 20px rgba(79, 195, 247, 0.4); +} + +.secondary-button { + background: linear-gradient(135deg, #f5f7fa 0%, #e0e0e0 100%); + color: #546e7a; + border: 2px solid #d0d7de; + padding: 1rem 2rem; + border-radius: 12px; + font-weight: 600; + cursor: pointer; + margin: 0.5rem; +} + +.secondary-button:hover { + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); + background: linear-gradient(135deg, #ffffff 0%, #f0f0f0 100%); +} + +.clickable { + cursor: pointer; + position: relative; +} + +.clickable:hover { + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15) !important; +} + +/* Stat Card Action Text */ +.stat-action-text { + margin-top: 1rem; + padding-top: 0.8rem; + border-top: 1px solid rgba(79, 195, 247, 0.1); +} + +.stat-action-text span { + background: transparent; + color: #7db3d3; + padding: 0.4rem 0.8rem; + border-radius: 6px; + font-size: 0.75rem; + font-weight: 500; + display: inline-block; + border: none; + opacity: 0.8; +} + +.stat-card.clickable:hover .stat-action-text span { + color: #2196f3; + opacity: 1; +} + +/* Dashboard Styles */ +.dashboard { + max-width: 1400px; + margin: 0 auto; + padding: 0 1rem; +} + +.dashboard-header { + text-align: center; + margin-bottom: 2rem; + padding: 2rem 0; + position: relative; +} + +.dashboard-header::after { + content: ""; + position: absolute; + bottom: 0; + left: 50%; + transform: translateX(-50%); + width: 80px; + height: 3px; + background: linear-gradient(90deg, #4fc3f7, #ffc107); + border-radius: 2px; +} + +.dashboard-title { + margin-bottom: 1.5rem; +} + +.title-text-branded { + font-size: 3.5rem; + font-weight: 700; + background: linear-gradient( + 135deg, + #4fc3f7 0%, + #e91e63 25%, + #ffc107 50%, + #ff9800 75%, + #4caf50 100% + ); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + letter-spacing: -1px; + text-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +.dashboard-subtitle { + color: #546e7a; + font-size: 1.2rem; + margin: 0; + font-weight: 400; +} + +/* Page Header Styles */ +.page-header { + margin-bottom: 3rem; + text-align: center; +} + +.page-title { + margin: 2rem 0; +} + +.breadcrumb { + display: flex; + align-items: center; + justify-content: center; + gap: 1rem; + margin-bottom: 1rem; + flex-wrap: wrap; +} + +.breadcrumb-separator { + color: #546e7a; + font-weight: 600; +} + +.current-page { + color: #546e7a; + font-weight: 600; + padding: 0.8rem 1.5rem; + background: rgba(79, 195, 247, 0.1); + border-radius: 12px; + border: 2px solid rgba(79, 195, 247, 0.2); +} + +.dashboard-stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 2.5rem; + margin-bottom: 5rem; + max-width: 1000px; + margin-left: auto; + margin-right: auto; +} + +.stat-card:nth-child(1) { + background: linear-gradient( + 135deg, + rgba(79, 195, 247, 0.1) 0%, + rgba(79, 195, 247, 0.02) 100% + ); + border: 2px solid rgba(79, 195, 247, 0.2); +} + +.stat-card:nth-child(1) .stat-value { + color: #0277bd; +} + +.stat-card:nth-child(2) { + background: linear-gradient( + 135deg, + rgba(183, 0, 255, 0.1) 0%, + rgba(233, 30, 99, 0.02) 100% + ); + border: 2px solid rgba(189, 0, 236, 0.2); +} + +.stat-card:nth-child(2) .stat-value { + color: #ba01df; +} + +.stat-card:nth-child(3) { + background: linear-gradient( + 135deg, + rgba(255, 193, 7, 0.1) 0%, + rgba(255, 193, 7, 0.02) 100% + ); + border: 2px solid rgba(255, 193, 7, 0.3); +} + +.stat-card:nth-child(3) .stat-value { + color: #f57c00; +} + +.stat-card { + background: rgba(255, 255, 255, 0.95); + border-radius: 16px; + padding: 2.5rem 2rem; + text-align: center; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); +} + +.stat-card:hover { + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12); +} + +.stat-card:nth-child(1):hover { + border-color: rgba(79, 195, 247, 0.4); +} + +.stat-card:nth-child(2):hover { + border-color: rgba(233, 30, 99, 0.4); +} + +.stat-card:nth-child(3):hover { + border-color: rgba(255, 193, 7, 0.5); +} + +.stat-card .stat-value { + font-size: 3rem; + font-weight: 800; + display: block; + margin-bottom: 0.8rem; + line-height: 1; +} + +.stat-card .stat-label { + color: #546e7a; + font-size: 1rem; + text-transform: uppercase; + letter-spacing: 1px; + font-weight: 600; +} + +.subjects-section { + margin-bottom: 3rem; + position: relative; +} + +.subjects-section h2 { + color: #263238; + margin-bottom: 3rem; + font-size: 2.5rem; + font-weight: 700; + text-align: center; + position: relative; + display: inline-block; + width: 100%; +} + +.subjects-section h2::after { + content: ""; + position: absolute; + bottom: -10px; + left: 50%; + transform: translateX(-50%); + width: 120px; + height: 4px; + background: linear-gradient(90deg, #4fc3f7, #ffc107); + border-radius: 2px; +} + +.subjects-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(420px, 1fr)); + gap: 3rem; + max-width: 1400px; + margin: 0 auto; +} + +.subject-widget { + background: rgba(255, 255, 255, 0.98); + border: 2px solid #f0f0f0; + border-radius: 20px; + padding: 2.5rem; + box-shadow: 0 6px 25px rgba(0, 0, 0, 0.08); + position: relative; + backdrop-filter: blur(10px); +} + +.subject-widget:nth-child(3n + 1) { + background: linear-gradient( + 135deg, + rgba(79, 195, 247, 0.1) 0%, + rgba(79, 195, 247, 0.02) 100% + ); + border: 2px solid rgba(79, 195, 247, 0.2); +} + +.subject-widget:nth-child(3n + 2) { + background: linear-gradient( + 135deg, + rgba(183, 0, 255, 0.1) 0%, + rgba(233, 30, 99, 0.02) 100% + ); + border: 2px solid rgba(189, 0, 236, 0.2); +} + +.subject-widget:nth-child(3n + 3) { + background: linear-gradient( + 135deg, + rgba(255, 193, 7, 0.1) 0%, + rgba(255, 193, 7, 0.02) 100% + ); + border: 2px solid rgba(255, 193, 7, 0.3); +} + +.subject-widget:hover { + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.1); +} + +.subject-widget:nth-child(3n + 1):hover { + border-color: rgba(79, 195, 247, 0.4); +} + +.subject-widget:nth-child(3n + 2):hover { + border-color: rgba(233, 30, 99, 0.4); +} + +.subject-widget:nth-child(3n + 3):hover { + border-color: rgba(255, 193, 7, 0.5); +} + +.widget-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2.5rem; + padding-bottom: 1.5rem; + border-bottom: 3px solid #f0f3f7; +} + +.widget-header h3 { + margin: 0; + color: #263238; + font-size: 1.6rem; + font-weight: 800; + letter-spacing: -0.5px; +} + +.average-badge { + color: white; + padding: 1.2rem 1.8rem; + border-radius: 16px; + font-weight: 900; + font-size: 2rem; + min-width: 90px; + text-align: center; + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25); + letter-spacing: -0.5px; +} + +.average-badge:hover { + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.35); +} + +.widget-stats { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 2rem; +} + +.stat { + text-align: center; + padding: 2rem 1.5rem; + background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); + border-radius: 16px; + border: 2px solid #e9ecef; + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.05); +} + +.stat:hover { + background: linear-gradient(135deg, #ffffff 0%, #fdfdfd 100%); + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12); + border-color: #d0d7de; +} + +.stat-label { + display: block; + color: #546e7a; + font-size: 0.9rem; + text-transform: uppercase; + letter-spacing: 1px; + margin-bottom: 1rem; + font-weight: 700; +} + +.stat-value { + display: block; + font-size: 2.2rem; + font-weight: 900; + color: #263238; + line-height: 1; + letter-spacing: -1px; +} + +/* Student Overview Styles */ +.student-overview { + max-width: 1400px; + margin: 0 auto; + padding: 0 1rem; +} + +.table-container { + background: rgba(255, 255, 255, 0.98); + border-radius: 20px; + padding: 2rem; + box-shadow: 0 6px 25px rgba(0, 0, 0, 0.08); + overflow-x: auto; + backdrop-filter: blur(10px); + border: 2px solid rgba(79, 195, 247, 0.1); +} + +.action-cell, +.view-details-btn { + display: none; +} + +.students-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)); + gap: 2.5rem; + max-width: 1400px; + margin: 0 auto; +} + +.student-card { + background: rgba(255, 255, 255, 0.98); + border: 2px solid #f0f0f0; + border-radius: 20px; + padding: 2rem; + box-shadow: 0 6px 25px rgba(0, 0, 0, 0.08); + transition: all 0.3s ease; + position: relative; + backdrop-filter: blur(10px); +} + +.student-card:nth-child(4n + 1) { + background: linear-gradient( + 135deg, + rgba(79, 195, 247, 0.1) 0%, + rgba(79, 195, 247, 0.02) 100% + ); + border: 2px solid rgba(79, 195, 247, 0.2); +} + +.student-card:nth-child(4n + 2) { + background: linear-gradient( + 135deg, + rgba(183, 0, 255, 0.1) 0%, + rgba(233, 30, 99, 0.02) 100% + ); + border: 2px solid rgba(189, 0, 236, 0.2); +} + +.student-card:nth-child(4n + 3) { + background: linear-gradient( + 135deg, + rgba(255, 193, 7, 0.1) 0%, + rgba(255, 193, 7, 0.02) 100% + ); + border: 2px solid rgba(255, 193, 7, 0.3); +} + +.student-card:nth-child(4n + 4) { + background: linear-gradient( + 135deg, + rgba(76, 175, 80, 0.1) 0%, + rgba(76, 175, 80, 0.02) 100% + ); + border: 2px solid rgba(76, 175, 80, 0.3); +} + +.student-card:hover { + transform: translateY(-4px); + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.1); +} + +.student-card:nth-child(4n + 1):hover { + border-color: rgba(79, 195, 247, 0.4); +} + +.student-card:nth-child(4n + 2):hover { + border-color: rgba(233, 30, 99, 0.4); +} + +.student-card:nth-child(4n + 3):hover { + border-color: rgba(255, 193, 7, 0.5); +} + +.student-card:nth-child(4n + 4):hover { + border-color: rgba(76, 175, 80, 0.5); +} + +.student-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; + padding-bottom: 1rem; + border-bottom: 2px solid #f0f3f7; +} + +.student-info { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.student-id { + color: #546e7a; + font-size: 0.9rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 1px; +} + +.student-name { + margin: 0; + color: #263238; + font-size: 1.4rem; + font-weight: 800; + letter-spacing: -0.5px; +} + +.student-averages h4 { + color: #546e7a; + margin: 0 0 1rem 0; + font-size: 1rem; + text-transform: uppercase; + letter-spacing: 1px; + font-weight: 700; +} + +.averages-grid { + display: grid; + gap: 0.8rem; +} + +.subject-average { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.8rem 1rem; + background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); + border-radius: 12px; + border: 1px solid #e9ecef; + transition: all 0.3s ease; +} + +.subject-average:hover { + transform: translateX(4px); + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); +} + +.subject-name { + font-weight: 600; + color: #263238; +} + +.average-badge-small { + color: white; + padding: 0.4rem 0.8rem; + border-radius: 8px; + font-weight: 700; + font-size: 0.9rem; + min-width: 50px; + text-align: center; + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2); +} + +/* Student Page Styles */ +.student-page { + max-width: 1400px; + margin: 0 auto; + padding: 0 1rem; +} + +.coming-soon { + display: flex; + justify-content: center; + align-items: center; + min-height: 50vh; + padding: 2rem; +} + +.coming-soon-content { + text-align: center; + max-width: 600px; + background: rgba(255, 255, 255, 0.95); + border-radius: 20px; + padding: 3rem; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1); + border: 2px solid rgba(79, 195, 247, 0.2); +} + +.coming-soon-content h2 { + color: #4fc3f7; + margin-bottom: 1.5rem; + font-size: 2.5rem; + font-weight: 700; +} + +.coming-soon-content p { + color: #546e7a; + margin-bottom: 1rem; + font-size: 1.1rem; + line-height: 1.6; +} + +.action-buttons { + margin-top: 2rem; + display: flex; + gap: 1rem; + justify-content: center; + flex-wrap: wrap; +} + +.error-state, +.empty-state { + text-align: center; + padding: 4rem 2rem; + color: #546e7a; + background: rgba(255, 255, 255, 0.9); + border-radius: 16px; + margin: 2rem auto; + max-width: 600px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); +} + +.error-state h2, +.empty-state h2 { + color: #ff6b35; + margin-bottom: 1rem; + font-size: 1.8rem; +} + +.loading { + text-align: center; + padding: 4rem 2rem; + font-size: 1.4rem; + color: #546e7a; + background: rgba(255, 255, 255, 0.9); + border-radius: 16px; + margin: 2rem auto; + max-width: 400px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); +} + +.error-page { + text-align: center; + padding: 2rem; + color: #ff6b35; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .main-content { + padding: 2rem 1rem; + } + + .dashboard, + .student-overview, + .student-page { + padding: 0 0.5rem; + } + + .title-text-branded { + font-size: 2.8rem; + } + + .dashboard-subtitle { + font-size: 1rem; + } + + .dashboard-stats { + grid-template-columns: 1fr; + gap: 2rem; + margin-bottom: 3rem; + } + + .subjects-grid, + .students-grid { + grid-template-columns: 1fr; + gap: 2rem; + } + + .stat-card { + padding: 2rem 1.5rem; + } + + .subject-widget, + .student-card { + padding: 1.5rem; + } + + .subjects-section h2 { + font-size: 2rem; + } + + .widget-stats { + gap: 1rem; + } + + .stat { + padding: 1.2rem 0.8rem; + } + + .average-badge { + font-size: 1.5rem; + padding: 0.8rem 1.2rem; + } + + .breadcrumb { + flex-direction: column; + gap: 0.5rem; + } + + .breadcrumb-separator { + display: none; + } + + .action-buttons { + flex-direction: column; + align-items: center; + } + + .stat-action-text span { + font-size: 0.75rem; + padding: 0.5rem 1rem; + } +} + +@media (max-width: 480px) { + .title-text-branded { + font-size: 2.2rem; + } + + .dashboard-subtitle { + font-size: 0.9rem; + } + + .stat-card .stat-value { + font-size: 2.5rem; + } + + .subjects-grid, + .students-grid { + grid-template-columns: 1fr; + } + + .dashboard-stats { + grid-template-columns: 1fr; + } + + .average-badge { + font-size: 1.3rem; + padding: 0.7rem 1rem; + } + + .coming-soon-content { + padding: 2rem 1.5rem; + } + + .coming-soon-content h2 { + font-size: 2rem; + } + + .stat-action-text span { + font-size: 0.7rem; + padding: 0.4rem 0.8rem; + } +} diff --git a/ui/src/App.tsx b/ui/src/App.tsx new file mode 100644 index 000000000..a0bb2c73d --- /dev/null +++ b/ui/src/App.tsx @@ -0,0 +1,9 @@ +import { RouterProvider } from 'react-router-dom'; +import Router from './Router.tsx'; +import './App.css'; + +function App() { + return ; +} + +export default App; diff --git a/ui/src/Router.tsx b/ui/src/Router.tsx new file mode 100644 index 000000000..93961058d --- /dev/null +++ b/ui/src/Router.tsx @@ -0,0 +1,30 @@ +import { createBrowserRouter, createRoutesFromElements, Route } from 'react-router-dom'; +import Layout from './components/layout/Layout.tsx'; +import Dashboard from './components/dashboard/Dashboard'; +import StudentOverviewPage from './components/students/StudentOverviewPage'; +import Error from './components/common/Error'; +import StudentPage from './components/students/StudentPage.tsx'; + +const Router = createBrowserRouter( + createRoutesFromElements( + }> + } + errorElement={} + /> + } + errorElement={} + /> + } + errorElement={} + /> + + ) +); + +export default Router; \ No newline at end of file diff --git a/ui/src/assets/react.svg b/ui/src/assets/react.svg new file mode 100644 index 000000000..6c87de9bb --- /dev/null +++ b/ui/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/src/components/common/Error.tsx b/ui/src/components/common/Error.tsx new file mode 100644 index 000000000..2c069058e --- /dev/null +++ b/ui/src/components/common/Error.tsx @@ -0,0 +1,15 @@ +import { useRouteError } from 'react-router-dom'; + +export default function Error() { + const error = useRouteError(); + + return ( +
+

Oops!

+

Sorry, an unexpected error has occurred.

+

+ {(error as Error)?.message || (error as { statusText?: string })?.statusText} +

+
+ ); +} \ No newline at end of file diff --git a/ui/src/components/dashboard/Dashboard.tsx b/ui/src/components/dashboard/Dashboard.tsx new file mode 100644 index 000000000..6e5df3fdd --- /dev/null +++ b/ui/src/components/dashboard/Dashboard.tsx @@ -0,0 +1,136 @@ +import { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { apiService } from '../../services/api'; +import type { DashboardSummary, SubjectAverage } from '../../models/Dashboard'; +import { getAverageColor } from '../../utils/averageColors'; +import Error from "../common/Error.tsx" + +interface SubjectWidgetProps { + subjectAverage: SubjectAverage; +} + +function SubjectWidget({ subjectAverage }: SubjectWidgetProps) { + const averageColor = getAverageColor(subjectAverage.average); + + return ( +
+
+

{subjectAverage.subjectName}

+
+ {subjectAverage.average.toFixed(1)} +
+
+
+
+ Leerlingen + {subjectAverage.studentCount} +
+
+ Oefeningen + {subjectAverage.totalExercises} +
+
+
+ ); +} + +interface DashboardStatsProps { + dashboardData: DashboardSummary; +} + +function DashboardStats({ dashboardData }: DashboardStatsProps) { + const navigate = useNavigate(); + const totalStudents = Math.max(...dashboardData.subjectAverages.map(sa => sa.studentCount)); + const totalExercises = dashboardData.subjectAverages.reduce((sum, sa) => sum + sa.totalExercises, 0); + const overallAverage = dashboardData.subjectAverages.reduce((sum, sa) => sum + sa.average, 0) / dashboardData.subjectAverages.length; + + const handleStudentsClick = () => { + navigate('/students'); + }; + + return ( +
+
+
{totalStudents}
+
Actieve Leerlingen
+
+ 👥 Bekijk alle leerlingen +
+
+
+
{totalExercises}
+
Totaal Oefeningen
+
+
+
{overallAverage.toFixed(1)}
+
Gemiddeld Cijfer
+
+
+ ); +} + +export default function Dashboard() { + const [dashboardData, setDashboardData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchDashboardData = async () => { + setLoading(true); + setError(null); + + const response = await apiService.getDashboardGemiddelden(); + + if (response.error) { + setError(response.error); + } else { + setDashboardData(response.data || null); + } + + setLoading(false); + }; + + fetchDashboardData(); + }, []); + + if (loading) { + return
Dashboard wordt geladen...
; + } + + if (error) { + return ; + } + + if (!dashboardData) { + return ( +
+

Geen gegevens beschikbaar

+

Er zijn geen leerlinggegevens gevonden voor het dashboard.

+
+ ); + } + + return ( +
+
+
+ Leraar Dashboard +
+
+ + + +
+

Vakgemiddelden

+
+ {dashboardData.subjectAverages.map((subjectAverage) => ( + + ))} +
+
+
+ ); +} \ No newline at end of file diff --git a/ui/src/components/layout/Layout.tsx b/ui/src/components/layout/Layout.tsx new file mode 100644 index 000000000..7384e2dc4 --- /dev/null +++ b/ui/src/components/layout/Layout.tsx @@ -0,0 +1,11 @@ +import { Outlet } from 'react-router-dom'; + +export default function Layout() { + return ( +
+
+ +
+
+ ); +} \ No newline at end of file diff --git a/ui/src/components/students/StudentOverviewPage.tsx b/ui/src/components/students/StudentOverviewPage.tsx new file mode 100644 index 000000000..4d5c88676 --- /dev/null +++ b/ui/src/components/students/StudentOverviewPage.tsx @@ -0,0 +1,208 @@ +import { useState, useEffect, useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { MaterialReactTable, type MRT_ColumnDef, type MRT_Row } from 'material-react-table'; +import { apiService } from '../../services/api'; +import { Subject } from '../../models/enums/Subject'; +import type { StudentListItem } from '../../models/Student'; +import Error from "../common/Error.tsx"; +import { getAverageColor } from '../../utils/averageColors'; + +export default function StudentOverviewPage() { + const [students, setStudents] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const navigate = useNavigate(); + + useEffect(() => { + const fetchStudents = async () => { + setLoading(true); + setError(null); + + const response = await apiService.getStudents(); + + if (response.error) { + setError(response.error); + } else { + setStudents(response.data || []); + } + + setLoading(false); + }; + + fetchStudents(); + }, []); + + const handleStudentClick = (studentId: number) => { + navigate(`/students/${studentId}`); + }; + + const handleBackToDashboard = () => { + navigate('/'); + }; + + const getSubjectDisplayName = (subject: Subject): string => { + switch (subject) { + case Subject.BegrijpendLezen: + return "Begrijpend Lezen"; + case Subject.Rekenen: + return "Rekenen"; + case Subject.Spelling: + return "Spelling"; + default: + return `Vak ${subject}`; + } + }; + + const columns = useMemo[]>( + () => { + const allSubjects = [Subject.BegrijpendLezen, Subject.Rekenen, Subject.Spelling]; + + return [ + { + accessorKey: 'userId', + header: 'ID', + size: 80, + enableColumnFilter: false, + Cell: ({ cell }) => `#${cell.getValue()}`, + }, + { + accessorKey: 'name', + header: 'Naam', + size: 200, + enableColumnFilter: true, + filterVariant: 'text', + }, + ...allSubjects.map(subject => ({ + id: `subject-${subject}`, + header: getSubjectDisplayName(subject), + size: 120, + enableColumnFilter: false, + enableSorting: true, + accessorFn: (row: StudentListItem) => { + const subjectAverage = row.subjectAverages.find(sa => sa.subject === subject); + return subjectAverage ? subjectAverage.average : null; + }, + Cell: ({ row }: { row: MRT_Row }) => { + const subjectAverage = row.original.subjectAverages.find(sa => sa.subject === subject); + const average = subjectAverage ? subjectAverage.average : null; + return average !== null && typeof average === 'number' ? ( +
+ {average.toFixed(1)} +
+ ) : ( + - + ); + }, + })) + ]; + }, + [] + ); + + if (loading) { + return
Leerlinggegevens worden geladen...
; + } + + if (error) { + return ; + } + + if (!students.length) { + return ( +
+

Geen leerlingen gevonden

+

Er zijn geen leerlinggegevens beschikbaar.

+ +
+ ); + } + + return ( +
+
+
+ +
+
+

Leerling Overzicht

+
+
+ +
+ ({ + onClick: () => handleStudentClick(row.original.userId), + sx: { + cursor: 'pointer', + '&:hover': { + backgroundColor: 'rgba(79, 195, 247, 0.08)', + boxShadow: '0 6px 20px rgba(0, 0, 0, 0.1)', + borderLeft: '4px solid #4fc3f7', + }, + }, + })} + muiTableProps={{ + sx: { + '& .MuiTableHead-root': { + backgroundColor: 'transparent', + }, + '& .MuiTableCell-head': { + backgroundColor: '#4fc3f7', + color: 'white', + fontWeight: 700, + textTransform: 'uppercase', + letterSpacing: '1px', + fontSize: '0.9rem', + }, + '& .MuiTableCell-root': { + borderColor: '#f0f3f7', + }, + }, + }} + muiTopToolbarProps={{ + sx: { + backgroundColor: 'transparent', + '& .MuiToolbar-root': { + padding: '1rem 0', + }, + }, + }} + muiBottomToolbarProps={{ + sx: { + backgroundColor: 'transparent', + }, + }} + /> +
+
+ ); +} \ No newline at end of file diff --git a/ui/src/components/students/StudentPage.tsx b/ui/src/components/students/StudentPage.tsx new file mode 100644 index 000000000..b47a3708d --- /dev/null +++ b/ui/src/components/students/StudentPage.tsx @@ -0,0 +1,241 @@ +import { useState, useEffect, useMemo } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { MaterialReactTable, type MRT_ColumnDef } from 'material-react-table'; +import { apiService } from '../../services/api'; +import type { StudentData, StudentExerciseResult } from '../../models/Student'; +import { getAverageColor } from '../../utils/averageColors'; +import Error from "../common/Error.tsx"; + +export default function StudentPage() { + const { studentId } = useParams<{ studentId: string }>(); + const navigate = useNavigate(); + const [studentData, setStudentData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const tableData = useMemo(() => { + return studentData?.exercises || []; + }, [studentData?.exercises]); + + useEffect(() => { + const fetchStudentData = async () => { + if (!studentId) { + setError('Geen leerling ID opgegeven'); + setLoading(false); + return; + } + + setLoading(true); + setError(null); + + const response = await apiService.getStudentData(parseInt(studentId)); + + if (response.error) { + setError(response.error); + } else { + setStudentData(response.data || null); + } + + setLoading(false); + }; + + fetchStudentData(); + }, [studentId]); + + const columns = useMemo[]>( + () => [ + { + accessorKey: 'subjectName', + header: 'Vak', + size: 120, + enableColumnFilter: true, + filterVariant: 'select', + filterSelectOptions: studentData ? Array.from(new Set(studentData.exercises.map(ex => ex.subjectName))) : [], + }, + { + accessorKey: 'exerciseName', + header: 'Oefening', + size: 200, + enableColumnFilter: false, + }, + { + accessorKey: 'correct', + header: 'Resultaat', + size: 100, + enableColumnFilter: false, + Cell: ({ cell }) => ( + + {cell.getValue() ? 'Goed' : 'Fout'} + + ), + }, + { + accessorKey: 'difficulty', + header: 'Moeilijkheid', + size: 120, + enableColumnFilter: false, + }, + { + accessorKey: 'progress', + header: 'Voortgang', + size: 100, + enableColumnFilter: false, + Cell: ({ cell }) => `${cell.getValue()}%`, + }, + { + accessorKey: 'submitDateTime', + header: 'Datum', + size: 140, + enableColumnFilter: false, + Cell: ({ cell }) => { + const date = new Date(cell.getValue() as string); + return date.toLocaleDateString('nl-NL', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + hour: '2-digit', + minute: '2-digit' + }); + }, + }, + ], + [studentData] + ); + + const handleBackToStudents = () => { + navigate('/students'); + }; + + if (loading) { + return
Leerlinggegevens worden geladen...
; + } + + if (error) { + return ; + } + + if (!studentData) { + return ( +
+

Geen gegevens beschikbaar

+

Er zijn geen gegevens gevonden voor deze leerling.

+
+ +
+
+ ); + } + + return ( +
+
+
+ +
+
+

{studentData.name}

+
+
+ +
+
+ {studentData.subjectAverages.map((subjectAverage) => { + const averageColor = getAverageColor(subjectAverage.average); + const subjectName = studentData.exercises.find(ex => ex.subject === subjectAverage.subject)?.subjectName || `Subject ${subjectAverage.subject}`; + + return ( +
+

+ {subjectName} +

+
+ {subjectAverage.average.toFixed(1)} +
+
+ ); + })} +
+
+ +
+

Oefeningen Overzicht

+
+ +
+ +
+
+ ); +} \ No newline at end of file diff --git a/ui/src/config.json b/ui/src/config.json new file mode 100644 index 000000000..5cfbd0048 --- /dev/null +++ b/ui/src/config.json @@ -0,0 +1,3 @@ +{ + "API_BASE_URL": "http://localhost:5050/api" +} diff --git a/ui/src/main.tsx b/ui/src/main.tsx new file mode 100644 index 000000000..de5a20b54 --- /dev/null +++ b/ui/src/main.tsx @@ -0,0 +1,6 @@ +import ReactDOM from "react-dom/client"; +import App from "./App"; + +const root = document.getElementById("root"); + +ReactDOM.createRoot(root!).render(); \ No newline at end of file diff --git a/ui/src/models/ApiResponse.ts b/ui/src/models/ApiResponse.ts new file mode 100644 index 000000000..6c8bf671d --- /dev/null +++ b/ui/src/models/ApiResponse.ts @@ -0,0 +1,4 @@ +export interface ApiResponse { + data?: T; + error?: string; +} \ No newline at end of file diff --git a/ui/src/models/Dashboard.ts b/ui/src/models/Dashboard.ts new file mode 100644 index 000000000..73cd8a5dc --- /dev/null +++ b/ui/src/models/Dashboard.ts @@ -0,0 +1,11 @@ +export interface SubjectAverage { + subject: number; + subjectName: string; + average: number; + totalExercises: number; + studentCount: number; +} + +export interface DashboardSummary { + subjectAverages: SubjectAverage[]; +} \ No newline at end of file diff --git a/ui/src/models/Student.ts b/ui/src/models/Student.ts new file mode 100644 index 000000000..c2eaa74df --- /dev/null +++ b/ui/src/models/Student.ts @@ -0,0 +1,30 @@ +import type { Subject } from './enums/Subject'; + +export interface StudentSubjectAverage { + subject: Subject; + average: number; +} + +export interface StudentListItem { + userId: number; + name: string; + subjectAverages: StudentSubjectAverage[]; +} + +export interface StudentData { + userId: number; + name: string; + subjectAverages: StudentSubjectAverage[]; + exercises: StudentExerciseResult[]; +} + +export interface StudentExerciseResult { + subject: Subject; + subjectName: string; + exerciseId: number; + exerciseName: string; + correct: boolean; + difficulty: number; + progress: number; + submitDateTime: string; +} \ No newline at end of file diff --git a/ui/src/models/enums/Subject.ts b/ui/src/models/enums/Subject.ts new file mode 100644 index 000000000..8d9e7a4d5 --- /dev/null +++ b/ui/src/models/enums/Subject.ts @@ -0,0 +1,7 @@ +export const Subject = { + BegrijpendLezen: 0, + Rekenen: 1, + Spelling: 2 +} as const; + +export type Subject = typeof Subject[keyof typeof Subject]; \ No newline at end of file diff --git a/ui/src/services/api.ts b/ui/src/services/api.ts new file mode 100644 index 000000000..717ecf6fe --- /dev/null +++ b/ui/src/services/api.ts @@ -0,0 +1,38 @@ +import type { ApiResponse } from '../models/ApiResponse'; +import type { DashboardSummary } from '../models/Dashboard'; +import type { StudentListItem, StudentData } from '../models/Student'; +import config from '../config.json'; + +const API_BASE_URL = config.API_BASE_URL; + +class ApiService { + private async request(endpoint: string): Promise> { + try { + const response = await fetch(`${API_BASE_URL}${endpoint}`); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + return { data }; + } catch (error) { + console.error('API request failed:', error); + return { error: error instanceof Error ? error.message : 'Unknown error' }; + } + } + + async getDashboardGemiddelden(): Promise> { + return this.request('/dashboard/gemiddelden'); + } + + async getStudents(): Promise> { + return this.request('/student'); + } + + async getStudentData(studentId: number): Promise> { + return this.request(`/student/${studentId}`); + } +} + +export const apiService = new ApiService(); \ No newline at end of file diff --git a/ui/src/utils/averageColors.ts b/ui/src/utils/averageColors.ts new file mode 100644 index 000000000..f7e0275e9 --- /dev/null +++ b/ui/src/utils/averageColors.ts @@ -0,0 +1,5 @@ +export const getAverageColor = (average: number): string => { + if (average >= 7) return '#4caf50'; + if (average >= 5.5) return '#ffc107'; + return '#FF0000'; +}; \ No newline at end of file diff --git a/ui/src/vite-env.d.ts b/ui/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/ui/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/ui/tsconfig.app.json b/ui/tsconfig.app.json new file mode 100644 index 000000000..227a6c672 --- /dev/null +++ b/ui/tsconfig.app.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/ui/tsconfig.json b/ui/tsconfig.json new file mode 100644 index 000000000..1ffef600d --- /dev/null +++ b/ui/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/ui/tsconfig.node.json b/ui/tsconfig.node.json new file mode 100644 index 000000000..f85a39906 --- /dev/null +++ b/ui/tsconfig.node.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/ui/vite.config.ts b/ui/vite.config.ts new file mode 100644 index 000000000..8b0f57b91 --- /dev/null +++ b/ui/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +})