diff --git a/src/Otchinslator/Components/Layout/NavMenu.razor b/src/Otchinslator/Components/Layout/NavMenu.razor index 8c40302..aafd392 100644 --- a/src/Otchinslator/Components/Layout/NavMenu.razor +++ b/src/Otchinslator/Components/Layout/NavMenu.razor @@ -7,7 +7,7 @@ { { "Анкета", "/questionnaire" }, { "Заявление", "/statement" }, - { "Отправка", "/otchislenie/result" }, + { "Отправка", "/result" }, { "Свобода", "/otchislenie/congratulation" } }; @@ -23,7 +23,7 @@ case "statement": _lvl = 2; break; - case "otchislenie/result": + case "result": _lvl = 3; break; case "otchislenie/congratulation": diff --git a/src/Otchinslator/Components/Pages/ChooseInstitut.razor b/src/Otchinslator/Components/Pages/ChooseInstitut.razor index 69a6cbe..20152e6 100644 --- a/src/Otchinslator/Components/Pages/ChooseInstitut.razor +++ b/src/Otchinslator/Components/Pages/ChooseInstitut.razor @@ -16,19 +16,19 @@
- - - - diff --git a/src/Otchinslator/Components/Pages/Congratulation.razor b/src/Otchinslator/Components/Pages/Congratulation.razor index d44599e..07095f4 100644 --- a/src/Otchinslator/Components/Pages/Congratulation.razor +++ b/src/Otchinslator/Components/Pages/Congratulation.razor @@ -3,14 +3,16 @@ @using Otchinslator.Components.Layout @layout OtchislenieLayout @attribute [Authorize] +@using BlazorPageScript Поздравляем! | Отчислятор 3000 +
Поздравляем!
@* TODO: Исправить расположение названия *@ -

Отчислятор 3000

pre-alpha

+

Отчислятор 3000

alpha

Мне нужно...

diff --git a/src/Otchinslator/Components/Pages/Questionnaire.razor b/src/Otchinslator/Components/Pages/Questionnaire.razor index 6ee5021..03d20ed 100644 --- a/src/Otchinslator/Components/Pages/Questionnaire.razor +++ b/src/Otchinslator/Components/Pages/Questionnaire.razor @@ -114,12 +114,11 @@
-
Небольшая анкета + class="text-center font-bold text-4xl md:text-5xl w-max absolute left-1/2 -top-1/4 transform -translate-x-1/2 italic"> +
Кто ты воин?
-

Кто ты воин?

@@ -133,6 +132,10 @@
+

Направление

+

Платник?

