5 Revize c8f35b869e ... abb88e89d4

Autor SHA1 Zpráva Datum
  Christopher Leggett abb88e89d4 Implements deleting notes and switching between public and private. před 5 roky
  Christopher Leggett c904fa5d58 Implements spinning circle for Notes. před 5 roky
  Christopher Leggett 986429c884 Implements circle spinner for deleting credentials. před 5 roky
  Christopher Leggett db67051644 Implements circle spinner for Saving and Creating credentials. před 5 roky
  Christopher Leggett 5815f5a3d5 Fixes bug causing AssetInfo to not live update properly. před 5 roky

+ 40 - 0
app/Events/WorkOrderNoteDeleted.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace App\Events;
+
+use Illuminate\Broadcasting\Channel;
+use Illuminate\Broadcasting\InteractsWithSockets;
+use Illuminate\Broadcasting\PresenceChannel;
+use Illuminate\Broadcasting\PrivateChannel;
+use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
+use Illuminate\Foundation\Events\Dispatchable;
+use Illuminate\Queue\SerializesModels;
+use App\WorkOrderNote;
+
+class WorkOrderNoteDeleted implements ShouldBroadcast
+{
+    use Dispatchable, InteractsWithSockets, SerializesModels;
+
+    public $note;
+
+    /**
+     * Create a new event instance.
+     *
+     * @return void
+     */
+    public function __construct($note)
+    {
+        $array = json_decode($note, true);
+        $this->note = $array;
+    }
+
+    /**
+     * Get the channels the event should broadcast on.
+     *
+     * @return \Illuminate\Broadcasting\Channel|array
+     */
+    public function broadcastOn()
+    {
+        return new Channel('wonotes.'.$this->note['woid']);
+    }
+}

+ 5 - 1
app/Http/Controllers/Api/WorkOrderNotesController.php

@@ -57,6 +57,7 @@ class WorkOrderNotesController extends Controller
     public function update(Request $request, WorkOrderNote $workOrderNote)
     {
         $workOrderNote->thenote = $request->input('thenote');
+        $workOrderNote->notetype = $request->input('notetype');
         $workOrderNote->save();
         event(new \App\Events\WorkOrderNoteUpdated($workOrderNote));
         return response()->json($workOrderNote, 200);
@@ -70,6 +71,9 @@ class WorkOrderNotesController extends Controller
      */
     public function destroy(WorkOrderNote $workOrderNote)
     {
-        //
+        $deletedNote = $workOrderNote->toJson();
+        $workOrderNote->delete();
+        event(new \App\Events\WorkOrderNoteDeleted($deletedNote));
+        return response()->json("", 204);
     }
 }

+ 1 - 1
composer.json

@@ -14,7 +14,7 @@
         "laravel/framework": "^6.2",
         "laravel/passport": "^8.4",
         "laravel/tinker": "^2.0",
