From a0e4e96160cb137116fe826ee4fe2c288cf058d5 Mon Sep 17 00:00:00 2001
From: Nelson Chan <chakflying@hotmail.com>
Date: Fri, 10 Sep 2021 14:22:34 +0800
Subject: [PATCH] Fix: Fix tag removal, reuse and validation

---
 server/server.js               |   5 +-
 src/components/TagsManager.vue | 112 +++++++++++++++++----------------
 2 files changed, 61 insertions(+), 56 deletions(-)

diff --git a/server/server.js b/server/server.js
index 003d25ae..d75f8de9 100644
--- a/server/server.js
+++ b/server/server.js
@@ -752,13 +752,14 @@ let indexHTML = fs.readFileSync("./dist/index.html").toString();
             }
         });
 
-        socket.on("deleteMonitorTag", async (tagID, monitorID, callback) => {
+        socket.on("deleteMonitorTag", async (tagID, monitorID, value, callback) => {
             try {
                 checkLogin(socket)
 
-                await R.exec("DELETE FROM monitor_tag WHERE tag_id = ? AND monitor_id = ?", [
+                await R.exec("DELETE FROM monitor_tag WHERE tag_id = ? AND monitor_id = ? AND value = ?", [
                     tagID,
                     monitorID,
+                    value,
                 ])
 
                 // Cleanup unused Tags
diff --git a/src/components/TagsManager.vue b/src/components/TagsManager.vue
index ebe3e16a..fc30a4d3 100644
--- a/src/components/TagsManager.vue
+++ b/src/components/TagsManager.vue
@@ -40,7 +40,7 @@
             </vue-multiselect>
             <div v-if="newDraftTag.select?.id == null" class="d-flex mb-2">
                 <div class="w-50 pe-2">
-                    <input v-model="newDraftTag.name" class="form-control" :class="{'is-invalid': newDraftTag.nameInvalid}" placeholder="name" />
+                    <input v-model="newDraftTag.name" class="form-control" :class="{'is-invalid': validateDraftTag.nameInvalid}" placeholder="name" />
                     <div class="invalid-feedback">
                         {{ $t("Tag with this name already exist.") }}
                     </div>
@@ -77,7 +77,7 @@
                 </div>
             </div>
             <div class="mb-2">
-                <input v-model="newDraftTag.value" class="form-control" :class="{'is-invalid': newDraftTag.valueInvalid}" :placeholder="$t('value (optional)')" />
+                <input v-model="newDraftTag.value" class="form-control" :class="{'is-invalid': validateDraftTag.valueInvalid}" :placeholder="$t('value (optional)')" />
                 <div class="invalid-feedback">
                     {{ $t("Tag with this value already exist.") }}
                 </div>
@@ -86,7 +86,7 @@
                 <button
                     type="button"
                     class="btn btn-secondary float-end"
-                    :disabled="processing || newDraftTag.invalid"
+                    :disabled="processing || validateDraftTag.invalid"
                     @click.stop="addDraftTag"
                 >
                     {{ $t("Add") }}
@@ -131,7 +131,13 @@ export default {
     },
     computed: {
         tagOptions() {
-            return this.existingTags;
+            const tagOptions = this.existingTags;
+            for (const tag of this.newTags) {
+                if (!tagOptions.find(t => t.name == tag.name && t.color == tag.color)) {
+                    tagOptions.push(tag);
+                }
+            }
+            return tagOptions;
         },
         selectedTags() {
             return this.preSelectedTags.concat(this.newTags).filter(tag => !this.deleteTags.find(monitorTag => monitorTag.id == tag.id));
@@ -155,24 +161,47 @@ export default {
                 { name: this.$t("Pink"),
                     color: "#DB2777" },
             ]
-        }
-    },
-    watch: {
-        "newDraftTag.select": function (newSelected) {
-            this.newDraftTag.select = newSelected;
-            this.validateDraftTag();
         },
-        "newDraftTag.name": function (newName) {
-            this.newDraftTag.name = newName;
-            this.validateDraftTag();
-        },
-        "newDraftTag.color": function (newColor) {
-            this.newDraftTag.color = newColor;
-            this.validateDraftTag();
-        },
-        "newDraftTag.value": function (newValue) {
-            this.newDraftTag.value = newValue;
-            this.validateDraftTag();
+        validateDraftTag() {
+            let nameInvalid = false;
+            let valueInvalid = false;
+            let invalid = true;
+            if (this.deleteTags.find(tag => tag.name == this.newDraftTag.select?.name && tag.value == this.newDraftTag.value)) {
+                // Undo removing a Tag
+                nameInvalid = false;
+                valueInvalid = false;
+                invalid = false;
+            } else if (this.existingTags.filter(tag => tag.name === this.newDraftTag.name).length > 0) {
+                // Try to create new tag with existing name
+                nameInvalid = true;
+                invalid = true;
+            } else if (this.newTags.concat(this.preSelectedTags).filter(tag => (
+                tag.name == this.newDraftTag.select?.name && tag.value == this.newDraftTag.value
+            ) || (
+                tag.name == this.newDraftTag.name && tag.value == this.newDraftTag.value
+            )).length > 0) {
+                // Try to add a tag with existing name and value
+                valueInvalid = true;
+                invalid = true;
+            } else if (this.newDraftTag.select != null) {
+                // Select an existing tag, no need to validate
+                invalid = false;
+                valueInvalid = false;
+            } else if (this.newDraftTag.color == null || this.newDraftTag.name === "") {
+                // Missing form inputs
+                nameInvalid = false;
+                invalid = true;
+            } else {
+                // Looks valid
+                invalid = false;
+                nameInvalid = false;
+                valueInvalid = false;
+            }
+            return {
+                invalid,
+                nameInvalid,
+                valueInvalid,
+            }
         },
     },
     mounted() {
@@ -197,39 +226,6 @@ export default {
                 this.deleteTags.push(item);
             }
         },
-        validateDraftTag() {
-            if (this.deleteTags.find(tag => tag.name == this.newDraftTag.select?.name && tag.value == this.newDraftTag.value)) {
-                // Undo removing a Tag
-                this.newDraftTag.nameInvalid = false;
-                this.newDraftTag.valueInvalid = false;
-                this.newDraftTag.invalid = false;
-            } else if (this.newTags.concat(this.preSelectedTags).filter(tag => (
-                tag.name == this.newDraftTag.select?.name && tag.value == this.newDraftTag.value
-            ) || (
-                tag.name == this.newDraftTag.name && tag.value == this.newDraftTag.value
-            )).length > 0) {
-                // Try to add a tag with existing name and value
-                this.newDraftTag.valueInvalid = true;
-                this.newDraftTag.invalid = true;
-            } else if (this.newDraftTag.select != null) {
-                // Select an existing tag, no need to validate
-                this.newDraftTag.invalid = false;
-                this.newDraftTag.valueInvalid = false;
-            } else if (this.existingTags.filter(tag => tag.name === this.newDraftTag.name).length > 0) {
-                // Try to create new tag with existing name
-                this.newDraftTag.nameInvalid = true;
-                this.newDraftTag.invalid = true;
-            } else if (this.newDraftTag.color == null || this.newDraftTag.name === "") {
-                // Missing form inputs
-                this.newDraftTag.nameInvalid = false;
-                this.newDraftTag.invalid = true;
-            } else {
-                // Looks valid
-                this.newDraftTag.invalid = false;
-                this.newDraftTag.nameInvalid = false;
-                this.newDraftTag.valueInvalid = false;
-            }
-        },
         textColor(option) {
             if (option.color) {
                 return "white";
@@ -296,6 +292,7 @@ export default {
             for (const newTag of this.newTags) {
                 let tagId;
                 if (newTag.id == null) {
+                    // Create a New Tag
                     let newTagResult;
                     await this.addTagAsync(newTag).then((res) => {
                         if (!res.ok) {
@@ -310,11 +307,18 @@ export default {
                         return;
                     }
                     tagId = newTagResult.id;
+                    // Assign the new ID to the tags of the same name & color
+                    this.newTags.map(tag => {
+                        if (tag.name == newTag.name && tag.color == newTag.color) {
+                            tag.id = newTagResult.id;
+                        }
+                    })
                 } else {
                     tagId = newTag.id;
                 }
 
                 let newMonitorTagResult;
+                // Assign tag to monitor
                 await this.addMonitorTagAsync(tagId, monitorId, newTag.value).then((res) => {
                     if (!res.ok) {
                         toast.error(res.msg);