From b9367ae8cbeeb2c0d70cf22eb134b58019e76f59 Mon Sep 17 00:00:00 2001 From: Andrew Dickinson Date: Sun, 12 Jan 2025 16:08:15 -0500 Subject: [PATCH] Warn the user if they forgot to set install/abandon date (#806) * Warn the user if they forgot to set install/abandon date * Move DOM element construction into HTML template * Add node support --- src/meshapi/admin/models/install.py | 8 +- src/meshapi/admin/models/node.py | 8 +- .../static/widgets/warn_about_date.css | 3 + src/meshapi/static/widgets/warn_about_date.js | 98 +++++++++++++++++++ .../templates/widgets/warn_about_date.html | 11 +++ src/meshapi/widgets.py | 10 ++ src/meshweb/static/admin/admin_ext.css | 8 +- 7 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 src/meshapi/static/widgets/warn_about_date.css create mode 100644 src/meshapi/static/widgets/warn_about_date.js create mode 100644 src/meshapi/templates/widgets/warn_about_date.html diff --git a/src/meshapi/admin/models/install.py b/src/meshapi/admin/models/install.py index 8c7778bc..9859e855 100644 --- a/src/meshapi/admin/models/install.py +++ b/src/meshapi/admin/models/install.py @@ -12,7 +12,7 @@ from simple_history.admin import SimpleHistoryAdmin from meshapi.models import Install -from meshapi.widgets import ExternalHyperlinkWidget +from meshapi.widgets import ExternalHyperlinkWidget, WarnAboutDatesWidget from ..ranked_search import RankedSearchMixin @@ -31,6 +31,11 @@ class Meta: class InstallAdminForm(forms.ModelForm): + validate_install_abandon_date_set_widget = forms.Field( + required=False, + widget=WarnAboutDatesWidget(), + ) + class Meta: model = Install fields = "__all__" @@ -139,6 +144,7 @@ class InstallAdmin(RankedSearchMixin, ImportExportMixin, ExportActionMixin, Simp "diy", "referral", "notes", + "validate_install_abandon_date_set_widget", # Hidden by widget CSS ] }, ), diff --git a/src/meshapi/admin/models/node.py b/src/meshapi/admin/models/node.py index 8166058a..fccbcb65 100644 --- a/src/meshapi/admin/models/node.py +++ b/src/meshapi/admin/models/node.py @@ -12,7 +12,7 @@ from simple_history.admin import SimpleHistoryAdmin from meshapi.models import Building, Node -from meshapi.widgets import AutoPopulateLocationWidget +from meshapi.widgets import AutoPopulateLocationWidget, WarnAboutDatesWidget from ..inlines import ( AccessPointInline, @@ -39,6 +39,11 @@ class Meta: class NodeAdminForm(forms.ModelForm): + validate_install_abandon_date_set_widget = forms.Field( + required=False, + widget=WarnAboutDatesWidget(), + ) + auto_populate_location_field = forms.Field( required=False, widget=AutoPopulateLocationWidget("Building"), @@ -104,6 +109,7 @@ class NodeAdmin(RankedSearchMixin, ExportActionMixin, ImportExportModelAdmin, Si { "fields": [ "notes", + "validate_install_abandon_date_set_widget", ] }, ), diff --git a/src/meshapi/static/widgets/warn_about_date.css b/src/meshapi/static/widgets/warn_about_date.css new file mode 100644 index 00000000..c394e2e2 --- /dev/null +++ b/src/meshapi/static/widgets/warn_about_date.css @@ -0,0 +1,3 @@ +.field-validate_install_abandon_date_set_widget { + display: none; +} diff --git a/src/meshapi/static/widgets/warn_about_date.js b/src/meshapi/static/widgets/warn_about_date.js new file mode 100644 index 00000000..2b9bb12a --- /dev/null +++ b/src/meshapi/static/widgets/warn_about_date.js @@ -0,0 +1,98 @@ +$(document).ready(function($) { + const getCurrentISODate = () => { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); // Months are 0-indexed + const date = String(now.getDate()).padStart(2, '0'); + return `${year}-${month}-${date}`; + }; + + function submitAdminForm(saveAction) { + const adminForm = document.querySelector('#content-main form'); + + // Create a hidden input to simulate the button action + const hiddenInput = document.createElement('input'); + hiddenInput.type = 'hidden'; + hiddenInput.name = saveAction; + hiddenInput.value = '1'; + adminForm.appendChild(hiddenInput); + adminForm.submit(); + + return true; + } + + const saveButtons = $('.submit-row input[value*="Save"]'); + + + function hideWarning(){ + $('#dateMissingWarning').remove(); + saveButtons.each((i, button) => { + $(button).removeClass("disabled-button"); + }) + } + + function showWarning(dateField, statusField, saveAction, dateType){ + const errorNotice = $("#dateMissingWarningTemplate").clone(); + errorNotice.prop("id", "dateMissingWarning"); + + errorNotice.find("#saveCurrentDateButton").on("click", () => { + dateField.val(getCurrentISODate()) + submitAdminForm(saveAction); + return false; + }); + + errorNotice.find("#saveNoDateButton").on("click", () => { + submitAdminForm(saveAction); + return false; + }); + + errorNotice.find("#statusText").text(statusField.val()); + errorNotice.find("#dateTypeText").text(dateType); + + saveButtons.each((i, button) => { + $(button).addClass("disabled-button"); + }) + + errorNotice.insertBefore($('.submit-row').first()) + } + + $('#id_install_date').on("input", (e) => { + hideWarning(); + }); + + $('#id_abandon_date').on("input", (e) => { + hideWarning(); + }); + + $('#id_status').on("change", (e) => { + hideWarning(); + }); + + saveButtons.on('click', (e) => { + const status = $('#id_status').val(); + const installDate = $('#id_install_date').val(); + const abandonDate = $('#id_abandon_date').val(); + + $('.datetimeshortcuts a').on("click", (e) => { + hideWarning(); + }); + + if (status === "Active") { + if (!installDate) { + hideWarning(); + showWarning($("#id_install_date"), $('#id_status'), e.target.name, "Install") + + return false; + } + } else if (["Closed", "Inactive"].indexOf(status) !== -1){ + if (installDate && !abandonDate) { + hideWarning(); + showWarning($("#id_abandon_date"), $('#id_status'), e.target.name, "Abandon") + + return false; + } + } + + return true; + }) +}); diff --git a/src/meshapi/templates/widgets/warn_about_date.html b/src/meshapi/templates/widgets/warn_about_date.html new file mode 100644 index 00000000..c4cfdf3b --- /dev/null +++ b/src/meshapi/templates/widgets/warn_about_date.html @@ -0,0 +1,11 @@ +
+

+ Warning: Status set to without setting Date +

+ + Use Today's Date + + + Continue Without Setting Date + +
diff --git a/src/meshapi/widgets.py b/src/meshapi/widgets.py index 4d4bd54e..d1a52fc3 100644 --- a/src/meshapi/widgets.py +++ b/src/meshapi/widgets.py @@ -111,3 +111,13 @@ def get_context(self, name: str, value: str, attrs: Optional[dict] = None) -> di context["auto_populate_source"] = self.source context["auto_populate_url"] = self.source return context + + +class WarnAboutDatesWidget(forms.Widget): + template_name = "widgets/warn_about_date.html" + + class Media: + css = { + "all": ("widgets/warn_about_date.css",), + } + js = ["widgets/warn_about_date.js"] diff --git a/src/meshweb/static/admin/admin_ext.css b/src/meshweb/static/admin/admin_ext.css index f5bf1fa6..daac1558 100644 --- a/src/meshweb/static/admin/admin_ext.css +++ b/src/meshweb/static/admin/admin_ext.css @@ -29,6 +29,12 @@ color: black; } +.disabled-button { + background-color: #afafaf !important; + opacity: 20%; + cursor: unset !important; +} + @media screen and (max-width: 600px) { .warning-box { display: none; @@ -187,4 +193,4 @@ fieldset h2 { .hidden { display: none; -} \ No newline at end of file +}