-        "pusher/pusher-php-server": "~4.0"
+        "pusher/pusher-php-server": "~3.0"
     },
     "require-dev": {
         "facade/ignition": "^1.4",

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 168 - 232
composer.lock


+ 2 - 2
config/websockets.php

@@ -68,10 +68,10 @@ return [
      * the chance to add your own middleware to this list or change any of
      * the existing middleware. Or, you can simply stick with this list.
      */
-    'middleware' => [
+    /* 'middleware' => [
         'web',
         Authorize::class,
-    ],
+    ], */
 
     'statistics' => [
         /*

+ 518 - 97
public/js/app.js

@@ -1932,13 +1932,6 @@ __webpack_require__.r(__webpack_exports__);
       type: Object,
       required: true
     }
-  },
-  mounted: function mounted() {
-    var _this = this;
-
-    Echo.channel('asset.' + this.asset.pcid).listen('AssetUpdated', function (e) {
-      _this.asset = e.data;
-    });
   }
 });
 
@@ -2225,6 +2218,10 @@ __webpack_require__.r(__webpack_exports__);
 __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _components_CredentialFormModal_vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../components/CredentialFormModal.vue */ "./resources/js/components/CredentialFormModal.vue");
 /* harmony import */ var _components_Modal_vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../components/Modal.vue */ "./resources/js/components/Modal.vue");
+/* harmony import */ var _components_CircleSpinner_vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../components/CircleSpinner.vue */ "./resources/js/components/CircleSpinner.vue");
+//
+//
+//
 //
 //
 //
@@ -2270,10 +2267,12 @@ __webpack_require__.r(__webpack_exports__);
 //
 
 
+
 /* harmony default export */ __webpack_exports__["default"] = ({
   components: {
     CredentialFormModal: _components_CredentialFormModal_vue__WEBPACK_IMPORTED_MODULE_0__["default"],
-    Modal: _components_Modal_vue__WEBPACK_IMPORTED_MODULE_1__["default"]
+    Modal: _components_Modal_vue__WEBPACK_IMPORTED_MODULE_1__["default"],
+    CircleSpinner: _components_CircleSpinner_vue__WEBPACK_IMPORTED_MODULE_2__["default"]
   },
   props: {
     credential: {
@@ -2288,7 +2287,8 @@ __webpack_require__.r(__webpack_exports__);
   data: function data() {
     return {
       data: this.credential,
-      creddescList: this.descriptions
+      creddescList: this.descriptions,
+      credentialDeleting: false
     };
   },
   mounted: function mounted() {
@@ -2302,9 +2302,12 @@ __webpack_require__.r(__webpack_exports__);
     deleteCredential: function deleteCredential() {
       var _this2 = this;
 
+      this.credentialDeleting = true;
       axios["delete"]('/api/credentials/' + this.data.credid, this.data).then(function (response) {
+        _this2.credentialDeleting = false;
         $('#credential' + _this2.data.credid + 'deleteModal').modal('hide');
       })["catch"](function (error) {
+        _this2.credentialDeleting = false;
         console.error(error);
       });
     }
@@ -2324,6 +2327,8 @@ __webpack_require__.r(__webpack_exports__);
 __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _components_Modal_vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../components/Modal.vue */ "./resources/js/components/Modal.vue");
 /* harmony import */ var _components_AutocompleteCustomDropdown_vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../components/AutocompleteCustomDropdown.vue */ "./resources/js/components/AutocompleteCustomDropdown.vue");
+/* harmony import */ var _components_CircleSpinner_vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../components/CircleSpinner.vue */ "./resources/js/components/CircleSpinner.vue");
+//
 //
 //
 //
@@ -2350,12 +2355,16 @@ __webpack_require__.r(__webpack_exports__);
 //
 //
 //
+//
+//
+
 
 
 /* harmony default export */ __webpack_exports__["default"] = ({
   components: {
     AutocompleteCustomDropdown: _components_AutocompleteCustomDropdown_vue__WEBPACK_IMPORTED_MODULE_1__["default"],
-    Modal: _components_Modal_vue__WEBPACK_IMPORTED_MODULE_0__["default"]
+    Modal: _components_Modal_vue__WEBPACK_IMPORTED_MODULE_0__["default"],
+    CircleSpinner: _components_CircleSpinner_vue__WEBPACK_IMPORTED_MODULE_2__["default"]
   },
   props: {
     populateWith: {
@@ -2394,7 +2403,8 @@ __webpack_require__.r(__webpack_exports__);
       data: JSON.parse(JSON.stringify(this.populateWith)),
       creddesc: {},
       id: this.modalId,
-      errors: []
+      errors: [],
+      credentialSaving: false
     };
   },
   mounted: function mounted() {
@@ -2420,6 +2430,8 @@ __webpack_require__.r(__webpack_exports__);
     updateCredential: function updateCredential() {
       var _this = this;
 
+      this.credentialSaving = true;
+
       if (this.creddesc.id === "custom") {
         this.data.credtype = 1;
         this.data.creddesc = this.creddesc.name;
@@ -2438,12 +2450,18 @@ __webpack_require__.r(__webpack_exports__);
         }
 
         axios.post('/api/credentials/', this.data).then(function (response) {
+          _this.credentialSaving = false;
           $('#' + _this.id).modal('hide');
+        })["catch"](function (error) {
+          _this.credentialSaving = false;
+          console.error(error);
         });
       } else {
         axios.put('/api/credentials/' + this.data.credid, this.data).then(function (response) {
+          _this.credentialSaving = false;
           $('#' + _this.id).modal('hide');
         })["catch"](function (error) {
+          _this.credentialSaving = false;
           console.error(error);
         });
       }
@@ -2462,7 +2480,7 @@ __webpack_require__.r(__webpack_exports__);
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
-/* harmony import */ var _components_Credential_vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../components/Credential.vue */ "./resources/js/components/Credential.vue");
+/* harmony import */ var _components_Credential_vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../components/Credential.vue */ "./resources/js/components/Credential.vue");
 /* harmony import */ var _components_CredentialFormModal_vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../components/CredentialFormModal.vue */ "./resources/js/components/CredentialFormModal.vue");
 //
 //
@@ -2477,7 +2495,7 @@ __webpack_require__.r(__webpack_exports__);
 
 /* harmony default export */ __webpack_exports__["default"] = ({
   components: {
-    Credential: _components_Credential_vue__WEBPACK_IMPORTED_MODULE_2__["default"],
+    Credential: _components_Credential_vue__WEBPACK_IMPORTED_MODULE_0__["default"],
     CredentialFormModal: _components_CredentialFormModal_vue__WEBPACK_IMPORTED_MODULE_1__["default"]
   },
   props: {
@@ -2579,6 +2597,74 @@ __webpack_require__.r(__webpack_exports__);
 
 /***/ }),
 
+/***/ "./node_modules/babel-loader/lib/index.js?!./node_modules/vue-loader/lib/index.js?!./resources/js/components/NoteDeleteModal.vue?vue&type=script&lang=js&":
+/*!**************************************************************************************************************************************************************************!*\
+  !*** ./node_modules/babel-loader/lib??ref--4-0!./node_modules/vue-loader/lib??vue-loader-options!./resources/js/components/NoteDeleteModal.vue?vue&type=script&lang=js& ***!
+  \**************************************************************************************************************************************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony import */ var _Modal_vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Modal.vue */ "./resources/js/components/Modal.vue");
+/* harmony import */ var _CircleSpinner_vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./CircleSpinner.vue */ "./resources/js/components/CircleSpinner.vue");
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+
+
+/* harmony default export */ __webpack_exports__["default"] = ({
+  components: {
+    Modal: _Modal_vue__WEBPACK_IMPORTED_MODULE_0__["default"],
+    CircleSpinner: _CircleSpinner_vue__WEBPACK_IMPORTED_MODULE_1__["default"]
+  },
+  props: {
+    modalId: {
+      type: String,
+      require: true
+    },
+    note: {
+      type: Object,
+      required: true
+    }
+  },
+  data: function data() {
+    return {
+      noteDeleting: false
+    };
+  },
+  methods: {
+    deleteNote: function deleteNote(note) {
+      var _this = this;
+
+      this.noteDeleting = true;
+      axios["delete"]("/api/workorders/notes/".concat(this.note.noteid)).then(function (response) {
+        _this.noteDeleting = false;
+        $("#".concat(_this.modalId)).modal('hide');
+      })["catch"](function (error) {
+        _this.noteDeleting = false;
+        console.log(error);
+      });
+    }
+  }
+});
+
+/***/ }),
+
 /***/ "./node_modules/babel-loader/lib/index.js?!./node_modules/vue-loader/lib/index.js?!./resources/js/components/NoteFormModal.vue?vue&type=script&lang=js&":
 /*!************************************************************************************************************************************************************************!*\
   !*** ./node_modules/babel-loader/lib??ref--4-0!./node_modules/vue-loader/lib??vue-loader-options!./resources/js/components/NoteFormModal.vue?vue&type=script&lang=js& ***!
@@ -2589,6 +2675,33 @@ __webpack_require__.r(__webpack_exports__);
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _components_Modal_vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../components/Modal.vue */ "./resources/js/components/Modal.vue");
+/* harmony import */ var _components_CircleSpinner_vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../components/CircleSpinner.vue */ "./resources/js/components/CircleSpinner.vue");
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
 //
 //
 //
@@ -2607,9 +2720,11 @@ __webpack_require__.r(__webpack_exports__);
 //
 //
 
