Notifications
diff --git a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/NotificationsSettingViewComponent.cs b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/NotificationsSettingViewComponent.cs
index 7796a666de..ff9a8bda3e 100644
--- a/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/NotificationsSettingViewComponent.cs
+++ b/applications/Unity.GrantManager/modules/Unity.Notifications/src/Unity.Notifications.Web/Views/Settings/NotificationsSettingGroup/NotificationsSettingViewComponent.cs
@@ -37,6 +37,9 @@ public override void ConfigureBundle(BundleConfigurationContext context)
context
.Files
.AddIfNotContains("/Views/Settings/NotificationsSettingGroup/Default.js");
+ context
+ .Files
+ .AddIfNotContains("/Views/Settings/NotificationsSettingGroup/InternalEmailGroups.js");
}
}
diff --git a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Components/Topbar/Default.cshtml b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Components/Topbar/Default.cshtml
index 9383cf230e..bcd69a27dd 100644
--- a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Components/Topbar/Default.cshtml
+++ b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/Themes/UX2/Components/Topbar/Default.cshtml
@@ -45,24 +45,16 @@
@if (CurrentTenant.Id != null)
{
Applicant Portal Configuration
- }
- @if (await FeatureChecker.IsEnabledAsync("Unity.Payments") && isAuthorizedForPaymentConfiguration)
- {
-
Payments Configuration
- }
- @if (await FeatureChecker.IsEnabledAsync("Unity.Flex"))
+ }
+ @if (CurrentUser.IsInRole("system_admin") && await FeatureChecker.IsEnabledAsync("SettingManagement.Enable"))
{
-
Scoresheets Configuration
-
Custom Fields Configuration
+
Configuration Management
}
@if (CurrentUser.IsInRole("ITOperations"))
{
Unity Admin
}
- @if (CurrentUser.IsInRole("system_admin") && await FeatureChecker.IsEnabledAsync("SettingManagement.Enable"))
- {
-
Settings
- }
+
Logout
diff --git a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/zone-extensions.js b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/zone-extensions.js
index 1003da21fe..188fe000fc 100644
--- a/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/zone-extensions.js
+++ b/applications/Unity.GrantManager/modules/Unity.Theme.UX2/src/Unity.Theme.UX2/wwwroot/themes/ux2/zone-extensions.js
@@ -4,7 +4,7 @@
}
/**
- * Unflatten dot separated JSON objects into nested objects
+ * Unflatten dot separated JSON objects into nested objects
*/
$.fn.unflattenObject = function(flatObj) {
const result = {};
@@ -268,6 +268,14 @@ class UnityChangeTrackingForm {
this.saveButton.prop('disabled', this.modifiedFields.size === 0);
}
+ setSaving(isSaving) {
+ if (isSaving) {
+ this.saveButton.prop('disabled', true);
+ } else {
+ this.updateSaveButtonState();
+ }
+ }
+
/**
* Reset tracking without changing values
*/
@@ -354,7 +362,7 @@ class UnityZoneForm extends UnityChangeTrackingForm {
// NOTE Get field value by name or id
isValid() {
- return this.form.valid();
+ return this.form.valid();
}
initializeNumericFields() {
@@ -441,10 +449,10 @@ class UnityZoneForm extends UnityChangeTrackingForm {
let expandedProperties = {
'name': name,
'tag': this.tagName.toLowerCase(),
- 'type': this.type
- };
-
- tableOutput = { ...tableOutput, ...expandedProperties };
+ 'type': this.type
+ };
+
+ tableOutput = { ...tableOutput, ...expandedProperties };
}
let changeProperties = {
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json
index 89d5df054e..560172e365 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Domain.Shared/Localization/GrantManager/en.json
@@ -18,6 +18,7 @@
"Menu:TenantManagement": "Tenants",
"Menu:EndpointManagement": "Endpoints",
"Menu:Applicants": "Applicants",
+ "Menu:ConfigurationManagement": "Configuration Management",
"Welcome": "Welcome",
"LongWelcomeMessage": "Welcome to Unity Grant Manager",
@@ -102,20 +103,20 @@
"ReviewerList:Subtotal": "Subtotal",
"ReviewerList:CloneAssessment": "Clone Assessment",
- "AssessmentResultAttachments:Id": "#",
- "AssessmentResultAttachments:DocumentName": "Document Name",
- "AssessmentResultAttachments:UploadedDate": "Date",
- "AssessmentResultAttachments:AttachedBy": "Attached by",
- "ChefsAttachments:Title": "Submission Attachments",
- "ChefsAttachments:Filter": "Filter",
- "ChefsAttachments:Download": "Download",
- "ChefsAttachments:GenerateSummaries": "Generate AI Summaries",
- "ChefsAttachments:GenerateSummary": "Generate Summary",
- "ChefsAttachments:HideAISummaries": "Hide AI Summaries",
- "ChefsAttachments:ShowAISummaries": "Show AI Summaries",
- "ChefsAttachments:HideSummaries": "Hide Summaries",
- "ChefsAttachments:ShowSummaries": "Show Summaries",
- "ChefsAttachments:NoSummariesAvailable": "No summaries available",
+ "AssessmentResultAttachments:Id": "#",
+ "AssessmentResultAttachments:DocumentName": "Document Name",
+ "AssessmentResultAttachments:UploadedDate": "Date",
+ "AssessmentResultAttachments:AttachedBy": "Attached by",
+ "ChefsAttachments:Title": "Submission Attachments",
+ "ChefsAttachments:Filter": "Filter",
+ "ChefsAttachments:Download": "Download",
+ "ChefsAttachments:GenerateSummaries": "Generate AI Summaries",
+ "ChefsAttachments:GenerateSummary": "Generate Summary",
+ "ChefsAttachments:HideAISummaries": "Hide AI Summaries",
+ "ChefsAttachments:ShowAISummaries": "Show AI Summaries",
+ "ChefsAttachments:HideSummaries": "Hide Summaries",
+ "ChefsAttachments:ShowSummaries": "Show Summaries",
+ "ChefsAttachments:NoSummariesAvailable": "No summaries available",
"Enum:AssessmentState.IN_PROGRESS": "In Progress",
"Enum:AssessmentState.IN_REVIEW": "Under Review by Team Lead",
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Menus/GrantManagerMenus.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Menus/GrantManagerMenus.cs
index 5151503b1b..872b27f475 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Menus/GrantManagerMenus.cs
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Menus/GrantManagerMenus.cs
@@ -19,5 +19,6 @@ public static class GrantManagerMenus
public const string EndpointManagement = Prefix + ".EndpointManagement";
public const string AIReporting = Prefix + ".AIReporting";
public const string Applicants = Prefix + ".Applicants";
+ public const string ConfigurationManagement = Prefix + ".ConfigurationManagement";
public const string UnityAdmin = Prefix + ".UnityAdmin";
}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js
index f24fa44924..1cc5e6af97 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ApplicationForms/Mapping.js
@@ -136,6 +136,7 @@
let jsonText = $('#jsonText').val();
$.parseJSON(jsonText);
let mappingJsonStr = jsonText.replace(/\s+/g, ' ').replace(/(\r\n|\n|\r)/gm, "");
+ UIElements.btnSaveMapping.prop('disabled', true);
handleSaveMapping($.parseJSON(mappingJsonStr));
handleCancelMapping();
@@ -150,6 +151,7 @@
}
catch (err) {
+ UIElements.btnSaveMapping.prop('disabled', false);
abp.notify.error(
'',
'The JSON is not valid:' + err
@@ -313,6 +315,7 @@
formData["availableChefsFields"] = document.getElementById('availableChefsFields').value;
formData["ChefsApplicationFormGuid"] = document.getElementById('applicationFormId').value;
+ UIElements.btnSave.prop('disabled', true);
$.ajax(
{
url: "/api/app/application-form-version/" + formVersionId,
@@ -332,6 +335,9 @@
data.responseText,
'Mapping Not Saved Successful'
);
+ },
+ complete: function () {
+ UIElements.btnSave.prop('disabled', false);
}
}
);
@@ -640,6 +646,7 @@
};
btnSaveAIConfig.addEventListener('click', function () {
+ btnSaveAIConfig.disabled = true;
abp.ajax({
url: `/api/app/application-form/${aiFormId}/ai-config`,
type: 'PATCH',
@@ -658,6 +665,9 @@
})
.fail(function () {
abp.notify.error('Failed to save AI configuration.');
+ })
+ .always(function () {
+ btnSaveAIConfig.disabled = false;
});
});
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ConfigurationManagement/Index.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ConfigurationManagement/Index.cshtml
new file mode 100644
index 0000000000..08ab554893
--- /dev/null
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ConfigurationManagement/Index.cshtml
@@ -0,0 +1,273 @@
+@page
+@using Microsoft.AspNetCore.Mvc.Localization
+@using Unity.Flex.Web.Views.Shared.Components.WorksheetList
+@using Unity.GrantManager.Localization
+@using Unity.GrantManager.Web.Pages.ConfigurationManagement
+@using Volo.Abp.AspNetCore.Mvc.UI.Layout
+@model IndexModel
+
+@inject IHtmlLocalizer
L
+@inject IPageLayout PageLayout
+
+@{
+ PageLayout.Content.MenuItemName = Unity.GrantManager.Web.Menus.GrantManagerMenus.ConfigurationManagement;
+ ViewBag.PageTitle = "Configuration Management";
+}
+
+@section styles {
+
+
+ @if (Model.ShowPayments)
+ {
+
+ }
+ @if (Model.ShowNotifications)
+ {
+
+ }
+
+}
+
+@section scripts {
+
+
+ @if (Model.ShowPayments)
+ {
+
+ }
+
+
+ @if (Model.ShowNotifications)
+ {
+
+
+ }
+ @if (Model.ShowTags)
+ {
+
+ }
+ @if (Model.ShowAI)
+ {
+
+ }
+
+}
+
+
+
+
+
+
+
+ @if (Model.ShowNotifications)
+ {
+
+ @await Component.InvokeAsync(typeof(Unity.Notifications.Web.Views.Settings.NotificationsSettingGroup.NotificationsSettingViewComponent))
+
+ }
+
+
+ @if (Model.ShowPayments)
+ {
+
+ }
+
+
+ @if (Model.ShowCustomFields)
+ {
+
+ }
+
+
+ @if (Model.ShowScoresheets)
+ {
+
+ }
+
+
+ @if (Model.ShowTags)
+ {
+
+ @await Component.InvokeAsync(typeof(Unity.GrantManager.Web.Views.Settings.TagManagement.TagManagementViewComponent))
+
+ }
+
+
+ @if (Model.ShowAI)
+ {
+
+ @await Component.InvokeAsync(typeof(Unity.AI.Web.Views.Settings.AISettingGroup.AISettingViewComponent))
+
+ }
+
+
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ConfigurationManagement/Index.cshtml.cs b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ConfigurationManagement/Index.cshtml.cs
new file mode 100644
index 0000000000..b60b82d183
--- /dev/null
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ConfigurationManagement/Index.cshtml.cs
@@ -0,0 +1,76 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.Extensions.Configuration;
+
+using System;
+using System.Threading.Tasks;
+
+using Unity.AI.Permissions;
+using Unity.GrantManager.Permissions;
+using Unity.Modules.Shared;
+using Unity.Notifications.Permissions;
+using Unity.Payments.PaymentConfigurations;
+using Unity.Payments.Permissions;
+
+using Volo.Abp.Authorization.Permissions;
+using Volo.Abp.Features;
+
+namespace Unity.GrantManager.Web.Pages.ConfigurationManagement;
+
+[Authorize(UnitySettingManagementPermissions.UserInterface)]
+public class IndexModel(
+ IPaymentConfigurationAppService paymentConfigurationAppService,
+ IConfiguration configuration,
+ IFeatureChecker featureChecker,
+ IPermissionChecker permissionChecker) : GrantManagerPageModel
+{
+ public Guid? AccountCodingId { get; set; }
+ public string? PaymentIdPrefix { get; set; }
+ public string MaxFileSize { get; set; } = string.Empty;
+
+ // Visibility flags
+ public bool ShowNotifications { get; set; }
+ public bool ShowPayments { get; set; }
+ public bool ShowPaymentAccountCoding { get; set; }
+ public bool ShowPaymentSettings { get; set; }
+ public bool ShowCustomFields { get; set; }
+ public bool ShowScoresheets { get; set; }
+ public bool ShowTags { get; set; }
+ public bool ShowAI { get; set; }
+
+ public async Task OnGetAsync()
+ {
+ MaxFileSize = configuration["S3:MaxFileSize"] ?? "";
+
+ // Resolve feature + permission flags
+ ShowNotifications = await featureChecker.IsEnabledAsync("Unity.Notifications")
+ && await permissionChecker.IsGrantedAsync(NotificationsPermissions.Settings);
+
+ bool isPaymentsFeatureEnabled = await featureChecker.IsEnabledAsync("Unity.Payments");
+ bool isAuthorizedForPaymentConfiguration = await permissionChecker.IsGrantedAsync(UnitySettingManagementPermissions.ConfigurePayments);
+ ShowPayments = isPaymentsFeatureEnabled && isAuthorizedForPaymentConfiguration;
+
+ ShowPaymentAccountCoding = ShowPayments
+ && await permissionChecker.IsGrantedAsync(PaymentsPermissions.Payments.Default);
+ ShowPaymentSettings = ShowPayments
+ && await permissionChecker.IsGrantedAsync(UnitySelector.Payment.Summary.Default);
+
+ ShowCustomFields = await featureChecker.IsEnabledAsync("Unity.Flex");
+ ShowScoresheets = await featureChecker.IsEnabledAsync("Unity.Flex");
+
+ ShowTags = await permissionChecker.IsGrantedAsync(UnitySelector.SettingManagement.Tags.Default);
+
+ ShowAI = await featureChecker.IsEnabledAsync("Unity.AI.Scoring")
+ && await permissionChecker.IsGrantedAsync(AIPermissions.Configuration.ConfigureAI);
+
+ // Load payment data only if visible
+ if (ShowPayments)
+ {
+ var paymentConfiguration = await paymentConfigurationAppService.GetAsync();
+ if (paymentConfiguration != null)
+ {
+ AccountCodingId = paymentConfiguration.DefaultAccountCodingId;
+ PaymentIdPrefix = paymentConfiguration.PaymentIdPrefix;
+ }
+ }
+ }
+}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ConfigurationManagement/Index.css b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ConfigurationManagement/Index.css
new file mode 100644
index 0000000000..3e2796dc65
--- /dev/null
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ConfigurationManagement/Index.css
@@ -0,0 +1,117 @@
+/* Page layout: side menu + content side-by-side */
+.config-page-layout {
+ display: flex;
+ align-items: flex-start;
+ width: 100%;
+ gap: 0;
+}
+
+#ConfigurationManagementSideMenu.side-menu {
+ position: sticky;
+ margin-top: 20px;
+ top: 100px;
+ flex-shrink: 0;
+ z-index: 10;
+}
+
+#ConfigurationManagementSideMenu ul {
+ padding-left: 0;
+}
+
+#ConfigurationManagementSideMenu li {
+ height: 40px;
+ padding: 10px;
+ min-width: 200px;
+ margin-top: 2px;
+ border-radius: 0 100em 100em 0;
+}
+
+#ConfigurationManagementSideMenu .nav-item {
+ justify-content: left;
+}
+
+.config-content {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.config-management {
+ padding: 1rem;
+ max-height: 80vh;
+ overflow: auto;
+ margin-top: 20px;
+ width: 95%;
+}
+
+.unity-app-main-container {
+ margin: auto;
+ display: flex;
+ align-items: flex-start;
+}
+
+.hide {
+ display: none !important;
+}
+
+/* Resizable split layout for Worksheets and Scoresheets */
+.config-split-container {
+ display: flex;
+ height: calc(80vh - 60px);
+ width: 100%;
+}
+
+.config-split-left,
+.config-split-right {
+ overflow-y: auto;
+}
+
+.config-split-right {
+ width: 67%;
+ padding-left: 0;
+}
+
+.config-split-left {
+ width: 33%;
+ padding-right: 0;
+}
+
+.config-split-divider {
+ width: 10px;
+ background-color: #ccc;
+ cursor: col-resize;
+ border-radius: 10px;
+ flex-shrink: 0;
+}
+
+ .config-split-divider:hover {
+ background-color: #aaa;
+ }
+
+/* Preview panel (shared by Worksheets and Scoresheets) */
+.sticky-preview {
+ position: sticky;
+ top: 0;
+}
+
+.preview-section {
+ display: flex;
+ flex-wrap: wrap;
+ flex-direction: row;
+ justify-content: space-evenly;
+ align-items: center;
+}
+
+ .preview-section:has(> :nth-child(1)) .preview-field {
+ flex: 0 0 99%;
+ }
+
+ .preview-section:has(> :nth-child(2)) .preview-field {
+ flex: 0 0 45%;
+ }
+
+ .preview-section:has(> :nth-child(3)) .preview-field {
+ flex: 0 0 30%;
+ }
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ConfigurationManagement/Index.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ConfigurationManagement/Index.js
new file mode 100644
index 0000000000..45bd4463e4
--- /dev/null
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ConfigurationManagement/Index.js
@@ -0,0 +1,122 @@
+$(function () {
+ const menuItems = $('#ConfigurationManagementSideMenu .nav-item');
+ const configSections = $('.config-section');
+
+ init();
+
+ function init() {
+ menuItems.on('click', menuItemClick);
+
+ // Adjust DataTables when Payments internal tabs are shown
+ $('button[data-bs-toggle="tab"]').on('shown.bs.tab', function () {
+ adjustDataTables();
+ });
+
+ // Auto-select the first visible menu item
+ const firstMenuItem = menuItems.first();
+ if (firstMenuItem.length) {
+ firstMenuItem.addClass('active');
+ const targetId = firstMenuItem.data('target');
+ $('#' + targetId).removeClass('hide');
+ }
+
+ // Auto-activate the first Payment tab (if rendered)
+ const firstPaymentTab = $('#payments-nav-tab .nav-link').first();
+ if (firstPaymentTab.length) {
+ firstPaymentTab.addClass('active');
+ const targetPane = $(firstPaymentTab.data('bs-target'));
+ if (targetPane.length) {
+ targetPane.addClass('show active');
+ }
+ }
+ }
+
+ const splitRestoreMap = {
+ 'custom-fields-div': initResizableSplit('worksheet-split-container', 'worksheet-left', 'worksheet-divider', 'worksheet-right', 'worksheetSplitWidth'),
+ 'scoresheets-div': initResizableSplit('scoresheet-split-container', 'scoresheet-left', 'scoresheet-divider', 'scoresheet-right', 'scoresheetSplitWidth')
+ };
+
+ function menuItemClick(e) {
+ const clickedItem = $(e.currentTarget);
+ const targetId = clickedItem.data('target');
+
+ // Update active menu item
+ menuItems.removeClass('active');
+ clickedItem.addClass('active');
+
+ // Toggle content sections
+ configSections.addClass('hide');
+ $('#' + targetId).removeClass('hide');
+
+ // Restore split widths now that the section is visible
+ if (splitRestoreMap[targetId]) {
+ splitRestoreMap[targetId]();
+ }
+
+ adjustDataTables();
+ }
+
+ function adjustDataTables() {
+ // Adjust any visible DataTables after tab/section switch
+ if (typeof accountCodingDataTable !== 'undefined' && accountCodingDataTable) {
+ accountCodingDataTable.columns.adjust().draw();
+ }
+ if (typeof paymentSettingsDataTable !== 'undefined' && paymentSettingsDataTable) {
+ paymentSettingsDataTable.columns.adjust().draw();
+ }
+ $.fn.dataTable.tables({ visible: true, api: true }).columns.adjust();
+ }
+});
+function initResizableSplit(containerId, leftId, dividerId, rightId, storageKey) {
+ const container = document.getElementById(containerId);
+ const leftDiv = document.getElementById(leftId);
+ const divider = document.getElementById(dividerId);
+ const rightDiv = document.getElementById(rightId);
+
+ if (!container || !leftDiv || !divider || !rightDiv) {
+ return null;
+ }
+
+ function restoreSavedWidth() {
+ const saved = localStorage.getItem(storageKey);
+ if (saved && container.clientWidth > 0) {
+ const containerWidth = container.clientWidth;
+ const percentage = Number.parseFloat(saved);
+ const leftWidth = containerWidth * percentage;
+ const rightWidth = containerWidth - leftWidth - divider.offsetWidth;
+ leftDiv.style.width = leftWidth + 'px';
+ rightDiv.style.width = rightWidth + 'px';
+ }
+ }
+
+ function resize(e) {
+ const containerRect = container.getBoundingClientRect();
+ const leftWidth = e.clientX - containerRect.left;
+ const rightWidth = containerRect.right - e.clientX - divider.offsetWidth;
+
+ if (leftWidth > 200 && rightWidth > 200) {
+ leftDiv.style.width = leftWidth + 'px';
+ rightDiv.style.width = rightWidth + 'px';
+
+ // Save as percentage for responsive recalculation
+ localStorage.setItem(storageKey, (leftWidth / container.clientWidth).toString());
+ }
+ }
+
+ divider.addEventListener('mousedown', function (e) {
+ e.preventDefault();
+
+ function stopResize() {
+ document.removeEventListener('mousemove', resize);
+ document.removeEventListener('mouseup', stopResize);
+ }
+
+ document.addEventListener('mousemove', resize);
+ document.addEventListener('mouseup', stopResize);
+ });
+
+ // Recalculate on window resize (guard prevents no-op on hidden sections)
+ globalThis.addEventListener('resize', restoreSavedWidth);
+
+ return restoreSavedWidth;
+}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ConfigurationManagement/NotificationSettings.css b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ConfigurationManagement/NotificationSettings.css
new file mode 100644
index 0000000000..41c1a36acd
--- /dev/null
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ConfigurationManagement/NotificationSettings.css
@@ -0,0 +1,73 @@
+.v-scroll {
+ max-height: 600px;
+ overflow-y: auto;
+ padding-right: 10px;
+ scroll-behavior: smooth;
+}
+
+.tui-editor-body {
+ min-height: 250px;
+}
+
+.note-text {
+ font-size: 12px;
+}
+
+/* Group Users Table - Push remove button to far right */
+#groupUsersTable td:last-child,
+#createGroupUsersTable td:last-child {
+ text-align: right !important;
+}
+
+#groupUsersTable th:last-child,
+#createGroupUsersTable th:last-child {
+ width: 30px !important;
+ min-width: 30px !important;
+ max-width: 30px !important;
+}
+
+/* Fix DataTable empty message positioning */
+#groupUsersTable td.dataTables_empty,
+#createGroupUsersTable td.dataTables_empty,
+#groupUsersTable td.dt-empty,
+#createGroupUsersTable td.dt-empty {
+ text-align: center !important;
+}
+
+/* Ensure table maintains proper width */
+#createGroupUsersTable {
+ width: 100% !important;
+}
+
+/* Add User Button Styling */
+.btn-add-user {
+ background-color: white !important;
+ border: 2px solid var(--bs-primary) !important;
+ color: var(--bs-primary) !important;
+ font-weight: 700;
+ padding: 0.25rem 0.5rem;
+ white-space: nowrap;
+ transition: all 0.15s ease-in-out;
+ border-radius: 4px;
+ font-size: 0.875rem;
+}
+
+ .btn-add-user:hover:not(:disabled) {
+ background-color: var(--bs-primary) !important;
+ color: white !important;
+ }
+
+ .btn-add-user:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ border-color: var(--bs-secondary) !important;
+ color: var(--bs-secondary) !important;
+ }
+
+ .btn-add-user i {
+ font-size: 0.8rem;
+ }
+
+.modal-footer {
+ margin-top: 10px;
+}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ConfigurationManagement/PaymentConfigurations.css b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ConfigurationManagement/PaymentConfigurations.css
new file mode 100644
index 0000000000..07651b7b9e
--- /dev/null
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ConfigurationManagement/PaymentConfigurations.css
@@ -0,0 +1,92 @@
+/* PaymentConfigurations styles - local copy for ConfigurationManagement */
+
+.readonly {
+ color: var(--bc-colors-grey-text-500);
+ background-color: var(--bc-colors-blue-100, #D8EAFD);
+ width: 332px !important;
+}
+
+.currencyinput {
+ float: left;
+}
+
+.currency {
+ text-align: right;
+ width: 100%;
+ margin-top: -31px;
+}
+
+.dm-approval {
+ width: 100%;
+ max-width: 100%;
+}
+
+.payment-form h4 {
+ padding: 4px;
+ font-weight: 700;
+}
+
+.payment-form .mb-3:nth-child(-n+3) {
+ width: 30%;
+}
+
+.payment-form .mb-3:nth-child(n+4) {
+ width: 45%;
+}
+
+.payment-form .mb-3:nth-child(n+6) {
+ width: 100%;
+}
+
+.submit-section {
+ display: inline-flex;
+ gap: 1rem;
+}
+
+.note {
+ margin-left: 5px;
+}
+
+.payment-prefix {
+ width: 200px;
+ margin-top: -7px;
+}
+
+.field-validation-error {
+ float: inline-start;
+ display: block;
+ max-width: 100%;
+ width: 100%;
+}
+
+#payments-div .modal-dialog {
+ max-width: 850px !important;
+ min-width: 850px !important;
+}
+
+#payments-div input.form-control:focus, #payments-div input.form-control:active, #payments-div textarea.form-control:focus, #payments-div textarea.form-control:active, #payments-div .form-select:focus, #payments-div .form-select:active {
+ font-weight: inherit !important;
+}
+
+#payment-settings-div {
+ width: 750px;
+}
+
+#PaymentThreshold_Threshold {
+ text-align: right;
+}
+
+label.display-input-label {
+ font-size: var(--bc-font-size-sm);
+ color: var(--bc-colors-grey-text-300);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ margin-bottom: 0;
+}
+
+@media only screen and (max-width: 850px) {
+ .field-validation-error {
+ float: none;
+ }
+}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ConfigurationManagement/PaymentConfigurations.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ConfigurationManagement/PaymentConfigurations.js
new file mode 100644
index 0000000000..8cd446f471
--- /dev/null
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ConfigurationManagement/PaymentConfigurations.js
@@ -0,0 +1,429 @@
+let accountCodingDataTable;
+let paymentSettingsDataTable;
+
+$(function () {
+ let createModal = new abp.ModalManager(abp.appPath + 'AccountCoding/CreateModal');
+ let updateModal = new abp.ModalManager(abp.appPath + 'AccountCoding/UpdateModal');
+ let updateThresholdModal = new abp.ModalManager(abp.appPath + 'PaymentThresholds/UpdateModal');
+ const formatter = createNumberFormatter();
+
+ const l = abp.localization.getResource('GrantManager');
+ toastr.options.positionClass = 'toast-top-center';
+
+ const UIElements = {
+ accountCodingDT: $('#AccountCodesDataTable'),
+ paymentSettingsDT: $('#PaymentSettingsDataTable'),
+ accountCodingId: $('#AccountCodingId'),
+ paymentPrefixSaveButton: $('#PaymentPrefixSaveButton'),
+ paymentPrefixDiscardButton: $('#PaymentPrefixDiscardButton'),
+ paymentPrefixInput: $('#payment-id-prefix'),
+ originalPaymentPrefix: $('#payment-id-prefix-original')
+ };
+
+ init();
+
+ function init() {
+ const hasAccountCodesDataTable = UIElements.accountCodingDT.length > 0;
+ const hasPaymentSettingsDataTable = UIElements.paymentSettingsDT.length > 0;
+ if (!hasAccountCodesDataTable && !hasPaymentSettingsDataTable) {
+ return;
+ }
+ if (hasAccountCodesDataTable) {
+ accountCodingDataTable = initializeAccountCodesDataTable();
+ }
+ if (hasPaymentSettingsDataTable) {
+ paymentSettingsDataTable = initializePaymentSettingsDataTable();
+ }
+ bindUIElements();
+ }
+
+ function bindUIElements() {
+ UIElements.paymentPrefixSaveButton.on('click', updatePaymentPrefix);
+ UIElements.paymentPrefixDiscardButton.on('click', discardPaymentPrefix);
+ UIElements.paymentPrefixInput.on('keyup', checkEnableDiscard);
+ }
+
+ function bindModalElements() {
+ const UIElements = {
+ inputMinistryClient: $('input[name="AccountCoding.MinistryClient"]'),
+ inputResponsibility: $('input[name="AccountCoding.Responsibility"]'),
+ inputServiceLine: $('input[name="AccountCoding.ServiceLine"]'),
+ inputStob: $('input[name="AccountCoding.Stob"]'),
+ inputProjectNumber: $('input[name="AccountCoding.ProjectNumber"]'),
+ inputPaymentThreshold: $('#PaymentThreshold_Threshold'),
+ readOnlyAccountCoding: $('#account-coding')
+ };
+
+ UIElements.inputMinistryClient.on('keyup', setAccountCodingDisplay);
+ UIElements.inputResponsibility.on('keyup', setAccountCodingDisplay);
+ UIElements.inputServiceLine.on('keyup', setAccountCodingDisplay);
+ UIElements.inputStob.on('keyup', setAccountCodingDisplay);
+ UIElements.inputProjectNumber.on('keyup', setAccountCodingDisplay);
+
+ UIElements.inputPaymentThreshold.on('keyup', preventDecimalKeyUp);
+ UIElements.inputPaymentThreshold.on('keypress', preventNonCurrencyKeyPress);
+
+
+
+
+ function setAccountCodingDisplay() {
+ let currentAccount = $(UIElements.inputMinistryClient).val() + "." +
+ $(UIElements.inputResponsibility).val() + "." +
+ $(UIElements.inputServiceLine).val() + "." +
+ $(UIElements.inputStob).val() + "." +
+ $(UIElements.inputProjectNumber).val();
+
+ $(UIElements.readOnlyAccountCoding).val(currentAccount);
+ }
+
+ setAccountCodingDisplay();
+ }
+
+ function initializePaymentSettingsDataTable() {
+ let actionButtons = [];
+ const listColumns = getPaymentSettingsColumns();
+
+ const defaultVisibleColumns = [
+ 'userName',
+ 'paymentThreshold',
+ 'description'
+ ];
+
+ let dt = UIElements.paymentSettingsDT;
+ return initializeDataTable({
+ dt,
+ defaultVisibleColumns,
+ listColumns,
+ maxRowsPerPage: 25,
+ defaultSortColumn: {
+ name: 'userName',
+ dir: 'asc'
+ },
+ dataEndpoint: unity.grantManager.payments.paymentSettings.getL2ApproversThresholds,
+ data: {},
+ responseCallback: paymentSettingsResponseCallback,
+ actionButtons,
+ pagingEnabled: true,
+ reorderEnabled: false,
+ languageSetValues: {},
+ dataTableName: 'PaymentSettingsDataTable',
+ dynamicButtonContainerId: 'dynamicButtonContainerId',
+ useNullPlaceholder: true,
+ disableColumnSelect: true,
+ externalSearchId: 'search-data-table'
+ });
+
+
+ }
+
+ function initializeAccountCodesDataTable() {
+ $.fn.dataTable.Buttons.defaults.dom.button.className = 'btn flex-none';
+ let actionButtons = [
+ {
+ text: ' ' + l('Common:Command:Create') + '',
+ titleAttr: l('Common:Command:Create'),
+ id: 'CreateButton',
+ className: 'btn-light rounded-1',
+ action: (e, dt, node, config) => createAccountCodingBtn(e)
+ }
+ ];
+
+ const listColumns = getAccountCodingColumns();
+
+ const defaultVisibleColumns = [
+ 'ministryClient',
+ 'responsibility',
+ 'serviceLine',
+ 'stob',
+ 'projectNumber',
+ 'description',
+ 'defaultRadio',
+ 'rowActions',
+ ];
+
+ let dt = UIElements.accountCodingDT;
+ return initializeDataTable({
+ dt,
+ defaultVisibleColumns,
+ listColumns,
+ maxRowsPerPage: 25,
+ defaultSortColumn: {
+ name: 'ministryClient',
+ dir: 'asc'
+ },
+ dataEndpoint: unity.grantManager.payments.accountCoding.getList,
+ data: {},
+ responseCallback: accountCodesResponseCallback,
+ actionButtons,
+ pagingEnabled: true,
+ reorderEnabled: false,
+ languageSetValues: {},
+ dataTableName: 'AccountCodesDataTable',
+ dynamicButtonContainerId: 'dynamicButtonContainerId',
+ useNullPlaceholder: true,
+ disableColumnSelect: true,
+ externalSearchId: 'search-data-table'
+ });
+ }
+
+ function getAccountCodingColumns() {
+ let index = 0;
+ return [
+ {
+ title: 'Ministry Client',
+ name: "ministryClient",
+ data: "ministryClient",
+ visible: true,
+ index: index++
+ },
+ {
+ title: 'Responsibility',
+ name: "responsibility",
+ data: "responsibility",
+ visible: true,
+ index: index++
+ },
+ {
+ title: 'Service Line',
+ name: "serviceLine",
+ data: "serviceLine",
+ visible: true,
+ index: index++
+ },
+ {
+ title: 'Stob',
+ name: "stob",
+ data: "stob",
+ visible: true,
+ index: index++
+ },
+ {
+ title: 'Project #',
+ name: "projectNumber",
+ data: "projectNumber",
+ visible: true,
+ index: index++
+ },
+ {
+ title: 'Description',
+ name: "description",
+ data: "description",
+ visible: true,
+ index: index++
+ },
+ {
+ title: 'Default',
+ orderable: false,
+ visible: true,
+ className: 'notexport text-center',
+ name: 'defaultRadio',
+ index: index++,
+ data: 'id',
+ render: function (data, type, full, meta) {
+ let checked = UIElements.accountCodingId.val() == data ? 'checked' : '';
+ return ``;
+ }
+ },
+ {
+ title: 'Action',
+ orderable: false,
+ sortable: false,
+ data: 'id',
+ className: 'notexport text-center',
+ name: 'rowActions',
+ visible: true,
+ index: index++,
+ rowAction: {
+ items:
+ [
+ {
+ text: 'Edit',
+ action: (data) => editAccountCodingBtn(data.record.id)
+ }
+ ]
+ }
+ }
+ ];
+ }
+
+ createModal.onResult(function () {
+ accountCodingDataTable.ajax.reload();
+ });
+
+ updateModal.onResult(function () {
+ accountCodingDataTable.ajax.reload();
+ });
+
+ updateThresholdModal.onResult(function () {
+ paymentSettingsDataTable.ajax.reload();
+ });
+
+ function editThresholdBtn(id, userName) {
+ updateThresholdModal.open({ id: id, userName: userName });
+ updateThresholdModal.onOpen(function () {
+ bindModalElements();
+ });
+ }
+
+ function editAccountCodingBtn(id) {
+ updateModal.open({ id: id });
+ updateModal.onOpen(function () {
+ bindModalElements();
+ });
+ }
+
+ function createAccountCodingBtn(e) {
+ e.preventDefault();
+ createModal.open();
+ createModal.onOpen(function () {
+ bindModalElements();
+ });
+ }
+
+ function updatePaymentPrefix() {
+ unity.payments.paymentConfigurations.paymentConfiguration.updatePaymentPrefix(UIElements.paymentPrefixInput.val())
+ .done(function () {
+ toastr.success('Payment prefix updated successfully.');
+ $('#payment-id-prefix-original').val(UIElements.paymentPrefixInput.val());
+ checkEnableDiscard();
+ })
+ .fail(function () {
+ toastr.error('Failed to update payment prefix.');
+ });
+ }
+
+ function checkEnableDiscard() {
+ const originalPrefix = UIElements.originalPaymentPrefix.val();
+ const currentPrefix = UIElements.paymentPrefixInput.val();
+ UIElements.paymentPrefixDiscardButton.prop('disabled', currentPrefix === originalPrefix);
+ }
+
+ function discardPaymentPrefix() {
+ UIElements.paymentPrefixInput.val(UIElements.originalPaymentPrefix.val());
+ toastr.info('Payment prefix changes discarded.');
+ checkEnableDiscard();
+ }
+
+ function getPaymentSettingsColumns() {
+ let index = 0;
+ return [
+ {
+ title: 'Id',
+ name: "id",
+ data: "id",
+ visible: false,
+ index: index++
+ },
+ {
+ title: 'User Id',
+ name: "userId",
+ data: "userId",
+ visible: false,
+ index: index++
+ },
+ {
+ title: 'Expense Authority',
+ name: "userName",
+ data: "userName",
+ visible: true,
+ index: index++
+ },
+ {
+ title: 'Approval Threshold',
+ name: "paymentThreshold",
+ className: 'dt-body-right',
+ data: "threshold",
+ visible: true,
+ index: index++,
+ render: function (data, type, row) {
+ if (data == null || data === '') return '';
+ return formatter.format(data);
+ }
+ },
+ {
+ title: 'Description',
+ name: "description",
+ data: "description",
+ visible: true,
+ index: index++
+ },
+ {
+ title: 'Action',
+ orderable: false,
+ sortable: false,
+ data: 'id',
+ className: 'notexport text-center',
+ name: 'rowActions',
+ visible: true,
+ index: index++,
+ rowAction: {
+ items:
+ [
+ {
+ text: 'Edit',
+ action: (data) => editThresholdBtn(data.record.id, data.record.userName)
+ }
+ ]
+ }
+ }
+ ];
+ }
+});
+
+function paymentSettingsResponseCallback(result) {
+ return {
+ recordsTotal: result.length,
+ recordsFiltered: result.length,
+ data: result
+ };
+}
+
+function accountCodesResponseCallback(result) {
+ return {
+ recordsTotal: result.totalCount,
+ recordsFiltered: result.items.length,
+ data: result.items
+ };
+}
+
+function clearFilter() {
+ $('#search-data-table').val('');
+ $('#search-data-table').trigger("keyup");
+}
+
+function handleDefaultAccountCodeRadioClick(id) {
+ $('#AccountCodingId').val(id);
+ unity.payments.paymentConfigurations.paymentConfiguration.setDefaultAccountCode(id).done(function () {
+ toastr.success('Successfully set default account code. Reloading account codes.');
+ clearAccountCodesSearchAndReload();
+ }).fail(function () {
+ toastr.error('Failed to set default account code.');
+ });
+}
+
+function clearAccountCodesSearchAndReload() {
+ clearFilter();
+ accountCodingDataTable.search('').draw();
+
+ localStorage.removeItem('DataTables_AccountCodesDataTable_/ConfigurationManagement');
+ localStorage.removeItem('DataTables_PaymentSettingsDataTable_/ConfigurationManagement');
+
+ accountCodingDataTable.ajax.reload();
+}
+
+function preventNonCurrencyKeyPress(e) {
+ if (/[a-zA-Z]/.test(e.key) || e.key === ' ' || e.key === '-' || e.keyCode === 45) {
+ e.preventDefault();
+ }
+}
+
+function preventDecimalKeyUp(e) {
+ const input = e.target;
+ const cursorPosition = input.selectionStart;
+ const decimalMatch = input.value.match(/\.(\d+)/);
+
+ if (decimalMatch && decimalMatch[1].length > 2) {
+ input.value = input.value.replace(/\.(\d{2}).*/, '.$1');
+ input.setSelectionRange(cursorPosition, cursorPosition);
+ }
+}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ConfigurationManagement/ScoresheetConfiguration.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ConfigurationManagement/ScoresheetConfiguration.js
new file mode 100644
index 0000000000..b60cbf7393
--- /dev/null
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/ConfigurationManagement/ScoresheetConfiguration.js
@@ -0,0 +1,103 @@
+/* ScoresheetConfiguration JS - local copy for ConfigurationManagement */
+
+(function ($) {
+ const scoresheetModal = new abp.ModalManager({
+ viewUrl: 'ScoresheetConfiguration/ScoresheetModal'
+ });
+
+ const cloneScoresheetModal = new abp.ModalManager({
+ viewUrl: 'ScoresheetConfiguration/CloneScoresheetModal'
+ });
+
+ const publishScoresheetModal = new abp.ModalManager({
+ viewUrl: 'ScoresheetConfiguration/PublishScoresheetModal'
+ });
+
+ let scoresheetToEditId = null;
+
+ scoresheetModal.onResult(function (response) {
+ const actionType = $(response.currentTarget).find('#ActionType').val();
+ if (actionType.startsWith('Delete')) {
+ PubSub.publish('refresh_scoresheet_list', { scoresheetId: null });
+ } else {
+ PubSub.publish('refresh_scoresheet_list', { scoresheetId: scoresheetToEditId });
+ }
+ abp.notify.success(
+ actionType + ' is successful.',
+ 'Scoresheet'
+ );
+ });
+
+ cloneScoresheetModal.onResult(function (response) {
+ PubSub.publish('refresh_scoresheet_list', { scoresheetId: null });
+ abp.notify.success(
+ 'Scoresheet cloning is successful.',
+ 'Scoresheet'
+ );
+ });
+
+ publishScoresheetModal.onResult(function (response) {
+ PubSub.publish('refresh_scoresheet_list', { scoresheetId: scoresheetToEditId });
+ abp.notify.success(
+ 'Scoresheet publishing is successful.',
+ 'Scoresheet'
+ );
+ });
+
+ // Exposed globally — called from inline onclick attributes in Scoresheet component HTML
+ window.openScoresheetModal = function (scoresheetId, actionType) {
+ scoresheetToEditId = scoresheetId;
+ scoresheetModal.open({
+ scoresheetId: scoresheetId,
+ actionType: actionType
+ });
+ };
+
+ window.openCloneScoresheetModal = function (scoresheetId) {
+ scoresheetToEditId = scoresheetId;
+ cloneScoresheetModal.open({
+ scoresheetId: scoresheetId
+ });
+ };
+
+ window.openPublishScoresheetModal = function (scoresheetId) {
+ scoresheetToEditId = scoresheetId;
+ publishScoresheetModal.open({
+ scoresheetId: scoresheetId
+ });
+ };
+
+ function showAccordion(scoresheetId) {
+ if (!scoresheetId) {
+ return;
+ }
+ const accordionId = 'collapse-' + scoresheetId;
+ const accordion = document.getElementById(accordionId);
+ accordion.classList.add('show');
+
+ const buttonId = 'accordion-button-' + scoresheetId;
+ const accordionButton = document.getElementById(buttonId);
+ accordionButton.classList.remove('collapsed');
+ }
+
+ function refreshScoresheetInfoWidget(scoresheetId) {
+ const url = `../Flex/Widget/Scoresheet/Refresh`;
+ fetch(url)
+ .then(response => response.text())
+ .then(data => {
+ document.getElementById('scoresheet-info-widget').innerHTML = data;
+ showAccordion(scoresheetId);
+ PubSub.publish('refresh_scoresheet_configuration_page');
+ })
+ .catch(error => {
+ console.error('Error refreshing scoresheet-info-widget:', error);
+ });
+ }
+
+ PubSub.subscribe(
+ 'refresh_scoresheet_list',
+ (msg, data) => {
+ refreshScoresheetInfoWidget(data.scoresheetId);
+ }
+ );
+})(jQuery);
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.js
index 41ecd22540..d0d87af7c8 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.js
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Pages/GrantApplications/Details.js
@@ -1305,6 +1305,9 @@ function updateCustomForm(
.update(customFormUpdate)
.done(function () {
abp.notify.success('Information has been updated.');
+ })
+ .fail(function () {
+ $(`#${saveId}`).prop('disabled', false);
});
}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Settings/TagManagement/TagManagement.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Settings/TagManagement/TagManagement.js
index 127d0257a0..3cfaf63254 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Settings/TagManagement/TagManagement.js
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Settings/TagManagement/TagManagement.js
@@ -1,9 +1,7 @@
const TagTypes = {};
-let userCanUpdate = abp.auth.isGranted('Unity.GrantManager.SettingManagement.Tags.Update');
-let userCanDelete = abp.auth.isGranted('Unity.GrantManager.SettingManagement.Tags.Delete');
-let addNewTagModal = new abp.ModalManager({
- viewUrl: 'Tags/CreateTagsModal'
-});
+let userCanUpdate;
+let userCanDelete;
+let addNewTagModal;
function defineTagSummaryColumnDefs() {
const columnDefs = [
@@ -162,6 +160,12 @@ function getUnifiedTagSummaryAjax(requestData, callback, settings) {
});
}
$(function () {
+ userCanUpdate = abp.auth.isGranted('Unity.GrantManager.SettingManagement.Tags.Update');
+ userCanDelete = abp.auth.isGranted('Unity.GrantManager.SettingManagement.Tags.Delete');
+ addNewTagModal = new abp.ModalManager({
+ viewUrl: 'Tags/CreateTagsModal'
+ });
+
abp.log.debug('TagManagement.js initialized!');
abp.modals.RenameTag = function () {
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Settings/TagManagement/TagManagementViewComponent.cshtml b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Settings/TagManagement/TagManagementViewComponent.cshtml
index 6e0cb90dd0..861e5710d7 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Settings/TagManagement/TagManagementViewComponent.cshtml
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Settings/TagManagement/TagManagementViewComponent.cshtml
@@ -3,8 +3,6 @@
@inject IPermissionChecker PermissionChecker
-
-
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantContacts/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantContacts/Default.js
index de2d963e45..e59d9a1039 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantContacts/Default.js
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantContacts/Default.js
@@ -140,6 +140,7 @@ $(function () {
}
};
+ zoneForm.setSaving(true);
unity.grantManager.applicants.applicant
.updateApplicantContactAddresses(applicantId, payload)
.done(function () {
@@ -149,6 +150,7 @@ $(function () {
})
.fail(function () {
abp.notify.error('Failed to update contact.');
+ zoneForm.setSaving(false);
});
});
}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantHistory/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantHistory/Default.js
index 61450ef2d4..c9688e999e 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantHistory/Default.js
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantHistory/Default.js
@@ -23,6 +23,7 @@ $(function () {
return;
}
+ zoneForm.setSaving(true);
unity.grantManager.applicantProfile.applicantHistory
.saveNotes(applicantId, {
fundingHistoryComments: $('#FundingHistoryComments').val(),
@@ -35,6 +36,7 @@ $(function () {
})
.fail(function () {
abp.notify.error('Failed to save history notes.');
+ zoneForm.setSaving(false);
});
});
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantInfo/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantInfo/Default.js
index f85c5e42b4..b782da861c 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantInfo/Default.js
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ApplicantInfo/Default.js
@@ -90,6 +90,7 @@ abp.widgets.ApplicantInfo = function ($wrapper) {
let applicationId = document.getElementById('ApplicantInfo_ApplicationId').value;
let applicantInfoSubmission = self.getPartialUpdate();
+ self.zoneForm.setSaving(true);
try {
unity.grantManager.grantApplications.applicationApplicant
.updatePartialApplicantInfo(applicationId, applicantInfoSubmission)
@@ -102,10 +103,12 @@ abp.widgets.ApplicantInfo = function ($wrapper) {
.fail(function (error) {
abp.notify.error('Failed to update Applicant Info.');
console.log(error);
+ self.zoneForm.setSaving(false);
});
} catch (error) {
abp.notify.error('An unexpected error occurred.');
console.log(error);
+ self.zoneForm.setSaving(false);
}
});
},
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/AssessmentScoresWidget/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/AssessmentScoresWidget/Default.js
index 428d691d21..fb6bcdc61c 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/AssessmentScoresWidget/Default.js
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/AssessmentScoresWidget/Default.js
@@ -34,6 +34,8 @@ function saveScoresSection(formId, sectionId) {
};
//Calls an enpoint and disabled buttons
+ secSaveButton.disabled = true;
+ secDiscardButton.disabled = true;
unity.grantManager.assessments.assessment
.saveScoresheetSectionAnswers(data)
.done(function () {
@@ -52,14 +54,15 @@ function saveScoresSection(formId, sectionId) {
}
}
- secSaveButton.disabled = true;
- secDiscardButton.disabled = true;
-
updateSubtotal();
PubSub.publish(
'refresh_review_list_without_sidepanel',
assessmentId
);
+ })
+ .fail(function () {
+ secSaveButton.disabled = false;
+ secDiscardButton.disabled = false;
});
}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/EmailsWidget/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/EmailsWidget/Default.js
index fadcee3017..375a71bf30 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/EmailsWidget/Default.js
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/EmailsWidget/Default.js
@@ -334,6 +334,7 @@
templateName = $('#templateText').val();
}
+ UIElements.btnSave.prop('disabled', true);
unity.grantManager.emails.email
.saveDraft({
emailId: UIElements.inputEmailId[0].value,
@@ -353,6 +354,7 @@
abp.notify.success('Your email has been saved.');
PubSub.publish('refresh_application_emails');
}).catch(function () {
+ UIElements.btnSave.prop('disabled', false);
abp.notify.error('An error ocurred your email could not be saved.');
});
} else {
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/FundingAgreementInfo/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/FundingAgreementInfo/Default.js
index 285a05f607..f74ebbc6b9 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/FundingAgreementInfo/Default.js
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/FundingAgreementInfo/Default.js
@@ -49,6 +49,7 @@
}
function updateFundingAgreementInfo(applicationId, fundingAgreementInfoObj) {
+ $('#saveFundingAgreementInfoBtn').prop('disabled', true);
try {
unity.grantManager.grantApplications.grantApplication
.updateFundingAgreementInfo(applicationId, fundingAgreementInfoObj)
@@ -59,6 +60,9 @@
$('#saveFundingAgreementInfoBtn').prop('disabled', true);
PubSub.publish('funding_agreement_info_saved', fundingAgreementInfoObj);
PubSub.publish('refresh_detail_panel_summary');
+ })
+ .fail(function () {
+ $('#saveFundingAgreementInfoBtn').prop('disabled', false);
});
}
catch (error) {
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/PaymentConfiguration/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/PaymentConfiguration/Default.js
index 44e7a40851..c9d3e5bbe8 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/PaymentConfiguration/Default.js
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/PaymentConfiguration/Default.js
@@ -197,6 +197,8 @@
return;
}
+ UIElements.btnSave.prop('disabled', true);
+
const hierarchyValue = UIElements.formHierarchy.val();
const formHierarchy = hierarchyValue ? parseInt(hierarchyValue, 10) : null;
const parentFormId = UIElements.parentFormSelect.val();
@@ -235,6 +237,9 @@
confirmButton: 'btn btn-primary'
}
});
+ })
+ .catch(() => {
+ UIElements.btnSave.prop('disabled', false);
});
}
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ProjectInfo/Default.js b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ProjectInfo/Default.js
index f2101f7c80..190f22e6fb 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ProjectInfo/Default.js
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/Views/Shared/Components/ProjectInfo/Default.js
@@ -90,6 +90,7 @@ abp.widgets.ProjectInfo = function ($wrapper) {
data: modifiedFieldData
};
+ self.zoneForm.setSaving(true);
try {
unity.grantManager.grantApplications.grantApplication
.updatePartialProjectInfo(applicationId, projectInfoSubmission)
@@ -98,11 +99,14 @@ abp.widgets.ProjectInfo = function ($wrapper) {
self.zoneForm.resetTracking();
PubSub.publish('project_info_saved', projectInfoObj);
PubSub.publish('refresh_detail_panel_summary');
+ })
+ .fail(function () {
+ self.zoneForm.setSaving(false);
});
}
catch (error) {
console.log(error);
- self.zoneForm.resetTracking();
+ self.zoneForm.setSaving(false);
}
});
diff --git a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.json b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.json
index 50a4bfa585..b74c91d2f4 100644
--- a/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.json
+++ b/applications/Unity.GrantManager/src/Unity.GrantManager.Web/appsettings.json
@@ -165,6 +165,8 @@
}
},
"OpenAI": {
+ "ApiKey": "",
+ "Endpoint": "",
"Profiles": {
"Gpt4oMini": {
"ApiUrl": "/openai/deployments/gpt-4o-mini/chat/completions?api-version=2024-02-01",
diff --git a/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/AI/Runtime/OpenAIConfigurationResolverTests.cs b/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/AI/Runtime/OpenAIConfigurationResolverTests.cs
new file mode 100644
index 0000000000..aa95ac7b8a
--- /dev/null
+++ b/applications/Unity.GrantManager/test/Unity.GrantManager.Application.Tests/AI/Runtime/OpenAIConfigurationResolverTests.cs
@@ -0,0 +1,29 @@
+using Microsoft.Extensions.Configuration;
+using Shouldly;
+using System.Collections.Generic;
+using Unity.AI.Runtime;
+using Xunit;
+
+namespace Unity.GrantManager.AI.Runtime;
+
+public class OpenAIConfigurationResolverTests
+{
+ [Fact]
+ public void ResolveApiUrl_Should_CombineEndpointWithLeadingSlashProfilePath()
+ {
+ var configuration = new ConfigurationBuilder()
+ .AddInMemoryCollection(new Dictionary
+ {
+ ["Azure:Operations:Defaults:Provider"] = "OpenAI",
+ ["Azure:Operations:Defaults:Profile"] = "Gpt4oMini",
+ ["Azure:OpenAI:Endpoint"] = "https://d837ad-test-recap-webapp.azurewebsites.net",
+ ["Azure:OpenAI:Profiles:Gpt4oMini:ApiUrl"] = "/openai/deployments/gpt-4o-mini/chat/completions?api-version=2024-02-01"
+ })
+ .Build();
+
+ var resolver = new OpenAIConfigurationResolver(configuration);
+
+ resolver.ResolveApiUrl().ShouldBe(
+ "https://d837ad-test-recap-webapp.azurewebsites.net/openai/deployments/gpt-4o-mini/chat/completions?api-version=2024-02-01");
+ }
+}