diff --git a/FMS/Pages/Maintenance/Chemical/Detail.cshtml b/FMS/Pages/Maintenance/Chemical/Detail.cshtml
new file mode 100644
index 00000000..98ebc107
--- /dev/null
+++ b/FMS/Pages/Maintenance/Chemical/Detail.cshtml
@@ -0,0 +1,163 @@
+@page
+@using FMS.Pages.Maintenance
+@model FMS.Pages.Maintenance.Chemical.DetailModel
+@{
+}
+@section Scripts {
+
+
+}
+
+@{
+ ViewData["Title"] = $"Site Maintenance: {MaintenanceOptions.Chemical}";
+}
+
+
+ @MaintenanceOptions.Chemical
+
+
+
+
+
\ No newline at end of file
diff --git a/FMS/Pages/Maintenance/Chemical/Detail.cshtml.cs b/FMS/Pages/Maintenance/Chemical/Detail.cshtml.cs
new file mode 100644
index 00000000..22eece2b
--- /dev/null
+++ b/FMS/Pages/Maintenance/Chemical/Detail.cshtml.cs
@@ -0,0 +1,71 @@
+using FMS.Domain.Dto;
+using FMS.Domain.Repositories;
+using FMS.Platform.Extensions;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.EntityFrameworkCore;
+
+namespace FMS.Pages.Maintenance.Chemical
+{
+ public class DetailModel : PageModel
+ {
+ private readonly IChemicalRepository _repository;
+ public DetailModel(IChemicalRepository repository) => _repository = repository;
+
+ public IReadOnlyList
Chemicals { get; private set; }
+
+ public ChemicalSummaryDto Chemical { get; }
+
+ public DisplayMessage DisplayMessage { get; private set; }
+
+ public async Task OnGetAsync()
+ {
+ Chemicals = await _repository.GetChemicalListAsync();
+ DisplayMessage = TempData?.GetDisplayMessage();
+ return Page();
+ }
+
+ public async Task OnPostAsync(Guid? itemId)
+ {
+ if (itemId == null)
+ {
+ return BadRequest();
+ }
+
+ if (!ModelState.IsValid)
+ {
+ return Page();
+ }
+
+ var chemical = await _repository.GetChemicalByIdAsync(itemId.Value);
+
+ if (chemical == null)
+ {
+ return NotFound();
+ }
+
+ try
+ {
+ await _repository.UpdateChemicalStatusAsync(itemId.Value, !chemical.Active);
+ }
+ catch (DbUpdateConcurrencyException)
+ {
+ if (!await _repository.ChemicalExistsAsync(itemId.Value))
+ {
+ return NotFound();
+ }
+
+ throw;
+ }
+
+ Chemicals = await _repository.GetChemicalListAsync();
+
+ TempData?.SetDisplayMessage(Context.Success,
+ chemical.Active
+ ? $"{MaintenanceOptions.Chemical} \"{chemical.ChemicalName}\" successfully removed from list."
+ : $"{MaintenanceOptions.Chemical} \"{chemical.ChemicalName}\" successfully restored.");
+
+ return RedirectToPage("./Index");
+ }
+ }
+}
diff --git a/FMS/wwwroot/css/site.css b/FMS/wwwroot/css/site.css
index 418bf432..b604fb9a 100644
--- a/FMS/wwwroot/css/site.css
+++ b/FMS/wwwroot/css/site.css
@@ -93,9 +93,9 @@ a:focus {
text-decoration: underline;
}
-a:focus:not(.btn) {
- text-decoration: none;
-}
+ a:focus:not(.btn) {
+ text-decoration: none;
+ }
.btn-primary:focus {
color: #fff;
@@ -159,7 +159,7 @@ a:focus:not(.btn) {
.btn-outline-info {
color: #fff;
- background-color: #17a2b8; /*#5BAFD1;*/ /*#507033;*/
+ background-color: #17a2b8; /*#5BAFD1;*/ /*#507033;*/
}
.btn-outline-warning:focus {
@@ -208,17 +208,17 @@ body {
color: #008000;
}
-.footer a:hover, .footer a:focus {
- color: #106717;
-}
+ .footer a:hover, .footer a:focus {
+ color: #106717;
+ }
.footer .bg-brand a {
color: #ffff00;
}
-.footer .bg-brand a:hover, .footer .bg-brand a:focus {
- color: #d9d900;
-}
+ .footer .bg-brand a:hover, .footer .bg-brand a:focus {
+ color: #d9d900;
+ }
/* Skip to content link
-------------------------------------------------- */
@@ -234,15 +234,15 @@ body {
z-index: 100;
}
-.skipnav:focus {
- background: #fff;
- left: 0;
- outline: 0;
- box-shadow: 0 0 3px #1B98CA, 0 0 7px #1B98CA;
- position: absolute;
- top: 0;
- transition: all 0.2s ease-in-out;
-}
+ .skipnav:focus {
+ background: #fff;
+ left: 0;
+ outline: 0;
+ box-shadow: 0 0 3px #1B98CA, 0 0 7px #1B98CA;
+ position: absolute;
+ top: 0;
+ transition: all 0.2s ease-in-out;
+ }
/* Button with image
-------------------------------------------------- */
@@ -250,9 +250,9 @@ body {
line-height: 1;
}
-.btn-img img {
- height: 35px;
-}
+ .btn-img img {
+ height: 35px;
+ }
.btn-img-bg {
background: white;
@@ -344,17 +344,17 @@ a[target]::after {
border-left: 0.25rem solid var(--callout-border, var(--bs-gray-400));
}
-.callout > :last-child {
- margin-bottom: 0;
-}
+ .callout > :last-child {
+ margin-bottom: 0;
+ }
-.callout + .callout {
- margin-top: -.25rem;
-}
+ .callout + .callout {
+ margin-top: -.25rem;
+ }
-.callout .highlight {
- background-color: rgba(0, 0, 0, 0.05);
-}
+ .callout .highlight {
+ background-color: rgba(0, 0, 0, 0.05);
+ }
.callout-info {
--callout-color: var(--bs-info-text-emphasis);
@@ -384,9 +384,9 @@ a[target]::after {
border: 1px solid;
}
- .banner-test-site::after {
- content: "—Not for public distribution";
- }
+ .banner-test-site::after {
+ content: "—Not for public distribution";
+ }
}
/* Chemical select box
-------------------------------------------------- */
@@ -412,7 +412,17 @@ a[target]::after {
/* Apply directly to your custom accordion or override globally */
.accordion {
--bs-accordion-bg: #c8f8d5; /* Body background */
- /*--bs-accordion-color: #0d5f06;*/ /* Body text color */
+ /*--bs-accordion-color: #013901;*/ /* Body text color */
+ --bs-accordion-btn-bg: #c8f8d5; /* Collapsed button background */
+ --bs-accordion-btn-color: #212529; /* Collapsed button text */
+ --bs-accordion-active-bg: #013901; /* Expanded button background */
+ --bs-accordion-active-color: #fff; /* Expanded button text */
+ --bs-accordion-border-color: #09601d; /* Border color */
+}
+
+[data-bs-theme="dark"] .accordion {
+ --bs-accordion-bg: #c8f8d5; /* Body background */
+ --bs-accordion-color: #c8f8d5; /* Body text color */
--bs-accordion-btn-bg: #c8f8d5; /* Collapsed button background */
--bs-accordion-btn-color: #212529; /* Collapsed button text */
--bs-accordion-active-bg: #013901; /* Expanded button background */
@@ -420,20 +430,29 @@ a[target]::after {
--bs-accordion-border-color: #09601d; /* Border color */
}
- /*1. Header Button when EXPANDED (Active)*/
+/*1. Header Button when EXPANDED (Active)*/
.accordion-button:not(.collapsed) {
- background-color: #013901; /*Custom active background */
- color: #c8f8d5; /*Custom active text color */
+ background-color: #013901; /*Custom active background */
+ color: #c8f8d5; /*Custom active text color */
}
- /*2. Remove or change the blue focus halo outline*/
+/*2. Remove or change the blue focus halo outline*/
.accordion-button:focus {
box-shadow: initial;
border-color: #09601d;
}
- /*3. The inner body background */
+/*3. The inner body background */
.accordion-body {
background-color: #013901;
}
+/* Valid form control styles for flashing boxes*/
+.form-control.is-valid, .was-validated .form-control:valid {
+ border-color: var(--bs-form-valid-border-color);
+ padding-right: calc(1.5em + .75rem);
+ background-image: url("data:image/svg+xml,");
+ background-repeat: no-repeat;
+ background-position: right calc(.375em + .1875rem) center;
+ background-size: calc(.75em + .375rem) calc(.75em + .375rem);
+}
diff --git a/FMS/wwwroot/js/dateControlBorderFlash.js b/FMS/wwwroot/js/dateControlBorderFlash.js
new file mode 100644
index 00000000..18316174
--- /dev/null
+++ b/FMS/wwwroot/js/dateControlBorderFlash.js
@@ -0,0 +1,82 @@
+document.addEventListener('DOMContentLoaded', () => {
+ const reportingPeriodType = document.getElementById('Item_ReportingPeriodType');
+ const reportingPeriodStart = document.getElementById('Item_ReportingPeriodStart');
+ const reportingPeriodEnd = document.getElementById('Item_ReportingPeriodEnd');
+
+ if (!reportingPeriodType || !reportingPeriodStart || !reportingPeriodEnd) return;
+
+ reportingPeriodType.addEventListener('change', () => {
+ const today = new Date();
+ const currentYear = today.getFullYear();
+ const currentMonth = today.getMonth(); // 0-indexed
+
+ let startDate, endDate;
+
+ switch (reportingPeriodType.value) {
+ case '1': // Monthly
+ // Last month
+ startDate = new Date(currentYear, currentMonth - 1, 1);
+ endDate = new Date(currentYear, currentMonth, 0);
+ break;
+
+ case '2': // First Quarter
+ startDate = new Date(currentYear - (currentMonth < 3 ? 1 : 0), 0, 1);
+ endDate = new Date(currentYear - (currentMonth < 3 ? 1 : 0), 3, 0);
+ break;
+
+ case '3': // Second Quarter
+ startDate = new Date(currentYear - (currentMonth < 6 ? 1 : 0), 3, 1);
+ endDate = new Date(currentYear - (currentMonth < 6 ? 1 : 0), 6, 0);
+ break;
+
+ case '4': // Third Quarter
+ startDate = new Date(currentYear - (currentMonth < 9 ? 1 : 0), 6, 1);
+ endDate = new Date(currentYear - (currentMonth < 9 ? 1 : 0), 9, 0);
+ break;
+
+ case '5': // Fourth Quarter
+ startDate = new Date(currentYear - 1, 9, 1);
+ endDate = new Date(currentYear, 0, 0);
+ break;
+
+ case '6': // First Semiannual
+ startDate = new Date(currentYear - (currentMonth < 6 ? 1 : 0), 0, 1);
+ endDate = new Date(currentYear - (currentMonth < 6 ? 1 : 0), 6, 0);
+ break;
+
+ case '7': // Second Semiannual
+ startDate = new Date(currentYear - 1, 6, 1);
+ endDate = new Date(currentYear, 0, 0);
+ break;
+
+ case '8': // Annual
+ startDate = new Date(currentYear - 1, 0, 1);
+ endDate = new Date(currentYear, 0, 0);
+ break;
+
+ default:
+ return;
+ }
+
+ if (startDate && endDate) {
+ reportingPeriodStart.value = FormatDate(startDate);
+ reportingPeriodEnd.value = FormatDate(endDate);
+ reportingPeriodStart.classList.remove('flashOutline');
+ reportingPeriodEnd.classList.remove('flashOutline');
+ // `offsetWidth` is only referenced here to trigger a layout reflow, so that adding back the class restarts the CSS animation.
+ // See: https://css-tricks.com/restart-css-animation/#aa-update-another-javascript-method-to-restart-a-css-animation
+ reportingPeriodStart.offsetWidth; //NOSONAR triggers reflow
+ reportingPeriodEnd.offsetWidth; //NOSONAR triggers reflow
+ reportingPeriodStart.classList.add('flashOutline');
+ reportingPeriodEnd.classList.add('flashOutline');
+ }
+ });
+});
+
+function FormatDate(date) {
+ return [
+ date.getFullYear(),
+ String(date.getMonth() + 1).padStart(2, '0'),
+ String(date.getDate()).padStart(2, '0'),
+ ].join('-');
+}