+
 /* harmony default export */ __webpack_exports__["default"] = ({
   components: {
-    Modal: _components_Modal_vue__WEBPACK_IMPORTED_MODULE_0__["default"]
+    Modal: _components_Modal_vue__WEBPACK_IMPORTED_MODULE_0__["default"],
+    CircleSpinner: _components_CircleSpinner_vue__WEBPACK_IMPORTED_MODULE_1__["default"]
   },
   props: {
     populateWith: {
@@ -2620,26 +2735,40 @@ __webpack_require__.r(__webpack_exports__);
       type: String,
       require: true
     },
-    noteType: {
-      type: Number
-    },
     noteUser: {
       type: String
     },
     woid: {
       type: Number
+    },
+    // If true, user is intending to switch the note
+    // from public to private or vice/versa. Modal
+    // Will be displayed differently, but the update
+    // logic is only different for one line of code.
+    changeType: {
+      type: Boolean,
+      "default": false
     }
   },
   data: function data() {
     return {
-      note: JSON.parse(JSON.stringify(this.populateWith))
+      note: JSON.parse(JSON.stringify(this.populateWith)),
+      noteSaving: false
     };
   },
   methods: {
-    updateNote: function updateNote(note) {
+    updateNote: function updateNote() {
       var _this = this;
 
-      axios.put('/api/workorders/notes/' + note.noteid, note).then(function (response) {
+      this.noteSaving = true;
+
+      if (this.changeType) {
+        this.note.notetype = this.note.notetype === 1 ? 0 : 1;
+      }
+
+      axios.put('/api/workorders/notes/' + this.note.noteid, this.note).then(function (response) {
+        _this.noteSaving = false;
+
         _this.hideModal();
       });
     },
@@ -2661,7 +2790,14 @@ __webpack_require__.r(__webpack_exports__);
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _mixins_dateMixin__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../mixins/dateMixin */ "./resources/js/mixins/dateMixin.js");
-/* harmony import */ var _components_NoteFormModal_vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../components/NoteFormModal.vue */ "./resources/js/components/NoteFormModal.vue");
+/* harmony import */ var _NoteFormModal_vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./NoteFormModal.vue */ "./resources/js/components/NoteFormModal.vue");
+/* harmony import */ var _CircleSpinner_vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./CircleSpinner.vue */ "./resources/js/components/CircleSpinner.vue");
+/* harmony import */ var _NoteDeleteModal_vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./NoteDeleteModal.vue */ "./resources/js/components/NoteDeleteModal.vue");
+//
+//
+//
+//
+//
 //
 //
 //
@@ -2702,9 +2838,13 @@ __webpack_require__.r(__webpack_exports__);
 //
 
 
+
+
 /* harmony default export */ __webpack_exports__["default"] = ({
   components: {
-    NoteFormModal: _components_NoteFormModal_vue__WEBPACK_IMPORTED_MODULE_1__["default"]
+    NoteFormModal: _NoteFormModal_vue__WEBPACK_IMPORTED_MODULE_1__["default"],
+    CircleSpinner: _CircleSpinner_vue__WEBPACK_IMPORTED_MODULE_2__["default"],
+    NoteDeleteModal: _NoteDeleteModal_vue__WEBPACK_IMPORTED_MODULE_3__["default"]
   },
   mixins: [_mixins_dateMixin__WEBPACK_IMPORTED_MODULE_0__["default"]],
   props: {
@@ -2732,7 +2872,8 @@ __webpack_require__.r(__webpack_exports__);
         thenote: '',
         noteuser: this.authusername,
         woid: this.woid
-      }
+      },
+      noteSaving: false
     };
   },
   computed: {
@@ -2744,9 +2885,14 @@ __webpack_require__.r(__webpack_exports__);
     createNote: function createNote() {
       var _this = this;
 
+      this.noteSaving = true;
       axios.post('/api/workorders/notes', this.newNote).then(function (response) {
+        _this.noteSaving = false;
         $('#note' + _this.noteType + 'add').collapse('hide');
         _this.newNote.thenote = '';
+      })["catch"](function (error) {
+        _this.noteSaving = false;
+        console.log(error);
       });
     },
     getNoteOrders: function getNoteOrders(notes) {
@@ -2782,7 +2928,7 @@ __webpack_require__.r(__webpack_exports__);
 __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _components_Modal_vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../components/Modal.vue */ "./resources/js/components/Modal.vue");
 /* harmony import */ var _components_AutocompleteCustomDropdown_vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../components/AutocompleteCustomDropdown.vue */ "./resources/js/components/AutocompleteCustomDropdown.vue");
-/* harmony import */ var _components_ErrorList_vue__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../components/ErrorList.vue */ "./resources/js/components/ErrorList.vue");
+/* harmony import */ var _components_ErrorList_vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../components/ErrorList.vue */ "./resources/js/components/ErrorList.vue");
 /* harmony import */ var _components_CircleSpinner_vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../components/CircleSpinner.vue */ "./resources/js/components/CircleSpinner.vue");
 //
 //
@@ -2828,7 +2974,7 @@ __webpack_require__.r(__webpack_exports__);
   components: {
     Modal: _components_Modal_vue__WEBPACK_IMPORTED_MODULE_0__["default"],
     AutocompleteCustomDropdown: _components_AutocompleteCustomDropdown_vue__WEBPACK_IMPORTED_MODULE_1__["default"],
-    ErrorList: _components_ErrorList_vue__WEBPACK_IMPORTED_MODULE_4__["default"],
+    ErrorList: _components_ErrorList_vue__WEBPACK_IMPORTED_MODULE_2__["default"],
     CircleSpinner: _components_CircleSpinner_vue__WEBPACK_IMPORTED_MODULE_3__["default"]
   },
   props: {
@@ -3286,7 +3432,7 @@ __webpack_require__.r(__webpack_exports__);
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _components_WorkOrderInfo_vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../components/WorkOrderInfo.vue */ "./resources/js/components/WorkOrderInfo.vue");
-/* harmony import */ var _components_AssetInfo_vue__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../components/AssetInfo.vue */ "./resources/js/components/AssetInfo.vue");
+/* harmony import */ var _components_AssetInfo_vue__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../components/AssetInfo.vue */ "./resources/js/components/AssetInfo.vue");
 /* harmony import */ var _components_CircleSpinner_vue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../components/CircleSpinner.vue */ "./resources/js/components/CircleSpinner.vue");
 /* harmony import */ var _components_CredentialList_vue__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../components/CredentialList.vue */ "./resources/js/components/CredentialList.vue");
 /* harmony import */ var _components_Notes_vue__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../components/Notes.vue */ "./resources/js/components/Notes.vue");
@@ -3399,7 +3545,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony default export */ __webpack_exports__["default"] = ({
   components: {
     WorkOrderInfo: _components_WorkOrderInfo_vue__WEBPACK_IMPORTED_MODULE_0__["default"],
-    AssetInfo: _components_AssetInfo_vue__WEBPACK_IMPORTED_MODULE_5__["default"],
+    AssetInfo: _components_AssetInfo_vue__WEBPACK_IMPORTED_MODULE_1__["default"],
     CircleSpinner: _components_CircleSpinner_vue__WEBPACK_IMPORTED_MODULE_2__["default"],
     CredentialList: _components_CredentialList_vue__WEBPACK_IMPORTED_MODULE_3__["default"],
     Notes: _components_Notes_vue__WEBPACK_IMPORTED_MODULE_4__["default"]
@@ -3471,7 +3617,13 @@ __webpack_require__.r(__webpack_exports__);
 
     axios.get("/api/workorders/".concat(this.id, "/asset")).then(function (response) {
       _this.asset = response.data;
-      _this.assetLoading = false; // Get Credentials for this asset. May be relocated to be part of asset
+      _this.assetLoading = false; // Establish listener for liveupdate broadcasts.
+      // Must be done here or we can't guarantee this.asset
+      // exists by the time we execute this code.
+
+      Echo.channel('asset.' + _this.asset.pcid).listen('AssetUpdated', function (e) {
+        _this.asset = e.data;
+      }); // Get Credentials for this asset. May be relocated to be part of asset
       // Component when WOs can have more than one asset.
 
       axios.get("/api/assets/".concat(_this.asset.pcid, "/credentials")).then(function (response) {
@@ -3505,17 +3657,24 @@ __webpack_require__.r(__webpack_exports__);
     }).listen('WorkOrderNoteUpdated', function (e) {
       var index = _this.workOrderNotes.findIndex(function (note) {
         return note.noteid === e.note.noteid;
-      }); // Note has to be edited this way, or else Vue cannot
-      // see it to recompute the computed values.
+      }); // Sometimes when switching or deleting items from the end of a list,
+      // They go away before the modal can be hidden, this will hide the
+      // modal in case the component can't hide it itself. There may be 
+      // a better way to do this.
+
 
+      $("#note".concat(e.note.noteid, "switchModal")).modal('hide'); // Note has to be edited this way, or else Vue cannot
+      // see it to recompute the computed values.
 
       _this.workOrderNotes.splice(index, 1, e.note);
     }).listen('WorkOrderNoteDeleted', function (e) {
-      var index = _this.notes.findIndex(function (note) {
-        return note.noteid === e.noteid;
+      var index = _this.workOrderNotes.findIndex(function (note) {
+        return note.noteid === e.note['noteid'];
       });
 
-      _this.notes.splice(index, 1);
+      $("#note".concat(e.note['noteid'], "deleteModal")).modal('hide');
+
+      _this.workOrderNotes.splice(index, 1);
     });
   }
 });
@@ -50946,11 +51105,11 @@ var render = function() {
               },
               slot: "header"
             },
-            [_vm._v("\n            Delete Credential\n        ")]
+            [_vm._v("\r\n            Delete Credential\r\n        ")]
           ),
           _vm._v(" "),
           _c("div", { attrs: { slot: "body" }, slot: "body" }, [
-            _vm._v("\n            Are you sure?\n        ")
+            _vm._v("\r\n            Are you sure?\r\n        ")
           ]),
           _vm._v(" "),
           _c("div", { attrs: { slot: "footer" }, slot: "footer" }, [
@@ -50974,7 +51133,12 @@ var render = function() {
                   }
                 }
               },