diff --git a/src/Otchinslator/Components/Pages/Questionnaire.razor.js b/src/Otchinslator/Components/Pages/Questionnaire.razor.js index 3c6351b..80c57bb 100644 --- a/src/Otchinslator/Components/Pages/Questionnaire.razor.js +++ b/src/Otchinslator/Components/Pages/Questionnaire.razor.js @@ -19,6 +19,7 @@ export function onLoad() { } }); + const specialitySelect = document.getElementById('speciality'); const continueButton = document.querySelector('a[href="/statement"]'); const options = document.querySelectorAll('input[name="options"]'); const kursElements = document.querySelectorAll('input[name="kurs"]'); @@ -31,6 +32,26 @@ export function onLoad() { const kursElement4 = Array.from(kursElements).find(k => k.getAttribute('aria-label') === '4'); const kursElement5 = Array.from(kursElements).find(k => k.getAttribute('aria-label') === '5'); + fetch('/api/getSpecialities?institutId=' + localStorage.getItem('institut')) + .then(response => response.json()) + .then(data => { + const specialities = data.filter(speciality => speciality.name === 'Программная инженерия'); + const otherSpecialities = data.filter(speciality => speciality.name !== 'Программная инженерия'); + + specialities.concat(otherSpecialities).forEach(speciality => { + const option = document.createElement('option'); + option.value = speciality.id; + option.text = speciality.code + " " + (speciality.name.includes('Программная инженерия') ? '🏆 ' + speciality.name : speciality.name); + specialitySelect.add(option); + }); + + let specialityData = localStorage.getItem('speciality'); + if (specialityData) { + specialitySelect.value = specialityData; + } + validateForm(); + }); + // Загрузка данных из хранилища если данные есть function loadFromLocalStorage() { const phoneNumber = localStorage.getItem('phoneNumber'); @@ -71,6 +92,7 @@ export function onLoad() { const isKursSelected = Array.from(kursElements).some(k => k.checked); const isPhoneNumberValid = phoneNumberInput.value.length === 12; + // Блокировка курсов в зависимости от выбранного типа обучения if (isOptionSelected) { var typeOfEducation = (Array.from(options).find(option => option.checked).getAttribute('aria-label')); if (typeOfEducation === "Баклан") { @@ -100,26 +122,28 @@ export function onLoad() { kursElement5.disabled = false; } } - - if (isOptionSelected && isKursSelected && isPhoneNumberValid) { - + + if (isOptionSelected && isKursSelected && isPhoneNumberValid && specialitySelect.value !== '0') { continueButton.classList.remove('btn-disabled'); localStorage.setItem('phoneNumber', phoneNumberInput.value); localStorage.setItem('paid', checkboxPaid.checked); localStorage.setItem('ochno', !checkboxOchno.checked); localStorage.setItem('option', typeOfEducation); localStorage.setItem('kurs', Array.from(kursElements).find(k => k.checked).getAttribute('aria-label')); + localStorage.setItem('speciality', specialitySelect.value); } else { continueButton.classList.add('btn-disabled'); } } + loadFromLocalStorage(); + options.forEach(option => option.addEventListener('change', validateForm)); kursElements.forEach(k => k.addEventListener('change', validateForm)); phoneNumberInput.addEventListener('input', validateForm); checkboxPaid.addEventListener('change', validateForm); checkboxOchno.addEventListener('change', validateForm); + specialitySelect.addEventListener('change', validateForm); - loadFromLocalStorage(); validateForm(); } \ No newline at end of file diff --git a/src/Otchinslator/Components/Pages/Result.razor b/src/Otchinslator/Components/Pages/Result.razor index 6bdd72e..e93dd3b 100644 --- a/src/Otchinslator/Components/Pages/Result.razor +++ b/src/Otchinslator/Components/Pages/Result.razor @@ -1,23 +1,23 @@ -@page "/otchislenie/result" -@using Microsoft.AspNetCore.Authorization +@page "/result" @using Otchinslator.Components.Layout @layout OtchislenieLayout +@using Microsoft.AspNetCore.Authorization @attribute [Authorize] Уже почти | Отчислятор 3000 - -
- +@* *@ +@* *@ +@* *@
Заявление готово
-

Не удалось отобразить заявление, попробуйте скачать (тык)

+

Не удалось отобразить заявление, попробуйте скачать (тык) или открыть в браузере на основе Firefox

- + + Печать + @* Скачать PDF *@ @@ -43,37 +42,4 @@
- - @*
*@ - @*
*@ - @* *@ - @* *@ - @* Скачать PDF *@ - @*
*@ - @* *@ - @*
*@ - @*
*@ - @*
*@ - @*
*@ - @*
*@ - @* $1$ TODO: Сделать адаптив #1# *@ - @* +
\ No newline at end of file diff --git a/src/Otchinslator/Components/Pages/Statement.razor.js b/src/Otchinslator/Components/Pages/Statement.razor.js index b726d25..839ae46 100644 --- a/src/Otchinslator/Components/Pages/Statement.razor.js +++ b/src/Otchinslator/Components/Pages/Statement.razor.js @@ -27,13 +27,17 @@ export function onLoad() { console.log('Начата генерация заявления'); generateStatementModal.showModal(); + console.log(localStorage.getItem('ochno')) + const data = { "phone": "+7 " + localStorage.getItem('phoneNumber'), "kurs": localStorage.getItem('kurs'), "isFreeEducation": localStorage.getItem('paid') === "false", - "isOchno": localStorage.getItem('ochno') === true, - "speciality": optionMapping[localStorage.getItem('option')], - "reason": localStorage.getItem('statement') + "isOchno": localStorage.getItem('ochno') === "true", + "typeOfEducation": optionMapping[localStorage.getItem('option')], + "reason": localStorage.getItem('statement'), + "institut": localStorage.getItem('institut'), + "direction": localStorage.getItem('speciality') } try { @@ -47,7 +51,7 @@ export function onLoad() { if (response.ok) { console.log('Заявление успешно сгенерировано'); - location.href='otchislenie/result'; + location.href='/result'; } else { hideLoadingModal(); console.log('Ошибка при генерации заявления'); diff --git a/src/Otchinslator/Controllers/PublicDataController.cs b/src/Otchinslator/Controllers/PublicDataController.cs new file mode 100644 index 0000000..369ac57 --- /dev/null +++ b/src/Otchinslator/Controllers/PublicDataController.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Otchinslator.Controllers; + +[ApiController] +public class PublicDataController(DatabaseContext db) : Controller +{ + [HttpGet("/api/getSpecialities")] + public IActionResult GetSpecialities(int institutId) + { + if (institutId <= 0 || institutId > 4) + { + return BadRequest("Некорректный институт\nДоступные институты: 1 - ИКТИБ, 2 - ИРТСУ, 3 - ИУЭС, 4 - ИНЭП"); + } + + var specialities = db.Specialties.Where(s => s.InstitutId == institutId).ToList(); + var specialitiesDto = specialities.Select(s => new + { + s.Id, + s.Name, + s.Code, + }); + + return Ok(specialitiesDto); + } +} \ No newline at end of file diff --git a/src/Otchinslator/Controllers/StatementController.cs b/src/Otchinslator/Controllers/StatementController.cs index 02361b2..68fd5ba 100644 --- a/src/Otchinslator/Controllers/StatementController.cs +++ b/src/Otchinslator/Controllers/StatementController.cs @@ -7,17 +7,17 @@ namespace Otchinslator; [ApiController] [Authorize] -public class StatementController(IStatementGenerator statementGenerator, IConfiguration configuration) : Controller +public class StatementController(IStatementGenerator statementGenerator, IConfiguration configuration, DatabaseContext db) : Controller { [HttpPost("/generateStatement")] public async Task GenerateStatement(UserDTO userDto) { - var speciality = userDto.speciality switch + var speciality = userDto.typeOfEducation switch { 1 => SpecialityType.Bakalavriat, 2 => SpecialityType.Magistatura, 3 => SpecialityType.Specialitet, - _ => throw new ArgumentOutOfRangeException() + _ => throw new ArgumentOutOfRangeException() // Модель валидации не позволит передать некорректное значение }; if (speciality == SpecialityType.Bakalavriat && userDto.kurs > 4) @@ -45,6 +45,13 @@ public class StatementController(IStatementGenerator statementGenerator, IConfig userFIO = petrovich.InflectFirstNameTo(Case.Genitive) + " " + petrovich.InflectLastNameTo(Case.Genitive) + " " + petrovich.InflectMiddleNameTo(Case.Genitive); + var direction = db.Specialties.FirstOrDefault(x => x.Id == userDto.direction); + if (direction == null) + { + Utils.LogToTg("Походу ломают `direction == null`", configuration); + return BadRequest("Некорректное направление"); + } + var userData = new UserData { reason = userDto.reason, @@ -54,7 +61,8 @@ public class StatementController(IStatementGenerator statementGenerator, IConfig kurs = userDto.kurs, isFreeEducation = userDto.isFreeEducation, isOchno = userDto.isOchno, - speciality = speciality + speciality = speciality, + direction = direction.Code + " " + direction.Name.ToLower() }; var statement = await statementGenerator.GenerateStatementAsync(userData); var pdf = await statementGenerator.ConvertToPDFAsync(statement); @@ -79,12 +87,5 @@ public class StatementController(IStatementGenerator statementGenerator, IConfig outStream.Position = 0; return File(outStream, "application/pdf"); } - - // [HttpGet("/getUserData")] - // public async Task getUserdata() - // { - // var userFIO = User.Claims.FirstOrDefault(x => x.Type == "name")?.Value; - // var userdata = User.Identities.FirstOrDefault()?.Claims.Select(x => new {x.Type, x.Value}); - // return Ok(userdata); - // } + } \ No newline at end of file diff --git a/src/Otchinslator/DTO/UserDTO.cs b/src/Otchinslator/DTO/UserDTO.cs index a7a3488..b2bdb1a 100644 --- a/src/Otchinslator/DTO/UserDTO.cs +++ b/src/Otchinslator/DTO/UserDTO.cs @@ -20,6 +20,11 @@ public class UserDTO public bool isOchno { get; set; } [Required] [Range(1, 3, ErrorMessage = "Некорректный тип специальности")] - public int speciality { get; set; } + public int typeOfEducation { get; set; } + [Required] + [Range(1, 4, ErrorMessage = "Некорректный институт")] + public int institut { get; set; } + [Required] + public int direction { get; set; } } diff --git a/src/Otchinslator/DatabaseContext.cs b/src/Otchinslator/DatabaseContext.cs new file mode 100644 index 0000000..79cb0c6 --- /dev/null +++ b/src/Otchinslator/DatabaseContext.cs @@ -0,0 +1,49 @@ +using Microsoft.EntityFrameworkCore; +using Otchinslator.Tables; + +namespace Otchinslator; + +public class DatabaseContext : DbContext +{ + public DbSet Instituts { get; set; } + public DbSet Specialties { get; set; } + public string DbPath { get; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().HasKey(i => i.Id); + modelBuilder.Entity().HasKey(s => s.Id); + + modelBuilder.Entity().HasData( + new Institut() { Id = 1, ShortName = "ИКТИБ", Name = "Институт компьютерных технологий и информационной безопасности" }, + new Institut() { Id = 2, ShortName = "ИРТСУ", Name = "Институт радиотехнических систем и управления" }, + new Institut() { Id = 3, ShortName = "ИУЭС", Name = "Институт управления в экономических, экологических и социальных системах" }, + new Institut() { Id = 4, ShortName = "ИНЭП", Name = "Институт нанотехнологий, электроники и приборостроения" } + ); + + modelBuilder.Entity().HasData( + // ИКТИБ + // Бакалавриат + new Specialty() { Id = 1, Name = "Математическое обеспечение и администрирование информационных систем", Code = "02.03.03", InstitutId = 1 }, + new Specialty() { Id = 2, Name = "Информатика и вычислительная техника", Code = "09.03.01", InstitutId = 1 }, // + заочка + new Specialty() { Id = 3, Name = "Информационные системы и технологии", Code = "09.03.02", InstitutId = 1 }, + new Specialty() { Id = 4, Name = "Программная инженерия", Code = "09.03.04", InstitutId = 1 }, + new Specialty() { Id = 5, Name = "Информационная безопасность", Code = "10.03.01", InstitutId = 1 }, + new Specialty() { Id = 6, Name = "Системный анализ и управление", Code = "27.03.03", InstitutId = 1 }, + // Специалитет + new Specialty() { Id = 7, Name = "Применение и эксплуатация автоматизированных систем специального назначения", Code = "09.05.01", InstitutId = 1 }, + new Specialty() { Id = 8, Name = "Информационная безопасность телекоммуникационных систем", Code = "10.05.02", InstitutId = 1 }, + new Specialty() { Id = 9, Name = "Информационная безопасность автоматизированных систем", Code = "10.05.03", InstitutId = 1 }, + new Specialty() { Id = 10, Name = "Безопасность информационных технологий в правоохранительной сфере", Code = "10.05.05", InstitutId = 1 } + ); + } + + public DatabaseContext() + { + var folder = Environment.SpecialFolder.LocalApplicationData; + DbPath = System.IO.Path.Join("./", "database.db"); + } + + protected override void OnConfiguring(DbContextOptionsBuilder options) + => options.UseSqlite($"Data Source={DbPath}"); +} \ No newline at end of file diff --git a/src/Otchinslator/Program.cs b/src/Otchinslator/Program.cs index a33b566..3fd6a28 100644 --- a/src/Otchinslator/Program.cs +++ b/src/Otchinslator/Program.cs @@ -4,8 +4,10 @@ using Gotenberg.Sharp.API.Client.Extensions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Mvc.Authorization; +using Microsoft.EntityFrameworkCore; using Microsoft.Identity.Web; using Microsoft.Identity.Web.UI; +using Otchinslator; using Otchinslator.Components; using Otchinslator.Services; @@ -26,6 +28,9 @@ builder.Services.Configure(options => options.KnownProxies.Clear(); options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; }); + +builder.Services.AddDbContext(); + // Add services to the container. builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); diff --git a/src/Otchinslator/Services/IStatementGenerator.cs b/src/Otchinslator/Services/IStatementGenerator.cs index 8afccc4..6312218 100644 --- a/src/Otchinslator/Services/IStatementGenerator.cs +++ b/src/Otchinslator/Services/IStatementGenerator.cs @@ -18,7 +18,7 @@ public class StatementGenerator(GotenbergSharpClient gotenbergSharpClient) : ISt private const string SpecialitetText = "специалитета по специальности"; private const string BakalavriatText = "бакалавриата по направлению подготовки"; private const string MagistaturaText = "магистратуры по направлению подготовки"; - private const string FreeEducationText = "обучение за счет ассигнований федерального бюджета"; + private const string FreeEducationText = "за счет ассигнований федерального бюджета"; private const string PaidEducationText = "на договорной (платной) основе"; public async Task GenerateStatementAsync(UserData userData) @@ -68,7 +68,7 @@ public class StatementGenerator(GotenbergSharpClient gotenbergSharpClient) : ISt SpecialityType.Magistatura => MagistaturaText, SpecialityType.Specialitet => SpecialitetText, _ => throw new ArgumentOutOfRangeException() - } + " ничего не делания"; + } + " " + userData.direction; break; } diff --git a/src/Otchinslator/Tables/Institut.cs b/src/Otchinslator/Tables/Institut.cs new file mode 100644 index 0000000..0193f88 --- /dev/null +++ b/src/Otchinslator/Tables/Institut.cs @@ -0,0 +1,10 @@ +namespace Otchinslator.Tables; + +public class Institut +{ + public int Id { get; set; } + public string Name { get; set; } + public string ShortName { get; set; } + + public List Specialties { get; } = new(); +} \ No newline at end of file diff --git a/src/Otchinslator/Tables/Specialty.cs b/src/Otchinslator/Tables/Specialty.cs new file mode 100644 index 0000000..c5711a2 --- /dev/null +++ b/src/Otchinslator/Tables/Specialty.cs @@ -0,0 +1,10 @@ +namespace Otchinslator.Tables; + +public class Specialty +{ + public int Id { get; set; } + public string Name { get; set; } + public string Code { get; set; } + public int InstitutId { get; set; } + public Institut Institut { get; set; } +} \ No newline at end of file diff --git a/src/Otchinslator/Templates/ictis.docx b/src/Otchinslator/Templates/ictis.docx index a539c50..eafdbf8 100644 Binary files a/src/Otchinslator/Templates/ictis.docx and b/src/Otchinslator/Templates/ictis.docx differ diff --git a/src/Otchinslator/UserData.cs b/src/Otchinslator/UserData.cs index 8bf9a93..bc14189 100644 --- a/src/Otchinslator/UserData.cs +++ b/src/Otchinslator/UserData.cs @@ -12,7 +12,8 @@ public class UserData int kurs, bool isFreeEducation, bool isOchno, - SpecialityType speciality) + SpecialityType speciality, + string direction) { } @@ -28,6 +29,7 @@ public class UserData public bool isFreeEducation { get; set; } public bool isOchno { get; set; } public SpecialityType speciality { get; set; } + public string direction { get; set; } } public enum SpecialityType