diff --git a/HwProj.Common/HwProj.Models/CoursesService/ViewModels/CriterionViewModel.cs b/HwProj.Common/HwProj.Models/CoursesService/ViewModels/CriterionViewModel.cs index 7f07a540a..c8005e5e3 100644 --- a/HwProj.Common/HwProj.Models/CoursesService/ViewModels/CriterionViewModel.cs +++ b/HwProj.Common/HwProj.Models/CoursesService/ViewModels/CriterionViewModel.cs @@ -4,7 +4,8 @@ namespace HwProj.Models.CoursesService.ViewModels { public enum CriterionType { - Free = 0 + Free = 0, + Deadline = 1 } public class CriterionViewModel @@ -13,5 +14,6 @@ public class CriterionViewModel public CriterionType Type { get; set; } [Required] public string Name { get; set; } = null!; [Range(0, int.MaxValue)] public int MaxPoints { get; set; } + public string? Arguments { get; set; } } } diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Domains/MappingExtensions.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Domains/MappingExtensions.cs index 152bf0410..48ce7a8ea 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Domains/MappingExtensions.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Domains/MappingExtensions.cs @@ -64,7 +64,8 @@ public static HomeworkTaskViewModel ToHomeworkTaskViewModel(this HomeworkTask ta Id = c.Id, Type = (CriterionType)c.Type, Name = c.Name, - MaxPoints = c.MaxPoints + MaxPoints = c.MaxPoints, + Arguments = c.Arguments }) .ToList(), }; @@ -120,7 +121,8 @@ public static CoursePreview ToCoursePreview(this Course course) Id = criterion.Id, Type = (Models.CriterionType)criterion.Type, Name = criterion.Name, - MaxPoints = criterion.MaxPoints + MaxPoints = criterion.MaxPoints, + Arguments = criterion.Arguments }; public static HomeworkTask ToHomeworkTask(this PostTaskViewModel postTaskViewModel) diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20260427093000_CriterionArguments.Designer.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20260427093000_CriterionArguments.Designer.cs new file mode 100644 index 000000000..6865af99a --- /dev/null +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20260427093000_CriterionArguments.Designer.cs @@ -0,0 +1,16 @@ +// +using HwProj.CoursesService.API.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace HwProj.CoursesService.API.Migrations +{ + [DbContext(typeof(CourseContext))] + [Migration("20260427093000_CriterionArguments")] + partial class CriterionArguments + { + } +} diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20260427093000_CriterionArguments.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20260427093000_CriterionArguments.cs new file mode 100644 index 000000000..fac8c4c21 --- /dev/null +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/20260427093000_CriterionArguments.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace HwProj.CoursesService.API.Migrations +{ + public partial class CriterionArguments : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Arguments", + table: "Criteria", + type: "nvarchar(max)", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Arguments", + table: "Criteria"); + } + } +} diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/CourseContextModelSnapshot.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/CourseContextModelSnapshot.cs index 4b3bb83b5..199917cf8 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/CourseContextModelSnapshot.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Migrations/CourseContextModelSnapshot.cs @@ -128,6 +128,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("MaxPoints") .HasColumnType("int"); + b.Property("Arguments") + .HasColumnType("nvarchar(max)"); + b.Property("Name") .HasColumnType("nvarchar(max)"); diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Models/Criterion.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Models/Criterion.cs index debc0b99b..1c65c1a2c 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Models/Criterion.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Models/Criterion.cs @@ -5,7 +5,8 @@ namespace HwProj.CoursesService.API.Models { public enum CriterionType { - Free = 0 + Free = 0, + Deadline = 1 } public class Criterion : IEntity @@ -17,5 +18,6 @@ public class Criterion : IEntity public CriterionType Type { get; set; } public string Name { get; set; } public int MaxPoints { get; set; } + public string? Arguments { get; set; } } } diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Repositories/TasksRepository.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Repositories/TasksRepository.cs index 769a23be6..b8405e9ee 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Repositories/TasksRepository.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Repositories/TasksRepository.cs @@ -89,10 +89,11 @@ public bool Equals(Criterion? x, Criterion? y) x.TaskId == y.TaskId && x.Type == y.Type && x.Name == y.Name && - x.MaxPoints == y.MaxPoints; + x.MaxPoints == y.MaxPoints && + x.Arguments == y.Arguments; } - public int GetHashCode(Criterion obj) => HashCode.Combine(obj.Id, obj.TaskId, obj.Name); + public int GetHashCode(Criterion obj) => HashCode.Combine(obj.Id, obj.TaskId, obj.Name, obj.Arguments); } } } diff --git a/HwProj.CoursesService/HwProj.CoursesService.API/Startup.cs b/HwProj.CoursesService/HwProj.CoursesService.API/Startup.cs index 62461edc6..94eeae315 100644 --- a/HwProj.CoursesService/HwProj.CoursesService.API/Startup.cs +++ b/HwProj.CoursesService/HwProj.CoursesService.API/Startup.cs @@ -76,6 +76,17 @@ public void Configure(IApplicationBuilder app, IHostEnvironment env, CourseConte app.UseEndpoints(x => x.MapControllers()); app.UseDatabase(env, context); + EnsureCriterionArgumentsColumn(context); + } + + private static void EnsureCriterionArgumentsColumn(CourseContext context) + { + context.Database.ExecuteSqlRaw(@" +IF OBJECT_ID(N'[Criteria]', N'U') IS NOT NULL + AND COL_LENGTH(N'[Criteria]', N'Arguments') IS NULL +BEGIN + ALTER TABLE [Criteria] ADD [Arguments] nvarchar(max) NULL; +END"); } } } diff --git a/hwproj.front/package-lock.json b/hwproj.front/package-lock.json index 1c7b32e30..b6f4352cf 100644 --- a/hwproj.front/package-lock.json +++ b/hwproj.front/package-lock.json @@ -4977,6 +4977,7 @@ "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.5.16.tgz", "integrity": "sha512-p3DqQi+8QRL5k7jXhXmJZLsE/GqHqyY6PcoA1oNTJr0try48uhTGUOYkgzmqtDaa/qPFO5LP+xCPzZXckGtquQ==", + "dev": true, "license": "MIT", "dependencies": { "@storybook/api": "6.5.16", @@ -5004,12 +5005,14 @@ "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true, "license": "MIT" }, "node_modules/@storybook/api": { "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/api/-/api-6.5.16.tgz", "integrity": "sha512-HOsuT8iomqeTMQJrRx5U8nsC7lJTwRr1DhdD0SzlqL4c80S/7uuCy4IZvOt4sYQjOzW5fOo/kamcoBXyLproTA==", + "dev": true, "license": "MIT", "dependencies": { "@storybook/channels": "6.5.16", @@ -5043,6 +5046,7 @@ "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true, "license": "MIT" }, "node_modules/@storybook/builder-webpack4": { @@ -5441,6 +5445,7 @@ "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-6.5.16.tgz", "integrity": "sha512-VylzaWQZaMozEwZPJdyJoz+0jpDa8GRyaqu9TGG6QGv+KU5POoZaGLDkRE7TzWkyyP0KQLo80K99MssZCpgSeg==", + "dev": true, "license": "MIT", "dependencies": { "core-js": "^3.8.2", @@ -5500,6 +5505,7 @@ "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.5.16.tgz", "integrity": "sha512-pxcNaCj3ItDdicPTXTtmYJE3YC1SjxFrBmHcyrN+nffeNyiMuViJdOOZzzzucTUG0wcOOX8jaSyak+nnHg5H1Q==", + "dev": true, "license": "MIT", "dependencies": { "core-js": "^3.8.2", @@ -5514,6 +5520,7 @@ "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/components/-/components-6.5.16.tgz", "integrity": "sha512-LzBOFJKITLtDcbW9jXl0/PaG+4xAz25PK8JxPZpIALbmOpYWOAPcO6V9C2heX6e6NgWFMUxjplkULEk9RCQMNA==", + "dev": true, "license": "MIT", "dependencies": { "@storybook/client-logger": "6.5.16", @@ -5538,6 +5545,7 @@ "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true, "license": "MIT" }, "node_modules/@storybook/core": { @@ -5704,6 +5712,7 @@ "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.5.16.tgz", "integrity": "sha512-qMZQwmvzpH5F2uwNUllTPg6eZXr2OaYZQRRN8VZJiuorZzDNdAFmiVWMWdkThwmyLEJuQKXxqCL8lMj/7PPM+g==", + "dev": true, "license": "MIT", "dependencies": { "core-js": "^3.8.2" @@ -5804,6 +5813,7 @@ "version": "0.0.2--canary.4566f4d.1", "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.0.2--canary.4566f4d.1.tgz", "integrity": "sha512-9OVvMVh3t9znYZwb0Svf/YQoxX2gVOeQTGe2bses2yj+a3+OJnCrUF3/hGv6Em7KujtOdL2LL+JnG49oMVGFgQ==", + "dev": true, "license": "MIT", "dependencies": { "lodash": "^4.17.15" @@ -6276,6 +6286,7 @@ "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/router/-/router-6.5.16.tgz", "integrity": "sha512-ZgeP8a5YV/iuKbv31V8DjPxlV4AzorRiR8OuSt/KqaiYXNXlOoQDz/qMmiNcrshrfLpmkzoq7fSo4T8lWo2UwQ==", + "dev": true, "license": "MIT", "dependencies": { "@storybook/client-logger": "6.5.16", @@ -6297,12 +6308,14 @@ "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true, "license": "MIT" }, "node_modules/@storybook/semver": { "version": "7.3.2", "resolved": "https://registry.npmjs.org/@storybook/semver/-/semver-7.3.2.tgz", "integrity": "sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg==", + "dev": true, "license": "ISC", "dependencies": { "core-js": "^3.6.5", @@ -6319,6 +6332,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, "license": "MIT", "dependencies": { "locate-path": "^5.0.0", @@ -6332,6 +6346,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, "license": "MIT", "dependencies": { "p-locate": "^4.1.0" @@ -6344,6 +6359,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, "license": "MIT", "dependencies": { "p-try": "^2.0.0" @@ -6359,6 +6375,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, "license": "MIT", "dependencies": { "p-limit": "^2.2.0" @@ -6442,6 +6459,7 @@ "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-6.5.16.tgz", "integrity": "sha512-hNLctkjaYLRdk1+xYTkC1mg4dYz2wSv6SqbLpcKMbkPHTE0ElhddGPHQqB362md/w9emYXNkt1LSMD8Xk9JzVQ==", + "dev": true, "license": "MIT", "dependencies": { "@storybook/client-logger": "6.5.16", @@ -6462,6 +6480,7 @@ "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true, "license": "MIT" }, "node_modules/@storybook/ui": { @@ -7088,6 +7107,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/is-function/-/is-function-1.0.3.tgz", "integrity": "sha512-/CLhCW79JUeLKznI6mbVieGbl4QU5Hfn+6udw1YHZoofASjbQ5zaP5LzAUZYDpRYEjS4/P+DhEgyJ/PQmGGTWw==", + "dev": true, "license": "MIT" }, "node_modules/@types/isomorphic-fetch": { @@ -7456,6 +7476,7 @@ "version": "1.18.8", "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.18.8.tgz", "integrity": "sha512-G9eAoJRMLjcvN4I08wB5I7YofOb/kaJNd5uoCMX+LbKXTPCF+ZIHuqTnFaK9Jz1rgs035f9JUPUhNFtqgucy/A==", + "dev": true, "license": "MIT" }, "node_modules/@types/webpack-sources": { @@ -18027,6 +18048,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", + "dev": true, "license": "MIT" }, "node_modules/is-generator-function": { @@ -18149,6 +18171,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -18226,6 +18249,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -18697,13 +18721,6 @@ "node": ">= 10.13.0" } }, - "node_modules/jquery": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", - "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", - "license": "MIT", - "peer": true - }, "node_modules/js-sha3": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", @@ -19343,6 +19360,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/map-or-similar/-/map-or-similar-1.5.0.tgz", "integrity": "sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==", + "dev": true, "license": "MIT" }, "node_modules/map-visit": { @@ -20535,6 +20553,7 @@ "version": "1.11.3", "resolved": "https://registry.npmjs.org/memoizerific/-/memoizerific-1.11.3.tgz", "integrity": "sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==", + "dev": true, "license": "MIT", "dependencies": { "map-or-similar": "^1.5.0" @@ -22501,6 +22520,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -22703,6 +22723,7 @@ "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" @@ -22855,18 +22876,6 @@ "node": ">=6" } }, - "node_modules/popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", - "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", - "license": "MIT", - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, "node_modules/portable-fetch": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/portable-fetch/-/portable-fetch-3.0.0.tgz", @@ -27164,6 +27173,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -28040,6 +28050,7 @@ "version": "2.14.4", "resolved": "https://registry.npmjs.org/store2/-/store2-2.14.4.tgz", "integrity": "sha512-srTItn1GOvyvOycgxjAnPA63FZNwy0PTyUBFMHRM+hVFltAeoh0LmNBz9SZqUS9mMqGk8rfyWyXn3GH5ReJ8Zw==", + "dev": true, "license": "MIT" }, "node_modules/stream-browserify": { @@ -28673,6 +28684,7 @@ "version": "6.0.8", "resolved": "https://registry.npmjs.org/telejson/-/telejson-6.0.8.tgz", "integrity": "sha512-nerNXi+j8NK1QEfBHtZUN/aLdDcyupA//9kAboYLrtzZlPLpUfqbVGWb9zz91f/mIjRbAYhbgtnJHY8I1b5MBg==", + "dev": true, "license": "MIT", "dependencies": { "@types/is-function": "^1.0.0", @@ -28689,6 +28701,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -29210,6 +29223,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.10" @@ -30102,6 +30116,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, "license": "MIT" }, "node_modules/util.promisify": { diff --git a/hwproj.front/src/api/api.ts b/hwproj.front/src/api/api.ts index 4e0c6dfec..e42934f1c 100644 --- a/hwproj.front/src/api/api.ts +++ b/hwproj.front/src/api/api.ts @@ -655,7 +655,8 @@ export interface CreateHomeworkViewModel { * @enum {string} */ export enum CriterionType { - NUMBER_0 = 0 + NUMBER_0 = 0, + NUMBER_1 = 1 } /** * @@ -687,6 +688,12 @@ export interface CriterionViewModel { * @memberof CriterionViewModel */ maxPoints?: number; + /** + * + * @type {string} + * @memberof CriterionViewModel + */ + arguments?: string; } /** * diff --git a/hwproj.front/src/components/Homeworks/CourseHomeworkExperimental.tsx b/hwproj.front/src/components/Homeworks/CourseHomeworkExperimental.tsx index 04f6b8911..ea6486fbe 100644 --- a/hwproj.front/src/components/Homeworks/CourseHomeworkExperimental.tsx +++ b/hwproj.front/src/components/Homeworks/CourseHomeworkExperimental.tsx @@ -43,6 +43,8 @@ import {FilesHandler} from "@/components/Files/FilesHandler"; import GroupSelector from "../Common/GroupSelector"; import GroupIcon from '@mui/icons-material/Group'; import AssignmentIcon from '@mui/icons-material/Assignment'; +import ErrorsHandler from "@/components/Utils/ErrorsHandler"; +import {enqueueSnackbar} from "notistack"; export interface HomeworkAndFilesInfo { homework: HomeworkViewModel & { isModified?: boolean }, @@ -243,43 +245,54 @@ const CourseHomeworkEditor: FC<{ e.preventDefault() setHandleSubmitLoading(true) - const update = { - homeworkId: homeworkId, - title: title!, - description: description, - tags: tags, - hasDeadline: metadata.hasDeadline, - deadlineDate: metadata.deadlineDate, - isDeadlineStrict: metadata.isDeadlineStrict, - publicationDate: metadata.publicationDate, - groupId: selectedGroupId, - actionOptions: editOptions, - tasks: isNewHomework ? homework.tasks!.map(t => { - const task: PostTaskViewModel = { - ...t, - title: t.title!, - maxRating: t.maxRating! - } - return task - }) : [] - } + try { + const update = { + homeworkId: homeworkId, + title: title!, + description: description, + tags: tags, + hasDeadline: metadata.hasDeadline, + deadlineDate: metadata.deadlineDate, + isDeadlineStrict: metadata.isDeadlineStrict, + publicationDate: metadata.publicationDate, + groupId: selectedGroupId, + actionOptions: editOptions, + tasks: isNewHomework ? homework.tasks!.map(t => { + const task: PostTaskViewModel = { + ...t, + title: t.title!, + maxRating: t.maxRating!, + criteria: t.criteria || [] + } + return task + }) : [] + } - const updatedHomework = isNewHomework - ? await ApiSingleton.homeworksApi.homeworksAddHomework(courseId!, update) - : await ApiSingleton.homeworksApi.homeworksUpdateHomework(+homeworkId!, update) - - const updatedHomeworkId = updatedHomework.value!.id! - await handleFilesChange( - courseId, CourseUnitType.Homework, updatedHomeworkId, - props.onStartProcessing, - () => { - if (isNewHomework) props.onUpdate({ - homework: update, - isDeleted: true - }) // remove fake homework - props.onUpdate({homework: updatedHomework.value!, isSaved: true}); - }, - ); + const updatedHomework = isNewHomework + ? await ApiSingleton.homeworksApi.homeworksAddHomework(courseId!, update) + : await ApiSingleton.homeworksApi.homeworksUpdateHomework(+homeworkId!, update) + + const updatedHomeworkId = updatedHomework.value!.id! + await handleFilesChange( + courseId, CourseUnitType.Homework, updatedHomeworkId, + props.onStartProcessing, + () => { + if (isNewHomework) props.onUpdate({ + homework: update, + isDeleted: true + }) // remove fake homework + props.onUpdate({homework: updatedHomework.value!, isSaved: true}); + }, + ); + } catch (error) { + const errors = await ErrorsHandler.getErrorMessages(error as Response, "errors"); + enqueueSnackbar(errors[0] || "Не удалось сохранить задание", { + variant: "error", + autoHideDuration: 4000, + }); + } finally { + setHandleSubmitLoading(false) + } } const isDisabled = hasErrors || !isLoaded || taskHasErrors @@ -569,4 +582,4 @@ const CourseHomeworkExperimental: FC<{ } } -export default CourseHomeworkExperimental; \ No newline at end of file +export default CourseHomeworkExperimental; diff --git a/hwproj.front/src/components/Solutions/TaskSolutionComponent.tsx b/hwproj.front/src/components/Solutions/TaskSolutionComponent.tsx index b5276a530..6fb326635 100644 --- a/hwproj.front/src/components/Solutions/TaskSolutionComponent.tsx +++ b/hwproj.front/src/components/Solutions/TaskSolutionComponent.tsx @@ -49,6 +49,8 @@ import {UserAvatar} from "@/components/Common/UserAvatar"; type TaskWithCriteria = HomeworkTaskViewModel & {}; +const CriterionTypeDeadline = 1; + type CriterionRating = { criterionId: number; name: string; @@ -136,9 +138,23 @@ const TaskSolutionComponent: FC = (props) => { const initialDraft = loadCriteriaDraft(); + const getDeadlineCriterionValue = (criterion: { arguments?: string; maxPoints?: number }) => { + if (!props.solution?.publicationDate || !criterion.arguments) return Number.NaN; + + const solutionDate = new Date(props.solution.publicationDate).getTime(); + const deadlineDate = new Date(criterion.arguments).getTime(); + + if (Number.isNaN(solutionDate) || Number.isNaN(deadlineDate)) return Number.NaN; + + return solutionDate <= deadlineDate ? 0 : -(criterion.maxPoints ?? 0); + }; + const [criterionRatings, setCriterionRatings] = useState(() => (taskWithCriteria.criteria ?? []).map(c => { const id = c.id!; + const deadlineValue = c.type === CriterionTypeDeadline + ? getDeadlineCriterionValue(c) + : Number.NaN; const draftValue = initialDraft?.criteria ?.find(x => x.criterionId === id)?.value; @@ -146,7 +162,7 @@ const TaskSolutionComponent: FC = (props) => { criterionId: id, name: c.name ?? "", maxPoints: c.maxPoints ?? 0, - value: draftValue || NaN, + value: Number.isFinite(deadlineValue) ? deadlineValue : (draftValue ?? NaN), comment: "", }; }) @@ -171,6 +187,9 @@ const TaskSolutionComponent: FC = (props) => { setCriterionRatings( (taskWithCriteria.criteria ?? []).map(c => { const id = c.id ?? 0; + const deadlineValue = c.type === CriterionTypeDeadline + ? getDeadlineCriterionValue(c) + : Number.NaN; const draftValue = draft?.criteria ?.find(x => x.criterionId === id)?.value; @@ -178,7 +197,7 @@ const TaskSolutionComponent: FC = (props) => { criterionId: id, name: c.name ?? "", maxPoints: c.maxPoints ?? 0, - value: draftValue || NaN, + value: Number.isFinite(deadlineValue) ? deadlineValue : (draftValue ?? NaN), comment: "", }; }) @@ -195,22 +214,24 @@ const TaskSolutionComponent: FC = (props) => { useEffect(() => { if (!hasCriteria || !state.addBonusPoints || !state.clickedForRate || !criteriaModified) return; - const criteriaTotal = criterionRatings.reduce( + const criteriaTotalRaw = criterionRatings.reduce( (sum, c) => sum + (Number.isFinite(c.value) ? Number(c.value) : 0), 0 ); + const criteriaTotal = Math.max(0, criteriaTotalRaw); const total = criteriaTotal + (Number.isFinite(extraScore) ? extraScore : 0); setState(prev => ({...prev, points: total})); }, [criterionRatings, extraScore, hasCriteria, state.addBonusPoints, state.clickedForRate, criteriaModified]); - const criteriaSum = + const criteriaTotalRaw = criterionRatings.reduce( (sum, c) => sum + (Number.isFinite(c.value) ? Number(c.value) : 0), 0 - ) + (Number.isFinite(extraScore) ? extraScore : 0); + ); + const criteriaSum = Math.max(0, criteriaTotalRaw) + (Number.isFinite(extraScore) ? extraScore : 0); - const isRateButtonDisabled = hasCriteria && criteriaSum < 0; + const isRateButtonDisabled = false; const [isCtrlPressed, setIsCtrlPressed] = useState(false) @@ -309,7 +330,21 @@ const TaskSolutionComponent: FC = (props) => { if (!hasCriteria || criterionRatings.length === 0) return baseComment; const rows: string[] = criterionRatings.map(cr => { + const criterion = taskWithCriteria.criteria?.find(c => c.id === cr.criterionId); const safeValue = Number.isFinite(cr.value) ? cr.value : 0; + + if (criterion?.type === CriterionTypeDeadline) { + const deadlineDelay = criterion.arguments && solution?.publicationDate + ? getDatesDiff(solution.publicationDate!, new Date(criterion.arguments)) + : ""; + const statusText = safeValue === 0 + ? "Сдано вовремя" + : `Сдано позже${deadlineDelay ? ` на ${deadlineDelay}` : ""}`; + const valueText = safeValue === 0 ? "✅" : `${safeValue}`; + + return `| ${cr.name} (${statusText.toLowerCase()}) | ${valueText} |`; + } + return `| ${cr.name} | ${safeValue} / ${cr.maxPoints} |`; }); @@ -423,9 +458,10 @@ const TaskSolutionComponent: FC = (props) => { return (