-              [_vm._v("Confirm")]
+              [
+                !_vm.credentialDeleting
+                  ? _c("div", [_vm._v("Confirm")])
+                  : _c("circle-spinner", { attrs: { size: 2 } })
+              ],
+              1
             )
           ])
         ]
@@ -51266,7 +51430,12 @@ var render = function() {
               }
             }
           },
-          [_vm._v("Save")]
+          [
+            !_vm.credentialSaving
+              ? _c("div", [_vm._v("Save")])
+              : _c("circle-spinner", { attrs: { size: 2 } })
+          ],
+          1
         )
       ])
     ]
@@ -51436,10 +51605,10 @@ render._withStripped = true
 
 /***/ }),
 
-/***/ "./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/vue-loader/lib/index.js?!./resources/js/components/NoteFormModal.vue?vue&type=template&id=1391a09c&":
-/*!****************************************************************************************************************************************************************************************************************!*\
-  !*** ./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options!./resources/js/components/NoteFormModal.vue?vue&type=template&id=1391a09c& ***!
-  \****************************************************************************************************************************************************************************************************************/
+/***/ "./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/vue-loader/lib/index.js?!./resources/js/components/NoteDeleteModal.vue?vue&type=template&id=49f798d5&":
+/*!******************************************************************************************************************************************************************************************************************!*\
+  !*** ./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options!./resources/js/components/NoteDeleteModal.vue?vue&type=template&id=49f798d5& ***!
+  \******************************************************************************************************************************************************************************************************************/
 /*! exports provided: render, staticRenderFns */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
@@ -51469,38 +51638,11 @@ var render = function() {
           attrs: { slot: "header", id: _vm.modalId + "Label" },
           slot: "header"
         },
-        [_vm._v("\n                Edit Note\n            ")]
+        [_vm._v("\n        Delete Note\n    ")]
       ),
       _vm._v(" "),
       _c("div", { attrs: { slot: "body" }, slot: "body" }, [
-        _c("div", { staticClass: "form-group" }, [
-          _c("label", { attrs: { for: "content" } }, [_vm._v("Content")]),
-          _vm._v(" "),
-          _c("textarea", {
-            directives: [
-              {
-                name: "model",
-                rawName: "v-model",
-                value: _vm.note.thenote,
-                expression: "note.thenote"
-              }
-            ],
-            staticClass: "form-control",
-            attrs: {
-              name: "content" + _vm.note.noteid,
-              id: "content" + _vm.note.noteid
-            },
-            domProps: { value: _vm.note.thenote },
-            on: {
-              input: function($event) {
-                if ($event.target.composing) {
-                  return
-                }
-                _vm.$set(_vm.note, "thenote", $event.target.value)
-              }
-            }
-          })
-        ])
+        _vm._v("\n        Are you sure?\n    ")
       ]),
       _vm._v(" "),
       _c("div", { attrs: { slot: "footer" }, slot: "footer" }, [
@@ -51516,15 +51658,20 @@ var render = function() {
         _c(
           "button",
           {
-            staticClass: "btn btn-primary",
+            staticClass: "btn btn-danger",
             attrs: { type: "button" },
             on: {
               click: function($event) {
-                return _vm.updateNote(_vm.note)
+                return _vm.deleteNote(_vm.note)
               }
             }
           },
-          [_vm._v("Save")]
+          [
+            !_vm.noteDeleting
+              ? _c("div", [_vm._v("Confirm")])
+              : _c("circle-spinner", { attrs: { size: 2 } })
+          ],
+          1
         )
       ])
     ]
@@ -51535,6 +51682,187 @@ render._withStripped = true
 
 
 
