From 10dd03fae1ddfabb6ed7a6a2f5b2bfeb821c69c0 Mon Sep 17 00:00:00 2001 From: mlan225 <17088502+mlan225@users.noreply.github.com> Date: Thu, 1 Feb 2024 12:06:29 -0500 Subject: [PATCH 1/2] Add blank table to participation page (#103) * Add blank table to participation page * POC of the milestones appearing on the participation view Currently showing fewer details than the uds version, but currently pulling milestones where they need to be * In progress commit for applying milestone fields to page Focusing on getting the inputs on the view for now, will need to go back to restyle the details and apply validation * Build out the create view with client side validation * Create the milestone via the form * Create a base pageModel and add edit razor page code * Add in status for edit and adjust the milestone type logic * Add in details view, adjustments to edit and create views * Push up for in progress pair programming * Add milestoneType behavior with JS and adjust milestone type property * Add client side validation for edit and create views * Add fancy checkboxes and deceased/discont validation * Fix css numbering and adjust label styles * Address TODO items and add description to .js file * remove TODO comments and adjust spacing * fix issue with milestone type * Fix linter errors * Fix linter issue * Fix whitespace errors in details * Update Details.cshtml.cs * fix participations/details linter error * Add range validation to other month and day properties * update dto and client * Address PR change requests, apply date validation, add getMilestone endpoint Some of the changes include: - text changes in questions - adjusted layout of checkbox questions - removed labels from "other" type inputs and date inputs - added styling for instructional text / used descriptions where possible for guidance - Add date validation for month, day, and year * Fix whitespace errors * Additional view adjustments * Update year ranges and invalid date text * Layout adjustments * Update the milestone create view * Address review changes * adjust comment to provide a more detailed reasoning * Fix whitespace formatting * remove comment in details view The isDeleted in the API is handled slightly different than the rest of the checkbox properties, I implemented it like this to match how the other checkbox values are displayed. Type = text forces the input to show true or false as a value. for example: The checkbox values on the milestone in the API are converted between nullable ints and used as nullable bools on the views, however isDeleted is a derived property from the base entity and it is always a non nullable bool. * Cleanup --------- Co-authored-by: Ashley Wilson --- .../Extensions/DomainToViewModelMapper.cs | 48 ++ .../Extensions/ViewModelToDomainMapper.cs | 43 ++ src/UDS.Net.Forms/Models/MilestoneModel.cs | 73 +++ .../Models/PageModels/MilestonePageModel.cs | 272 +++++++++++ .../Pages/Milestones/Create.cshtml | 449 ++++++++++++++++++ .../Pages/Milestones/Create.cshtml.cs | 50 ++ .../Pages/Milestones/Details.cshtml | 301 ++++++++++++ .../Pages/Milestones/Details.cshtml.cs | 17 + .../Pages/Milestones/Edit.cshtml | 448 +++++++++++++++++ .../Pages/Milestones/Edit.cshtml.cs | 49 ++ .../Pages/Participations/Details.cshtml | 62 ++- .../Pages/Participations/Details.cshtml.cs | 12 +- .../Pages/Shared/_MilestoneDetails.cshtml | 76 +++ .../js_controllers/MilestoneTypeBehavior.js | 51 ++ .../DomainModels/Milestone.cs | 45 ++ .../Extensions/DomainToDtoMapper.cs | 43 +- .../Extensions/DtoToDomainMapper.cs | 50 +- src/UDS.Net.Services/IParticipationService.cs | 5 + src/UDS.Net.Services/UDS.Net.Services.csproj | 2 +- .../ParticipationService.cs | 30 +- .../UDS.Net.Web.MVC.Services.csproj | 4 +- src/UDS.Net.Web.MVC/UDS.Net.Web.MVC.csproj | 4 +- 22 files changed, 2117 insertions(+), 17 deletions(-) create mode 100644 src/UDS.Net.Forms/Models/MilestoneModel.cs create mode 100644 src/UDS.Net.Forms/Models/PageModels/MilestonePageModel.cs create mode 100644 src/UDS.Net.Forms/Pages/Milestones/Create.cshtml create mode 100644 src/UDS.Net.Forms/Pages/Milestones/Create.cshtml.cs create mode 100644 src/UDS.Net.Forms/Pages/Milestones/Details.cshtml create mode 100644 src/UDS.Net.Forms/Pages/Milestones/Details.cshtml.cs create mode 100644 src/UDS.Net.Forms/Pages/Milestones/Edit.cshtml create mode 100644 src/UDS.Net.Forms/Pages/Milestones/Edit.cshtml.cs create mode 100644 src/UDS.Net.Forms/Pages/Shared/_MilestoneDetails.cshtml create mode 100644 src/UDS.Net.Forms/wwwroot/js/js_controllers/MilestoneTypeBehavior.js create mode 100644 src/UDS.Net.Services/DomainModels/Milestone.cs diff --git a/src/UDS.Net.Forms/Extensions/DomainToViewModelMapper.cs b/src/UDS.Net.Forms/Extensions/DomainToViewModelMapper.cs index bfe575da..3a644310 100644 --- a/src/UDS.Net.Forms/Extensions/DomainToViewModelMapper.cs +++ b/src/UDS.Net.Forms/Extensions/DomainToViewModelMapper.cs @@ -79,6 +79,54 @@ public static VisitModel ToVM(this Visit visit) }; } + public static MilestoneModel ToVM(this Milestone milestone) + { + return new MilestoneModel() + { + Id = milestone.Id, + FormId = milestone.FormId, + ParticipationId = milestone.ParticipationId, + Status = milestone.Status, + CHANGEMO = milestone.CHANGEMO, + CHANGEDY = milestone.CHANGEDY, + CHANGEYR = milestone.CHANGEYR, + PROTOCOL = milestone.PROTOCOL, + ACONSENT = milestone.ACONSENT, + RECOGIM = milestone.RECOGIM.HasValue ? true : false, + REPHYILL = milestone.REPHYILL.HasValue ? true : false, + REREFUSE = milestone.REREFUSE.HasValue ? true : false, + RENAVAIL = milestone.RENAVAIL.HasValue ? true : false, + RENURSE = milestone.RENURSE.HasValue ? true : false, + NURSEMO = milestone.NURSEMO, + NURSEDY = milestone.NURSEDY, + NURSEYR = milestone.NURSEYR, + REJOIN = milestone.REJOIN.HasValue ? true : false, + FTLDDISC = milestone.FTLDDISC.HasValue ? true : false, + FTLDREAS = milestone.FTLDREAS, + FTLDREAX = milestone.FTLDREAX, + DECEASED = milestone.DECEASED.HasValue ? true : false, + DISCONT = milestone.DISCONT.HasValue ? true : false, + DEATHMO = milestone.DEATHMO, + DEATHDY = milestone.DEATHDY, + DEATHYR = milestone.DEATHYR, + AUTOPSY = milestone.AUTOPSY, + DISCMO = milestone.DISCMO, + DISCDAY = milestone.DISCDAY, + DISCYR = milestone.DISCYR, + DROPREAS = milestone.DROPREAS, + CreatedAt = milestone.CreatedAt, + CreatedBy = milestone.CreatedBy, + ModifiedBy = milestone.ModifiedBy, + DeletedBy = milestone.DeletedBy, + IsDeleted = milestone.IsDeleted, + }; + } + + public static IEnumerable ToVM(this IEnumerable milestones) + { + return milestones.Select(m => m.ToVM()).ToList(); + } + public static List ToVM(this IList
forms) { List vm = new List(); diff --git a/src/UDS.Net.Forms/Extensions/ViewModelToDomainMapper.cs b/src/UDS.Net.Forms/Extensions/ViewModelToDomainMapper.cs index 3198309b..b5a7a66b 100644 --- a/src/UDS.Net.Forms/Extensions/ViewModelToDomainMapper.cs +++ b/src/UDS.Net.Forms/Extensions/ViewModelToDomainMapper.cs @@ -26,6 +26,49 @@ public static Participation ToEntity(this ParticipationModel vm) }; } + public static Milestone ToEntity(this MilestoneModel vm) + { + return new Milestone + { + Id = vm.Id, + FormId = vm.FormId, + ParticipationId = vm.ParticipationId, + Status = vm.Status, + CHANGEMO = vm.CHANGEMO, + CHANGEDY = vm.CHANGEDY, + CHANGEYR = vm.CHANGEYR, + PROTOCOL = vm.PROTOCOL, + ACONSENT = vm.ACONSENT, + RECOGIM = vm.RECOGIM == true ? 1 : null, + REPHYILL = vm.REPHYILL == true ? 1 : null, + REREFUSE = vm.REREFUSE == true ? 1 : null, + RENAVAIL = vm.RENAVAIL == true ? 1 : null, + RENURSE = vm.RENURSE == true ? 1 : null, + NURSEMO = vm.NURSEMO, + NURSEDY = vm.NURSEDY, + NURSEYR = vm.NURSEYR, + REJOIN = vm.REJOIN == true ? 1 : null, + FTLDDISC = vm.FTLDDISC == true ? 1 : null, + FTLDREAS = vm.FTLDREAS, + FTLDREAX = vm.FTLDREAX, + DECEASED = vm.DECEASED == true ? 1 : null, + DISCONT = vm.DISCONT == true ? 1 : null, + DEATHMO = vm.DEATHMO, + DEATHDY = vm.DEATHDY, + DEATHYR = vm.DEATHYR, + AUTOPSY = vm.AUTOPSY, + DISCMO = vm.DISCMO, + DISCDAY = vm.DISCDAY, + DISCYR = vm.DISCYR, + DROPREAS = vm.DROPREAS, + CreatedAt = vm.CreatedAt, + CreatedBy = vm.CreatedBy, + ModifiedBy = vm.ModifiedBy, + DeletedBy = vm.DeletedBy, + IsDeleted = vm.IsDeleted, + }; + } + public static Visit ToEntity(this VisitModel vm) { return new Visit(vm.Id, vm.Number, vm.ParticipationId, vm.Version, vm.Kind, vm.StartDateTime, vm.CreatedAt, vm.CreatedBy, vm.ModifiedBy, vm.DeletedBy, vm.IsDeleted, vm.Forms.ToEntity()); diff --git a/src/UDS.Net.Forms/Models/MilestoneModel.cs b/src/UDS.Net.Forms/Models/MilestoneModel.cs new file mode 100644 index 00000000..9716d69b --- /dev/null +++ b/src/UDS.Net.Forms/Models/MilestoneModel.cs @@ -0,0 +1,73 @@ +using System; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using UDS.Net.Forms.DataAnnotations; +using UDS.Net.Services.Enums; + +namespace UDS.Net.Forms.Models +{ + public class MilestoneModel + { + public int Id { get; set; } + public int FormId { get; set; } + public int ParticipationId { get; set; } + [Display(Name = "Status")] + public string Status { get; set; } = "Complete"; + [Display(Name = "Month")] + public int? CHANGEMO { get; set; } + [Display(Name = "Day")] + public int? CHANGEDY { get; set; } + [Display(Name = "Year")] + public int? CHANGEYR { get; set; } + [Display(Name = "UDS data collection status changed; participant's new status is")] + public int? PROTOCOL { get; set; } + [Display(Name = "Autopsy consent on file?")] + public int? ACONSENT { get; set; } + [Display(Name = "Participant is too cognitively impaired.")] + public bool? RECOGIM { get; set; } + [Display(Name = "Participant is too ill or physically impaired.")] + public bool? REPHYILL { get; set; } + [Display(Name = "Participant refuses neuropsychological testing or clinical exam.")] + public bool? REREFUSE { get; set; } + [Display(Name = "Participant or co-participant unreachable, not available, or moved away.")] + public bool? RENAVAIL { get; set; } + [Display(Name = "Participant has permanently entered nursing home.")] + public bool? RENURSE { get; set; } + public int? NURSEMO { get; set; } + public int? NURSEDY { get; set; } + public int? NURSEYR { get; set; } + [Display(Name = "Participant is REJOINING ADC.")] + public bool? REJOIN { get; set; } + [Display(Name = "Participant will no longer receive FTLD Module follow-up, but annual in-person UDS visits will continue")] + public bool? FTLDDISC { get; set; } + public int? FTLDREAS { get; set; } + public string? FTLDREAX { get; set; } + [Display(Name = "Participant has died")] + public bool? DECEASED { get; set; } + [Display(Name = "Participant has been dropped from ADC")] + public bool? DISCONT { get; set; } + public int? DEATHMO { get; set; } + public int? DEATHDY { get; set; } + public int? DEATHYR { get; set; } + [Display(Name = "ADC autopsy")] + public int? AUTOPSY { get; set; } + public int? DISCMO { get; set; } + public int? DISCDAY { get; set; } + public int? DISCYR { get; set; } + [Display(Name = "Main reason for being dropped from ADC")] + public int? DROPREAS { get; set; } + public DateTime CreatedAt { get; set; } + public string? CreatedBy { get; set; } + public string? ModifiedBy { get; set; } + public string? DeletedBy { get; set; } + public bool IsDeleted { get; set; } + //Temporary properties only used in the view and NOT sent to the API + [Required] + [Display(Name = "Which milestone type are you reporting?")] + [Range(0, 1)] + //MilestoneType plays a role in client-side validation, must be answered but not stored + public int MilestoneType { get; set; } + //validation properties used as targets for validation messages in the manual validation + public int ProtocolReasonValidation { get; set; } + } +} \ No newline at end of file diff --git a/src/UDS.Net.Forms/Models/PageModels/MilestonePageModel.cs b/src/UDS.Net.Forms/Models/PageModels/MilestonePageModel.cs new file mode 100644 index 00000000..75738715 --- /dev/null +++ b/src/UDS.Net.Forms/Models/PageModels/MilestonePageModel.cs @@ -0,0 +1,272 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using UDS.Net.Forms.TagHelpers; +using UDS.Net.Services; + +namespace UDS.Net.Forms.Models.PageModels +{ + public class MilestonePageModel : PageModel + { + protected readonly IParticipationService _participationService; + + [BindProperty] + public MilestoneModel? Milestone { get; set; } + + public MilestonePageModel(IParticipationService participationService) + { + _participationService = participationService; + } + + public List MilestoneTypeItems { get; } = new List + { + new RadioListItem("Data-collection status CHANGE followed by CONTINUED CONTACT with participant (Box A)", "1"), + new RadioListItem("Change followed by NO FURTHER CONTACT with participant (Box B)", "0") + }; + + public List ProtocolItems { get; } = new List + { + new RadioListItem("Annual UDS follow-up by telephone (CONTINUE TO QUESTION 2A1)", "1"), + new RadioListItem("Minimal contact (CONTINUE TO QUESTION 2A1)", "2"), + new RadioListItem("Annual in-person UDS follow-up", "3") + }; + + public List AconsentItems { get; } = new List + { + new RadioListItem("No (CONTINUE TO QUESTION 2B)", "0"), + new RadioListItem("Yes (CONTINUE TO QUESTION 2B)", "1") + }; + + public List AutopsyItems { get; } = new List + { + new RadioListItem("No ADC autopsy expected", "0"), + new RadioListItem("An ADC autopsy has been done; data submitted or pending", "1") + }; + + public List DropReasonItems { get; } = new List + { + new RadioListItem("ADC decision or protocol", "1"), + new RadioListItem("Participant or co-participant asked to be dropped (ie. withdrawn)", "2") + }; + + public List FTLDREASItems { get; } = new List + { + new RadioListItem("ADC decision", "1"), + new RadioListItem("Participant/co-participant refused", "2"), + new RadioListItem("Co-participant not available", "3"), + new RadioListItem("Other, specify below", "4") + }; + + public Dictionary ProtocolBehavior = new Dictionary + { + { + "3", new UIBehavior + { + PropertyAttributes = new List + { + new UIDisableAttribute("Milestone.ACONSENT") + } + } + }, + { + "1", new UIBehavior + { + PropertyAttributes = new List + { + new UIDisableAttribute("Milestone.ACONSENT") + } + } + }, + { + "2", new UIBehavior + { + PropertyAttributes = new List + { + new UIEnableAttribute("Milestone.ACONSENT") + } + } + } + }; + + public Dictionary FTLDREASBehavior = new Dictionary + { + { + "4", new UIBehavior + { + PropertyAttributes = new List + { + new UIEnableAttribute("Milestone.FTLDREAX") + } + } + }, + { + "1", new UIBehavior + { + PropertyAttributes = new List + { + new UIDisableAttribute("Milestone.FTLDREAX") + } + } + }, + { + "2", new UIBehavior + { + PropertyAttributes = new List + { + new UIDisableAttribute("Milestone.FTLDREAX") + } + } + }, + { + "3", new UIBehavior + { + PropertyAttributes = new List + { + new UIDisableAttribute("Milestone.FTLDREAX") + } + } + }, + }; + + public Dictionary DECEASEDBehavior = new Dictionary + { + { + "true", new UIBehavior + { + PropertyAttributes = new List + { + new UIEnableAttribute("Milestone.DEATHDY"), + new UIEnableAttribute("Milestone.DEATHYR"), + new UIEnableAttribute("Milestone.AUTOPSY"), + new UIDisableAttribute("Milestone.DISCDAY"), + new UIDisableAttribute("Milestone.DISCYR"), + new UIDisableAttribute("Milestone.DROPREAS") + } + } + }, + { + "false", new UIBehavior + { + PropertyAttributes = new List + { + new UIDisableAttribute("Milestone.DEATHDY"), + new UIDisableAttribute("Milestone.DEATHYR"), + new UIDisableAttribute("Milestone.AUTOPSY"), + new UIEnableAttribute("Milestone.DISCDAY"), + new UIEnableAttribute("Milestone.DISCYR"), + new UIEnableAttribute("Milestone.DROPREAS") + } + } + } + }; + + protected private void IsValid(MilestoneModel milestone) + { + if (milestone.MilestoneType == 1) + { + ValidateMonth(milestone.CHANGEMO, "Milestone.CHANGEMO"); + ValidateDay(milestone.CHANGEDY, "Milestone.CHANGEDY"); + ValidateYear(milestone.CHANGEYR, "Milestone.CHANGEYR"); + + if (milestone.PROTOCOL == null) + { + ModelState.AddModelError("Milestone.PROTOCOL", "Must have a value when indicating continued contact"); + } + + if (milestone.PROTOCOL == 2 || milestone.PROTOCOL == 1 && milestone.ACONSENT == null) + { + ModelState.AddModelError("Milestone.ACONSENT", "Autopsy status required"); + } + + if (milestone.RECOGIM == false && milestone.REPHYILL == false && milestone.REREFUSE == false && milestone.RENAVAIL == false && milestone.RENURSE == false && milestone.REJOIN == false) + { + ModelState.AddModelError("Milestone.ProtocolReasonValidation", "Must select AT LEAST ONE reason for change as indicated in 2a"); + } + + if (milestone.RENURSE == true) + { + ValidateMonth(milestone.NURSEMO, "Milestone.NURSEMO"); + ValidateDay(milestone.NURSEDY, "Milestone.NURSEDY"); + ValidateYear(milestone.NURSEYR, "Milestone.NURSEYR"); + } + + if (milestone.FTLDREAS == 4 && String.IsNullOrEmpty(milestone.FTLDREAX)) + { + ModelState.AddModelError("Milestone.FTLDREAX", "Must have a value when indicating reason of other"); + } + } + + if (milestone.MilestoneType == 0) + { + if (milestone.DECEASED == false && milestone.DISCONT == false) + { + ModelState.AddModelError("Milestone.DECEASED", "When indicating no further contact, Deceased OR Discontinued must be select"); + ModelState.AddModelError("Milestone.DISCONT", "When indicating no further contact, Deceased OR Discontinued must be select"); + } + + if (milestone.DECEASED == true) + { + ValidateMonth(milestone.DEATHMO, "Milestone.DEATHMO"); + ValidateDay(milestone.DEATHDY, "Milestone.DEATHDY"); + ValidateYear(milestone.DEATHYR, "Milestone.DEATHYR"); + + if (milestone.AUTOPSY == null) + { + ModelState.AddModelError("Milestone.AUTOPSY", "Must have a value"); + } + } + + if (milestone.DISCONT == true) + { + ValidateMonth(milestone.DISCMO, "Milestone.DISCMO"); + ValidateDay(milestone.DISCDAY, "Milestone.DISCDAY"); + ValidateYear(milestone.DISCYR, "Milestone.DISCYR"); + + if (milestone.DROPREAS == null) + { + ModelState.AddModelError("Milestone.DROPREAS", "Must have a value"); + } + } + } + } + + private void ValidateMonth(int? monthValue, string property) + { + if (monthValue == null) + { + ModelState.AddModelError(property, "Must have a value for month"); + } + + if (monthValue < 1 || monthValue > 12 && monthValue != 99) + { + ModelState.AddModelError(property, "Provide a valid month between 1 - 12 or 99"); + } + } + + private void ValidateDay(int? dayValue, string property) + { + if (dayValue == null) + { + ModelState.AddModelError(property, "Must have a value for day"); + } + + if (dayValue < 1 || dayValue > 12 && dayValue != 99) + { + ModelState.AddModelError(property, "Provide a valid day between 1 - 31 or 99"); + } + } + + private void ValidateYear(int? yearValue, string property) + { + if (yearValue == null) + { + ModelState.AddModelError(property, "Must have a value for year"); + } + + if (yearValue < 2015 || yearValue > 2999) + { + ModelState.AddModelError(property, "Provide a valid year between 2015 - 2999"); + } + } + } +} + diff --git a/src/UDS.Net.Forms/Pages/Milestones/Create.cshtml b/src/UDS.Net.Forms/Pages/Milestones/Create.cshtml new file mode 100644 index 00000000..eaa655da --- /dev/null +++ b/src/UDS.Net.Forms/Pages/Milestones/Create.cshtml @@ -0,0 +1,449 @@ +@page +@model UDS.Net.Forms.Pages.Milestones.CreateModel + + +@{ + ViewData["Title"] = "Create milestone"; + string rowCSS = "sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:py-6"; + string pillNumberCSS = "inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-800"; + string labelInstructionCSS = "text-base uppercase text-indigo-600 italic"; +} + + +
+
+
+

M1 Milestones (February 18 Final, rev. 2/20)

+

+ INSTRUCTIONS: Please submit a new Milestones Form as soon as possible after each milestone event has occurred. +

+
+
+
+ +
+
+
+
+
+ + +
+
+
+ +
+
+
+
+
+
+

Box A

+

Change with continued contact

+
+
+
+ +
+
+
+

Unknown = 99/99/YEAR REQUIRED

+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ + + +
+
+
+
+
+ + Complete the UDS and FTLD sections below, as appropriate. + +
+
+
+
+ +
+
+
+
+
+
+
+ + +
+
+
+ + +
+ +
+
+ + +
+
+ +
+ + NOTE: Minimal contact status is reserved for participants who can no longer participate for one of the reasons listed in Question 2b, below. + +
+ +
+
+
+ + +
+ +
+
+ +
+
+ + +
+
+
+
+
+
+ +
+ +
+
+
+
+
+ @Html.CheckBox("Milestone.RECOGIM", Model.Milestone.RECOGIM, new { @class = "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600" }) +
+
+ +
+
+
+
+ @Html.CheckBox("Milestone.REPHYILL", Model.Milestone.REPHYILL, new { @class = "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600" }) +
+
+ +
+
+
+
+ @Html.CheckBox("Milestone.REREFUSE", Model.Milestone.REREFUSE, new { @class = "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600" }) +
+
+ +
+
+
+
+ @Html.CheckBox("Milestone.RENAVAIL", Model.Milestone.RENAVAIL, new { @class = "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600" }) +
+
+ +
+
+
+
+ @Html.CheckBox("Milestone.RENURSE", Model.Milestone.RENURSE, new { @class = "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600", data_affects = "true", data_affects_toggle_targets = "[ \"Milestone.NURSEMO\", \"Milestone.NURSEDY\", \"Milestone.NURSEYR\" ]" }) +
+
+ +
+
+
+
+ +
+
+
+
+

(unknown = 99/99/YEAR REQUIRED)

+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ + + +
+
+
+
+
+
+ @Html.CheckBox("Milestone.REJOIN", Model.Milestone.REJOIN, new { @class = "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600" }) +
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+ @Html.CheckBox("Milestone.FTLDDISC", Model.Milestone.FTLDDISC, new { @class = "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600" }) +
+
+
+ 1 +
+
+
+
+ +
+
+

+ + +
+
+ +
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+

Box B

+

No Further Contact

+
+
+
+
+ +
+
+ @Html.CheckBox("Milestone.DECEASED", Model.Milestone.DECEASED, new { @class = "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600", data_affects = "true", data_affects_toggle_targets = "[ \"Milestone.DEATHMO\", \"Milestone.DEATHDY\", \"Milestone.DEATHYR\", \"Milestone.AUTOPSY\" ]", data_fancycheckboxes_target = "checkbox" }) +
+
+ +
+ +
+
+
+
+
+
+
+ +
+
+ @Html.CheckBox("Milestone.DISCONT", Model.Milestone.DISCONT, new { @class = "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600", data_affects = "true", data_affects_toggle_targets = "[ \"Milestone.DISCMO\", \"Milestone.DISCDAY\", \"Milestone.DISCYR\", \"Milestone.DROPREAS\" ]", data_fancycheckboxes_target = "checkbox" }) +
+
+ +
+ +
+
+
+
+
+
+
+
+

Death

+
+
+
+
+
+
+
+ +
+
+
+

Unknown = 99/99/YEAR REQUIRED

+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ + + +
+
+
+
+
+ + +
+
+ +
+
+
+
+ END FORM HERE +
+
+
+
+
+
+
+
+
+
+

Dropped

+
+
+
+
+
+

DO NOT fill out this section if the participant has consented to autopsy or if you will maintain minimal contact. Instead, fill out BOX A QUESTION 2 UDS STATUS section.

+
+
+
+ +
+
+
+
+

Unknown = 99/99/YEAR REQUIRED

+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ + + +
+
+
+
+
+
+ + +
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+ + + + + + + + + + + +@section Scripts { + +} \ No newline at end of file diff --git a/src/UDS.Net.Forms/Pages/Milestones/Create.cshtml.cs b/src/UDS.Net.Forms/Pages/Milestones/Create.cshtml.cs new file mode 100644 index 00000000..e36041be --- /dev/null +++ b/src/UDS.Net.Forms/Pages/Milestones/Create.cshtml.cs @@ -0,0 +1,50 @@ +using Microsoft.AspNetCore.Mvc; +using UDS.Net.Forms.Extensions; +using UDS.Net.Forms.Models; +using UDS.Net.Forms.Models.PageModels; +using UDS.Net.Services; + + +namespace UDS.Net.Forms.Pages.Milestones +{ + public class CreateModel : MilestonePageModel + { + public CreateModel(IParticipationService participationService) : base(participationService) + { + } + + public async Task OnGet(int participationId) + { + MilestoneModel newMilstone = new MilestoneModel() + { + ParticipationId = participationId, + CreatedAt = DateTime.UtcNow, + CreatedBy = User.Identity!.IsAuthenticated ? User.Identity.Name : "Username", + IsDeleted = false, + }; + + Milestone = newMilstone; + + return Page(); + } + + public async Task OnPostAsync() + { + if (Milestone == null) + { + return Page(); + } + + IsValid(Milestone); + + if (ModelState.IsValid) + { + await _participationService.AddMilestone(Milestone.ParticipationId, Milestone.ToEntity()); + + return RedirectToPage("/Participations/Details", new { Id = Milestone.ParticipationId }); + } + + return Page(); + } + } +} diff --git a/src/UDS.Net.Forms/Pages/Milestones/Details.cshtml b/src/UDS.Net.Forms/Pages/Milestones/Details.cshtml new file mode 100644 index 00000000..bf26a730 --- /dev/null +++ b/src/UDS.Net.Forms/Pages/Milestones/Details.cshtml @@ -0,0 +1,301 @@ +@page +@model UDS.Net.Forms.Pages.Milestones.DetailsModel + +@{ + ViewData["Title"] = "Milestone details"; +} + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+
\ No newline at end of file diff --git a/src/UDS.Net.Forms/Pages/Milestones/Details.cshtml.cs b/src/UDS.Net.Forms/Pages/Milestones/Details.cshtml.cs new file mode 100644 index 00000000..de731537 --- /dev/null +++ b/src/UDS.Net.Forms/Pages/Milestones/Details.cshtml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using UDS.Net.Services; + +namespace UDS.Net.Forms.Pages.Milestones +{ + public class DetailsModel : EditModel + { + public DetailsModel(IParticipationService participationService) : base(participationService) + { + } + } +} diff --git a/src/UDS.Net.Forms/Pages/Milestones/Edit.cshtml b/src/UDS.Net.Forms/Pages/Milestones/Edit.cshtml new file mode 100644 index 00000000..9cac439b --- /dev/null +++ b/src/UDS.Net.Forms/Pages/Milestones/Edit.cshtml @@ -0,0 +1,448 @@ +@page +@model UDS.Net.Forms.Pages.Milestones.EditModel + +@{ + ViewData["Title"] = "Edit milestone"; + string rowCSS = "sm:grid sm:grid-cols-3 sm:items-start sm:gap-4 sm:py-6"; + string pillNumberCSS = "inline-flex items-center rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-800"; + string labelInstructionCSS = "text-base uppercase text-indigo-600 italic"; +} + +
+
+
+
+

M1 Milestones (February 18 Final, rev. 2/20)

+

+ INSTRUCTIONS: Please submit a new Milestones Form as soon as possible after each milestone event has occurred. +

+
+
+
+ +
+
+
+
+
+ + +
+
+
+ +
+
+
+
+
+
+

Box A

+

Change with continued contact

+
+
+
+ +
+
+
+

Unknown = 99/99/YEAR REQUIRED

+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ + + +
+
+
+
+
+ + Complete the UDS and FTLD sections below, as appropriate. + +
+
+
+
+ +
+
+
+
+
+
+
+ + +
+
+
+ + +
+ +
+
+ + +
+
+ +
+ + NOTE: Minimal contact status is reserved for participants who can no longer participate for one of the reasons listed in Question 2b, below. + +
+ +
+
+
+ + +
+ +
+
+ +
+
+ + +
+
+
+
+
+
+ +
+ +
+
+
+
+
+ @Html.CheckBox("Milestone.RECOGIM", Model.Milestone.RECOGIM, new { @class = "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600" }) +
+
+ +
+
+
+
+ @Html.CheckBox("Milestone.REPHYILL", Model.Milestone.REPHYILL, new { @class = "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600" }) +
+
+ +
+
+
+
+ @Html.CheckBox("Milestone.REREFUSE", Model.Milestone.REREFUSE, new { @class = "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600" }) +
+
+ +
+
+
+
+ @Html.CheckBox("Milestone.RENAVAIL", Model.Milestone.RENAVAIL, new { @class = "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600" }) +
+
+ +
+
+
+
+ @Html.CheckBox("Milestone.RENURSE", Model.Milestone.RENURSE, new { @class = "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600", data_affects = "true", data_affects_toggle_targets = "[ \"Milestone.NURSEMO\", \"Milestone.NURSEDY\", \"Milestone.NURSEYR\" ]" }) +
+
+ +
+
+
+
+ +
+
+
+
+

(unknown = 99/99/YEAR REQUIRED)

+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ + + +
+
+
+
+
+
+ @Html.CheckBox("Milestone.REJOIN", Model.Milestone.REJOIN, new { @class = "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600" }) +
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+ @Html.CheckBox("Milestone.FTLDDISC", Model.Milestone.FTLDDISC, new { @class = "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600" }) +
+
+
+ 1 +
+
+
+
+ +
+
+

+ + +
+
+ +
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+

Box B

+

No Further Contact

+
+
+
+
+ +
+
+ @Html.CheckBox("Milestone.DECEASED", Model.Milestone.DECEASED, new { @class = "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600", data_affects = "true", data_affects_toggle_targets = "[ \"Milestone.DEATHMO\", \"Milestone.DEATHDY\", \"Milestone.DEATHYR\", \"Milestone.AUTOPSY\" ]", data_fancycheckboxes_target = "checkbox" }) +
+
+ +
+ +
+
+
+
+
+
+
+ +
+
+ @Html.CheckBox("Milestone.DISCONT", Model.Milestone.DISCONT, new { @class = "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600", data_affects = "true", data_affects_toggle_targets = "[ \"Milestone.DISCMO\", \"Milestone.DISCDAY\", \"Milestone.DISCYR\", \"Milestone.DROPREAS\" ]", data_fancycheckboxes_target = "checkbox" }) +
+
+ +
+ +
+
+
+
+
+
+
+
+

Death

+
+
+
+
+
+
+
+ +
+
+
+

Unknown = 99/99/YEAR REQUIRED

+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ + + +
+
+
+
+
+ + +
+
+ +
+
+
+
+ END FORM HERE +
+
+
+
+
+
+
+
+
+
+

Dropped

+
+
+
+
+
+

DO NOT fill out this section if the participant has consented to autopsy or if you will maintain minimal contact. Instead, fill out BOX A QUESTION 2 UDS STATUS section.

+
+
+
+ +
+
+
+
+

Unknown = 99/99/YEAR REQUIRED

+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ + + +
+
+
+
+
+
+ + +
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+ + + + + + + + + +
+ +@section Scripts { + +} \ No newline at end of file diff --git a/src/UDS.Net.Forms/Pages/Milestones/Edit.cshtml.cs b/src/UDS.Net.Forms/Pages/Milestones/Edit.cshtml.cs new file mode 100644 index 00000000..d0243d3f --- /dev/null +++ b/src/UDS.Net.Forms/Pages/Milestones/Edit.cshtml.cs @@ -0,0 +1,49 @@ +using Microsoft.AspNetCore.Mvc; +using UDS.Net.Forms.Extensions; +using UDS.Net.Forms.Models.PageModels; +using UDS.Net.Services; + +namespace UDS.Net.Forms.Pages.Milestones +{ + public class EditModel : MilestonePageModel + { + public EditModel(IParticipationService participationService) : base(participationService) + { + } + + public async Task OnGet(int id, int formId) + { + var milestoneFound = await _participationService.GetMilestoneById(id, formId); + + if (milestoneFound == null) + { + return NotFound($"No milestones found within formId of: {formId}"); + } + + Milestone = milestoneFound.ToVM(); + + return Page(); + } + + public async Task OnPostAsync() + { + if (Milestone == null) + { + return Page(); + } + + Milestone.ModifiedBy = User.Identity.Name; + + IsValid(Milestone); + + if (ModelState.IsValid) + { + await _participationService.UpdateMilestone(Milestone.Id, Milestone.FormId, Milestone.ToEntity()); + + return RedirectToPage("/Participations/Details", new { Id = Milestone.ParticipationId }); + } + + return Page(); + } + } +} diff --git a/src/UDS.Net.Forms/Pages/Participations/Details.cshtml b/src/UDS.Net.Forms/Pages/Participations/Details.cshtml index 30b4da17..8c075453 100644 --- a/src/UDS.Net.Forms/Pages/Participations/Details.cshtml +++ b/src/UDS.Net.Forms/Pages/Participations/Details.cshtml @@ -7,7 +7,7 @@ @if (Model.Participation != null) -{ + {
@@ -47,7 +47,65 @@ } -} +
+
+
+

Milestones

+
+ +
+
+
+
+ + + + + + + + + + + + + + @foreach (var milestone in Model.Milestones) + { + + + @{ + if (milestone.DECEASED == true || milestone.DISCONT == true) + { + + } + else + { + + } + } + + @await Html.PartialAsync("_MilestoneDetails", milestone) + + + + + + } + +
Datetypedetailsprogress
@milestone.CreatedAt.ToShortDateString()Change followed by NO FURTHER CONTACTChange followed by continued contact@milestone.StatusEditView
+
+
+
+
+ } diff --git a/src/UDS.Net.Forms/Pages/Participations/Details.cshtml.cs b/src/UDS.Net.Forms/Pages/Participations/Details.cshtml.cs index bf40300b..21ac2f3d 100644 --- a/src/UDS.Net.Forms/Pages/Participations/Details.cshtml.cs +++ b/src/UDS.Net.Forms/Pages/Participations/Details.cshtml.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using UDS.Net.Forms.Extensions; using UDS.Net.Forms.Models; @@ -16,6 +12,8 @@ public class DetailsModel : PageModel public ParticipationModel? Participation { get; set; } + public IEnumerable Milestones { get; set; } + public DetailsModel(IParticipationService participationService) { _participationService = participationService; @@ -33,6 +31,10 @@ public async Task OnGet(int? id) Participation = participation.ToVM(); + var milestones = await _participationService.GetMilestonesByParticipationId(id.Value); + + Milestones = milestones.ToVM(); + return Page(); } } diff --git a/src/UDS.Net.Forms/Pages/Shared/_MilestoneDetails.cshtml b/src/UDS.Net.Forms/Pages/Shared/_MilestoneDetails.cshtml new file mode 100644 index 00000000..c3f7069f --- /dev/null +++ b/src/UDS.Net.Forms/Pages/Shared/_MilestoneDetails.cshtml @@ -0,0 +1,76 @@ +@model MilestoneModel + +@{ + string statusChangeMessage = null; + var statusChangeReasons = new List(); + + switch (@Model.PROTOCOL) + { + case 1: + statusChangeMessage = "Status changed to annual UDS follow-up by telephone"; + break; + + case 2: + statusChangeMessage = "Status changed to minimal contact"; + break; + + case 3: + statusChangeMessage = "Status changed to annual in-person UDS follow-up"; + break; + + default: + statusChangeMessage = null; + break; + } + + if (!String.IsNullOrEmpty(statusChangeMessage)) + { + if (@Model.RECOGIM == true) + { + statusChangeReasons.Add("Subject is too cognitively impaired"); + }; + + if (@Model.REPHYILL == true) + { + statusChangeReasons.Add("Subject is too ill or\nphysically impaired"); + }; + + if (@Model.REREFUSE == true) + { + statusChangeReasons.Add("Subject refuses neuropsychological testing or clinical exam"); + }; + + if (@Model.RENAVAIL == true) + { + statusChangeReasons.Add("Subject or coparticipant unreachable, not available, or moved away"); + }; + + if (@Model.RENURSE == true) + { + statusChangeReasons.Add("Subject has permanently entered nursing home"); + }; + + if (@Model.REJOIN == true) + { + statusChangeReasons.Add("Subject is rejoining ADC"); + }; + } +} + +@if (!String.IsNullOrEmpty(statusChangeMessage)) +{ + +

@statusChangeMessage

+

Reson(s)

+
    + @foreach (var reason in statusChangeReasons) + { +
  • @reason
  • + } +
+ +} +else +{ + N/A +} \ No newline at end of file diff --git a/src/UDS.Net.Forms/wwwroot/js/js_controllers/MilestoneTypeBehavior.js b/src/UDS.Net.Forms/wwwroot/js/js_controllers/MilestoneTypeBehavior.js new file mode 100644 index 00000000..32d3bfc2 --- /dev/null +++ b/src/UDS.Net.Forms/wwwroot/js/js_controllers/MilestoneTypeBehavior.js @@ -0,0 +1,51 @@ +//MilestoneTypeBehavior.js is an alternative to the edit/create pageModel Behavior for Milestone. +//This file will disable and enable fields based on the milestoneType while also +//setting the milestoneType value when loading an existing form by checking the Deceased and Discont values +//if either deceased or discont are select, milestoneType == NO FURTHER CONTACT + +class MilestoneTypeBehavior { + SetMilestoneType() { + let milestoneTypes = $("input[name='Milestone.MilestoneType']") + let deceasedInput = $("#Milestone_DECEASED") + let discontInput = $("#Milestone_DISCONT") + + if ($(deceasedInput).is(':checked') || $(discontInput).is(':checked')) { + console.log('test') + $('#Milestone\\.MilestoneType\\[1\\]').prop("checked", true) + this.DisableBoxA() + } else { + $('#Milestone\\.MilestoneType\\[0\\]').prop("checked", true) + this.DisableBoxB() + } + } + + DisableSectionOnChange() { + let milestoneTypeValue = $('input[name="Milestone.MilestoneType"]:checked').val() + if (milestoneTypeValue == 0) { + this.DisableBoxA() + } + if (milestoneTypeValue == 1) { + this.DisableBoxB() + } + } + + DisableBoxA() { + $(".boxA input").prop("disabled", true) + $(".boxB input").prop("disabled", false) + } + + DisableBoxB() { + $(".boxA input").prop("disabled", false) + $(".boxB input").prop("disabled", true) + } +} + +$(() => { + const milestoneTypeBehavior = new MilestoneTypeBehavior() + + milestoneTypeBehavior.SetMilestoneType() + + $("input[name='Milestone.MilestoneType']").on("change", () => { + milestoneTypeBehavior.DisableSectionOnChange() + }) +}) \ No newline at end of file diff --git a/src/UDS.Net.Services/DomainModels/Milestone.cs b/src/UDS.Net.Services/DomainModels/Milestone.cs new file mode 100644 index 00000000..663f9d95 --- /dev/null +++ b/src/UDS.Net.Services/DomainModels/Milestone.cs @@ -0,0 +1,45 @@ +using System; + +namespace UDS.Net.Services.DomainModels +{ + public class Milestone + { + public int Id { get; set; } + public int FormId { get; set; } + public int ParticipationId { get; set; } + public string Status { get; set; } + public int? CHANGEMO { get; set; } + public int? CHANGEDY { get; set; } + public int? CHANGEYR { get; set; } + public int? PROTOCOL { get; set; } + public int? ACONSENT { get; set; } + public int? RECOGIM { get; set; } + public int? REPHYILL { get; set; } + public int? REREFUSE { get; set; } + public int? RENAVAIL { get; set; } + public int? RENURSE { get; set; } + public int? NURSEMO { get; set; } + public int? NURSEDY { get; set; } + public int? NURSEYR { get; set; } + public int? REJOIN { get; set; } + public int? FTLDDISC { get; set; } + public int? FTLDREAS { get; set; } + public string FTLDREAX { get; set; } + public int? DECEASED { get; set; } + public int? DISCONT { get; set; } + public int? DEATHMO { get; set; } + public int? DEATHDY { get; set; } + public int? DEATHYR { get; set; } + public int? AUTOPSY { get; set; } + public int? DISCMO { get; set; } + public int? DISCDAY { get; set; } + public int? DISCYR { get; set; } + public int? DROPREAS { get; set; } + public DateTime CreatedAt { get; set; } + public string CreatedBy { get; set; } + public string ModifiedBy { get; set; } + public string DeletedBy { get; set; } + public bool IsDeleted { get; set; } + } +} + diff --git a/src/UDS.Net.Services/Extensions/DomainToDtoMapper.cs b/src/UDS.Net.Services/Extensions/DomainToDtoMapper.cs index ee236510..72e98029 100644 --- a/src/UDS.Net.Services/Extensions/DomainToDtoMapper.cs +++ b/src/UDS.Net.Services/Extensions/DomainToDtoMapper.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net.NetworkInformation; using UDS.Net.Dto; using UDS.Net.Services.DomainModels; using UDS.Net.Services.DomainModels.Forms; @@ -65,6 +64,48 @@ public static VisitDto ToDto(this Visit visit) return dto; } + public static M1Dto ToDto(this Milestone milestone) + { + return new M1Dto + { + FormId = milestone.FormId, + ParticipationId = milestone.ParticipationId, + Status = milestone.Status, + CHANGEMO = milestone.CHANGEMO, + CHANGEDY = milestone.CHANGEDY, + CHANGEYR = milestone.CHANGEYR, + PROTOCOL = milestone.PROTOCOL, + ACONSENT = milestone.ACONSENT, + RECOGIM = milestone.RECOGIM, + REPHYILL = milestone.REPHYILL, + REREFUSE = milestone.REREFUSE, + RENAVAIL = milestone.RENAVAIL, + RENURSE = milestone.RENURSE, + NURSEMO = milestone.NURSEMO, + NURSEDY = milestone.NURSEDY, + NURSEYR = milestone.NURSEYR, + REJOIN = milestone.REJOIN, + FTLDDISC = milestone.FTLDDISC, + FTLDREAS = milestone.FTLDREAS, + FTLDREAX = milestone.FTLDREAX, + DECEASED = milestone.DECEASED, + DISCONT = milestone.DISCONT, + DEATHMO = milestone.DEATHMO, + DEATHDY = milestone.DEATHDY, + DEATHYR = milestone.DEATHYR, + AUTOPSY = milestone.AUTOPSY, + DISCMO = milestone.DISCMO, + DISCDAY = milestone.DISCDAY, + DISCYR = milestone.DISCYR, + DROPREAS = milestone.DROPREAS, + CreatedAt = milestone.CreatedAt, + CreatedBy = milestone.CreatedBy, + ModifiedBy = milestone.ModifiedBy, + DeletedBy = milestone.DeletedBy, + IsDeleted = milestone.IsDeleted, + }; + } + public static VisitDto ToDto(this Visit visit, string formKind) { var dto = new VisitDto() diff --git a/src/UDS.Net.Services/Extensions/DtoToDomainMapper.cs b/src/UDS.Net.Services/Extensions/DtoToDomainMapper.cs index 51de8a04..0d0c7c02 100644 --- a/src/UDS.Net.Services/Extensions/DtoToDomainMapper.cs +++ b/src/UDS.Net.Services/Extensions/DtoToDomainMapper.cs @@ -24,7 +24,6 @@ public static Participation ToDomain(this ParticipationDto dto, string username) IsDeleted = dto.IsDeleted, VisitCount = dto.VisitCount, LastVisitNumber = dto.LastVisitNumber - }; if (dto.Visits != null) @@ -51,6 +50,54 @@ public static Visit ToDomain(this VisitDto dto, string username) return new Visit(dto.Id, dto.Number, dto.ParticipationId, dto.Version, visitKind, dto.StartDateTime, dto.CreatedAt, dto.CreatedBy, dto.ModifiedBy, dto.DeletedBy, dto.IsDeleted, existingForms); } + public static Milestone ToDomain(this M1Dto dto) + { + return new Milestone() + { + Id = dto.Id, + FormId = dto.FormId, + ParticipationId = dto.ParticipationId, + Status = dto.Status, + CHANGEMO = dto.CHANGEMO, + CHANGEDY = dto.CHANGEDY, + CHANGEYR = dto.CHANGEYR, + PROTOCOL = dto.PROTOCOL, + ACONSENT = dto.ACONSENT, + RECOGIM = dto.RECOGIM, + REPHYILL = dto.REPHYILL, + REREFUSE = dto.REREFUSE, + RENAVAIL = dto.RENAVAIL, + RENURSE = dto.RENURSE, + NURSEMO = dto.NURSEMO, + NURSEDY = dto.NURSEDY, + NURSEYR = dto.NURSEYR, + REJOIN = dto.REJOIN, + FTLDDISC = dto.FTLDDISC, + FTLDREAS = dto.FTLDREAS, + FTLDREAX = dto.FTLDREAX, + DECEASED = dto.DECEASED, + DISCONT = dto.DISCONT, + DEATHMO = dto.DEATHMO, + DEATHDY = dto.DEATHDY, + DEATHYR = dto.DEATHYR, + AUTOPSY = dto.AUTOPSY, + DISCMO = dto.DISCMO, + DISCDAY = dto.DISCDAY, + DISCYR = dto.DISCYR, + DROPREAS = dto.DROPREAS, + CreatedAt = dto.CreatedAt, + CreatedBy = dto.CreatedBy, + ModifiedBy = dto.ModifiedBy, + DeletedBy = dto.DeletedBy, + IsDeleted = dto.IsDeleted + }; + } + + public static IEnumerable ToDomain(this IEnumerable m1Dtos) + { + return m1Dtos.Select(m => m.ToDomain()).ToList(); + } + public static IList
ToDomain(this List dto, int visitId, string username) { if (dto != null) @@ -188,7 +235,6 @@ public static Form ToDomain(this FormDto dto, int visitId, string username) return new Form(visitId, dto.Id, title, dto.Kind, formStatus, formLanguage, reasonCode, dto.CreatedAt, dto.CreatedBy, dto.ModifiedBy, dto.DeletedBy, dto.IsDeleted, formFields); } - } } diff --git a/src/UDS.Net.Services/IParticipationService.cs b/src/UDS.Net.Services/IParticipationService.cs index ab9dcf24..fbc53e37 100644 --- a/src/UDS.Net.Services/IParticipationService.cs +++ b/src/UDS.Net.Services/IParticipationService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using UDS.Net.Dto; using UDS.Net.Services.DomainModels; @@ -7,7 +8,11 @@ namespace UDS.Net.Services { public interface IParticipationService : IService { + Task AddMilestone(int participationId, Milestone milestone); + Task UpdateMilestone(int id, int formId, Milestone milestone); Task GetByLegacyId(string legacyId); + Task> GetMilestonesByParticipationId(int participationId); + Task GetMilestoneById(int id, int formId); } } diff --git a/src/UDS.Net.Services/UDS.Net.Services.csproj b/src/UDS.Net.Services/UDS.Net.Services.csproj index 9762891e..b5897b01 100644 --- a/src/UDS.Net.Services/UDS.Net.Services.csproj +++ b/src/UDS.Net.Services/UDS.Net.Services.csproj @@ -13,6 +13,6 @@ 1.0.4 - + \ No newline at end of file diff --git a/src/UDS.Net.Web.MVC.Services/ParticipationService.cs b/src/UDS.Net.Web.MVC.Services/ParticipationService.cs index 7b9bafd5..255d13be 100644 --- a/src/UDS.Net.Web.MVC.Services/ParticipationService.cs +++ b/src/UDS.Net.Web.MVC.Services/ParticipationService.cs @@ -26,6 +26,13 @@ public async Task Add(string username, Participation entity) return entity; // TODO update client to return new object or id } + public async Task AddMilestone(int participationId, Milestone milestone) + { + await _apiClient.ParticipationClient.PostMilestone(participationId, milestone.ToDto()); + + return milestone; + } + public async Task Count(string username) { return await _apiClient.ParticipationClient.Count(); @@ -68,16 +75,35 @@ public async Task> List(string username, int pageSize return new List(); } + public async Task UpdateMilestone(int id, int formId, Milestone milestone) + { + await _apiClient.ParticipationClient.PutMilestone(id, formId, milestone.ToDto()); + + return milestone; + } + + public async Task> GetMilestonesByParticipationId(int participationId) + { + IEnumerable milestones = await _apiClient.ParticipationClient.GetMilestones(participationId); + + return milestones.ToDomain(); + } + + public async Task GetMilestoneById(int id, int formId) + { + M1Dto milestone = await _apiClient.ParticipationClient.GetMilestone(id, formId); + + return milestone.ToDomain(); + } + public async Task Patch(string username, Participation entity) { // TODO update participation return entity; } - public async Task Remove(string username, Participation entity) { } - public async Task Update(string username, Participation entity) { // TODO update participation diff --git a/src/UDS.Net.Web.MVC.Services/UDS.Net.Web.MVC.Services.csproj b/src/UDS.Net.Web.MVC.Services/UDS.Net.Web.MVC.Services.csproj index 0dfd4307..7c6956a0 100644 --- a/src/UDS.Net.Web.MVC.Services/UDS.Net.Web.MVC.Services.csproj +++ b/src/UDS.Net.Web.MVC.Services/UDS.Net.Web.MVC.Services.csproj @@ -14,8 +14,8 @@ - - + + diff --git a/src/UDS.Net.Web.MVC/UDS.Net.Web.MVC.csproj b/src/UDS.Net.Web.MVC/UDS.Net.Web.MVC.csproj index 58b7409f..be36d0b3 100644 --- a/src/UDS.Net.Web.MVC/UDS.Net.Web.MVC.csproj +++ b/src/UDS.Net.Web.MVC/UDS.Net.Web.MVC.csproj @@ -25,8 +25,8 @@ all - - + + From e7221887ce098d06902efd84430e9d10a24e1696 Mon Sep 17 00:00:00 2001 From: Smith Thay <77938144+smiththay@users.noreply.github.com> Date: Fri, 9 Feb 2024 11:35:23 -0500 Subject: [PATCH 2/2] A3 Family History Behavior (#107) * Adds script tag * adds jquery disabling for birth month * adds disabling for YOB * adds behavior for Age at death & Primary neurological problem/psychiatric condition* * Primary neurological problem behavior progress * Redo Primary neurological problem JS behavior * Clears disabled row values * adds JS to check for special case * adds behavior for parents section * fixes range * Initialize child and sibling instances for new A3 instance * Include the property for the child object in a hidden field * modifies to allow null values for diagnoses * modified for more accurate functionality * Updates with correct variable * Modifies to add allowunknown property * Adds required if attribute * Add some mapping for sibs * adds mapping for sibs and kids * adds model state validation for NEU and AGD for sibs and children * removes whitespace --------- Co-authored-by: Ashley Wilson --- .../DataAnnotations/DiagnosisAttribute.cs | 84 +++++++++---------- src/UDS.Net.Forms/Models/UDS3/A3.cs | 38 ++++++++- .../Models/UDS3/A3FamilyMember.cs | 12 ++- .../EditorTemplates/A3FamilyMember.cshtml | 5 +- src/UDS.Net.Forms/Pages/UDS3/A3.cshtml | 4 + src/UDS.Net.Forms/Pages/UDS3/A3.cshtml.cs | 54 +++++++++++- .../DomainModels/Forms/A3FormFields.cs | 41 ++++++--- .../Extensions/DomainToDtoMapper.cs | 75 ++++++++++++++++- src/UDS.Net.Web.MVC/wwwroot/js/A3.js | 74 ++++++++++++++++ 9 files changed, 323 insertions(+), 64 deletions(-) create mode 100644 src/UDS.Net.Web.MVC/wwwroot/js/A3.js diff --git a/src/UDS.Net.Forms/DataAnnotations/DiagnosisAttribute.cs b/src/UDS.Net.Forms/DataAnnotations/DiagnosisAttribute.cs index 25d06888..366a65e6 100644 --- a/src/UDS.Net.Forms/DataAnnotations/DiagnosisAttribute.cs +++ b/src/UDS.Net.Forms/DataAnnotations/DiagnosisAttribute.cs @@ -1,64 +1,62 @@ -using System; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using System.ComponentModel.DataAnnotations; -using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using UDS.Net.Forms.Models; +using UDS.Net.Forms.Models.UDS3; using UDS.Net.Services.Enums; -namespace UDS.Net.Forms.DataAnnotations +[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] +public class DiagnosisAttribute : ValidationAttribute, IClientModelValidator { - [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] - public class DiagnosisAttribute : ValidationAttribute, IClientModelValidator - { - private static string ERRORMESSAGE = "Diagnosis code invalid. Please see reference."; - private static int[] CODES = new int[] { 40, 41, 42, 43, 44, 45, 50, 70, 80, 100, 110, 120, 130, 131, 132, 133, 140, 150, 160, 170, 180, 190, 200, 210, 220, 230, 240, 250, 260, 270, 280, 310, 320, 400, 410, 420, 421, 430, 431, 432, 433, 434, 435, 436, 439, 440, 450, 490, 999 }; + private static string ERRORMESSAGE = "Diagnosis code invalid. Please see reference."; + private static int[] CODES = new int[] { 40, 41, 42, 43, 44, 45, 50, 70, 80, 100, 110, 120, 130, 131, 132, 133, 140, 150, 160, 170, 180, 190, 200, 210, 220, 230, 240, 250, 260, 270, 280, 310, 320, 400, 410, 420, 421, 430, 431, 432, 433, 434, 435, 436, 439, 440, 450, 490, 999 }; - protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) - { - if (validationContext.ObjectType.IsSubclassOf(typeof(FormModel))) - { - var form = (FormModel)validationContext.ObjectInstance; + public bool AllowUnknown { get; set; } = false; + protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) + { + if (validationContext.ObjectType.IsSubclassOf(typeof(FormModel))) + { + var form = (FormModel)validationContext.ObjectInstance; - // only validate if the form is attempting to be completed - if (form.Status == FormStatus.Complete) + // Only validate if the form is attempting to be completed + if (form.Status == FormStatus.Complete) + { + if (value is int) { - if (value is int) + int code = (int)value; + if (CODES.Contains(code)) { - int code = (int)value; - - if (CODES.Contains(code)) - { - return ValidationResult.Success; - } - + return ValidationResult.Success; } + } - return new ValidationResult(ERRORMESSAGE); + if (AllowUnknown && value == null) + { + return ValidationResult.Success; } - } - return ValidationResult.Success; + return new ValidationResult(ERRORMESSAGE); + } } + return ValidationResult.Success; + } + public void AddValidation(ClientModelValidationContext context) + { + MergeAttribute(context.Attributes, "data-val", "true"); + MergeAttribute(context.Attributes, "data-val-diagnosis", ERRORMESSAGE); + } - public void AddValidation(ClientModelValidationContext context) + /// + /// See https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-7.0#iclientmodelvalidator-for-client-side-validation + /// + private static bool MergeAttribute(IDictionary attributes, string key, string value) + { + if (attributes.ContainsKey(key)) { - MergeAttribute(context.Attributes, "data-val", "true"); - MergeAttribute(context.Attributes, "data-val-diagnosis", ERRORMESSAGE); + return false; } - /// - /// See https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-7.0#iclientmodelvalidator-for-client-side-validation - /// - private static bool MergeAttribute(IDictionary attributes, string key, string value) - { - if (attributes.ContainsKey(key)) - { - return false; - } - - attributes.Add(key, value); - return true; - } + attributes.Add(key, value); + return true; } } - diff --git a/src/UDS.Net.Forms/Models/UDS3/A3.cs b/src/UDS.Net.Forms/Models/UDS3/A3.cs index 365ba186..88b961ab 100644 --- a/src/UDS.Net.Forms/Models/UDS3/A3.cs +++ b/src/UDS.Net.Forms/Models/UDS3/A3.cs @@ -98,22 +98,39 @@ public class A3 : FormModel [Display(Name = "Mother — age at death")] [RegularExpression("^(\\d|[1-9]\\d|10\\d|110|888|999)$", ErrorMessage = "Mother age at death must be 0-110, or 888 (N/A), or 999 (unknown)")] + [Required(ErrorMessage = "Please provide an age at death or indicate otherwise")] public int? MOMDAGE { get; set; } [Display(Name = "Mother — neurological problem")] [RegularExpression("^([1-5]|8|9)$", ErrorMessage = "Mother neurological problem/psychiatric condition invalid. Please see reference.")] + [Required(ErrorMessage = "Please provide a value for Primary neurological problem/psychiatric condition")] public int? MOMNEUR { get; set; } [Display(Name = "Mother — primary diagnosis")] - [Diagnosis] + [Diagnosis(AllowUnknown = true)] + [RequiredIf(nameof(MOMNEUR), "1", ErrorMessage = "Please provide a Primary Dx, refer to the codes in APPENDIX 1")] + [RequiredIf(nameof(MOMNEUR), "2", ErrorMessage = "Please provide a Primary Dx, refer to the codes in APPENDIX 1")] + [RequiredIf(nameof(MOMNEUR), "3", ErrorMessage = "Please provide a Primary Dx, refer to the codes in APPENDIX 1")] + [RequiredIf(nameof(MOMNEUR), "4", ErrorMessage = "Please provide a Primary Dx, refer to the codes in APPENDIX 1")] + [RequiredIf(nameof(MOMNEUR), "5", ErrorMessage = "Please provide a Primary Dx, refer to the codes in APPENDIX 1")] public int? MOMPRDX { get; set; } [Display(Name = "Mother — method of evaluation")] [Range(1, 7)] + [RequiredIf(nameof(MOMNEUR), "1", ErrorMessage = "Please provide a method of evaluation")] + [RequiredIf(nameof(MOMNEUR), "2", ErrorMessage = "Please provide a method of evaluation")] + [RequiredIf(nameof(MOMNEUR), "3", ErrorMessage = "Please provide a method of evaluation")] + [RequiredIf(nameof(MOMNEUR), "4", ErrorMessage = "Please provide a method of evaluation")] + [RequiredIf(nameof(MOMNEUR), "5", ErrorMessage = "Please provide a method of evaluation")] public int? MOMMOE { get; set; } [Display(Name = "Mother — age of onset")] [RegularExpression("^(\\d|[1-9]\\d|10\\d|110|999)$", ErrorMessage = "Mother age of onset must be 0-110, or 999 (unknown)")] + [RequiredIf(nameof(MOMNEUR), "1", ErrorMessage = "Please provide the age of onset")] + [RequiredIf(nameof(MOMNEUR), "2", ErrorMessage = "Please provide the age of onset")] + [RequiredIf(nameof(MOMNEUR), "3", ErrorMessage = "Please provide the age of onset")] + [RequiredIf(nameof(MOMNEUR), "4", ErrorMessage = "Please provide the age of onset")] + [RequiredIf(nameof(MOMNEUR), "5", ErrorMessage = "Please provide the age of onset")] public int? MOMAGEO { get; set; } [Display(Name = "Father — birth month")] @@ -126,22 +143,39 @@ public class A3 : FormModel [Display(Name = "Father — age at death")] [RegularExpression("^(\\d|[1-9]\\d|10\\d|110|888|999)$", ErrorMessage = "Father age at death must be 0-110, or 888 (N/A), or 999 (unknown)")] + [Required(ErrorMessage = "Please provide an age at death or indicate otherwise")] public int? DADDAGE { get; set; } [Display(Name = "Father — neurological problem")] [RegularExpression("^([1-5]|8|9)$", ErrorMessage = "Father neurological problem/psychiatric condition invalid. Please see reference.")] + [Required(ErrorMessage = "Please provide a value for Primary neurological problem/psychiatric condition")] public int? DADNEUR { get; set; } [Display(Name = "Father — primary diagnosis")] - [Diagnosis] + [Diagnosis(AllowUnknown = true)] + [RequiredIf(nameof(DADNEUR), "1", ErrorMessage = "Please provide a Primary Dx, refer to the codes in APPENDIX 1")] + [RequiredIf(nameof(DADNEUR), "2", ErrorMessage = "Please provide a Primary Dx, refer to the codes in APPENDIX 1")] + [RequiredIf(nameof(DADNEUR), "3", ErrorMessage = "Please provide a Primary Dx, refer to the codes in APPENDIX 1")] + [RequiredIf(nameof(DADNEUR), "4", ErrorMessage = "Please provide a Primary Dx, refer to the codes in APPENDIX 1")] + [RequiredIf(nameof(DADNEUR), "5", ErrorMessage = "Please provide a Primary Dx, refer to the codes in APPENDIX 1")] public int? DADPRDX { get; set; } [Display(Name = "Father — method of evaluation")] [Range(1, 7)] + [RequiredIf(nameof(DADNEUR), "1", ErrorMessage = "Please provide a method of evaluation")] + [RequiredIf(nameof(DADNEUR), "2", ErrorMessage = "Please provide a method of evaluation")] + [RequiredIf(nameof(DADNEUR), "3", ErrorMessage = "Please provide a method of evaluation")] + [RequiredIf(nameof(DADNEUR), "4", ErrorMessage = "Please provide a method of evaluation")] + [RequiredIf(nameof(DADNEUR), "5", ErrorMessage = "Please provide a method of evaluation")] public int? DADMOE { get; set; } [Display(Name = "Father — age of onset")] [RegularExpression("^(\\d|[1-9]\\d|10\\d|110|999)$", ErrorMessage = "Father age of onset must be 0-110, or 999 (unknown)")] + [RequiredIf(nameof(DADNEUR), "1", ErrorMessage = "Please provide the age of onset")] + [RequiredIf(nameof(DADNEUR), "2", ErrorMessage = "Please provide the age of onset")] + [RequiredIf(nameof(DADNEUR), "3", ErrorMessage = "Please provide the age of onset")] + [RequiredIf(nameof(DADNEUR), "4", ErrorMessage = "Please provide the age of onset")] + [RequiredIf(nameof(DADNEUR), "5", ErrorMessage = "Please provide the age of onset")] public int? DADAGEO { get; set; } [Display(Name = "How many full siblings does the participant have? (77 = adopted, unknown)")] diff --git a/src/UDS.Net.Forms/Models/UDS3/A3FamilyMember.cs b/src/UDS.Net.Forms/Models/UDS3/A3FamilyMember.cs index 359d8f1f..d497967e 100644 --- a/src/UDS.Net.Forms/Models/UDS3/A3FamilyMember.cs +++ b/src/UDS.Net.Forms/Models/UDS3/A3FamilyMember.cs @@ -28,11 +28,21 @@ public class A3FamilyMember public int? NEU { get; set; } [Display(Name = "Primary Dx")] - [Diagnosis] + [Diagnosis(AllowUnknown = true)] + [RequiredIf(nameof(NEU), "1", ErrorMessage = "Please provide a Primary Dx, refer to the codes in APPENDIX 1")] + [RequiredIf(nameof(NEU), "2", ErrorMessage = "Please provide a Primary Dx, refer to the codes in APPENDIX 1")] + [RequiredIf(nameof(NEU), "3", ErrorMessage = "Please provide a Primary Dx, refer to the codes in APPENDIX 1")] + [RequiredIf(nameof(NEU), "4", ErrorMessage = "Please provide a Primary Dx, refer to the codes in APPENDIX 1")] + [RequiredIf(nameof(NEU), "5", ErrorMessage = "Please provide a Primary Dx, refer to the codes in APPENDIX 1")] public int? PDX { get; set; } [Display(Name = "Method of evaluation")] [Range(1, 7)] + [RequiredIf(nameof(NEU), "1", ErrorMessage = "Please provide a method of evaluation")] + [RequiredIf(nameof(NEU), "2", ErrorMessage = "Please provide a method of evaluation")] + [RequiredIf(nameof(NEU), "3", ErrorMessage = "Please provide a method of evaluation")] + [RequiredIf(nameof(NEU), "4", ErrorMessage = "Please provide a method of evaluation")] + [RequiredIf(nameof(NEU), "5", ErrorMessage = "Please provide a method of evaluation")] public int? MOE { get; set; } [Display(Name = "Age of onset")] diff --git a/src/UDS.Net.Forms/Pages/Shared/EditorTemplates/A3FamilyMember.cshtml b/src/UDS.Net.Forms/Pages/Shared/EditorTemplates/A3FamilyMember.cshtml index a551cbdc..0ba87db6 100644 --- a/src/UDS.Net.Forms/Pages/Shared/EditorTemplates/A3FamilyMember.cshtml +++ b/src/UDS.Net.Forms/Pages/Shared/EditorTemplates/A3FamilyMember.cshtml @@ -1,7 +1,10 @@ @model UDS.Net.Forms.Models.UDS3.A3FamilyMember - @Model.FamilyMemberIndex + + @Model.FamilyMemberIndex + +
diff --git a/src/UDS.Net.Forms/Pages/UDS3/A3.cshtml b/src/UDS.Net.Forms/Pages/UDS3/A3.cshtml index 7b688d36..d369e4f3 100644 --- a/src/UDS.Net.Forms/Pages/UDS3/A3.cshtml +++ b/src/UDS.Net.Forms/Pages/UDS3/A3.cshtml @@ -482,3 +482,7 @@
+ +@section Scripts { + +} diff --git a/src/UDS.Net.Forms/Pages/UDS3/A3.cshtml.cs b/src/UDS.Net.Forms/Pages/UDS3/A3.cshtml.cs index 1f5181e1..6c1b0be6 100644 --- a/src/UDS.Net.Forms/Pages/UDS3/A3.cshtml.cs +++ b/src/UDS.Net.Forms/Pages/UDS3/A3.cshtml.cs @@ -183,8 +183,58 @@ public async Task OnGetAsync(int? id) Visit.Forms.Add(A3); // visit needs updated form as well - return await base.OnPostAsync(id); // checks for validation, etc. + if (A3 != null) + { + if (A3.Siblings != null) + { + foreach (var sibling in A3.Siblings) + { + if (sibling != null) + { + if (sibling.MOB.HasValue || sibling.YOB.HasValue) + { + if (!sibling.AGD.HasValue) + { + ModelState.AddModelError($"A3.Siblings[{A3.Siblings.IndexOf(sibling)}].AGD", "Please provide a value for age at death."); + } + } + if (sibling.MOB.HasValue || sibling.YOB.HasValue || sibling.AGD.HasValue) + { + if (!sibling.NEU.HasValue) + { + ModelState.AddModelError($"A3.Siblings[{A3.Siblings.IndexOf(sibling)}].NEU", "Please provide a value for Primary neurological problem/psychiatric condition for this sibling."); + } + } + } + } + } + + if (A3.Children != null) + { + foreach (var child in A3.Children) + { + if (child != null) + { + if (child.MOB.HasValue || child.YOB.HasValue) + { + if (!child.AGD.HasValue) + { + ModelState.AddModelError($"A3.Children[{A3.Children.IndexOf(child)}].AGD", "Please provide a value for age at death."); + } + } + if (child.MOB.HasValue || child.YOB.HasValue || child.AGD.HasValue) + { + if (!child.NEU.HasValue) + { + ModelState.AddModelError($"A3.Children[{A3.Children.IndexOf(child)}].NEU", "Please provide a value for Primary neurological problem/psychiatric condition for this child."); + } + } + } + } + } + return await base.OnPostAsync(id); // checks for validation, etc. + } + return RedirectToPage(); } - } } diff --git a/src/UDS.Net.Services/DomainModels/Forms/A3FormFields.cs b/src/UDS.Net.Services/DomainModels/Forms/A3FormFields.cs index 627edad0..18c118c2 100644 --- a/src/UDS.Net.Services/DomainModels/Forms/A3FormFields.cs +++ b/src/UDS.Net.Services/DomainModels/Forms/A3FormFields.cs @@ -64,23 +64,42 @@ private A3FamilyMemberFormFields GetFamilyMemberFormFields(int index, string pro PropertyInfo prop = typeof(A3Dto).GetProperty($"{propertyPrefix}{index}"); if (prop != null) { - A3FamilyMemberDto value = (A3FamilyMemberDto)prop.GetValue(dto); - if (value != null) + if (dto != null) { - familyMember.MOB = value.MOB; - familyMember.YOB = value.YOB; - familyMember.AGD = value.AGD; - familyMember.NEU = value.NEU; - familyMember.PDX = value.PDX; - familyMember.MOE = value.MOE; - familyMember.AGO = value.AGO; + A3FamilyMemberDto value = (A3FamilyMemberDto)prop.GetValue(dto); + if (value != null) + { + familyMember.MOB = value.MOB; + familyMember.YOB = value.YOB; + familyMember.AGD = value.AGD; + familyMember.NEU = value.NEU; + familyMember.PDX = value.PDX; + familyMember.MOE = value.MOE; + familyMember.AGO = value.AGO; + } } } return familyMember; } - public A3FormFields() { } + public A3FormFields() + { + // always 20 spots for siblings + for (int i = 1; i <= 20; i++) + { + var sibling = GetFamilyMemberFormFields(i, "SIB", null); + + this.SiblingFormFields.Add(sibling); + } + // always 15 spots for kids + for (int i = 1; i <= 15; i++) + { + var kid = GetFamilyMemberFormFields(i, "KID", null); + + this.KidsFormFields.Add(kid); + } + } public A3FormFields(FormDto dto) { if (dto is A3Dto) @@ -112,7 +131,7 @@ public A3FormFields(FormDto dto) this.DADDAGE = a3Dto.DADDAGE; this.DADNEUR = a3Dto.DADNEUR; this.DADPRDX = a3Dto.DADPRDX; - this.DADMOE = a3Dto.DADMOB; + this.DADMOE = a3Dto.DADMOE; this.DADAGEO = a3Dto.DADAGEO; this.SIBS = a3Dto.SIBS; // the count of siblings diff --git a/src/UDS.Net.Services/Extensions/DomainToDtoMapper.cs b/src/UDS.Net.Services/Extensions/DomainToDtoMapper.cs index 72e98029..3e1a21fc 100644 --- a/src/UDS.Net.Services/Extensions/DomainToDtoMapper.cs +++ b/src/UDS.Net.Services/Extensions/DomainToDtoMapper.cs @@ -423,15 +423,82 @@ public static A3Dto ToDto(this A3FormFields fields, int formId) foreach (var sib in fields.SiblingFormFields) { - // TODO map a3 siblings var sibDto = sib.ToDto(formId); + if (sib.FamilyMemberIndex == 1) + dto.SIB1 = sibDto; + else if (sib.FamilyMemberIndex == 2) + dto.SIB2 = sibDto; + else if (sib.FamilyMemberIndex == 3) + dto.SIB3 = sibDto; + else if (sib.FamilyMemberIndex == 4) + dto.SIB4 = sibDto; + else if (sib.FamilyMemberIndex == 5) + dto.SIB5 = sibDto; + else if (sib.FamilyMemberIndex == 6) + dto.SIB6 = sibDto; + else if (sib.FamilyMemberIndex == 7) + dto.SIB7 = sibDto; + else if (sib.FamilyMemberIndex == 8) + dto.SIB8 = sibDto; + else if (sib.FamilyMemberIndex == 9) + dto.SIB9 = sibDto; + else if (sib.FamilyMemberIndex == 10) + dto.SIB10 = sibDto; + else if (sib.FamilyMemberIndex == 11) + dto.SIB11 = sibDto; + else if (sib.FamilyMemberIndex == 12) + dto.SIB12 = sibDto; + else if (sib.FamilyMemberIndex == 13) + dto.SIB13 = sibDto; + else if (sib.FamilyMemberIndex == 14) + dto.SIB14 = sibDto; + else if (sib.FamilyMemberIndex == 15) + dto.SIB15 = sibDto; + else if (sib.FamilyMemberIndex == 16) + dto.SIB16 = sibDto; + else if (sib.FamilyMemberIndex == 17) + dto.SIB17 = sibDto; + else if (sib.FamilyMemberIndex == 18) + dto.SIB18 = sibDto; + else if (sib.FamilyMemberIndex == 19) + dto.SIB19 = sibDto; + else if (sib.FamilyMemberIndex == 20) + dto.SIB20 = sibDto; } foreach (var kid in fields.KidsFormFields) { - // TODO map a3 children - var kidSto = kid.ToDto(formId); - + var kidDto = kid.ToDto(formId); + if (kid.FamilyMemberIndex == 1) + dto.KID1 = kidDto; + else if (kid.FamilyMemberIndex == 2) + dto.KID2 = kidDto; + else if (kid.FamilyMemberIndex == 3) + dto.KID3 = kidDto; + else if (kid.FamilyMemberIndex == 4) + dto.KID4 = kidDto; + else if (kid.FamilyMemberIndex == 5) + dto.KID5 = kidDto; + else if (kid.FamilyMemberIndex == 6) + dto.KID6 = kidDto; + else if (kid.FamilyMemberIndex == 7) + dto.KID7 = kidDto; + else if (kid.FamilyMemberIndex == 8) + dto.KID8 = kidDto; + else if (kid.FamilyMemberIndex == 9) + dto.KID9 = kidDto; + else if (kid.FamilyMemberIndex == 10) + dto.KID10 = kidDto; + else if (kid.FamilyMemberIndex == 11) + dto.KID11 = kidDto; + else if (kid.FamilyMemberIndex == 12) + dto.KID12 = kidDto; + else if (kid.FamilyMemberIndex == 13) + dto.KID13 = kidDto; + else if (kid.FamilyMemberIndex == 14) + dto.KID14 = kidDto; + else if (kid.FamilyMemberIndex == 15) + dto.KID15 = kidDto; } return dto; diff --git a/src/UDS.Net.Web.MVC/wwwroot/js/A3.js b/src/UDS.Net.Web.MVC/wwwroot/js/A3.js new file mode 100644 index 00000000..90a89814 --- /dev/null +++ b/src/UDS.Net.Web.MVC/wwwroot/js/A3.js @@ -0,0 +1,74 @@ +class RelationshipTable { + constructor(tableId, inputId, maxRows) { + this.tableId = tableId; + this.inputId = inputId; + this.maxRows = maxRows; + this.initialize(); + } + + initialize() { + this.updateRows(); + $(`#${this.inputId}`).on('change', () => this.updateRows()); + for (let i = 0; i < this.maxRows; i++) { + this.updateNeuroControls(i); + this.addNeuroChangeWatch(i); + } + this.addParentNeuroChangeWatch(); + } + + updateRows() { + let rowCount = parseInt($(`#${this.inputId}`).val(), 10) || 0; + rowCount = this.adjustRowCountForSpecialCases(rowCount); + + for (let i = 0; i < this.maxRows; i++) { + const enabled = i < rowCount; + const rowInputs = $(`#${this.tableId}_${i}__MOB, #${this.tableId}_${i}__YOB, #${this.tableId}_${i}__AGD, #${this.tableId}_${i}__NEU`); + rowInputs.prop('disabled', !enabled); + if (!enabled) { + rowInputs.val(''); + } + this.updateNeuroControls(i); + } + this.updateParentControls(); + } + + adjustRowCountForSpecialCases(rowCount) { + if (this.tableId === 'A3_Siblings' && rowCount === 77) { + return 0; + } + return rowCount; + } + + updateNeuroControls(index) { + const neuValue = parseInt($(`#${this.tableId}_${index}__NEU`).val(), 10); + const isEnabled = neuValue >= 1 && neuValue <= 5; + $(`#${this.tableId}_${index}__PDX, #${this.tableId}_${index}__MOE, #${this.tableId}_${index}__AGO`).prop('disabled', !isEnabled); + } + + addNeuroChangeWatch(index) { + $(`#${this.tableId}_${index}__NEU`).change(() => { + this.updateNeuroControls(index); + }); + } + + addParentNeuroChangeWatch() { + $(`#A3_MOMNEUR, #A3_DADNEUR`).change(() => { + this.updateParentControls(); + }); + } + + updateParentControls() { + const momNeurValue = parseInt($(`#A3_MOMNEUR`).val(), 10); + const isMomEnabled = momNeurValue >= 1 && momNeurValue <= 5; + $(`#A3_MOMPRDX, #A3_MOMMOE, #A3_MOMAGEO`).prop('disabled', !isMomEnabled); + + const dadNeurValue = parseInt($(`#A3_DADNEUR`).val(), 10); + const isDadEnabled = dadNeurValue >= 1 && dadNeurValue <= 5; + $(`#A3_DADPRDX, #A3_DADMOE, #A3_DADAGEO`).prop('disabled', !isDadEnabled); + } +} + +$(document).ready(function () { + const siblings = new RelationshipTable('A3_Siblings', 'A3_SIBS', 20); + const children = new RelationshipTable('A3_Children', 'A3_KIDS', 15); +});