浏览代码

Appointment overhaul (wip)

Vijayakrishnan 3 年之前
父节点
当前提交
a750f50e5e

+ 1 - 0
app/Http/Controllers/AppointmentController.php

@@ -282,6 +282,7 @@ class AppointmentController extends Controller
                         "title" => $pro->displayName() . " (available)",
                         "proId" => $pro->id,
                         "proUid" => $pro->uid,
+                        "proName" => $pro->displayName(),
                         "start" => $eStart->format('Y-m-d H:i:s'),
                         "end" => $eEnd->format('Y-m-d H:i:s'),
                         "editable" => false

+ 4 - 0
public/css/style.css

@@ -2106,3 +2106,7 @@ body.in-iframe .main-row > .sidebar {
 [open-in-stag-popup] * {
     pointer-events: none;
 }
+.stag-menu {
+    position: absolute;
+    z-index: 2;
+}

+ 464 - 23
resources/views/app/patient/appointment-calendar.blade.php

@@ -225,10 +225,10 @@
             </form>
         </div>
         <div class="stag-popup stag-popup-sm mcp-theme-1" stag-popup-key="client-edit-appointment">
-            <form method="POST" action="/api/appointment/update" id="editApptForm"
+            <form method="POST" action="/api/appointment/updateDateAndTime" id="editApptForm"
                   :class="editAppointment.isTrainingEvent ? 'non-interactive' : ''">
                 <h3 class="stag-popup-title">
-                    <span>Edit Appointment</span>
+                    <span>Edit Date &amp; Time</span>
                     <a native target="_blank" v-if="editAppointment.isTrainingEvent" class="ml-3"
                        :href="'{{config('app.hrm2_url')}}/patients/view/' + editAppointment.clientUid + '/calendar/' + editAppointment.uid">
                         <i class="fa fa-external-link-alt"></i>
@@ -254,17 +254,9 @@
                         Pro
                     </div>
                     <div class="col-9 font-weight-bold">
-                        <select id="editApptPro" name="proUid" provider-search
-                                v-model="editAppointment.proUid"
-                                :data-pro-uid="editAppointment.proUid"
-                                no-auto-pro-suggest-init
-                                class="form-control form-control-sm">
-                            @foreach($pros as $iPro)
-                                <option value="{{$iPro->uid}}">
-                                    {{$iPro->displayName()}}
-                                </option>
-                            @endforeach
-                        </select>
+                        <input type="text"
+                               class="form-control form-control-sm"
+                               :value="editAppointment.proName" readonly>
                     </div>
                 </div>
                 <div class="row mb-2">
@@ -314,14 +306,13 @@
                         Status
                     </div>
                     <div class="col-9 font-weight-bold">
-                        <select id="editApptStatus" name="status" required
+                        <select id="editApptStatus" readonly
                                 v-model="editAppointment.status"
                                 class="form-control form-control-sm font-weight-bold px-1">
-                            <option value="CREATED">CREATED</option>
+                            <option value="PENDING">PENDING</option>
                             <option value="CONFIRMED">CONFIRMED</option>
                             <option value="CANCELLED">CANCELLED</option>
                             <option value="COMPLETED">COMPLETED</option>
-                            <option value="ABANDONED">ABANDONED</option>
                         </select>
                     </div>
                 </div>
@@ -330,7 +321,7 @@
                         Title
                     </div>
                     <div class="col-9 font-weight-bold">
-                        <input type="text" name="title"
+                        <input type="text" readonly
                                class="form-control form-control-sm"
                                v-model="editAppointment.title">
                     </div>
@@ -340,7 +331,7 @@
                         Description
                     </div>
                     <div class="col-9 font-weight-bold">
-                        <textarea name="description"
+                        <textarea readonly
                                   class="form-control form-control-sm"
                                   v-model="editAppointment.description"></textarea>
                     </div>
@@ -348,7 +339,7 @@
                 <div v-if="editAppointment.allowEdit" class="d-flex align-items-center justify-content-center">
                     <button class="btn btn-sm btn-primary mr-2"
                             :disabled="inProgress"
-                            v-on:click.prevent="updateAppointment()">Submit</button>
+                            v-on:click.prevent="updateAppointment_Timing()">Save Date &amp; Time</button>
                     <button class="btn btn-sm btn-default border"
                             onclick="return closeStagPopup()">Cancel</button>
                 </div>
@@ -358,6 +349,261 @@
                 </div>
             </form>
         </div>
+        <div class="stag-popup stag-popup-sm mcp-theme-1" stag-popup-key="client-edit-appointment-status">
+            <form method="POST" action="/api/appointment/updateStatus" id="editApptForm_Status"
+                  :class="editAppointment.isTrainingEvent ? 'non-interactive' : ''">
+                <h3 class="stag-popup-title">
+                    <span>Edit Date &amp; Time</span>
+                    <a native target="_blank" v-if="editAppointment.isTrainingEvent" class="ml-3"
+                       :href="'{{config('app.hrm2_url')}}/patients/view/' + editAppointment.clientUid + '/calendar/' + editAppointment.uid">
+                        <i class="fa fa-external-link-alt"></i>
+                        Open in HRM
+                    </a>
+                    <a href="#" class="ml-auto text-secondary"
+                       onclick="return closeStagPopup()"><i class="fa fa-times-circle"></i></a>
+                </h3>
+                <div class="form-content" :title="editAppointment.isTrainingEvent ? 'Training appointment. Please open in HRM.' : ''">
+                    <input type="hidden" name="uid" :value="editAppointment.uid">
+                    <div class="row mb-2">
+                        <div class="col-3 text-secondary">
+                            Patient
+                        </div>
+                        <div class="col-9 font-weight-bold">
+                            <input type="text"
+                                   class="form-control form-control-sm"
+                                   :value="editAppointment.clientName" readonly>
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col-3 text-secondary">
+                            Pro
+                        </div>
+                        <div class="col-9 font-weight-bold">
+                            <input type="text"
+                                   class="form-control form-control-sm"
+                                   :value="editAppointment.proName" readonly>
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col-3 text-secondary">
+                            Date
+                        </div>
+                        <div class="col-9 font-weight-bold">
+                            <input type="date" readonly
+                                   class="form-control form-control-sm"
+                                   v-model="editAppointment.date">
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col-3 text-secondary">
+                            Start Time
+                        </div>
+                        <div class="col-9 font-weight-bold">
+                            <input type="time" readonly
+                                   class="form-control form-control-sm"
+                                   v-model="editAppointment.startTime">
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col-3 text-secondary">
+                            End Time
+                        </div>
+                        <div class="col-9 font-weight-bold">
+                            <input type="time" readonly
+                                   class="form-control form-control-sm"
+                                   v-model="editAppointment.endTime">
+                        </div>
+                    </div>
+                    <input type="hidden" :value="timezone">
+                    <div class="row mb-2">
+                        <div class="col-3 text-secondary">
+                            Timezone
+                        </div>
+                        <div class="col-9 font-weight-bold">
+                            <input type="text"
+                                   class="form-control form-control-sm"
+                                   :value="timezone" readonly>
+                            <div class="text-secondary font-weight-normal py-1 text-sm">Timezone as set in the calendar</div>
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col-3 text-secondary">
+                            Status
+                        </div>
+                        <div class="col-9 font-weight-bold">
+                            <select id="editApptStatus" name="status"
+                                    v-model="editAppointment.status"
+                                    class="form-control form-control-sm font-weight-bold px-1">
+                                <option value="PENDING">PENDING</option>
+                                <option value="CONFIRMED">CONFIRMED</option>
+                                <option value="CANCELLED">CANCELLED</option>
+                                <option value="COMPLETED">COMPLETED</option>
+                            </select>
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col-3 text-secondary">
+                            Title
+                        </div>
+                        <div class="col-9 font-weight-bold">
+                            <input type="text" readonly
+                                   class="form-control form-control-sm"
+                                   v-model="editAppointment.title">
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col-3 text-secondary">
+                            Description
+                        </div>
+                        <div class="col-9 font-weight-bold">
+                        <textarea readonly
+                                  class="form-control form-control-sm"
+                                  v-model="editAppointment.description"></textarea>
+                        </div>
+                    </div>
+                    <div v-if="editAppointment.allowEdit" class="d-flex align-items-center justify-content-center">
+                        <button class="btn btn-sm btn-primary mr-2"
+                                :disabled="inProgress"
+                                v-on:click.prevent="updateAppointment_Status()">Save Status</button>
+                        <button class="btn btn-sm btn-default border"
+                                onclick="return closeStagPopup()">Cancel</button>
+                    </div>
+                    <div v-else class="alert alert-secondary mb-0">
+                        You cannot make changes to this appointment.
+                    </div>
+                </div>
+            </form>
+        </div>
+        <div class="stag-popup stag-popup-sm mcp-theme-1" stag-popup-key="client-edit-appointment-basic-details">
+            <form method="POST" action="/api/appointment/updateBasic" id="editApptForm_BasicDetails"
+                  :class="editAppointment.isTrainingEvent ? 'non-interactive' : ''">
+                <h3 class="stag-popup-title">
+                    <span>Edit Basic Details</span>
+                    <a native target="_blank" v-if="editAppointment.isTrainingEvent" class="ml-3"
+                       :href="'{{config('app.hrm2_url')}}/patients/view/' + editAppointment.clientUid + '/calendar/' + editAppointment.uid">
+                        <i class="fa fa-external-link-alt"></i>
+                        Open in HRM
+                    </a>
+                    <a href="#" class="ml-auto text-secondary"
+                       onclick="return closeStagPopup()"><i class="fa fa-times-circle"></i></a>
+                </h3>
+                <div class="form-content" :title="editAppointment.isTrainingEvent ? 'Training appointment. Please open in HRM.' : ''">
+                    <input type="hidden" name="uid" :value="editAppointment.uid">
+                    <div class="row mb-2">
+                        <div class="col-3 text-secondary">
+                            Patient
+                        </div>
+                        <div class="col-9 font-weight-bold">
+                            <input type="text"
+                                   class="form-control form-control-sm"
+                                   :value="editAppointment.clientName" readonly>
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col-3 text-secondary">
+                            Pro
+                        </div>
+                        <div class="col-9 font-weight-bold">
+                            <input type="text"
+                                   class="form-control form-control-sm"
+                                   :value="editAppointment.proName" readonly>
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col-3 text-secondary">
+                            Date
+                        </div>
+                        <div class="col-9 font-weight-bold">
+                            <input type="date" readonly
+                                   class="form-control form-control-sm"
+                                   v-model="editAppointment.date">
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col-3 text-secondary">
+                            Start Time
+                        </div>
+                        <div class="col-9 font-weight-bold">
+                            <input type="time" readonly
+                                   class="form-control form-control-sm"
+                                   v-model="editAppointment.startTime">
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col-3 text-secondary">
+                            End Time
+                        </div>
+                        <div class="col-9 font-weight-bold">
+                            <input type="time" readonly
+                                   class="form-control form-control-sm"
+                                   v-model="editAppointment.endTime">
+                        </div>
+                    </div>
+                    <input type="hidden" :value="timezone">
+                    <div class="row mb-2">
+                        <div class="col-3 text-secondary">
+                            Timezone
+                        </div>
+                        <div class="col-9 font-weight-bold">
+                            <input type="text"
+                                   class="form-control form-control-sm"
+                                   :value="timezone" readonly>
+                            <div class="text-secondary font-weight-normal py-1 text-sm">Timezone as set in the calendar</div>
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col-3 text-secondary">
+                            Status
+                        </div>
+                        <div class="col-9 font-weight-bold">
+                            <select id="editApptStatus" readonly
+                                    v-model="editAppointment.status"
+                                    class="form-control form-control-sm font-weight-bold px-1">
+                                <option value="PENDING">PENDING</option>
+                                <option value="CONFIRMED">CONFIRMED</option>
+                                <option value="CANCELLED">CANCELLED</option>
+                                <option value="COMPLETED">COMPLETED</option>
+                            </select>
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col-3 text-secondary">
+                            Title
+                        </div>
+                        <div class="col-9 font-weight-bold">
+                            <input type="text" name="title"
+                                   class="form-control form-control-sm"
+                                   v-model="editAppointment.title">
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col-3 text-secondary">
+                            Description
+                        </div>
+                        <div class="col-9 font-weight-bold">
+                        <textarea name="description"
+                                  class="form-control form-control-sm"
+                                  v-model="editAppointment.description"></textarea>
+                        </div>
+                    </div>
+                    <div v-if="editAppointment.allowEdit" class="d-flex align-items-center justify-content-center">
+                        <button class="btn btn-sm btn-primary mr-2"
+                                :disabled="inProgress"
+                                v-on:click.prevent="updateAppointment_BasicDetails()">Save Basic Details</button>
+                        <button class="btn btn-sm btn-default border"
+                                onclick="return closeStagPopup()">Cancel</button>
+                    </div>
+                    <div v-else class="alert alert-secondary mb-0">
+                        You cannot make changes to this appointment.
+                    </div>
+                </div>
+            </form>
+        </div>
+        <div class="stag-menu bg-white border calendar-event-menu pt-1" style="display: none">
+            <a start show href="#" class="px-2 pb-1 d-block text-nowrap" data-action="showEditAppointmentModal_BasicDetails">Edit basic details</a>
+            <a start show href="#" class="px-2 pb-1 d-block text-nowrap" data-action="showEditAppointmentModal">Edit date and time</a>
+            <a start show href="#" class="px-2 pb-1 d-block text-nowrap" data-action="showEditAppointmentModal_Status">Edit status</a>
+        </div>
     </div>
     <script>
         (function() {
@@ -630,7 +876,8 @@
                                 },
                                 eventClick: function(info) {
                                     self.selectedEvent = info.event;
-                                    self.showEditAppointmentModal();
+                                    // self.showEditAppointmentModal();
+                                    self.showEventMenu(info.el);
                                 },
                                 selectAllow: function(info) { // allow only single selections
                                     let seconds = info.end.getTime() - info.start.getTime(),
@@ -794,6 +1041,13 @@
                                 hideMask();
                             }, 'json');
                         },