+/***/ }),
+
+/***/ "./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/vue-loader/lib/index.js?!./resources/js/components/NoteFormModal.vue?vue&type=template&id=1391a09c&":
+/*!****************************************************************************************************************************************************************************************************************!*\
+  !*** ./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options!./resources/js/components/NoteFormModal.vue?vue&type=template&id=1391a09c& ***!
+  \****************************************************************************************************************************************************************************************************************/
+/*! exports provided: render, staticRenderFns */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "render", function() { return render; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "staticRenderFns", function() { return staticRenderFns; });
+var render = function() {
+  var _vm = this
+  var _h = _vm.$createElement
+  var _c = _vm._self._c || _h
+  return _c(
+    "div",
+    [
+      _vm.changeType
+        ? _c(
+            "modal",
+            {
+              attrs: {
+                id: _vm.modalId,
+                tabindex: "-1",
+                role: "dialog",
+                "aria-labelledby": _vm.modalId + "Label"
+              }
+            },
+            [
+              _vm.populateWith.notetype === 0
+                ? _c(
+                    "h5",
+                    {
+                      staticClass: "modal-title",
+                      attrs: { slot: "header", id: _vm.modalId + "Label" },
+                      slot: "header"
+                    },
+                    [_vm._v("\n            Change to Private Note\n        ")]
+                  )
+                : _c(
+                    "h5",
+                    {
+                      staticClass: "modal-title",
+                      attrs: { slot: "header", id: _vm.modalId + "Label" },
+                      slot: "header"
+                    },
+                    [_vm._v("\n            Change to Public Note\n        ")]
+                  ),
+              _vm._v(" "),
+              _c("div", { attrs: { slot: "body" }, slot: "body" }, [
+                _vm._v("\n            Are you sure?\n        ")
+              ]),
+              _vm._v(" "),
+              _c("div", { attrs: { slot: "footer" }, slot: "footer" }, [
+                _c(
+                  "button",
+                  {
+                    staticClass: "btn btn-secondary",
+                    attrs: { type: "button", "data-dismiss": "modal" }
+                  },
+                  [_vm._v("Close")]
+                ),
+                _vm._v(" "),
+                _c(
+                  "button",
+                  {
+                    staticClass: "btn btn-primary",
+                    attrs: { type: "button" },
+                    on: {
+                      click: function($event) {
+                        return _vm.updateNote()
+                      }
+                    }
+                  },
+                  [
+                    !_vm.noteSaving
+                      ? _c("div", [_vm._v("Confirm")])
+                      : _c("circle-spinner", { attrs: { size: 2 } })
+                  ],
+                  1
+                )
+              ])
+            ]
+          )
+        : _c(
+            "modal",
+            {
+              attrs: {
+                id: _vm.modalId,
+                tabindex: "-1",
+                role: "dialog",
+                "aria-labelledby": _vm.modalId + "Label"
+              }
+            },
+            [
+              _c(
+                "h5",
+                {
+                  staticClass: "modal-title",
+                  attrs: { slot: "header", id: _vm.modalId + "Label" },
+                  slot: "header"
+                },
+                [_vm._v("\n            Edit Note\n        ")]
+              ),
+              _vm._v(" "),
+              _c("div", { attrs: { slot: "body" }, slot: "body" }, [
+                _c("div", { staticClass: "form-group" }, [
+                  _c("label", { attrs: { for: "content" } }, [
+                    _vm._v("Content")
+                  ]),
+                  _vm._v(" "),
+                  _c("textarea", {
+                    directives: [
+                      {
+                        name: "model",
+                        rawName: "v-model",
+                        value: _vm.note.thenote,
+                        expression: "note.thenote"
+                      }
+                    ],
+                    staticClass: "form-control",
+                    attrs: {
+                      name: "content" + _vm.note.noteid,
+                      id: "content" + _vm.note.noteid
+                    },
+                    domProps: { value: _vm.note.thenote },
+                    on: {
+                      input: function($event) {
+                        if ($event.target.composing) {
+                          return
+                        }
+                        _vm.$set(_vm.note, "thenote", $event.target.value)
+                      }
+                    }
+                  })
+                ])
+              ]),
+              _vm._v(" "),
+              _c("div", { attrs: { slot: "footer" }, slot: "footer" }, [
+                _c(
+                  "button",
+                  {
+                    staticClass: "btn btn-secondary",
+                    attrs: { type: "button", "data-dismiss": "modal" }
+                  },
+                  [_vm._v("Close")]
+                ),
+                _vm._v(" "),
+                _c(
+                  "button",
+                  {
+                    staticClass: "btn btn-primary",
+                    attrs: { type: "button" },
+                    on: {
+                      click: function($event) {
+                        return _vm.updateNote()
+                      }
+                    }
+                  },
+                  [
+                    !_vm.noteSaving
+                      ? _c("div", [_vm._v("Save")])
+                      : _c("circle-spinner", { attrs: { size: 2 } })
+                  ],
+                  1
+                )
+              ])
+            ]
+          )
+    ],
+    1
+  )
+}
+var staticRenderFns = []
+render._withStripped = true
+
+
+
 /***/ }),
 
 /***/ "./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/vue-loader/lib/index.js?!./resources/js/components/Notes.vue?vue&type=template&id=3d897ee6&":
@@ -51633,7 +51961,12 @@ var render = function() {
                         }
                       }
                     },
-                    [_vm._v("Save")]
+                    [
+                      !_vm.noteSaving
+                        ? _c("div", [_vm._v("Save")])
+                        : _c("circle-spinner", { attrs: { size: 2 } })
+                    ],
+                    1
                   )
                 ]
               )
