diff --git a/CandidateReviewBusinessLogic/BusinessLogic/AssessmentLogic.cs b/CandidateReviewBusinessLogic/BusinessLogic/AssessmentLogic.cs index 91307f3..2d6ee60 100644 --- a/CandidateReviewBusinessLogic/BusinessLogic/AssessmentLogic.cs +++ b/CandidateReviewBusinessLogic/BusinessLogic/AssessmentLogic.cs @@ -67,40 +67,15 @@ namespace CandidateReviewBusinessLogic.BusinessLogic public List<AssessmentViewModel>? ReadList(AssessmentSearchModel? model) { var list = model == null ? _assessmentStorage.GetFullList() : _assessmentStorage.GetFilteredList(model); - List<AssessmentViewModel> result = new(); - foreach (var elem in list) - { - var user = _userStorage.GetElement(new UserSearchModel { Id = elem.UserId }); - var assessmentCriterions = _assessmentStorage.GetAssessmentCriterions(elem.Id); - - var assessmentViewModel = new AssessmentViewModel - { - Id = elem.Id, - ResumeId = elem.ResumeId, - UserId = elem.UserId, - UserName = user.Name + " " + user.Name, - Comment = elem.Comment, - AssessmentCriterions = assessmentCriterions.Select(ac => new AssessmentCriterionViewModel - { - AssessmentId = ac.AssessmentId, - CriterionId = ac.CriterionId, - Value = ac.Value, - CriterionName = _criterionStorage.GetElement(new CriterionSearchModel { Id = ac.CriterionId })?.Name - }).ToList() - }; - - result.Add(assessmentViewModel); - } - - if (result == null) + if (list == null) { _logger.LogWarning("ReadList return null list"); return null; } - _logger.LogInformation("ReadList. Count: {Count}", result.Count); - return result; + _logger.LogInformation("ReadList. Count: {Count}", list.Count); + return list; } public bool Update(AssessmentBindingModel model) diff --git a/CandidateReviewBusinessLogic/BusinessLogic/ResumeLogic.cs b/CandidateReviewBusinessLogic/BusinessLogic/ResumeLogic.cs index 0bdcafc..c8acf0c 100644 --- a/CandidateReviewBusinessLogic/BusinessLogic/ResumeLogic.cs +++ b/CandidateReviewBusinessLogic/BusinessLogic/ResumeLogic.cs @@ -64,6 +64,7 @@ namespace CandidateReviewBusinessLogic.BusinessLogic { Id = a.Id, ResumeId = a.ResumeId, + UserName = a.UserName, UserId = a.UserId, Comment = a.Comment, AssessmentCriterions = a.AssessmentCriterions @@ -107,6 +108,7 @@ namespace CandidateReviewBusinessLogic.BusinessLogic Id = a.Id, ResumeId = a.ResumeId, UserId = a.UserId, + UserName = a.UserName, Comment = a.Comment, AssessmentCriterions = a.AssessmentCriterions }).ToList() ?? new List<AssessmentViewModel>(); diff --git a/CandidateReviewBusinessLogic/BusinessLogic/VacancyLogic.cs b/CandidateReviewBusinessLogic/BusinessLogic/VacancyLogic.cs index d843b3f..a34c6d3 100644 --- a/CandidateReviewBusinessLogic/BusinessLogic/VacancyLogic.cs +++ b/CandidateReviewBusinessLogic/BusinessLogic/VacancyLogic.cs @@ -104,6 +104,12 @@ namespace CandidateReviewBusinessLogic.BusinessLogic _logger.LogWarning("ReadList return null list"); return null; } + foreach (var item in list) + { + var companyName = _companyStorage.GetElement(new CompanySearchModel { Id = item.CompanyId }).Name; + item.CompanyName = companyName; + } + _logger.LogInformation("ReadList. Count: {Count}", list.Count); return list; } diff --git a/CandidateReviewClientApp/Controllers/AssessmentController.cs b/CandidateReviewClientApp/Controllers/AssessmentController.cs index 4fa8dcb..379c30b 100644 --- a/CandidateReviewClientApp/Controllers/AssessmentController.cs +++ b/CandidateReviewClientApp/Controllers/AssessmentController.cs @@ -5,6 +5,8 @@ using CandidateReviewContracts.ViewModels; using CandidateReviewDataModels.Models; using Microsoft.AspNetCore.Mvc; using System.Diagnostics; +using System.Reflection; +using System.Security.Cryptography.Xml; namespace CandidateReviewClientApp.Controllers { @@ -22,7 +24,7 @@ namespace CandidateReviewClientApp.Controllers [HttpPost] public async Task<IActionResult> AddAssessmentCriterion(int resumeId, int[] criterion, int[] value, string comment) { - string returnUrl = HttpContext.Request.Headers["Referer"].ToString(); + string returnUrl = HttpContext.Request.Headers.Referer.ToString(); try { @@ -35,22 +37,54 @@ namespace CandidateReviewClientApp.Controllers { throw new ArgumentException("Массивы критериев и оценок должны быть не null и одинаковой длины."); } + Dictionary<int, (CriterionModel, int)> assCriterions = new(); + var userId = APIClient.User?.Id; - var assessmentData = new Dictionary<int, (ICriterionModel, int)>(); - var assessmentModel = new AssessmentBindingModel { ResumeId = resumeId, UserId = userId, Comment = comment, - AssessmentCriterions = criterion.Select((t, i) => new KeyValuePair<int, (ICriterionModel, int)> - (t, (new CriterionViewModel { Id = t }, value[i]))).ToDictionary(kv => kv.Key, kv => kv.Value) + AssessmentCriterions = assCriterions }; - int assessmentId = await APIClient.PostRequestAsync("api/assessment/create", assessmentModel); + for (int i = 0; i < criterion.Length; i++) + { + var crit = APIClient.GetRequest<CriterionViewModel>($"api/criterion/details?id={criterion[i]}"); + assCriterions.Add(assessmentId, (new CriterionModel { Id = crit.Id, Name = crit.Name }, value[i])); + } + + assessmentModel.AssessmentCriterions = assCriterions; + assessmentModel.Id = assessmentId; + APIClient.PostRequest("api/assessment/update", assessmentModel); + + var user = APIClient.GetRequest<UserViewModel>($"api/user/profile?id={APIClient.User?.Id}"); + + APIClient.User?.Assessments.Add(new AssessmentViewModel + { + ResumeId = assessmentModel.ResumeId, + UserId = assessmentModel.UserId, + Comment = assessmentModel.Comment, + AssessmentCriterions = assessmentModel.AssessmentCriterions + }); + var resume = APIClient.GetRequest<ResumeViewModel>($"api/resume/details?id={resumeId}"); + if (resume != null) + { + resume?.Assessments.Add(new AssessmentViewModel + { + ResumeId = assessmentModel.ResumeId, + UserId = assessmentModel.UserId, + Comment = assessmentModel.Comment, + AssessmentCriterions = assessmentModel.AssessmentCriterions + }); + } + else + { + throw new Exception("Пользователь не найден"); + } return Redirect($"~/Resume/ResumeDetails/{resumeId}"); } catch (Exception ex) @@ -59,6 +93,22 @@ namespace CandidateReviewClientApp.Controllers } } + public IActionResult Delete(int id, int resumeId) + { + string returnUrl = HttpContext.Request.Headers["Referer"].ToString(); + + try + { + APIClient.PostRequest($"api/assessment/delete", new AssessmentBindingModel { Id = id }); + + return Redirect($"~/Resume/ResumeDetails/{resumeId}") ; + } + catch (Exception ex) + { + return RedirectToAction("Error", new { errorMessage = $"{ex.Message}", returnUrl }); + } + } + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error(string errorMessage, string returnUrl) { diff --git a/CandidateReviewClientApp/Controllers/ResumeController.cs b/CandidateReviewClientApp/Controllers/ResumeController.cs index 1378462..d955349 100644 --- a/CandidateReviewClientApp/Controllers/ResumeController.cs +++ b/CandidateReviewClientApp/Controllers/ResumeController.cs @@ -105,7 +105,15 @@ namespace CandidateReviewClientApp.Controllers { APIClient.User?.Resumes.Add(new ResumeViewModel { - Id = model.Id + Id = model.Id, + Description = model.Description, + Education = model.Education, + Experience = model.Experience, + Skills = model.Skills, + Status = model.Status, + Title = model.Title, + UserId = model.UserId, + VacancyId = model.VacancyId }); } else @@ -116,7 +124,15 @@ namespace CandidateReviewClientApp.Controllers { vacancy.Resumes.Add(new ResumeViewModel { - Id = model.Id + Id = model.Id, + Description = model.Description, + Education = model.Education, + Experience = model.Experience, + Skills = model.Skills, + Status = model.Status, + Title = model.Title, + UserId = model.UserId, + VacancyId = model.VacancyId }); } else diff --git a/CandidateReviewClientApp/Controllers/UserController.cs b/CandidateReviewClientApp/Controllers/UserController.cs index 3c2383c..71d9d1e 100644 --- a/CandidateReviewClientApp/Controllers/UserController.cs +++ b/CandidateReviewClientApp/Controllers/UserController.cs @@ -43,7 +43,11 @@ namespace CandidateReviewClientApp.Controllers if (!id.HasValue) { - return View(new UserViewModel()); + var model = new UserViewModel + { + CompanyId = APIClient.Company.Id + }; + return View(model); } else if (id.HasValue) { @@ -68,7 +72,7 @@ namespace CandidateReviewClientApp.Controllers APIClient.PostRequest("api/user/update", model); } else - { + { APIClient.PostRequest("api/user/register", model); if (APIClient.Company != null) { diff --git a/CandidateReviewClientApp/Controllers/VacancyController.cs b/CandidateReviewClientApp/Controllers/VacancyController.cs index 922e9a9..9c2bb73 100644 --- a/CandidateReviewClientApp/Controllers/VacancyController.cs +++ b/CandidateReviewClientApp/Controllers/VacancyController.cs @@ -30,6 +30,17 @@ namespace CandidateReviewClientApp.Controllers return View(); } + [HttpGet] + public IActionResult Vacancies(int? companyId) + { + if (APIClient.User == null) + { + return Redirect("~/Home/Enter"); + } + var vacancies = APIClient.GetRequest<List<VacancyViewModel>?>($"api/vacancy/list?companyId={companyId}"); + return View(vacancies); + } + [HttpGet] public IActionResult EditVacancy(int? id) { diff --git a/CandidateReviewClientApp/Views/Company/CompanyProfile.cshtml b/CandidateReviewClientApp/Views/Company/CompanyProfile.cshtml index e623865..f8f33a8 100644 --- a/CandidateReviewClientApp/Views/Company/CompanyProfile.cshtml +++ b/CandidateReviewClientApp/Views/Company/CompanyProfile.cshtml @@ -3,6 +3,7 @@ @{ ViewData["Title"] = "Профиль компании"; + var isAdmin = APIClient.User.Role == CandidateReviewDataModels.Enums.RoleEnum.Администратор ? true : false; } <div class="container mt-5"> @@ -48,20 +49,54 @@ <div class="card-header d-flex justify-content-between align-items-center"> <h2>Вакансии компании</h2> <a asp-action="EditVacancy" asp-controller="Vacancy" asp-route-companyId="@Model.Id" - class="btn btn-success">Добавить вакансию</a> + class="btn btn-success"> <i class="bi bi-plus-circle me-2"></i> Добавить вакансию</a> </div> <div class="card-body"> @if (Model.Vacancies != null && Model.Vacancies.Any()) { - @await Html.PartialAsync("_VacanciesTable", Model.Vacancies.Take(5)) + <div class="table-responsive"> + <table class="table table-striped table-hover align-middle"> + <thead class="table-light"> + <tr> + <th>Название вакансии</th> + <th>Статус</th> + <th>Действия</th> + </tr> + </thead> + <tbody> + @foreach (var vacancy in Model.Vacancies.Take(5)) + { + <tr> + <td>@vacancy.JobTitle</td> + <td>@vacancy.Status</td> + <td> + <div class="d-flex justify-content-center"> + <a asp-action="VacancyDetails" asp-controller="Vacancy" asp-route-id="@vacancy.Id" + class="btn btn-info btn-sm me-2" title="Просмотр"> + <i class="bi bi-eye"></i> + </a> + <a asp-action="EditVacancy" asp-controller="Vacancy" asp-route-id="@vacancy.Id" + class="btn btn-warning btn-sm me-2" title="Редактировать"> + <i class="bi bi-pencil-square"></i> + </a> + <a asp-action="Delete" asp-controller="Vacancy" asp-route-id="@vacancy.Id" + class="btn btn-danger btn-sm" title="Удалить" + onclick="return confirm('Вы уверены, что хотите удалить вакансию?');"> + <i class="bi bi-trash"></i> + </a> + </div> + </td> + </tr> + } + </tbody> + </table> + </div> + @if (Model.Vacancies.Count() > 5) { - <button id="showAllVacanciesBtn" class="btn btn-primary" onclick="toggleVacancies()">Показать все</button> + <a asp-action="Vacancies" asp-controller="Vacancy" asp-route-companyId="@Model.Id" + class="btn btn-primary mt-3">Посмотреть все</a> } - - <div id="allVacancies" style="display:none;"> - @await Html.PartialAsync("_VacanciesTable", Model.Vacancies) - </div> } else { @@ -73,44 +108,50 @@ <div class="card shadow-sm"> <div class="card-header d-flex justify-content-between align-items-center"> <h2>Сотрудники компании</h2> + @if (@isAdmin) { <a asp-action="UserProfileEdit" asp-controller="User" asp-route-companyId="@Model.Id" - class="btn btn-success">Добавить сотрудника</a> + class="btn btn-success"> <i class="bi bi-plus-circle me-2"></i> Добавить сотрудника</a> + } </div> <div class="card-body"> @if (Model.Employees != null && Model.Employees.Any()) { - <table class="table table-striped"> - <thead> - <tr> - <th>Фамилия</th> - <th>Имя</th> - <th>Эл. почта</th> - <th>Действия</th> - </tr> - </thead> - <tbody> - @foreach (var employee in Model.Employees) + + <div class="table-responsive"> + <table class="table table-striped table-hover align-middle"> + <thead class="table-light"> + <tr> + <th>Фамилия</th> + <th>Имя</th> + <th>Эл. почта</th> + <th>Действия</th> + </tr> + </thead> + <tbody> + @foreach (var employee in Model.Employees.Take(5)) { <tr> <td>@employee.Surname</td> <td>@employee.Name</td> + <td>@employee.Email</td> <td> - <a asp-action="UserProfile" asp-controller="User" asp-route-id="@employee.Id" class="btn btn-info btn-sm rounded-pill" title="Просмотр"> - <i class="bi bi-eye"></i> Просмотр + <a asp-action="UserProfile" asp-controller="User" asp-route-id="@employee.Id" class="btn btn-info btn-sm me-2" title="Просмотр"> + <i class="bi bi-eye"></i> </a> - <a asp-action="UserProfileEdit" asp-controller="User" asp-route-id="@employee.Id" class="btn btn-warning btn-sm rounded-pill" title="Редактировать"> - <i class="bi bi-pencil"></i> Редактировать + <a asp-action="UserProfileEdit" asp-controller="User" asp-route-id="@employee.Id" class="btn btn-warning btn-sm me-2" title="Редактировать"> + <i class="bi bi-pencil"></i> </a> - <a asp-action="DeleteEmployee" asp-controller="User" asp-route-id="@employee.Id" class="btn btn-danger btn-sm rounded-pill" title="Удалить" onclick="return confirm('Вы уверены, что хотите удалить сотрудника?');"> - <i class="bi bi-trash"></i> Удалить + <a asp-action="DeleteEmployee" asp-controller="User" asp-route-id="@employee.Id" class="btn btn-danger btn-sm me-2" title="Удалить" onclick="return confirm('Вы уверены, что хотите удалить сотрудника?');"> + <i class="bi bi-trash"></i> </a> </td> </tr> } - </tbody> - </table> + </tbody> + </table> + </div> } else { @@ -121,20 +162,19 @@ </div> </div> </div> +<style> + .btn { + background-color: #0A1128; + color: white; + border: none; + border-radius: 10px; + padding: 12px; + font-size: 16px; + transition: background-color 0.3s ease, transform 0.2s ease; + } -@section Scripts { - <script> - function toggleVacancies() { - var allVacancies = document.getElementById('allVacancies'); - var button = document.getElementById('showAllVacanciesBtn'); - - if (allVacancies.style.display === 'none') { - allVacancies.style.display = 'block'; - button.innerText = 'Скрыть'; - } else { - allVacancies.style.display = 'none'; - button.innerText = 'Показать все'; - } + .btn:hover { + background-color: #1C3273; + transform: scale(1.05); } - </script> -} +</style> diff --git a/CandidateReviewClientApp/Views/Company/EditCompanyProfile.cshtml b/CandidateReviewClientApp/Views/Company/EditCompanyProfile.cshtml index 4823d99..631d595 100644 --- a/CandidateReviewClientApp/Views/Company/EditCompanyProfile.cshtml +++ b/CandidateReviewClientApp/Views/Company/EditCompanyProfile.cshtml @@ -78,4 +78,20 @@ }) })() </script> +<style> + .btn { + background-color: #0A1128; + color: white; + border: none; + border-radius: 10px; + padding: 12px; + font-size: 16px; + transition: background-color 0.3s ease, transform 0.2s ease; + } + + .btn:hover { + background-color: #1C3273; + transform: scale(1.05); + } +</style> diff --git a/CandidateReviewClientApp/Views/Criterion/ManageCriterions.cshtml b/CandidateReviewClientApp/Views/Criterion/ManageCriterions.cshtml index c9027dd..650bd35 100644 --- a/CandidateReviewClientApp/Views/Criterion/ManageCriterions.cshtml +++ b/CandidateReviewClientApp/Views/Criterion/ManageCriterions.cshtml @@ -70,4 +70,20 @@ </form> </div> </div> -</div> \ No newline at end of file +</div> +<style> + .btn { + background-color: #0A1128; + color: white; + border: none; + border-radius: 10px; + padding: 12px; + font-size: 16px; + transition: background-color 0.3s ease, transform 0.2s ease; + } + + .btn:hover { + background-color: #1C3273; + transform: scale(1.05); + } +</style> \ No newline at end of file diff --git a/CandidateReviewClientApp/Views/Home/Enter.cshtml b/CandidateReviewClientApp/Views/Home/Enter.cshtml index e2d229c..666e842 100644 --- a/CandidateReviewClientApp/Views/Home/Enter.cshtml +++ b/CandidateReviewClientApp/Views/Home/Enter.cshtml @@ -3,40 +3,28 @@ Layout = "_LoginLayout"; } -@if (!ViewData.ModelState.IsValid) -{ - <div class="alert alert-danger"> - <ul> - @foreach (var error in ViewData.ModelState.Values.SelectMany(v => v.Errors)) - { - <li>@error.ErrorMessage</li> - } - </ul> - </div> -} - <div class="container mt-5"> <div class="row justify-content-center"> <div class="col-md-6"> - <div class="card shadow-sm"> - <div class="card-header"> - <h3 class="text-center">Вход в приложение</h3> + <div class="card shadow-lg rounded"> + <div class="card-header bg-primary text-white text-center rounded-top"> + <h3 class="mb-0">Вход в приложение</h3> </div> - <div class="card-body"> + <div class="card-body p-4"> <form method="post"> - <div class="form-group"> - <label for="login">Логин:</label> + <div class="form-group mb-3"> + <label for="login" class="form-label">Логин:</label> <input type="text" class="form-control" id="login" name="login" required> </div> - <div class="form-group"> - <label for="password">Пароль:</label> + <div class="form-group mb-3"> + <label for="password" class="form-label">Пароль:</label> <input type="password" class="form-control" id="password" name="password" required> </div> - <div class="form-group text-center mt-3"> - <button type="submit" class="btn btn-primary">Войти</button> + <div class="form-group text-center mt-4"> + <button type="submit" class="btn btn-primary w-100 py-2">Войти</button> </div> - <div class="form-group text-center mt-2"> - <a asp-area="" asp-controller="Home" asp-action="Register" class="text-muted">Ещё не зарегистрированы? Регистрация</a> + <div class="form-group text-center mt-3"> + <a asp-area="" asp-controller="Home" asp-action="Register" class="text-muted text-decoration-none">Ещё не зарегистрированы? Регистрация</a> </div> </form> </div> @@ -44,4 +32,54 @@ </div> </div> </div> +<style> + body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background-color: #f0f4f7; + } + .card { + border-radius: 15px; + } + + .card-header { + background-color: #f0f4f7; /* Цвет фона формы */ + border-top-left-radius: 15px; + border-top-right-radius: 15px; + border-bottom: 1px solid #ddd; /* Тонкая линия, чтобы отделить заголовок от содержимого */ + } + + .form-control { + border-radius: 10px; + box-shadow: none; + transition: all 0.3s ease; + } + + .form-control:focus { + border-color: #0A1128; + box-shadow: 0 0 5px rgba(0, 123, 255, 0.5); + } + + .btn { + background-color: #0A1128; + color: white; + border: none; + border-radius: 10px; + padding: 12px; + font-size: 16px; + transition: background-color 0.3s ease, transform 0.2s ease; + } + + .btn:hover { + background-color: #1C3273; + transform: scale(1.05); + } + + .text-muted { + font-size: 14px; + } + + .text-muted:hover { + text-decoration: underline; + } +</style> diff --git a/CandidateReviewClientApp/Views/Home/Index.cshtml b/CandidateReviewClientApp/Views/Home/Index.cshtml index a220f38..21a1270 100644 --- a/CandidateReviewClientApp/Views/Home/Index.cshtml +++ b/CandidateReviewClientApp/Views/Home/Index.cshtml @@ -7,18 +7,21 @@ <p>Здесь вы можете:</p> <div class="d-flex justify-content-center mt-4"> - <div class="box"> - <h3>Создать профиль компании</h3> - </div> - <div class="box"> - <h3>Подобрать кандидатов на вакансию</h3> - </div> - <div class="box"> - <h3>Найти подходящую вакансию</h3> - </div> - <div class="box"> - <h3>Оценить кандидатов на вакансию</h3> - </div> + <div class="box"> + <i class="bi bi-building" style="font-size: 2rem; color: #007bff;"></i> + <h3 class="mt-3">Создать профиль компании</h3> + </div> + <div class="box"> + <i class="bi bi-search" style="font-size: 2rem; color: #007bff;"></i> + <h3 class="mt-3">Подобрать кандидатов на вакансию</h3> + </div> + <div class="box"> + <i class="bi bi-briefcase" style="font-size: 2rem; color: #007bff;"></i> + <h3 class="mt-3">Найти подходящую вакансию</h3> + </div> + <div class="box"> + <i class="bi bi-people" style="font-size: 2rem; color: #007bff;"></i> + <h3 class="mt-3">Оценить кандидатов на вакансию</h3> </div> </div> @@ -30,6 +33,10 @@ gap: 20px; } + .box i { + margin-bottom: 10px; + } + .box { border: 1px solid #ccc; border-radius: 8px; diff --git a/CandidateReviewClientApp/Views/Home/Register.cshtml b/CandidateReviewClientApp/Views/Home/Register.cshtml index 67a4af1..70562ea 100644 --- a/CandidateReviewClientApp/Views/Home/Register.cshtml +++ b/CandidateReviewClientApp/Views/Home/Register.cshtml @@ -6,8 +6,8 @@ <div class="container mt-5"> <div class="row justify-content-center"> <div class="col-md-6"> - <div class="card shadow-sm"> - <div class="card-header"> + <div class="card shadow-lg rounded"> + <div class="card-header bg-primary text-white text-center rounded-top"> <h3 class="text-center">Регистрация</h3> </div> <div class="card-body"> @@ -36,11 +36,62 @@ <button type="submit" class="btn btn-primary">Зарегистрироваться</button> </div> <div class="form-group text-center mt-2"> - <a asp-area="" asp-controller="Home" asp-action="Enter" class="text-muted">Уже зарегистрированы? Войти</a> + <a asp-area="" asp-controller="Home" asp-action="Enter" class="text-muted text-decoration-none">Уже зарегистрированы? Войти</a> </div> </form> </div> </div> </div> </div> -</div> \ No newline at end of file +</div> + + +<style> + body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background-color: #f0f4f7; + } + + .card { + border-radius: 15px; + } + + .card-header { + border-top-left-radius: 15px; + border-top-right-radius: 15px; + } + + .form-control { + border-radius: 10px; + box-shadow: none; + transition: all 0.3s ease; + } + + .form-control:focus { + border-color: #0A1128; + box-shadow: 0 0 5px rgba(0, 123, 255, 0.5); + } + + .btn { + background-color: #0A1128; + color: white; + border: none; + border-radius: 10px; + padding: 12px; + font-size: 16px; + transition: background-color 0.3s ease, transform 0.2s ease; + } + + .btn:hover { + background-color: #1C3273; + transform: scale(1.05); + } + + .text-muted { + font-size: 14px; + } + + .text-muted:hover { + text-decoration: underline; + } +</style> \ No newline at end of file diff --git a/CandidateReviewClientApp/Views/Resume/EditResume.cshtml b/CandidateReviewClientApp/Views/Resume/EditResume.cshtml index d69af52..655a242 100644 --- a/CandidateReviewClientApp/Views/Resume/EditResume.cshtml +++ b/CandidateReviewClientApp/Views/Resume/EditResume.cshtml @@ -40,12 +40,6 @@ <input type="text" class="form-control" id="description" name="description" value="@Model?.Description" placeholder="Введите описание резюме"> </div> - <div class="mb-3"> - <label for="PhotoFilePath" class="form-label">Ваше фото для текущего резюме</label> - <input type="file" class="form-control" id="PhotoFilePath" name="PhotoFilePath" accept=".jpg,.jpeg,.png" /> - <img id="avatarPreview" src="@Model?.PhotoFilePath" alt="Предварительный просмотр фото" style="max-width: 100px; max-height: 100px;" /> - <div class="invalid-feedback">Выберите файл изображения.</div> - </div> <div class="col-md-6"> @if (Model.Status == ResumeStatusEnum.Черновик) { @@ -54,6 +48,10 @@ @if (Model.Id <= 0) { <button type="submit" formmethod="post" class="btn btn-secondary" name="isDraft" value="true">Сохранить черновик</button> + } + else + { + <button type="submit" class="btn btn-primary">Сохранить</button> } <button class="btn btn-secondary" onclick="window.history.back();">Назад</button> </div> @@ -78,4 +76,20 @@ }, false) }) })() -</script> \ No newline at end of file +</script> +<style> + .btn { + background-color: #0A1128; + color: white; + border: none; + border-radius: 10px; + padding: 12px; + font-size: 16px; + transition: background-color 0.3s ease, transform 0.2s ease; + } + + .btn:hover { + background-color: #1C3273; + transform: scale(1.05); + } +</style> \ No newline at end of file diff --git a/CandidateReviewClientApp/Views/Resume/ResumeDetails.cshtml b/CandidateReviewClientApp/Views/Resume/ResumeDetails.cshtml index feb204d..2d4f95a 100644 --- a/CandidateReviewClientApp/Views/Resume/ResumeDetails.cshtml +++ b/CandidateReviewClientApp/Views/Resume/ResumeDetails.cshtml @@ -7,16 +7,14 @@ @{ ViewData["Title"] = "Резюме"; bool userRole = APIClient.User?.Role == CandidateReviewDataModels.Enums.RoleEnum.Сотрудник || APIClient.User.Role == CandidateReviewDataModels.Enums.RoleEnum.Администратор; + bool isAdmin = APIClient.User.Role == CandidateReviewDataModels.Enums.RoleEnum.Администратор ? true : false; + var assessmentCriterions = new Dictionary<int, (ICriterionModel, int)>(); } <div class="container mt-5"> <div class="d-flex justify-content-between mb-4"> <div class="card-header"> <h2>@Model.Title</h2> - <img src="@(Model.PhotoFilePath ?? "https://cdn-icons-png.flaticon.com/512/18/18601.png")" - style="max-width: 150px; max-height: 150px; object-fit: cover; " - class="card-img-top img-fluid rounded-circle mx-auto d-block" - alt="Аватар пользователя" /> <input type="hidden" name="id" value="@Model?.Id" /> </div> <button class="btn btn-secondary" onclick="window.history.back();">Назад</button> @@ -64,6 +62,14 @@ { <h2 class="mb-4">Оценка резюме</h2> + @if (isAdmin) + { + <div class="mt-4"> + <a asp-action="ManageCriterions" asp-controller="Criterion" class="btn btn-outline-secondary">Управление критериями</a> + </div> + <br></br> + } + <form method="post" asp-controller="Assessment" asp-action="AddAssessmentCriterion"> <input type="hidden" name="resumeId" value="@Model.Id" /> @@ -85,11 +91,11 @@ <div class="row mb-3 criterion-row"> <div class="col-8"> <select name="criterion" class="form-control" disabled> - <option value="@criterion.CriterionId" selected>@criterion.CriterionName</option> + <option value="@criterion.Value.Item1.Id" selected>@criterion.Value.Item1.Name</option> </select> </div> <div class="col-md-4"> - <input type="number" name="value" class="form-control" value="@criterion.Value" readonly /> + <input type="number" name="value" class="form-control" value="@criterion.Value.Item2" readonly /> </div> </div> } @@ -111,15 +117,20 @@ <div class="row mb-3 criterion-row"> <div class="col-8"> <select name="criterion" class="form-control" disabled> - <option value="@criterion.CriterionId" selected>@criterion.CriterionName</option> + <option value="@criterion.Value.Item1.Id" selected>@criterion.Value.Item1.Name</option> </select> </div> <div class="col-md-4"> - <input type="number" name="value" class="form-control" value="@criterion.Value" readonly /> + <input type="number" name="value" class="form-control" value="@criterion.Value.Item2" readonly /> </div> </div> } <p>Комментарий: @userAssessment.Comment</p> + + <a asp-controller="Assessment" asp-action="Delete" asp-route-id="@userAssessment.Id" asp-route-resumeId="@Model.Id" onclick="return confirm('Вы уверены, что хотите удалить оценку?');" + class="btn btn-info btn-sm me-2" title="Просмотр"> + Удалить оценку + </a> </div> } else @@ -160,11 +171,8 @@ } </div> </form> - - <div class="mt-4"> - <a asp-action="ManageCriterions" asp-controller="Criterion" class="btn btn-outline-secondary">Управление критериями</a> - </div> } + </div> <script> @@ -214,3 +222,20 @@ document.querySelectorAll('.remove-criterion').forEach(button => attachRemoveEvent(button)); </script> + +<style> + .btn { + background-color: #0A1128; + color: white; + border: none; + border-radius: 10px; + padding: 12px; + font-size: 16px; + transition: background-color 0.3s ease, transform 0.2s ease; + } + + .btn:hover { + background-color: #1C3273; + transform: scale(1.05); + } +</style> diff --git a/CandidateReviewClientApp/Views/Shared/_Layout.cshtml b/CandidateReviewClientApp/Views/Shared/_Layout.cshtml index 6e90a02..5f02239 100644 --- a/CandidateReviewClientApp/Views/Shared/_Layout.cshtml +++ b/CandidateReviewClientApp/Views/Shared/_Layout.cshtml @@ -14,25 +14,25 @@ </head> <body> <header> - <nav class="navbar navbar-expand-lg navbar-light bg-light"> + <nav class="navbar navbar-expand-lg navbar-dark shadow-md"> <div class="container"> <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Candidate Review</a> <button class="navbar-toggler" type="button" onclick="toggleNavbar()" aria-controls="mainNav" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="mainNav"> - <ul class="navbar-nav ml-auto"> + <ul class="navbar-nav ms-auto"> @if (APIClient.User?.Role == RoleEnum.Сотрудник || APIClient.User?.Role == RoleEnum.Администратор) { <li class="nav-item"> - <a class="nav-link text-dark" asp-area="" asp-controller="Company" asp-route-id="@(APIClient.Company?.Id)" asp-action="@(APIClient.Company == null ? "EditCompanyProfile" : "CompanyProfile")">Профиль компании</a> + <a class="nav-link" asp-area="" asp-controller="Company" asp-route-id="@(APIClient.Company?.Id)" asp-action="@(APIClient.Company == null ? "EditCompanyProfile" : "CompanyProfile")">Профиль компании</a> </li> } <li class="nav-item"> - <a class="nav-link text-dark" asp-area="" asp-controller="User" asp-action="UserProfile" asp-route-id="@(APIClient.User?.Id)">Профиль</a> + <a class="nav-link" asp-area="" asp-controller="User" asp-action="UserProfile" asp-route-id="@(APIClient.User?.Id)">Профиль</a> </li> <li class="nav-item"> - <a class="nav-link text-dark" asp-area="" asp-controller="Vacancy" asp-action="SearchVacancies">Поиск вакансий</a> + <a class="nav-link" asp-area="" asp-controller="Vacancy" asp-action="SearchVacancies">Поиск вакансий</a> </li> </ul> </div> @@ -58,5 +58,25 @@ navbar.classList.toggle('show'); } </script> + <style> + .navbar { + background-color: #0A1128; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + } + + .navbar .nav-link { + font-size: 1rem; + transition: color 0.2s ease-in-out; + } + + .navbar .nav-link:hover { + color: #D9D9D9; + } + + .navbar-brand { + font-size: 1.25rem; + } + </style> + </body> </html> \ No newline at end of file diff --git a/CandidateReviewClientApp/Views/Shared/_VacanciesTable.cshtml b/CandidateReviewClientApp/Views/Shared/_VacanciesTable.cshtml deleted file mode 100644 index 7d26726..0000000 --- a/CandidateReviewClientApp/Views/Shared/_VacanciesTable.cshtml +++ /dev/null @@ -1,33 +0,0 @@ -@model IEnumerable<CandidateReviewContracts.ViewModels.VacancyViewModel> - -<table class="table table-striped"> - <thead> - <tr> - <th>Название</th> - <th>Тип занятости</th> - <th>Статус</th> - <th>Действия</th> - </tr> - </thead> - <tbody> - @foreach (var vacancy in Model) - { - <tr> - <td>@vacancy.JobTitle</td> - <td>@vacancy.JobType</td> - <td>@vacancy.Status</td> - <td> - <a asp-action="VacancyDetails" asp-controller="Vacancy" asp-route-id="@vacancy.Id" class="btn btn-info btn-sm rounded-pill" title="Просмотр"> - <i class="bi bi-eye"></i> - </a> - <a asp-action="EditVacancy" asp-controller="Vacancy" asp-route-id="@vacancy.Id" class="btn btn-warning btn-sm rounded-pill" title="Редактировать"> - <i class="bi bi-pencil"></i> - </a> - <a asp-action="Delete" asp-controller="Vacancy" asp-route-id="@vacancy.Id" class="btn btn-danger btn-sm rounded-pill" title="Удалить" onclick="return confirm('Вы уверены, что хотите удалить вакансию?');"> - <i class="bi bi-trash"></i> - </a> - </td> - </tr> - } - </tbody> -</table> diff --git a/CandidateReviewClientApp/Views/User/Employees.cshtml b/CandidateReviewClientApp/Views/User/Employees.cshtml new file mode 100644 index 0000000..b2c5b6f --- /dev/null +++ b/CandidateReviewClientApp/Views/User/Employees.cshtml @@ -0,0 +1,101 @@ +@using CandidateReviewContracts.ViewModels +@model List<UserViewModel> + +@{ + ViewData["Title"] = "Сотрудники компании"; +} + +<div class="container mt-5"> + <div class="row justify-content-center"> + <div class="col-md-12"> + <h1 class="mb-4 text-center">Сотрудники</h1> + <div class="mb-4 text-end"> + <a asp-action="UserProfileEdit" asp-controller="User" asp-route-companyId="@APIClient.Company.Id" class="btn btn-success"> + <i class="bi bi-plus-circle me-2"></i> Добавить сотрудника + </a> + </div> + @if (Model != null) + { + @if (Model.Any()) + { + <div class="table-responsive"> + <table class="table table-striped table-hover align-middle"> + <thead class="table-light"> + <tr> + <th>Фамилия</th> + <th>Имя</th> + <th>Эл. почта</th> + <th>Номер телефона</th> + <th>Действия</th> + </tr> + </thead> + <tbody> + @foreach (var employee in Model) + { + <tr> + <td>@employee.Surname</td> + <td>@employee.Name</td> + <td>@employee.Email</td> + <td>@employee.PhoneNumber</td> + <td> + <a asp-action="UserProfile" asp-controller="User" asp-route-id="@employee.Id" class="btn btn-info btn-sm me-2" title="Просмотр"> + <i class="bi bi-eye"></i> Просмотр + </a> + + <a asp-action="UserProfileEdit" asp-controller="User" asp-route-id="@employee.Id" class="btn btn-warning btn-sm me-2" title="Редактировать"> + <i class="bi bi-pencil"></i> Редактировать + </a> + + <a asp-action="Delete" asp-controller="User" asp-route-id="@employee.Id" class="btn btn-danger btn-sm me-2" title="Удалить" onclick="return confirm('Вы уверены, что хотите удалить сотрудника?');"> + <i class="bi bi-trash"></i> Удалить + </a> + </td> + </tr> + } + </tbody> + </table> + </div> + } + else + { + <p class="text-center text-muted">Сотрудников нет.</p> + } + } + else + { + <p class="text-center text-danger">Произошла ошибка при получении данных.</p> + } + </div> + </div> +</div> + +<style> + .table-hover tbody tr:hover { + background-color: #f1f1f1; + } + + .btn-info, .btn-warning, .btn-danger { + display: flex; + align-items: center; + justify-content: center; + } + + .btn-info i, .btn-warning i, .btn-danger i { + margin-right: 5px; + } + + .btn { + background-color: #0A1128; + color: white; + border: none; + border-radius: 10px; + padding: 12px; + font-size: 16px; + transition: background-color 0.3s ease, transform 0.2s ease; + } + + .btn:hover { + background-color: #1C3273; + transform: scale(1.05); + } +</style> diff --git a/CandidateReviewClientApp/Views/User/UserProfile.cshtml b/CandidateReviewClientApp/Views/User/UserProfile.cshtml index 1059739..2ff8871 100644 --- a/CandidateReviewClientApp/Views/User/UserProfile.cshtml +++ b/CandidateReviewClientApp/Views/User/UserProfile.cshtml @@ -3,54 +3,68 @@ @{ ViewData["Title"] = "Профиль пользователя"; - var userRole = APIClient.User?.Role == CandidateReviewDataModels.Enums.RoleEnum.Пользователь ? true : false; + var userRole = APIClient.User?.Role == CandidateReviewDataModels.Enums.RoleEnum.Пользователь || APIClient.User?.Role == CandidateReviewDataModels.Enums.RoleEnum.Администратор ? true : false; } <div class="container mt-5"> - <div class="row"> - <div class="col-md-4 mb-4"> - <div class="card shadow-sm"> - <img src="@(Model.AvatarFilePath ?? "https://cdn-icons-png.flaticon.com/512/18/18601.png")" - class="card-img-top img-fluid rounded-circle mx-auto d-block" style="max-width: 150px; max-height: 150px;" alt="Аватар пользователя"> + <div class="row justify-content-center"> + <div class="col-md-6"> + <div class="card shadow-lg p-4 rounded"> + <div class="text-center"> + <img src="https://cdn-icons-png.flaticon.com/512/18/18601.png" + class="card-img-top img-fluid rounded-circle mx-auto d-block" + style="max-width: 150px; max-height: 150px;" alt="Аватар пользователя"> + </div> <div class="card-body text-center"> - <h5 class="card-title mb-0"> - @(string.IsNullOrEmpty(@Model?.Surname) ? "" : @Model?.Surname) @Model?.Name @(string.IsNullOrEmpty(@Model?.LastName) ? "" : @Model?.LastName) - </h5> + <h3 class="card-title mb-2"> + @(string.IsNullOrEmpty(@Model?.Surname) ? "" : @Model?.Surname) + @Model?.Name + @(string.IsNullOrEmpty(@Model?.LastName) ? "" : @Model?.LastName) + </h3> <dl class="row mt-3"> - <dt class="col-sm-4">Email:</dt> + <dt class="col-sm-4 text-muted">Email:</dt> <dd class="col-sm-8">@Model?.Email</dd> @if (!string.IsNullOrEmpty(@Model?.PhoneNumber)) { - <dt class="col-sm-4">Телефон:</dt> + <dt class="col-sm-4 text-muted">Телефон:</dt> <dd class="col-sm-8">@Model?.PhoneNumber</dd> } - <dt class="col-sm-4">Роль:</dt> + <dt class="col-sm-4 text-muted">Роль:</dt> <dd class="col-sm-8">@Model?.Role</dd> </dl> - <div class="btn-group mt-3" role="group" aria-label="Действия"> - <a asp-action="UserProfileEdit" asp-controller="User" asp-route-id="@Model?.Id" class="btn btn-primary">Редактировать профиль</a> - <form asp-action="Delete" asp-controller="User" method="post" onsubmit="return confirm('Вы уверены, что хотите удалить профиль?');"> + + <div class="btn-group mt-4" role="group" aria-label="Действия"> + <a asp-action="UserProfileEdit" asp-controller="User" asp-route-id="@Model?.Id" + class="btn btn-outline-primary px-4 py-2 me-2"> + <i class="bi bi-pencil me-2"></i>Редактировать профиль + </a> + <form asp-action="Delete" asp-controller="User" method="post" + onsubmit="return confirm('Вы уверены, что хотите удалить профиль?');" class="d-inline me-2"> <input type="hidden" name="id" value="@Model?.Id" /> - <button type="submit" class="btn btn-danger">Удалить профиль</button> + <button type="submit" class="btn btn-outline-danger px-4 py-2"> + <i class="bi bi-trash me-2"></i>Удалить профиль + </button> </form> - <a asp-action="Logout" asp-controller="User" class="btn btn-secondary">Выйти</a> + <a asp-action="Logout" asp-controller="User" class="btn btn-outline-secondary px-4 py-2"> + <i class="bi bi-box-arrow-right me-2"></i>Выйти + </a> </div> + </div> </div> </div> @if (userRole) { - <div class="col-md-8"> + <div class="col-md-8 mt-4"> <div class="card shadow-sm mb-4"> <div class="card-header d-flex justify-content-between align-items-center"> <h2>Мои резюме</h2> - <a asp-action="CreateResume" asp-controller="Resume" class="btn btn-success">Создать резюме</a> </div> <div class="card-body"> @if (Model.Resumes != null && Model.Resumes.Any()) { - <table class="table table-striped"> + <table class="table table-striped table-hover"> <thead> <tr> <th>Название</th> @@ -67,13 +81,13 @@ <td>@resume.VacancyName</td> <td>@resume.Status</td> <td> - <a asp-action="ResumeDetails" asp-controller="Resume" asp-route-id="@resume.Id" class="text-info" title="Просмотр"> + <a asp-action="ResumeDetails" asp-controller="Resume" asp-route-id="@resume.Id" class="btn btn-info btn-sm me-2" title="Просмотр"> <i class="bi bi-eye"></i> </a> - <a asp-action="EditResume" asp-controller="Resume" asp-route-id="@resume.Id" class="text-warning" title="Редактировать"> + <a asp-action="EditResume" asp-controller="Resume" asp-route-id="@resume.Id" class="btn btn-warning btn-sm me-2" title="Редактировать"> <i class="bi bi-pencil"></i> </a> - <a asp-action="Delete" asp-controller="Resume" asp-route-id="@resume.Id" class="text-danger" title="Удалить" onclick="return confirm('Вы уверены, что хотите удалить резюме?');"> + <a asp-action="Delete" asp-controller="Resume" asp-route-id="@resume.Id" class="btn btn-danger btn-sm me-2" title="Удалить" onclick="return confirm('Вы уверены, что хотите удалить резюме?');"> <i class="bi bi-trash"></i> </a> </td> @@ -84,7 +98,7 @@ } else { - <p>Нет резюме.</p> + <p class="text-muted">У вас нет резюме.</p> } </div> </div> @@ -106,53 +120,36 @@ margin-top: 20px; } - .table th, .table td { - vertical-align: middle; + .table th, .table td { + vertical-align: middle; + } + + .btn { + background-color: #0A1128; + color: white; + border: none; + border-radius: 10px; + padding: 12px; + font-size: 16px; + transition: background-color 0.3s ease, transform 0.2s ease; + } + + .btn:hover { + background-color: #1C3273; + transform: scale(1.05); } - .btn-sm { - font-size: 0.875rem; - padding: 0.375rem 0.75rem; - width: 100%; - margin-bottom: 10px; - } - - .btn-info, .btn-success { - margin-right: 10px; - } - .input-group { border-radius: 5px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } - .form-control { - border-radius: 5px; + .btn-group .btn { + border-radius: 15px; /* скругление кнопок внутри группы */ } - .form-control:focus { - border-color: #80bdff; - box-shadow: 0 0 0 0.25rem rgba(0, 123, 255, 0.25); - } - - .table-striped tbody tr:nth-of-type(odd) { - background-color: #f9f9f9; - } - - .table-light { - background-color: #f8f9fa; - } - - .text-muted { - font-style: italic; - } - - .text-danger { - color: #dc3545; - } - - .text-center { - text-align: center; + .btn-group button, .btn-group a { + border-radius: 15px; /* добавлено для кнопок внутри .btn-group */ } </style> diff --git a/CandidateReviewClientApp/Views/User/UserProfileEdit.cshtml b/CandidateReviewClientApp/Views/User/UserProfileEdit.cshtml index d3a065c..7dd5ff7 100644 --- a/CandidateReviewClientApp/Views/User/UserProfileEdit.cshtml +++ b/CandidateReviewClientApp/Views/User/UserProfileEdit.cshtml @@ -2,68 +2,60 @@ @model UserViewModel @{ - var title = @Model.Id <= 0 ? "Добавить сотрудника" : "Редактировать профиль"; + var title = @Model?.Id <= 0 ? "Добавить сотрудника" : "Редактировать профиль"; var userRole = APIClient.User?.Role == CandidateReviewDataModels.Enums.RoleEnum.Администратор ? true : false; } <div class="container mt-5"> <div class="row justify-content-center"> <div class="col-md-6"> - <h2 class="mb-4">Редактирование профиля</h2> + <h2 class="mb-4">@title</h2> <form method="post" class="needs-validation" novalidate> <input type="hidden" name="id" value="@Model?.Id" /> - <input type="hidden" name="password" value="@Model?.Password" /> + <input type="hidden" name="CompanyId" value="@Model?.CompanyId" /> <div class="mb-3"> - <label for="Surname" class="form-label">Фамилия <span class="text-danger">*</span></label> + <label for="Surname" class="form-label">Фамилия *</label> <input type="text" class="form-control" id="Surname" name="Surname" value="@Model?.Surname" required /> - <div class="invalid-feedback">Пожалуйста, введите фамилию.</div> </div> <div class="mb-3"> - <label for="Name" class="form-label">Имя <span class="text-danger">*</span></label> + <label for="Name" class="form-label">Имя *</label> <input type="text" class="form-control" id="Name" name="Name" value="@Model?.Name" required /> - <div class="invalid-feedback">Пожалуйста, введите имя.</div> </div> <div class="mb-3"> <label for="LastName" class="form-label">Отчество</label> <input type="text" class="form-control" id="LastName" name="LastName" value="@Model?.LastName" /> - <div class="invalid-feedback">Пожалуйста, введите отчество.</div> </div> <div class="mb-3"> - <label for="Email" class="form-label">Электронная почта <span class="text-danger">*</span></label> - <input type="text" class="form-control" id="Email" name="Email" value="@Model?.Email" readonly/> - <div class="invalid-feedback">Пожалуйста, введите электронную почту.</div> + <label for="Email" class="form-label">Электронная почта *</label> + <input type="email" class="form-control" id="Email" name="Email" value="@Model?.Email" @(Model?.Id < 0 ? "readonly" : "") required /> </div> - @if (Model.Id <= 0) + @if (Model?.Id <= 0) { <div class="mb-3"> - <label for="Password" class="form-label">Пароль <span class="text-danger">*</span></label> - <input type="text" class="form-control" id="Password" name="Password" value="@Model?.Password" /> - <div class="invalid-feedback">Пожалуйста, введите пароль.</div> + <label for="Password" class="form-label">Пароль *</label> + <input type="password" class="form-control" id="Password" name="Password" required /> </div> } + else + { + <input type="hidden" name="password" value="@Model?.Password" /> + } <div class="mb-3"> <label for="PhoneNumber" class="form-label">Телефон</label> <input type="tel" class="form-control" id="PhoneNumber" name="PhoneNumber" value="@Model?.PhoneNumber" /> - <div class="invalid-feedback">Пожалуйста, введите телефон.</div> </div> - <input type="hidden" name="Role" value="@Model?.Role" /> - <input type="hidden" name="CompanyId" value="@Model?.CompanyId" /> - <div class="mb-3"> - <label for="AvatarFilePath" class="form-label">Аватар</label> - <input type="file" class="form-control" id="AvatarFilePath" name="AvatarFilePath" accept=".jpg,.jpeg,.png" /> - <img id="avatarPreview" src="@Model?.AvatarFilePath" alt="Предварительный просмотр аватара" style="max-width: 100px; max-height: 100px;" /> - <div class="invalid-feedback">Выберите файл изображения.</div> - </div> + <input type="hidden" name="Role" value="@Model?.Role" /> + <div class="d-flex justify-content-between"> <button type="submit" class="btn btn-primary">Сохранить</button> - <button class="btn btn-secondary" onclick="window.history.back();">Назад</button> + <button type="button" class="btn btn-secondary" onclick="window.history.back();">Назад</button> </div> </form> </div> @@ -105,26 +97,26 @@ }) })() </script> - <script> - const avatarInput = document.getElementById('AvatarFilePath'); - const avatarPreview = document.getElementById('avatarPreview'); - - avatarInput.addEventListener('change', function (event) { - const file = event.target.files[0]; - if (file) { - const reader = new FileReader(); - reader.onload = function (e) { - avatarPreview.src = e.target.result; - } - reader.readAsDataURL(file); - } else { - avatarPreview.src = "@Model?.AvatarFilePath"; - } - }); - </script> @{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); } } +<style> + .btn { + background-color: #0A1128; + color: white; + border: none; + border-radius: 10px; + padding: 12px; + font-size: 16px; + transition: background-color 0.3s ease, transform 0.2s ease; + } + + .btn:hover { + background-color: #1C3273; + transform: scale(1.05); + } +</style> + diff --git a/CandidateReviewClientApp/Views/Vacancy/EditVacancy.cshtml b/CandidateReviewClientApp/Views/Vacancy/EditVacancy.cshtml index afa36e5..cf64d6e 100644 --- a/CandidateReviewClientApp/Views/Vacancy/EditVacancy.cshtml +++ b/CandidateReviewClientApp/Views/Vacancy/EditVacancy.cshtml @@ -91,3 +91,20 @@ })() </script> +<style> + .btn { + background-color: #0A1128; + color: white; + border: none; + border-radius: 10px; + padding: 12px; + font-size: 16px; + transition: background-color 0.3s ease, transform 0.2s ease; + } + + .btn:hover { + background-color: #1C3273; + transform: scale(1.05); + } +</style> + diff --git a/CandidateReviewClientApp/Views/Vacancy/SearchVacancies.cshtml b/CandidateReviewClientApp/Views/Vacancy/SearchVacancies.cshtml index 3e6dbc0..80270a6 100644 --- a/CandidateReviewClientApp/Views/Vacancy/SearchVacancies.cshtml +++ b/CandidateReviewClientApp/Views/Vacancy/SearchVacancies.cshtml @@ -26,6 +26,7 @@ <table class="table table-striped table-bordered"> <thead class="table-light"> <tr> + <th>Компания</th> <th>Название вакансии</th> <th>Тип работы</th> <th>Зарплата</th> @@ -37,6 +38,7 @@ @foreach (var vacancy in Model.Where(v => v != null)) { <tr> + <td>@vacancy.CompanyName</td> <td>@vacancy.JobTitle</td> <td>@vacancy.JobType</td> <td>@vacancy.Salary</td> @@ -87,10 +89,6 @@ margin-bottom: 10px; } - .btn-info, .btn-success { - margin-right: 10px; - } - .input-group { border-radius: 5px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); @@ -109,17 +107,20 @@ background-color: #f9f9f9; } - .table-light { - background-color: #f8f9fa; + .btn { + background-color: #0A1128; + color: white; + border: none; + border-radius: 10px; + padding: 12px; + font-size: 16px; + transition: background-color 0.3s ease, transform 0.2s ease; } - .text-muted { - font-style: italic; - } - - .text-danger { - color: #dc3545; - } + .btn:hover { + background-color: #1C3273; + transform: scale(1.05); + } .text-center { text-align: center; diff --git a/CandidateReviewClientApp/Views/Vacancy/Vacancies.cshtml b/CandidateReviewClientApp/Views/Vacancy/Vacancies.cshtml new file mode 100644 index 0000000..31f6bcc --- /dev/null +++ b/CandidateReviewClientApp/Views/Vacancy/Vacancies.cshtml @@ -0,0 +1,107 @@ +@using CandidateReviewContracts.ViewModels +@model List<VacancyViewModel> + +@{ + ViewData["Title"] = "Вакансии"; +} + +<div class="container mt-5"> + <div class="row justify-content-center"> + <div class="col-md-12"> + <h1 class="mb-4 text-center">Вакансии</h1> + <div class="mb-4 text-end"> + <a asp-action="EditVacancy" asp-controller="Vacancy" asp-route-companyId="@APIClient.Company?.Id" class="btn btn-success"> + <i class="bi bi-plus-circle me-2"></i> Добавить вакансию + </a> + </div> + @if (Model != null) + { + @if (Model.Any()) + { + <div class="table-responsive"> + <table class="table table-striped table-hover align-middle"> + <thead class="table-light"> + <tr> + <th>Название вакансии</th> + <th>Тип занятости</th> + <th>Зарплата</th> + <th>Статус</th> + <th>Тэги</th> + <th>Действия</th> + </tr> + </thead> + <tbody> + @foreach (var vacancy in Model) + { + <tr> + <td>@vacancy.JobTitle</td> + <td>@vacancy.JobType</td> + <td>@vacancy.Salary</td> + <td>@vacancy.Tags</td> + <td>@vacancy.Status</td> + <td> + <div class="d-flex justify-content-center"> + <a asp-action="VacancyDetails" asp-controller="Vacancy" asp-route-id="@vacancy.Id" + class="btn btn-info btn-sm me-2" title="Просмотр"> + <i class="bi bi-eye"></i> Просмотр + </a> + <a asp-action="EditVacancy" asp-controller="Vacancy" asp-route-id="@vacancy.Id" + class="btn btn-warning btn-sm me-2" title="Редактировать"> + <i class="bi bi-pencil-square"></i> Редактировать + </a> + <a asp-action="Delete" asp-controller="Vacancy" asp-route-id="@vacancy.Id" + class="btn btn-danger btn-sm" title="Удалить" + onclick="return confirm('Вы уверены, что хотите удалить вакансию?');"> + <i class="bi bi-trash"></i> Удалить + </a> + </div> + </td> + </tr> + } + </tbody> + </table> + </div> + } + else + { + <p class="text-center text-muted">Вакансий нет.</p> + } + } + else + { + <p class="text-center text-danger">Произошла ошибка при получении данных.</p> + } + </div> + </div> +</div> + +<style> + .table-hover tbody tr:hover { + background-color: #f1f1f1; + } + + .btn-info, .btn-warning, .btn-danger { + display: flex; + align-items: center; + justify-content: center; + } + + .btn-info i, .btn-warning i, .btn-danger i { + margin-right: 5px; + } + + .btn { + background-color: #0A1128; + color: white; + border: none; + border-radius: 10px; + padding: 12px; + font-size: 16px; + transition: background-color 0.3s ease, transform 0.2s ease; + } + + .btn:hover { + background-color: #1C3273; + transform: scale(1.05); + } +</style> diff --git a/CandidateReviewClientApp/Views/Vacancy/VacancyDetails.cshtml b/CandidateReviewClientApp/Views/Vacancy/VacancyDetails.cshtml index e48b09a..c524ec7 100644 --- a/CandidateReviewClientApp/Views/Vacancy/VacancyDetails.cshtml +++ b/CandidateReviewClientApp/Views/Vacancy/VacancyDetails.cshtml @@ -104,3 +104,20 @@ </div> <script src="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.min.js"></script> + +<style> + .btn { + background-color: #0A1128; + color: white; + border: none; + border-radius: 10px; + padding: 12px; + font-size: 16px; + transition: background-color 0.3s ease, transform 0.2s ease; + } + + .btn:hover { + background-color: #1C3273; + transform: scale(1.05); + } +</style> diff --git a/CandidateReviewContracts/BindingModels/AssessmentBindingModel.cs b/CandidateReviewContracts/BindingModels/AssessmentBindingModel.cs index 4e415cd..86d81f0 100644 --- a/CandidateReviewContracts/BindingModels/AssessmentBindingModel.cs +++ b/CandidateReviewContracts/BindingModels/AssessmentBindingModel.cs @@ -10,7 +10,7 @@ namespace CandidateReviewContracts.BindingModels public string? Comment { get; set; } public int Id { get; set; } - public Dictionary<int, (ICriterionModel, int)> AssessmentCriterions { get; set; } = new(); + public Dictionary<int, (CriterionModel, int)> AssessmentCriterions { get; set; } = new(); public int? ResumeId { get; set; } } diff --git a/CandidateReviewContracts/StoragesContracts/IAssessmentStorage.cs b/CandidateReviewContracts/StoragesContracts/IAssessmentStorage.cs index 3db51fd..525f924 100644 --- a/CandidateReviewContracts/StoragesContracts/IAssessmentStorage.cs +++ b/CandidateReviewContracts/StoragesContracts/IAssessmentStorage.cs @@ -8,7 +8,6 @@ namespace CandidateReviewContracts.StoragesContracts { List<AssessmentViewModel> GetFullList(); List<AssessmentViewModel> GetFilteredList(AssessmentSearchModel model); - List<AssessmentCriterionViewModel>? GetAssessmentCriterions(int? assessmentId); AssessmentViewModel? GetElement(AssessmentSearchModel model); int? Insert(AssessmentBindingModel model); AssessmentViewModel? Update(AssessmentBindingModel model); diff --git a/CandidateReviewContracts/ViewModels/AssessmentCriterionViewModel.cs b/CandidateReviewContracts/ViewModels/AssessmentCriterionViewModel.cs deleted file mode 100644 index 76ad759..0000000 --- a/CandidateReviewContracts/ViewModels/AssessmentCriterionViewModel.cs +++ /dev/null @@ -1,17 +0,0 @@ -using CandidateReviewDataModels.Models; - -namespace CandidateReviewContracts.ViewModels -{ - public class AssessmentCriterionViewModel : IAssessmentCriterionModel - { - public int AssessmentId { get; set; } - - public int CriterionId { get; set; } - - public string CriterionName { get; set; } = string.Empty; - - public int Value { get; set; } - - public int Id { get; set; } - } -} diff --git a/CandidateReviewContracts/ViewModels/AssessmentViewModel.cs b/CandidateReviewContracts/ViewModels/AssessmentViewModel.cs index 24b46d9..f4759d9 100644 --- a/CandidateReviewContracts/ViewModels/AssessmentViewModel.cs +++ b/CandidateReviewContracts/ViewModels/AssessmentViewModel.cs @@ -14,6 +14,6 @@ namespace CandidateReviewContracts.ViewModels public int? ResumeId { get; set; } - public List<AssessmentCriterionViewModel> AssessmentCriterions { get; set; } = new(); + public Dictionary<int, (CriterionModel, int)> AssessmentCriterions { get; set; } = new(); } } diff --git a/CandidateReviewContracts/ViewModels/UserViewModel.cs b/CandidateReviewContracts/ViewModels/UserViewModel.cs index fc5ac1c..4aeba8e 100644 --- a/CandidateReviewContracts/ViewModels/UserViewModel.cs +++ b/CandidateReviewContracts/ViewModels/UserViewModel.cs @@ -28,5 +28,6 @@ namespace CandidateReviewContracts.ViewModels public int Id { get; set; } public List<ResumeViewModel> Resumes { get; set; } = new(); + public List<AssessmentViewModel> Assessments { get; set; } = new(); } } diff --git a/CandidateReviewDataModels/Models/CriterionModel.cs b/CandidateReviewDataModels/Models/CriterionModel.cs new file mode 100644 index 0000000..7308547 --- /dev/null +++ b/CandidateReviewDataModels/Models/CriterionModel.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CandidateReviewDataModels.Models +{ + public class CriterionModel : ICriterionModel + { + public string Name { get; set; } = string.Empty; + + public int Id { get; set; } + } +} diff --git a/CandidateReviewDataModels/Models/IAssessmentCriterionModel.cs b/CandidateReviewDataModels/Models/IAssessmentCriterionModel.cs deleted file mode 100644 index 61d3dce..0000000 --- a/CandidateReviewDataModels/Models/IAssessmentCriterionModel.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace CandidateReviewDataModels.Models -{ - public interface IAssessmentCriterionModel : IId - { - int AssessmentId { get; } - int CriterionId { get; } - int Value { get; } - } -} diff --git a/CandidateReviewDatabaseImplement/Implements/AssessmentStorage.cs b/CandidateReviewDatabaseImplement/Implements/AssessmentStorage.cs index 8fa9982..e2d98ea 100644 --- a/CandidateReviewDatabaseImplement/Implements/AssessmentStorage.cs +++ b/CandidateReviewDatabaseImplement/Implements/AssessmentStorage.cs @@ -15,10 +15,10 @@ namespace CandidateReviewDatabaseImplement.Implements using var context = new CandidateReviewDatabase(); var element = context.Assessments - .Include(x => x.User) - .Include(x => x.Resume) - .Include(x => x.Criterions) - .ThenInclude(x => x.Criterion) + .Include(x => x.Criterions) + .ThenInclude(x => x.Criterion) + .Include(x => x.User) + .Include(x => x.Resume) .FirstOrDefault(rec => rec.Id == model.Id); if (element != null) @@ -62,21 +62,6 @@ namespace CandidateReviewDatabaseImplement.Implements ?.GetViewModel; } - public List<AssessmentCriterionViewModel>? GetAssessmentCriterions(int? assessmentId) - { - if (!assessmentId.HasValue) - { - return null; - } - using var context = new CandidateReviewDatabase(); - - return context.AssessmentCriterions - .Where(x => x.AssessmentId == assessmentId) - .ToList() - .Select(x => x.GetViewModel) - .ToList(); - } - public List<AssessmentViewModel> GetFilteredList(AssessmentSearchModel model) { if (model is null) @@ -123,26 +108,15 @@ namespace CandidateReviewDatabaseImplement.Implements { using var context = new CandidateReviewDatabase(); - var newAssessment = new Assessment + var newAssessment = Assessment.Create(context, model); + if (newAssessment == null) { - ResumeId = model.ResumeId, - UserId = model.UserId, - Comment = model.Comment - }; + return null; + } context.Assessments.Add(newAssessment); context.SaveChanges(); - /*var criterionsToAdd = model.AssessmentCriterions.Select(x => new AssessmentCriterion - { - AssessmentId = newAssessment.Id, - Criterion = context.Criterions.First(y => y.Id == x.Key), - Value = x.Value.Item2 - }).ToList(); - - context.AssessmentCriterions.AddRange(criterionsToAdd); - context.SaveChanges();*/ - return newAssessment.Id; } diff --git a/CandidateReviewDatabaseImplement/Implements/VacancyStorage.cs b/CandidateReviewDatabaseImplement/Implements/VacancyStorage.cs index 9d6a173..0d6e972 100644 --- a/CandidateReviewDatabaseImplement/Implements/VacancyStorage.cs +++ b/CandidateReviewDatabaseImplement/Implements/VacancyStorage.cs @@ -71,6 +71,15 @@ namespace CandidateReviewDatabaseImplement.Implements .Select(x => x.GetViewModel) .ToList(); } + if (model.CompanyId.HasValue) + { + return context.Vacancies + .Include(x => x.Company) + .Where(x => x.CompanyId == model.CompanyId) + .ToList() + .Select(x => x.GetViewModel) + .ToList(); + } if (!string.IsNullOrEmpty(model.Tags) && model.Status.HasValue) { var tags = model.Tags.Split(' ', StringSplitOptions.RemoveEmptyEntries).Select(t => t.ToLowerInvariant()).ToArray(); diff --git a/CandidateReviewDatabaseImplement/Models/Assessment.cs b/CandidateReviewDatabaseImplement/Models/Assessment.cs index 4fbaafe..430703e 100644 --- a/CandidateReviewDatabaseImplement/Models/Assessment.cs +++ b/CandidateReviewDatabaseImplement/Models/Assessment.cs @@ -18,9 +18,9 @@ namespace CandidateReviewDatabaseImplement.Models public virtual User User { get; set; } public virtual Resume Resume { get; set; } - private Dictionary<int, (ICriterionModel, int)>? _assessmentCriterions = null; + private Dictionary<int, (CriterionModel, int)>? _assessmentCriterions = null; - public Dictionary<int, (ICriterionModel, int)> AssessmentCriterions + public Dictionary<int, (CriterionModel, int)> AssessmentCriterions { get { @@ -28,7 +28,7 @@ namespace CandidateReviewDatabaseImplement.Models { _assessmentCriterions = Criterions .ToDictionary(recAC => recAC.CriterionId, recAC => - (recAC.Criterion as ICriterionModel, recAC.Value)); + (new CriterionModel { Id = recAC.Criterion.Id, Name = recAC.Criterion.Name }, recAC.Value)); } return _assessmentCriterions; } @@ -37,22 +37,20 @@ namespace CandidateReviewDatabaseImplement.Models [ForeignKey("AssessmentId")] public virtual List<AssessmentCriterion> Criterions { get; set; } = new(); - public static Assessment? Create(CandidateReviewDatabase context, AssessmentBindingModel model) + public static Assessment Create(CandidateReviewDatabase context, AssessmentBindingModel model) { - if (model == null) - { - return null; - } - - var assessment = new Assessment + return new Assessment() { Id = model.Id, ResumeId = model.ResumeId, UserId = model.UserId, Comment = model.Comment, + Criterions = model.AssessmentCriterions.Select(x => new AssessmentCriterion + { + Assessment = context.Assessments.First(y => y.Id == x.Key), + Value = x.Value.Item2 + }).ToList() }; - - return assessment; } public void Update(AssessmentBindingModel model) @@ -66,15 +64,6 @@ namespace CandidateReviewDatabaseImplement.Models Comment = model.Comment; } - private List<AssessmentCriterionViewModel> GetAssessmentCriterionsAsViewModel() - { - return AssessmentCriterions.Select(ac => new AssessmentCriterionViewModel - { - CriterionId = ac.Key, - Value = ac.Value.Item2, - }).ToList(); - } - public AssessmentViewModel GetViewModel => new() { @@ -82,7 +71,7 @@ namespace CandidateReviewDatabaseImplement.Models ResumeId = ResumeId, UserId = UserId, Comment = Comment, - AssessmentCriterions = GetAssessmentCriterionsAsViewModel() + AssessmentCriterions = AssessmentCriterions }; public void UpdateCriterions(CandidateReviewDatabase context, AssessmentBindingModel model) @@ -102,14 +91,20 @@ namespace CandidateReviewDatabaseImplement.Models var assessment = context.Assessments.First(x => x.Id == Id); foreach (var ac in model.AssessmentCriterions) { + var criterion = context.Criterions.First(x => x.Id == ac.Value.Item1.Id); + if (criterion == null) + { + throw new Exception($"Критерий с ID {ac.Key} не найден."); + } + context.AssessmentCriterions.Add(new AssessmentCriterion { Assessment = assessment, - Criterion = context.Criterions.First(x => x.Id == ac.Key), + Criterion = criterion, Value = ac.Value.Item2 }); - context.SaveChanges(); } + context.SaveChanges(); _assessmentCriterions = null; } } diff --git a/CandidateReviewDatabaseImplement/Models/AssessmentCriterion.cs b/CandidateReviewDatabaseImplement/Models/AssessmentCriterion.cs index c47a395..05de3ee 100644 --- a/CandidateReviewDatabaseImplement/Models/AssessmentCriterion.cs +++ b/CandidateReviewDatabaseImplement/Models/AssessmentCriterion.cs @@ -14,13 +14,5 @@ namespace CandidateReviewDatabaseImplement.Models public int Value { get; set; } public virtual Assessment Assessment { get; set; } = new(); public virtual Criterion Criterion { get; set; } = new(); - - public AssessmentCriterionViewModel GetViewModel => new() - { - Id = Id, - AssessmentId = AssessmentId, - CriterionId = CriterionId, - Value = Value - }; } } diff --git a/CandidateReviewRestApi/Controllers/VacancyController.cs b/CandidateReviewRestApi/Controllers/VacancyController.cs index 6e50368..50ea209 100644 --- a/CandidateReviewRestApi/Controllers/VacancyController.cs +++ b/CandidateReviewRestApi/Controllers/VacancyController.cs @@ -57,6 +57,27 @@ namespace CandidateReviewRestApi.Controllers } } + [HttpGet] + public List<VacancyViewModel>? List(int? companyId) + { + try + { + if (companyId.HasValue) + { + return _logic.ReadList(new VacancySearchModel + { + CompanyId = companyId + }); + } + else return new List<VacancyViewModel>(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Ошибка получения вакансий"); + throw; + } + } + [HttpPost] public void Create(VacancyBindingModel model) {