+                        showEventMenu: function(_el) {
+                            let menu = $(_el).find('.calendar-event-menu').first();
+                            if(!menu.length) {
+                                menu = $('.calendar-event-menu').clone().removeClass('.calendar-event-menu').appendTo(_el);
+                            }
+                            menu.show();
+                        },
                         showEditAppointmentModal: function() {
                             // setup model data
                             this.inProgress = false;
@@ -816,6 +1070,7 @@
                             @endif
 
                             this.editAppointment.proUid = this.selectedEvent.extendedProps.proUid;
+                            this.editAppointment.proName = this.selectedEvent.extendedProps.proName;
                             this.editAppointment.date = this.dateStr(this.selectedEvent.start);
                             this.editAppointment.startTime = this.timeStr(this.selectedEvent.start);
                             this.editAppointment.endTime = this.selectedEvent.end ? this.timeStr(this.selectedEvent.end) : '';
@@ -825,11 +1080,90 @@
                             this.editAppointment.status = this.selectedEvent.extendedProps.status;
                             this.editAppointment.isTrainingEvent = this.selectedEvent.extendedProps.isTrainingEvent;
                             Vue.nextTick(function() {
-                                $('#editApptPro').trigger('change');
                                 showStagPopup('client-edit-appointment', true);
-                                $('#editApptPro').removeAttr('no-auto-pro-suggest-init').removeAttr('pro-suggest-initialized');
-                                initProSuggest();
                             });
+
+                            setTimeout(() => {
+                                $('.stag-menu').hide();
+                            }, 100);
+                        },
+                        showEditAppointmentModal_BasicDetails: function() {
+                            // setup model data
+                            this.inProgress = false;
+                            this.editAppointment.uid = this.selectedEvent.extendedProps.appointmentUid;
+                            this.editAppointment.clientUid = this.selectedEvent.extendedProps.clientUid;
+                            @if($pro->isDefaultNA())
+                                this.editAppointment.allowEdit = false;
+                            this.editAppointment.clientName = this.selectedEvent.extendedProps.clientName.split(/\s+/).map(_x => _x[0]).join('');
+                            $.get('/can-access-patient/' + this.editAppointment.clientUid, _data => {
+                                if(!hasResponseError(_data)) {
+                                    if(_data.data) {
+                                        this.editAppointment.allowEdit = true;
+                                        this.editAppointment.clientName = this.selectedEvent.extendedProps.clientName;
+                                    }
+                                }
+                            }, 'json');
+                            @else
+                                this.editAppointment.allowEdit = true;
+                                this.editAppointment.clientName = this.selectedEvent.extendedProps.clientName;
+                            @endif
+
+                            this.editAppointment.proUid = this.selectedEvent.extendedProps.proUid;
+                            this.editAppointment.proName = this.selectedEvent.extendedProps.proName;
+                            this.editAppointment.date = this.dateStr(this.selectedEvent.start);
+                            this.editAppointment.startTime = this.timeStr(this.selectedEvent.start);
+                            this.editAppointment.endTime = this.selectedEvent.end ? this.timeStr(this.selectedEvent.end) : '';
+                            this.editAppointment.timeZone = this.timezone;
+                            this.editAppointment.title = this.selectedEvent.extendedProps._title;
+                            this.editAppointment.description = this.selectedEvent.extendedProps.description;
+                            this.editAppointment.status = this.selectedEvent.extendedProps.status;
+                            this.editAppointment.isTrainingEvent = this.selectedEvent.extendedProps.isTrainingEvent;
+                            Vue.nextTick(function() {
+                                showStagPopup('client-edit-appointment-basic-details', true);
+                            });
+
+                            setTimeout(() => {
+                                $('.stag-menu').hide();
+                            }, 100);
+                        },
+                        showEditAppointmentModal_Status: function() {
+                            // setup model data
+                            this.inProgress = false;
+                            this.editAppointment.uid = this.selectedEvent.extendedProps.appointmentUid;
+                            this.editAppointment.clientUid = this.selectedEvent.extendedProps.clientUid;
+                            @if($pro->isDefaultNA())
+                                this.editAppointment.allowEdit = false;
+                            this.editAppointment.clientName = this.selectedEvent.extendedProps.clientName.split(/\s+/).map(_x => _x[0]).join('');
+                            $.get('/can-access-patient/' + this.editAppointment.clientUid, _data => {
+                                if(!hasResponseError(_data)) {
+                                    if(_data.data) {
+                                        this.editAppointment.allowEdit = true;
+                                        this.editAppointment.clientName = this.selectedEvent.extendedProps.clientName;
+                                    }
+                                }
+                            }, 'json');
+                            @else
+                                this.editAppointment.allowEdit = true;
+                                this.editAppointment.clientName = this.selectedEvent.extendedProps.clientName;
+                            @endif
+
+                            this.editAppointment.proUid = this.selectedEvent.extendedProps.proUid;
+                            this.editAppointment.proName = this.selectedEvent.extendedProps.proName;
+                            this.editAppointment.date = this.dateStr(this.selectedEvent.start);
+                            this.editAppointment.startTime = this.timeStr(this.selectedEvent.start);
+                            this.editAppointment.endTime = this.selectedEvent.end ? this.timeStr(this.selectedEvent.end) : '';
+                            this.editAppointment.timeZone = this.timezone;
+                            this.editAppointment.title = this.selectedEvent.extendedProps._title;
+                            this.editAppointment.description = this.selectedEvent.extendedProps.description;
+                            this.editAppointment.status = this.selectedEvent.extendedProps.status;
+                            this.editAppointment.isTrainingEvent = this.selectedEvent.extendedProps.isTrainingEvent;
+                            Vue.nextTick(function() {
+                                showStagPopup('client-edit-appointment-status', true);
+                            });
+
+                            setTimeout(() => {
+                                $('.stag-menu').hide();
+                            }, 100);
                         },
                         updateAppointment: function() {
                             let form = $('#editApptForm');
@@ -848,6 +1182,97 @@
                                 }
                             }
 
+                            this.inProgress = true;
+                            let self = this;
+                            showMask();
+                            $.post(form.attr('action'), form.serialize(), function(_data) {
+                                if(_data) {
+                                    if(_data.success) {
+                                        self.refreshEvents();
+                                        closeStagPopup();
+                                    }
+                                    else {
+                                        toastr.error(_data.message);
+                                    }
+                                }
+                                else {
+                                    toastr.error('Unable to update appointment!');
+                                }
+                                self.inProgress = false;
+                                hideMask();
+                            }, 'json');
+                        },
+                        updateAppointment_Status: function() {
+                            let form = $('#editApptForm_Status');
+                            if(!form[0].checkValidity()) {
+                                form[0].reportValidity();
+                                return false;
+                            }
+
+                            // confirming if changing to cancelled, completed or abandoned
+                            if(this.selectedEvent.extendedProps.status !== this.editAppointment.status &&
+                                ['CANCELLED', 'COMPLETED', 'ABANDONED'].indexOf(this.editAppointment.status) !== -1) {
+                                if(!window.confirm('CANCELLED, COMPLETED and ABANDONED appointments will ' +
+                                    'not be displayed in the calendar by default.\n\n' +
+                                    'Are you sure you want to update the status of this appointment to ' + this.editAppointment.status + '?')) {
+                                    return false;
+                                }
+                            }
+
+                            this.inProgress = true;
+                            let self = this;
+                            showMask();
+                            $.post(form.attr('action'), form.serialize(), function(_data) {
+                                if(_data) {
+                                    if(_data.success) {
+                                        self.refreshEvents();
+                                        closeStagPopup();
+                                    }
+                                    else {
+                                        toastr.error(_data.message);
+                                    }
+                                }
+                                else {
+                                    toastr.error('Unable to update appointment!');
+                                }
+                                self.inProgress = false;
+                                hideMask();
+                            }, 'json');
+                        },
+                        updateAppointment_Timing: function() {
+                            let form = $('#editApptForm');
+                            if(!form[0].checkValidity()) {
+                                form[0].reportValidity();
+                                return false;
+                            }
+
+                            this.inProgress = true;
+                            let self = this;
+                            showMask();
+                            $.post(form.attr('action'), form.serialize(), function(_data) {
+                                if(_data) {
+                                    if(_data.success) {
+                                        self.refreshEvents();
+                                        closeStagPopup();
+                                    }
+                                    else {
+                                        toastr.error(_data.message);
+                                    }
+                                }
+                                else {
+                                    toastr.error('Unable to update appointment!');
+                                }
+                                self.inProgress = false;
+                                hideMask();
+                            }, 'json');
+                        },
+                        updateAppointment_BasicDetails: function() {
+                            let form = $('#editApptForm_BasicDetails');
+                            if(!form[0].checkValidity()) {
+                                form[0].reportValidity();
+                                return false;
+                            }
+
                             this.inProgress = true;
                             let self = this;
                             showMask();
@@ -885,6 +1310,22 @@
                             });
                         }
                         @endif
+
+                        $(document)
+                            .off('mousedown.discard-stag-menu')
+                            .on('mousedown.discard-stag-menu', function (_e) {
+                                if($(_e.target).closest('.stag-menu').length) return;
+                                $('.stag-menu').hide();
+                            });
+
+                        let self = this;
+                        $(document)
+                            .off('click.stag-menu-action', '.stag-menu>a[data-action]')
+                            .on('click.stag-menu-action', '.stag-menu>a[data-action]', function (_e) {
+                                self[$(this).attr('data-action')].call(self);
+                                return false;
+                            });
+
                     }
                 });
             }