@@ -51645,7 +51978,7 @@ var render = function() {
       _vm._l(this.notes, function(note, index) {
         return _c(
           "li",
-          { key: index, staticClass: "row no-gutters mb-2" },
+          { key: note.noteid, staticClass: "row no-gutters mb-2" },
           [
             _c("note-form-modal", {
               attrs: {
@@ -51654,6 +51987,21 @@ var render = function() {
               }
             }),
             _vm._v(" "),
+            _c("note-delete-modal", {
+              attrs: {
+                "modal-id": "note" + note.noteid + "deleteModal",
+                note: note
+              }
+            }),
+            _vm._v(" "),
+            _c("note-form-modal", {
+              attrs: {
+                "modal-id": "note" + note.noteid + "switchModal",
+                "populate-with": note,
+                "change-type": ""
+              }
+            }),
+            _vm._v(" "),
             _c(
               "div",
               {
@@ -51691,9 +52039,31 @@ var render = function() {
                             [_c("i", { staticClass: "fas fa-fw fa-edit" })]
                           ),
                           _vm._v(" "),
-                          _vm._m(0, true),
+                          _c(
+                            "button",
+                            {
+                              staticClass: "btn btn-sm btn-danger m-1",
+                              attrs: {
+                                "data-toggle": "modal",
+                                "data-target":
+                                  "#note" + note.noteid + "deleteModal"
+                              }
+                            },
+                            [_c("i", { staticClass: "fas fa-fw fa-trash-alt" })]
+                          ),
                           _vm._v(" "),
-                          _vm._m(1, true)
+                          _c(
+                            "button",
+                            {
+                              staticClass: "btn btn-sm btn-primary m-1",
+                              attrs: {
+                                "data-toggle": "modal",
+                                "data-target":
+                                  "#note" + note.noteid + "switchModal"
+                              }
+                            },
+                            [_c("i", { staticClass: "fa fa-fw fa-random" })]
+                          )
                         ]
                       : _vm._e()
                   ],
@@ -51721,24 +52091,7 @@ var render = function() {
     2
   )
 }
-var staticRenderFns = [
-  function() {
-    var _vm = this
-    var _h = _vm.$createElement
-    var _c = _vm._self._c || _h
-    return _c("button", { staticClass: "btn btn-sm btn-danger m-1" }, [
-      _c("i", { staticClass: "fas fa-fw fa-trash-alt" })
-    ])
-  },
-  function() {
-    var _vm = this
-    var _h = _vm.$createElement
-    var _c = _vm._self._c || _h
-    return _c("button", { staticClass: "btn btn-sm btn-primary m-1" }, [
-      _c("i", { staticClass: "fa fa-fw fa-random" })
-    ])
-  }
-]
+var staticRenderFns = []
 render._withStripped = true
 
 
@@ -51778,7 +52131,7 @@ var render = function() {
           attrs: { slot: "header", id: _vm.id + "Label" },
           slot: "header"
         },
-        [_vm._v("\n        Edit Work Order Information\n    ")]
+        [_vm._v("\r\n        Edit Work Order Information\r\n    ")]
       ),
       _vm._v(" "),
       _c("div", { attrs: { slot: "body" }, slot: "body" }, [
@@ -68036,7 +68389,6 @@ window.Pusher = __webpack_require__(/*! pusher-js */ "./node_modules/pusher-js/d
 window.Echo = new laravel_echo__WEBPACK_IMPORTED_MODULE_0__["default"]({
   broadcaster: 'pusher',
   key: "upccrt",
-  authEndpoint: '/broadcasting/auth',
   wsHost: window.location.hostname,
   wsPort: 6001,
   disableStats: true
@@ -68665,6 +69017,75 @@ __webpack_require__.r(__webpack_exports__);
 
 
 
+/***/ }),
+
+/***/ "./resources/js/components/NoteDeleteModal.vue":
+/*!*****************************************************!*\
+  !*** ./resources/js/components/NoteDeleteModal.vue ***!
+  \*****************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony import */ var _NoteDeleteModal_vue_vue_type_template_id_49f798d5___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./NoteDeleteModal.vue?vue&type=template&id=49f798d5& */ "./resources/js/components/NoteDeleteModal.vue?vue&type=template&id=49f798d5&");
+/* harmony import */ var _NoteDeleteModal_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./NoteDeleteModal.vue?vue&type=script&lang=js& */ "./resources/js/components/NoteDeleteModal.vue?vue&type=script&lang=js&");
+/* empty/unused harmony star reexport *//* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ "./node_modules/vue-loader/lib/runtime/componentNormalizer.js");
+
+
+
+
+
+/* normalize component */
+
+var component = Object(_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__["default"])(
+  _NoteDeleteModal_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__["default"],
+  _NoteDeleteModal_vue_vue_type_template_id_49f798d5___WEBPACK_IMPORTED_MODULE_0__["render"],
+  _NoteDeleteModal_vue_vue_type_template_id_49f798d5___WEBPACK_IMPORTED_MODULE_0__["staticRenderFns"],
+  false,
+  null,
+  null,
+  null
+  
+)
+
+/* hot reload */
+if (false) { var api; }
+component.options.__file = "resources/js/components/NoteDeleteModal.vue"
+/* harmony default export */ __webpack_exports__["default"] = (component.exports);
+
+/***/ }),
+
+/***/ "./resources/js/components/NoteDeleteModal.vue?vue&type=script&lang=js&":
+/*!******************************************************************************!*\
+  !*** ./resources/js/components/NoteDeleteModal.vue?vue&type=script&lang=js& ***!
+  \******************************************************************************/
+/*! exports provided: default */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony import */ var _node_modules_babel_loader_lib_index_js_ref_4_0_node_modules_vue_loader_lib_index_js_vue_loader_options_NoteDeleteModal_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/babel-loader/lib??ref--4-0!../../../node_modules/vue-loader/lib??vue-loader-options!./NoteDeleteModal.vue?vue&type=script&lang=js& */ "./node_modules/babel-loader/lib/index.js?!./node_modules/vue-loader/lib/index.js?!./resources/js/components/NoteDeleteModal.vue?vue&type=script&lang=js&");
+/* empty/unused harmony star reexport */ /* harmony default export */ __webpack_exports__["default"] = (_node_modules_babel_loader_lib_index_js_ref_4_0_node_modules_vue_loader_lib_index_js_vue_loader_options_NoteDeleteModal_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__["default"]); 
+
+/***/ }),
+
+/***/ "./resources/js/components/NoteDeleteModal.vue?vue&type=template&id=49f798d5&":
+/*!************************************************************************************!*\
+  !*** ./resources/js/components/NoteDeleteModal.vue?vue&type=template&id=49f798d5& ***!
+  \************************************************************************************/
+/*! exports provided: render, staticRenderFns */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony import */ var _node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_vue_loader_lib_index_js_vue_loader_options_NoteDeleteModal_vue_vue_type_template_id_49f798d5___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!../../../node_modules/vue-loader/lib??vue-loader-options!./NoteDeleteModal.vue?vue&type=template&id=49f798d5& */ "./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/vue-loader/lib/index.js?!./resources/js/components/NoteDeleteModal.vue?vue&type=template&id=49f798d5&");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "render", function() { return _node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_vue_loader_lib_index_js_vue_loader_options_NoteDeleteModal_vue_vue_type_template_id_49f798d5___WEBPACK_IMPORTED_MODULE_0__["render"]; });
+
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "staticRenderFns", function() { return _node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_vue_loader_lib_index_js_vue_loader_options_NoteDeleteModal_vue_vue_type_template_id_49f798d5___WEBPACK_IMPORTED_MODULE_0__["staticRenderFns"]; });
+
+
+
 /***/ }),
 
 /***/ "./resources/js/components/NoteFormModal.vue":

+ 0 - 1
resources/js/bootstrap.js

@@ -36,7 +36,6 @@ window.Pusher = require('pusher-js');
 window.Echo = new Echo({
     broadcaster: 'pusher',
     key: process.env.MIX_PUSHER_APP_KEY,
-    authEndpoint: '/broadcasting/auth',
     wsHost: window.location.hostname,
     wsPort: 6001,
     disableStats: true,

+ 0 - 6
resources/js/components/AssetInfo.vue

@@ -23,11 +23,5 @@ export default {
             required: true
         },
     },
-    mounted() {
-        Echo.channel('asset.'+this.asset.pcid)
-                .listen('AssetUpdated', (e) => {
-                this.asset = e.data;
-            });
-    },
 }
 </script>

+ 14 - 3
resources/js/components/Credential.vue

@@ -10,7 +10,10 @@
         </div>
         <div slot="footer">
             <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
-            <button type="button" class="btn btn-danger" @click=deleteCredential()>Confirm</button>
+            <button type="button" class="btn btn-danger" @click=deleteCredential()>
+                <div v-if="!credentialDeleting">Confirm</div>
+                <circle-spinner v-else :size="2"></circle-spinner>
+            </button>
         </div>
     </modal>
     <div class="row no-gutters">
@@ -44,10 +47,12 @@
 <script>
 import CredentialFormModal from '../components/CredentialFormModal.vue'
 import Modal from '../components/Modal.vue'
+import CircleSpinner from '../components/CircleSpinner.vue'
 export default {
     components: {
         CredentialFormModal,
         Modal,
+        CircleSpinner,
     },
     props: {
         credential: {
@@ -62,7 +67,8 @@ export default {
     data() {
         return {
             data: this.credential,
-            creddescList: this.descriptions
+            creddescList: this.descriptions,
+            credentialDeleting: false,
         }
     },
     mounted() {
@@ -73,10 +79,15 @@ export default {
     },
     methods: {
         deleteCredential() {
+            this.credentialDeleting = true
             axios.delete('/api/credentials/' + this.data.credid, this.data)
                 .then(response => {
+                    this.credentialDeleting = false
                     $('#credential'+this.data.credid+'deleteModal').modal('hide');
-                }).catch(error => { console.error(error) })
+                }).catch(error => { 
+                    this.credentialDeleting = false
+                    console.error(error)
+                })
         }
     }
 }

+ 18 - 3
resources/js/components/CredentialFormModal.vue

@@ -20,17 +20,22 @@
         </div>
         <div slot="footer">
             <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
-            <button type="button" class="btn btn-primary" @click="updateCredential()">Save</button>
+            <button type="button" class="btn btn-primary" @click="updateCredential()">
+                <div v-if="!credentialSaving">Save</div>
+                <circle-spinner v-else :size="2"></circle-spinner>
+            </button>
         </div>
     </modal>
 </template>
 <script>
 import Modal from '../components/Modal.vue'
 import AutocompleteCustomDropdown from '../components/AutocompleteCustomDropdown.vue'
+import CircleSpinner from '../components/CircleSpinner.vue'
 export default {
     components: {
         AutocompleteCustomDropdown,
         Modal,
+        CircleSpinner,
     },
     props: {
         populateWith: {
@@ -69,7 +74,8 @@ export default {
             data: JSON.parse(JSON.stringify(this.populateWith)),
             creddesc: {},
             id: this.modalId,
-            errors: []
+            errors: [],
+            credentialSaving: false,
         }
     },
     mounted () {
@@ -92,6 +98,7 @@ export default {
             this.data.credpass = this.rndStr(16);
         },
         updateCredential() {
+            this.credentialSaving = true
             if (this.creddesc.id === "custom") {
                 this.data.credtype = 1
                 this.data.creddesc = this.creddesc.name
@@ -110,13 +117,21 @@ export default {
                 }
                 axios.post('/api/credentials/', this.data)
                     .then(response => {
+                        this.credentialSaving = false
                         $('#'+this.id).modal('hide');
+                    }).catch(error => {
+                        this.credentialSaving = false
+                        console.error(error)
                     })
             } else {
                 axios.put('/api/credentials/' + this.data.credid, this.data)
                     .then(response => {
+                        this.credentialSaving = false
                         $('#'+this.id).modal('hide');
-                    }).catch(error =>{console.error(error)});
+                    }).catch(error => { 
+                        this.credentialSaving = false
+                        console.error(error)
+                    });
             }
         }
     }

+ 54 - 0
resources/js/components/NoteDeleteModal.vue

@@ -0,0 +1,54 @@
+<template>
+     <modal :id="modalId" tabindex="-1" role="dialog" :aria-labelledby="modalId+'Label'">
+        <h5 slot="header" class="modal-title" :id="modalId+'Label'">
+            Delete Note
+        </h5>
+        <div slot="body">
+            Are you sure?
+        </div>
+        <div slot="footer">
+            <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
+            <button type="button" class="btn btn-danger" @click="deleteNote(note)">
+                <div v-if="!noteDeleting">Confirm</div>
+                <circle-spinner v-else :size="2"></circle-spinner>
+            </button>
+        </div>
+    </modal>
+</template>
+<script>
+import Modal from './Modal.vue'
+import CircleSpinner from './CircleSpinner.vue'
+export default {
+    components: {
+        Modal,
+        CircleSpinner,
+    },
+    props: {
+        modalId: {
+            type: String,
+            require: true,
+        },
+        note: {
+            type: Object,
+            required: true,
+        },
+    },
+    data () {
+        return {
+            noteDeleting: false
+        }
+    },
+    methods: {
+        deleteNote: function(note) {
+            this.noteDeleting = true
+            axios.delete(`/api/workorders/notes/${this.note.noteid}`).then( response => {
+                this.noteDeleting = false
+                $(`#${this.modalId}`).modal('hide')
+            }).catch( error => {
+                this.noteDeleting = false
+                console.log(error)
+            })
+        }
+    }
+}
+</script>

+ 59 - 20
resources/js/components/NoteFormModal.vue

@@ -1,25 +1,53 @@
 <template>
-    <modal :id="modalId" tabindex="-1" role="dialog" :aria-labelledby="modalId+'Label'">
-                <h5 slot="header" class="modal-title" :id="modalId+'Label'">
-                    Edit Note
-                </h5>
-                <div slot="body">
-                    <div class="form-group">
-                        <label for="content">Content</label>
-                        <textarea :name="'content'+note.noteid" :id="'content'+note.noteid" class="form-control" v-model="note.thenote"></textarea>
-                    </div>
+    <div>
+        <modal v-if="changeType" :id="modalId" tabindex="-1" role="dialog" :aria-labelledby="modalId+'Label'">
+            <!-- We want the original notetype, if we used note.notetype the text would change as soon as we hit the Confirm button.
+            This way the change in text occurs when we receive the updated info from the backend, so we can make sure there are no
+            errors preventing the data from actually changing first -->
+            <h5 v-if="populateWith.notetype === 0 " slot="header" class="modal-title" :id="modalId+'Label'">
+                Change to Private Note
+            </h5>
+            <h5 v-else slot="header" class="modal-title" :id="modalId+'Label'">
+                Change to Public Note
+            </h5>
+            <div slot="body">
+                Are you sure?
+            </div>
+            <div slot="footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
+                <button type="button" class="btn btn-primary" @click="updateNote()">
+                    <div v-if="!noteSaving">Confirm</div>
+                    <circle-spinner v-else :size="2"></circle-spinner>
+                </button>
+            </div>
+        </modal>
+        <modal v-else :id="modalId" tabindex="-1" role="dialog" :aria-labelledby="modalId+'Label'">
+            <h5 slot="header" class="modal-title" :id="modalId+'Label'">
+                Edit Note
+            </h5>
+            <div slot="body">
+                <div class="form-group">
+                    <label for="content">Content</label>
+                    <textarea :name="'content'+note.noteid" :id="'content'+note.noteid" class="form-control" v-model="note.thenote"></textarea>
                 </div>
-                <div slot="footer">
-                    <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
-                    <button type="button" class="btn btn-primary" @click="updateNote(note)">Save</button>
-                </div>
-            </modal>
+            </div>
+            <div slot="footer">
+                <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
+                <button type="button" class="btn btn-primary" @click="updateNote()">
+                    <div v-if="!noteSaving">Save</div>
+                    <circle-spinner v-else :size="2"></circle-spinner>
+                </button>
+            </div>
+        </modal>
+    </div>
 </template>
 <script>
 import Modal from '../components/Modal.vue'
+import CircleSpinner from '../components/CircleSpinner.vue'
 export default {
     components: {
         Modal,
+        CircleSpinner,
     },
     props: {
         populateWith: {
@@ -30,25 +58,36 @@ export default {
             type: String,
             require: true
         },
-        noteType: {
-            type: Number
-        },
         noteUser: {
             type: String
         },
         woid: {
             type: Number
+        },
+        // If true, user is intending to switch the note
+        // from public to private or vice/versa. Modal
+        // Will be displayed differently, but the update
+        // logic is only different for one line of code.
+        changeType: {
+            type: Boolean,
+            default: false,
         }
     },
     data () {
         return {
-            note: JSON.parse(JSON.stringify(this.populateWith))
+            note: JSON.parse(JSON.stringify(this.populateWith)),
+            noteSaving: false,
         }
     },
     methods: {
-        updateNote (note) {
-            axios.put('/api/workorders/notes/' + note.noteid, note)
+        updateNote () {
+            this.noteSaving = true
+            if (this.changeType) {
+                this.note.notetype = (this.note.notetype === 1 ? 0 : 1)
+            }
+            axios.put('/api/workorders/notes/' + this.note.noteid, this.note)
                 .then((response) => {
+                    this.noteSaving = false
                     this.hideModal()
                 })
         },

+ 21 - 6
resources/js/components/Notes.vue

@@ -8,21 +8,26 @@
                     <div class="collapse" :id="'note'+noteType+'add'">
                         <div class="d-flex flex-wrap flex-md-nowrap justify-content-center">
                             <textarea :name="'newnote'+noteType" :id="'newnote'+noteType" class="form-control" v-model="newNote.thenote"></textarea>
-                            <button type="button" class="btn btn-secondary m-2" @click="createNote()">Save</button>
+                            <button type="button" class="btn btn-secondary m-2" @click="createNote()">
+                                <div v-if="!noteSaving">Save</div>
+                                <circle-spinner v-else :size="2"></circle-spinner>
+                            </button>
                         </div>
                     </div>
                 </div>
             </div>
-        <li class="row no-gutters mb-2" v-bind:key="index" v-for="(note, index) in this.notes">
+        <li class="row no-gutters mb-2" v-bind:key="note.noteid" v-for="(note, index) in this.notes">
             <note-form-modal :modal-id="'note'+note.noteid+'editModal'" :populate-with="note"></note-form-modal>
+            <note-delete-modal :modal-id="`note${note.noteid}deleteModal`" :note="note"></note-delete-modal>
+            <note-form-modal :modal-id="`note${note.noteid}switchModal`" :populate-with="note" change-type></note-form-modal>
             <div class="col-md-1 d-flex flex-column mx-md-3" :class="noteOrders[index]">
                 <div class="text-center p-0 m-0">{{note.noteuser}}</div>
                 <div class="text-muted text-small text-center p-0 m-0">{{getHRDate(note.notetime)}}</div>
                 <div class="btn-group justify-content-center p-0 m-0">
                     <template v-if="authusername === note.noteuser || authusername === 'admin'">
                         <button class="btn btn-sm btn-primary m-1" data-toggle="modal" :data-target="'#note'+note.noteid+'editModal'"><i class="fas fa-fw fa-edit"></i></button>
-                        <button class="btn btn-sm btn-danger m-1"><i class="fas fa-fw fa-trash-alt"></i></button>
-                        <button class="btn btn-sm btn-primary m-1"><i class="fa fa-fw fa-random"></i></button>
+                        <button class="btn btn-sm btn-danger m-1" data-toggle="modal" :data-target="`#note${note.noteid}deleteModal`"><i class="fas fa-fw fa-trash-alt"></i></button>
+                        <button class="btn btn-sm btn-primary m-1" data-toggle="modal" :data-target="`#note${note.noteid}switchModal`"><i class="fa fa-fw fa-random"></i></button>
                     </template>
                 </div>
             </div>
@@ -38,10 +43,14 @@
 </template>
 <script>
 import dateMixin from '../mixins/dateMixin'
-import NoteFormModal from '../components/NoteFormModal.vue'
+import NoteFormModal from './NoteFormModal.vue'
+import CircleSpinner from './CircleSpinner.vue'
+import NoteDeleteModal from './NoteDeleteModal.vue'
 export default {
     components: {
         NoteFormModal,
+        CircleSpinner,
+        NoteDeleteModal
     },
     mixins:[dateMixin],
     props: {
@@ -68,8 +77,9 @@ export default {
                 notetype: this.noteType,
                 thenote: '',
                 noteuser: this.authusername,
-                woid: this.woid
+                woid: this.woid,
             },
+            noteSaving: false
         }
     },
     computed: {
@@ -79,10 +89,15 @@ export default {
     },
     methods: {
         createNote () {
+            this.noteSaving = true
             axios.post('/api/workorders/notes', this.newNote)
                     .then((response) => {
+                        this.noteSaving = false
                         $('#note'+this.noteType+'add').collapse('hide')
                         this.newNote.thenote = ''
+                    }).catch(error => {
+                        this.noteSaving = false
+                        console.log(error)
                     })
         },
         getNoteOrders(notes) {

+ 17 - 3
resources/js/views/WorkOrder.vue

@@ -180,6 +180,14 @@ export default {
             this.asset = response.data
             this.assetLoading = false
 
+            // Establish listener for liveupdate broadcasts.
+            // Must be done here or we can't guarantee this.asset
+            // exists by the time we execute this code.
+             Echo.channel('asset.'+this.asset.pcid)
+            .listen('AssetUpdated', (e) => {
+                this.asset = e.data;
+            });
+
             // Get Credentials for this asset. May be relocated to be part of asset
             // Component when WOs can have more than one asset.
             axios.get(`/api/assets/${this.asset.pcid}/credentials`).then (response => {
@@ -221,15 +229,21 @@ export default {
                 let index = this.workOrderNotes.findIndex((note) => {
                     return note.noteid === e.note.noteid
                 })
+                // Sometimes when switching or deleting items from the end of a list,
+                // They go away before the modal can be hidden, this will hide the
+                // modal in case the component can't hide it itself. There may be 
+                // a better way to do this.
+                $(`#note${e.note.noteid}switchModal`).modal('hide')
                 // Note has to be edited this way, or else Vue cannot
                 // see it to recompute the computed values.
                 this.workOrderNotes.splice(index, 1, e.note)
             })
             .listen('WorkOrderNoteDeleted', (e) => {
-                let index = this.notes.findIndex((note) => {
-                    return note.noteid === e.noteid
+                let index = this.workOrderNotes.findIndex((note) => {
+                    return note.noteid === e.note['noteid']
                 })
-                this.notes.splice(index, 1)
+                $(`#note${e.note['noteid']}deleteModal`).modal('hide')
+                this.workOrderNotes.splice(index, 1)
             })
     },
 }

+ 1 - 0
routes/api.php

@@ -30,6 +30,7 @@ Route::middleware('auth:api')->group( function() {
 
     Route::post('/workorders/notes', 'Api\WorkOrderNotesController@store');
     Route::put('/workorders/notes/{workOrderNote}', 'Api\WorkOrderNotesController@update');
+    Route::delete('/workorders/notes/{workOrderNote}', 'Api\WorkOrderNotesController@destroy');
 
     Route::get('/assets/{asset}/credentials', 'Api\AssetsController@credentials');
     Route::put('/assets/{asset}', 'Api\AssetsController@update');

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů