|
@@ -0,0 +1,813 @@
|
|
|
|
+@extends ('layouts.template')
|
|
|
|
+
|
|
|
|
+@section('content')
|
|
|
|
+
|
|
|
|
+ <?php
|
|
|
|
+
|
|
|
|
+ function adjustBrightness($hex, $steps) {
|
|
|
|
+ $steps = max(-255, min(255, $steps));
|
|
|
|
+ $hex = str_replace('#', '', $hex);
|
|
|
|
+ if (strlen($hex) == 3) {
|
|
|
|
+ $hex = str_repeat(substr($hex,0,1), 2).str_repeat(substr($hex,1,1), 2).str_repeat(substr($hex,2,1), 2);
|
|
|
|
+ }
|
|
|
|
+ $color_parts = str_split($hex, 2);
|
|
|
|
+ $return = '#';
|
|
|
|
+ foreach ($color_parts as $color) {
|
|
|
|
+ $color = hexdec($color); // Convert to decimal
|
|
|
|
+ $color = max(0,min(255,$color + $steps)); // Adjust color
|
|
|
|
+ $return .= str_pad(dechex($color), 2, '0', STR_PAD_LEFT); // Make two char hex code
|
|
|
|
+ }
|
|
|
|
+ return $return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ $palette = [
|
|
|
|
+ ["bc" => '#522e92', "fc" => "#ffffff", "ac" => adjustBrightness('#522e92', 180) . 'aa'],
|
|
|
|
+ ["bc" => '#003152', "fc" => "#ffffff", "ac" => adjustBrightness('#003152', 180) . 'aa'],
|
|
|
|
+ ["bc" => '#111e6c', "fc" => "#ffffff", "ac" => adjustBrightness('#111e6c', 180) . 'aa'],
|
|
|
|
+ ["bc" => '#1034a6', "fc" => "#ffffff", "ac" => adjustBrightness('#1034a6', 180) . 'aa'],
|
|
|
|
+ ["bc" => '#0f52ba', "fc" => "#ffffff", "ac" => adjustBrightness('#0f52ba', 180) . 'aa'],
|
|
|
|
+ ["bc" => '#447684', "fc" => "#ffffff", "ac" => adjustBrightness('#447684', 180) . 'aa'],
|
|
|
|
+ ["bc" => '#d86700', "fc" => "#ffffff", "ac" => adjustBrightness('#d86700', 180) . 'aa'],
|
|
|
|
+ ["bc" => '#643c07', "fc" => "#ffffff", "ac" => adjustBrightness('#643c07', 180) . 'aa'],
|
|
|
|
+ ["bc" => '#ff3f3f', "fc" => "#ffffff", "ac" => adjustBrightness('#ff3f3f', 180) . 'aa'],
|
|
|
|
+ ["bc" => '#ffa395', "fc" => "#222222", "ac" => adjustBrightness('#ffa395', 180) . 'aa'],
|
|
|
|
+ ["bc" => '#6450ff', "fc" => "#ffffff", "ac" => adjustBrightness('#6450ff', 180) . 'aa'],
|
|
|
|
+ ["bc" => '#8ec7f4', "fc" => "#222222", "ac" => adjustBrightness('#8ec7f4', 180) . 'aa'],
|
|
|
|
+ ["bc" => '#522e92', "fc" => "#ffffff", "ac" => adjustBrightness('#522e92', 180) . 'aa'],
|
|
|
|
+ ["bc" => '#111e6c', "fc" => "#ffffff", "ac" => adjustBrightness('#111e6c', 180) . 'aa'],
|
|
|
|
+ ["bc" => '#003152', "fc" => "#ffffff", "ac" => adjustBrightness('#003152', 180) . 'aa'],
|
|
|
|
+ ["bc" => '#1034a6', "fc" => "#ffffff", "ac" => adjustBrightness('#1034a6', 180) . 'aa'],
|
|
|
|
+ ["bc" => '#0f52ba', "fc" => "#ffffff", "ac" => adjustBrightness('#0f52ba', 180) . 'aa'],
|
|
|
|
+ ["bc" => '#447684', "fc" => "#ffffff", "ac" => adjustBrightness('#447684', 180) . 'aa'],
|
|
|
|
+ ["bc" => '#d86700', "fc" => "#ffffff", "ac" => adjustBrightness('#d86700', 180) . 'aa'],
|
|
|
|
+ ["bc" => '#643c07', "fc" => "#ffffff", "ac" => adjustBrightness('#643c07', 180) . 'aa'],
|
|
|
|
+ ["bc" => '#ff3f3f', "fc" => "#ffffff", "ac" => adjustBrightness('#ff3f3f', 180) . 'aa'],
|
|
|
|
+ ["bc" => '#ffa395', "fc" => "#222222", "ac" => adjustBrightness('#ffa395', 180) . 'aa'],
|
|
|
|
+ ["bc" => '#6450ff', "fc" => "#ffffff", "ac" => adjustBrightness('#6450ff', 180) . 'aa'],
|
|
|
|
+ ["bc" => '#8ec7f4', "fc" => "#222222", "ac" => adjustBrightness('#8ec7f4', 180) . 'aa'],
|
|
|
|
+ ];
|
|
|
|
+ ?>
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ <link href="/select2/select2.min.css" rel="stylesheet" />
|
|
|
|
+ <script src="/select2/select2.min.js"></script>
|
|
|
|
+ <link href='/fullcalendar-5.3.2/lib/main.css' rel='stylesheet' />
|
|
|
|
+ <script src='/fullcalendar-5.3.2/lib/main.js'></script>
|
|
|
|
+
|
|
|
|
+ <div id="proCalendarApp" class="mcp-theme-1 p-3">
|
|
|
|
+
|
|
|
|
+ <div class="d-flex align-items-center mb-2">
|
|
|
|
+ <h4 class="font-weight-bold m-0 font-size-16">
|
|
|
|
+ @if($pro->pro_type == 'ADMIN')
|
|
|
|
+ Admin Calendar
|
|
|
|
+ @else
|
|
|
|
+ {{ $pro->displayName() }}'s Calendar
|
|
|
|
+ @endif
|
|
|
|
+ </h4>
|
|
|
|
+ <div class="ml-auto d-inline-flex align-items-center">
|
|
|
|
+ <label class="mr-2 my-0 text-secondary">Show</label>
|
|
|
|
+ <select id="eventTypes"
|
|
|
|
+ class="form-control form-control-sm w-180"
|
|
|
|
+ v-model="eventTypes"
|
|
|
|
+ v-on:change="refreshEvents()">
|
|
|
|
+ <option value="APPOINTMENTS">Active Appointments</option>
|
|
|
|
+ <option value="APPOINTMENTS_ALL">All Appointments</option>
|
|
|
|
+ <option value="PRO_AVAILABILITY">Availability</option>
|
|
|
|
+ <option value="BOTH">Availability & Active Appointments</option>
|
|
|
|
+ <option value="BOTH_ALL">Availability & All Appointments</option>
|
|
|
|
+ </select>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="ml-3 d-inline-flex align-items-center">
|
|
|
|
+ <label class="mr-2 my-0 text-secondary">Timezone</label>
|
|
|
|
+ <select id="eventTz" name="timeZone"
|
|
|
|
+ class="form-control form-control-sm w-180"
|
|
|
|
+ v-model="timezone">
|
|
|
|
+ <option value="EASTERN" selected>Eastern</option>
|
|
|
|
+ <option value="CENTRAL">Central</option>
|
|
|
|
+ <option value="MOUNTAIN">Mountain</option>
|
|
|
|
+ <option value="PACIFIC">Pacific</option>
|
|
|
|
+ <option value="ALASKA">Alaska</option>
|
|
|
|
+ <option value="HAWAII">Hawaii</option>
|
|
|
|
+ <option value="PUERTO_RICO">Puerto Rico</option>
|
|
|
|
+ </select>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="d-block appt-form">
|
|
|
|
+ @if($pro->pro_type == 'ADMIN')
|
|
|
|
+ <form class="appt-form-col w-100 d-flex align-items-center">
|
|
|
|
+ <label class="mr-2 my-0 text-secondary text-nowrap">Show all appointments for</label>
|
|
|
|
+ <select id="eventPros" name="proUid"
|
|
|
|
+ class="form-control form-control-sm flex-grow-1" multiple
|
|
|
|
+ v-model="proIds">
|
|
|
|
+ <?php
|
|
|
|
+ $proIndex = 0;
|
|
|
|
+ $proMeta = [];
|
|
|
|
+ ?>
|
|
|
|
+ @foreach($pros as $iPro)
|
|
|
|
+ <option value="{{$iPro->id}}"
|
|
|
|
+ data-bc="{{$palette[$proIndex]["bc"]}}"
|
|
|
|
+ data-fc="{{$palette[$proIndex]["fc"]}}"
|
|
|
|
+ data-initials="{{$iPro->initials()}}">
|
|
|
|
+ {{$iPro->displayName()}}
|
|
|
|
+ </option>
|
|
|
|
+ <?php
|
|
|
|
+ $proMeta[$iPro->uid] = [
|
|
|
|
+ "bc" => $palette[$proIndex]["bc"],
|
|
|
|
+ "fc" => $palette[$proIndex]["fc"],
|
|
|
|
+ "ac" => $palette[$proIndex]["ac"],
|
|
|
|
+ "initials" => $iPro->initials()
|
|
|
|
+ ];
|
|
|
|
+ $proIndex++;
|
|
|
|
+ if($proIndex >= count($palette)) $proIndex = 0;
|
|
|
|
+ ?>
|
|
|
|
+ @endforeach
|
|
|
|
+ </select>
|
|
|
|
+ </form>
|
|
|
|
+ @else
|
|
|
|
+ <?php
|
|
|
|
+ $proMeta[$pro->uid] = [
|
|
|
|
+ "bc" => $palette[0]["bc"],
|
|
|
|
+ "fc" => $palette[0]["fc"],
|
|
|
|
+ "ac" => $palette[0]["ac"],
|
|
|
|
+ "initials" => $pro->initials()
|
|
|
|
+ ];
|
|
|
|
+ ?>
|
|
|
|
+ @endif
|
|
|
|
+ <hr class="my-2">
|
|
|
|
+ <div class="appt-calendar-col mt-2">
|
|
|
|
+ <div class="stag-fc-container"></div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="stag-popup stag-popup-sm mcp-theme-1" stag-popup-key="client-add-appointment">
|
|
|
|
+ <form method="POST" action="/api/appointment/create" id="newApptForm">
|
|
|
|
+ <h3 class="stag-popup-title">
|
|
|
|
+ <span>Book New Appointment</span>
|
|
|
|
+ <a href="#" class="ml-auto text-secondary"
|
|
|
|
+ onclick="return closeStagPopup()"><i class="fa fa-times-circle"></i></a>
|
|
|
|
+ </h3>
|
|
|
|
+ <input type="hidden" name="clientUid" :value="newAppointment.clientUid">
|
|
|
|
+ <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="- TODO -" readonly>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="row mb-2">
|
|
|
|
+ <div class="col-3 text-secondary">
|
|
|
|
+ Pro
|
|
|
|
+ </div>
|
|
|
|
+ <div class="col-9 font-weight-bold">
|
|
|
|
+ <select id="addApptPro" name="proUid" required
|
|
|
|
+ v-model="newAppointment.proUid"
|
|
|
|
+ class="form-control form-control-sm">
|
|
|
|
+ @foreach($pros as $iPro)
|
|
|
|
+ <option value="{{$iPro->uid}}">
|
|
|
|
+ {{$iPro->displayName()}}
|
|
|
|
+ </option>
|
|
|
|
+ @endforeach
|
|
|
|
+ </select>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <input type="hidden" name="referringProUid" value="{{ $pro->uid }}">
|
|
|
|
+ <div class="row mb-2">
|
|
|
|
+ <div class="col-3 text-secondary">
|
|
|
|
+ Date
|
|
|
|
+ </div>
|
|
|
|
+ <div class="col-9 font-weight-bold">
|
|
|
|
+ <input type="date" name="date" required
|
|
|
|
+ class="form-control form-control-sm"
|
|
|
|
+ v-model="newAppointment.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" name="startTime" required
|
|
|
|
+ class="form-control form-control-sm"
|
|
|
|
+ v-model="newAppointment.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" name="endTime"
|
|
|
|
+ class="form-control form-control-sm"
|
|
|
|
+ v-model="newAppointment.endTime">
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <input type="hidden" name="timeZone" :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>
|
|
|
|
+ </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="newAppointment.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="newAppointment.description"></textarea>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="d-flex align-items-center justify-content-center">
|
|
|
|
+ <button class="btn btn-sm btn-primary mr-2"
|
|
|
|
+ :disabled="inProgress"
|
|
|
|
+ v-on:click.prevent="addAppointment()">Submit</button>
|
|
|
|
+ <button class="btn btn-sm btn-default border"
|
|
|
|
+ onclick="return closeStagPopup()">Cancel</button>
|
|
|
|
+ </div>
|
|
|
|
+ </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">
|
|
|
|
+ <h3 class="stag-popup-title">
|
|
|
|
+ <span>Edit Appointment</span>
|
|
|
|
+ <a href="#" class="ml-auto text-secondary"
|
|
|
|
+ onclick="return closeStagPopup()"><i class="fa fa-times-circle"></i></a>
|
|
|
|
+ </h3>
|
|
|
|
+ <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">
|
|
|
|
+ <select id="editApptPro" name="proUid" required
|
|
|
|
+ v-model="editAppointment.proUid"
|
|
|
|
+ class="form-control form-control-sm">
|
|
|
|
+ @foreach($pros as $iPro)
|
|
|
|
+ <option value="{{$iPro->uid}}">
|
|
|
|
+ {{$iPro->displayName()}}
|
|
|
|
+ </option>
|
|
|
|
+ @endforeach
|
|
|
|
+ </select>
|
|
|
|
+ </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" name="date" required
|
|
|
|
+ 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" name="startTime" required
|
|
|
|
+ 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" name="endTime"
|
|
|
|
+ class="form-control form-control-sm"
|
|
|
|
+ v-model="editAppointment.endTime">
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <input type="hidden" name="timeZone" :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>
|
|
|
|
+ </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" required
|
|
|
|
+ v-model="editAppointment.status"
|
|
|
|
+ class="form-control form-control-sm font-weight-bold px-1">
|
|
|
|
+ <option value="CREATED">CREATED</option>
|
|
|
|
+ <option value="CONFIRMED">CONFIRMED</option>
|
|
|
|
+ <option value="CANCELLED">CANCELLED</option>
|
|
|
|
+ <option value="COMPLETED">COMPLETED</option>
|
|
|
|
+ <option value="ABANDONED">ABANDONED</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 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>
|
|
|
|
+ <button class="btn btn-sm btn-default border"
|
|
|
|
+ onclick="return closeStagPopup()">Cancel</button>
|
|
|
|
+ </div>
|
|
|
|
+ </form>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <script>
|
|
|
|
+ (function() {
|
|
|
|
+
|
|
|
|
+ function init() {
|
|
|
|
+ window.proCalendarApp = new Vue({
|
|
|
|
+ el: '#proCalendarApp',
|
|
|
|
+ data: {
|
|
|
|
+ eventTypes: 'BOTH',
|
|
|
|
+ calendar: null,
|
|
|
|
+ proMeta: {!! json_encode($proMeta) !!},
|
|
|
|
+ proIds: ['{{ $pro->id }}'],
|
|
|
|
+ timezone: 'EASTERN',
|
|
|
|
+ today: new Date('{{ date('Y-m-d 00:00:00') }}'),
|
|
|
|
+
|
|
|
|
+ // user clicks/selection
|
|
|
|
+ selectedSlot: null,
|
|
|
|
+ selectedEvent: null,
|
|
|
|
+
|
|
|
|
+ // new appt.
|
|
|
|
+ newAppointment: {
|
|
|
|
+ proUid: '{{ $pro->uid }}',
|
|
|
|
+ referringProUid: '{{ $pro->uid }}',
|
|
|
|
+ date: '',
|
|
|
|
+ startTime: '',
|
|
|
|
+ endTime: '',
|
|
|
|
+ timeZone: '',
|
|
|
|
+ title: '',
|
|
|
|
+ description: '',
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // edit appt.
|
|
|
|
+ editAppointment: {
|
|
|
|
+ proUid: '',
|
|
|
|
+ clientName: '',
|
|
|
|
+ date: '',
|
|
|
|
+ startTime: '',
|
|
|
|
+ endTime: '',
|
|
|
|
+ timeZone: '',
|
|
|
|
+ title: '',
|
|
|
|
+ description: '',
|
|
|
|
+ status: '',
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // availability
|
|
|
|
+ availability: {},
|
|
|
|
+
|
|
|
|
+ inProgress: false,
|
|
|
|
+ editHonored: false,
|
|
|
|
+ },
|
|
|
|
+ methods: {
|
|
|
|
+ // init
|
|
|
|
+ init: function() {
|
|
|
|
+ this.initSelect2();
|
|
|
|
+ this.initCalendar();
|
|
|
|
+ },
|
|
|
|
+ initSelect2: function () {
|
|
|
|
+ let self = this;
|
|
|
|
+ $('#eventTz')
|
|
|
|
+ .select2({
|
|
|
|
+ templateResult: function(_state) {
|
|
|
|
+ return $('<span class="mcp-theme-1"><span>' + _state.text + '</span></span>');
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ .on('change', function() {
|
|
|
|
+ self.timezone = $(this).val();
|
|
|
|
+ @if($pro->pro_type == 'ADMIN')
|
|
|
|
+ localStorage.stagProCalendarTZ = self.timezone;
|
|
|
|
+ @endif
|
|
|
|
+ self.refreshEvents();
|
|
|
|
+ });
|
|
|
|
+ $('#eventPros')
|
|
|
|
+ .select2({
|
|
|
|
+ closeOnSelect: false,
|
|
|
|
+
|
|
|
|
+ // dropdown options
|
|
|
|
+ templateResult: function(_state) {
|
|
|
|
+ let element = _state.element;
|
|
|
|
+ if(!element || !element.value) {
|
|
|
|
+ return $('<span class="mcp-theme-1"><span>' + _state.text + '</span></span>');
|
|
|
|
+ }
|
|
|
|
+ element = $(element);
|
|
|
|
+ return $('<span class="mcp-theme-1 pro-option" ' +
|
|
|
|
+ 'data-initials="' + element.attr('data-initials') + '" ' +
|
|
|
|
+ 'data-bc="' + element.attr('data-bc') + '" ' +
|
|
|
|
+ 'data-fc="' + element.attr('data-fc') + '"><span>' +
|
|
|
|
+ '<span class="pro-option-initials" ' +
|
|
|
|
+ 'style="background: ' + element.attr('data-bc') + '; color: ' + element.attr('data-fc') + '">' +
|
|
|
|
+ element.attr('data-initials') + '</span>' +
|
|
|
|
+ _state.text +
|
|
|
|
+ '</span></span>');
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ // selected items
|
|
|
|
+ templateSelection: function(_state) {
|
|
|
|
+ let element = _state.element;
|
|
|
|
+ if(!element || !element.value) {
|
|
|
|
+ return $('<span class="mcp-theme-1"><span>' + _state.text + '</span></span>');
|
|
|
|
+ }
|
|
|
|
+ element = $(element);
|
|
|
|
+ return $('<span class="pro-selection" style="background: ' + element.attr('data-bc') + '; color: ' + element.attr('data-fc') + '">' +
|
|
|
|
+ _state.text + '</span>');
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ .on('change', function() {
|
|
|
|
+ self.proIds = $(this).val();
|
|
|
|
+ @if($pro->pro_type == 'ADMIN')
|
|
|
|
+ localStorage.stagProCalendarProIds = JSON.stringify(self.proIds);
|
|
|
|
+ @endif
|
|
|
|
+ self.refreshEvents();
|
|
|
|
+ });
|
|
|
|
+ $('#addApptPro')
|
|
|
|
+ .select2({
|
|
|
|
+ width: '100%',
|
|
|
|
+ templateResult: function(_state) {
|
|
|
|
+ return $('<span class="mcp-theme-1"><span>' + _state.text + '</span></span>');
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ .on('change', function() {
|
|
|
|
+ self.newAppointment.proUid = $(this).val();
|
|
|
|
+ });
|
|
|
|
+ $('#editApptPro')
|
|
|
|
+ .select2({
|
|
|
|
+ width: '100%',
|
|
|
|
+ templateResult: function(_state) {
|
|
|
|
+ return $('<span class="mcp-theme-1"><span>' + _state.text + '</span></span>');
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+ .on('change', function() {
|
|
|
|
+ self.editAppointment.proUid = $(this).val();
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+ initCalendar: function () {
|
|
|
|
+ let self = this;
|
|
|
|
+ <?php
|
|
|
|
+ $initialDate = date('Y-m-d');
|
|
|
|
+ ?>
|
|
|
|
+ this.calendar = new FullCalendar.Calendar($('.stag-fc-container')[0], {
|
|
|
|
+ headerToolbar: {
|
|
|
|
+ left: 'prev,next today',
|
|
|
|
+ center: 'title',
|
|
|
|
+ right: 'dayGridMonth,timeGridWeek,timeGridDay'
|
|
|
|
+ },
|
|
|
|
+ initialView: 'timeGridWeek',
|
|
|
|
+ initialDate: '{{ $initialDate }}',
|
|
|
|
+ editable: true,
|
|
|
|
+ selectable: true,
|
|
|
|
+ navLinks: true,
|
|
|
|
+ dayMaxEvents: false,
|
|
|
|
+ slotMinTime: '06:00',
|
|
|
|
+ slotMaxTime: '20:00',
|
|
|
|
+ slotDuration: '00:15:00',
|
|
|
|
+ events: function(info, successCallback, failureCallback) {
|
|
|
|
+ if(!self.proIds || !self.proIds.length) {
|
|
|
|
+ successCallback([]); // no events if no pro selected
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ if(!self.timezone) {
|
|
|
|
+ successCallback([]); // no events if no tz selected
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ $.get('/appointment/getAllAppointmentsForPros' +
|
|
|
|
+ '?proIds=' + self.proIds +
|
|
|
|
+ '&clientId=-1' +
|
|
|
|
+ '&start=' + info.startStr.substr(0, 10) +
|
|
|
|
+ '&end=' + info.endStr.substr(0, 10) +
|
|
|
|
+ '&timeZone=' + self.timezone, function(_data) {
|
|
|
|
+ if(_data && Array.isArray(_data)) {
|
|
|
|
+ let events = _data, displayEvents = [];
|
|
|
|
+ for(let e in events) {
|
|
|
|
+ if(events.hasOwnProperty(e) && self.proMeta[events[e].proUid]) {
|
|
|
|
+ let ev = events[e], meta = self.proMeta[ev.proUid];
|
|
|
|
+ if(ev.type === 'appointment') {
|
|
|
|
+ if(self.eventTypes.indexOf('APPOINTMENTS') === 0 || self.eventTypes.indexOf('BOTH') === 0) {
|
|
|
|
+ ev.backgroundColor = meta.bc;
|
|
|
|
+ ev.borderColor = meta.bc;
|
|
|
|
+ ev.textColor = meta.fc;
|
|
|
|
+ ev.initials = meta.initials;
|
|
|
|
+ ev.display = 'block';
|
|
|
|
+ ev.editable = true;
|
|
|
|
+
|
|
|
|
+ // active/all
|
|
|
|
+ if(['CANCELLED', 'COMPLETED', 'ABANDONED'].indexOf(ev.status) !== -1) {
|
|
|
|
+ if(self.eventTypes === 'APPOINTMENTS_ALL' || self.eventTypes === 'BOTH_ALL') {
|
|
|
|
+ displayEvents.push(ev);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ displayEvents.push(ev);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ if(self.eventTypes === 'PRO_AVAILABILITY' || self.eventTypes.indexOf('BOTH') === 0) {
|
|
|
|
+ ev.backgroundColor = meta.ac;
|
|
|
|
+ ev.borderColor = meta.bc;
|
|
|
|
+ ev.textColor = meta.bc;
|
|
|
|
+ ev.initials = meta.initials;
|
|
|
|
+ ev.display = 'block';
|
|
|
|
+ ev.editable = false;
|
|
|
|
+ displayEvents.push(ev);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ successCallback(displayEvents);
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ failureCallback('Unable to refresh appointments!');
|
|
|
|
+ }
|
|
|
|
+ }, 'json');
|
|
|
|
+ },
|
|
|
|
+ eventDidMount: function(view) {
|
|
|
|
+ self.afterRenderingEvents();
|
|
|
|
+ },
|
|
|
|
+ eventClassNames: function(arg) {
|
|
|
|
+ let classes = [];
|
|
|
|
+ /*if (arg.event.extendedProps.clientOnly) {
|
|
|
|
+ classes.push('client-only');
|
|
|
|
+ }
|
|
|
|
+ if (arg.event.extendedProps.otherClient) {
|
|
|
|
+ classes.push('other-client');
|
|
|
|
+ }*/
|
|
|
|
+ if (arg.event.extendedProps.type === 'availability') {
|
|
|
|
+ classes.push('availability');
|
|
|
|
+ }
|
|
|
|
+ if (['CANCELLED', 'COMPLETED', 'ABANDONED'].indexOf(arg.event.extendedProps.status) !== -1) {
|
|
|
|
+ classes.push('inactive-appointment');
|
|
|
|
+ }
|
|
|
|
+ if (arg.event.extendedProps.type === 'appointment') {
|
|
|
|
+ classes.push('appointment-' + arg.event.extendedProps.appointmentUid);
|
|
|
|
+ }
|
|
|
|
+ return classes;
|
|
|
|
+ },
|
|
|
|
+ loading: function(bool) {
|
|
|
|
+
|
|
|
|
+ },
|
|
|
|
+ eventClick: function(info) {
|
|
|
|
+ self.selectedEvent = info.event;
|
|
|
|
+ self.showEditAppointmentModal();
|
|
|
|
+ },
|
|
|
|
+ selectAllow: function(info) { // allow only single selections
|
|
|
|
+ let seconds = info.end.getTime() - info.start.getTime(),
|
|
|
|
+ maxDelta = 86400000;
|
|
|
|
+ console.log(seconds);
|
|
|
|
+ if(!info.allDay) {
|
|
|
|
+ maxDelta = 1800000;
|
|
|
|
+ }
|
|
|
|
+ return seconds <= maxDelta;
|
|
|
|
+ },
|
|
|
|
+ select: function(info) {
|
|
|
|
+ self.selectedSlot = info;
|
|
|
|
+ /*if(self.today.getTime() <= self.selectedSlot.start.getTime()) {
|
|
|
|
+ $('<a href="#" class="add-overlay ' +
|
|
|
|
+ (info.allDay ? 'add-overlay-day-grid' : 'add-overlay-time-grid') + '">Add</a>')
|
|
|
|
+ .on('mousedown', function() {
|
|
|
|
+ self.showAddAppointmentModal();
|
|
|
|
+ return false;
|
|
|
|
+ })
|
|
|
|
+ .appendTo('.fc-highlight');
|
|
|
|
+ }*/
|
|
|
|
+ },
|
|
|
|
+ eventDrop: self.eventMovedOrResized,
|
|
|
|
+ eventResize: self.eventMovedOrResized
|
|
|
|
+ });
|
|
|
|
+ $(document).on('mousedown', '.fc-highlight', function() {
|
|
|
|
+ if(self.selectedSlot && self.today.getTime() <= self.selectedSlot.start.getTime()) {
|
|
|
|
+ self.showAddAppointmentModal();
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ this.calendar.render();
|
|
|
|
+ },
|
|
|
|
+ afterRenderingEvents: function() {
|
|
|
|
+ let self = this;
|
|
|
|
+ $('.fc .availability').each(function () {
|
|
|
|
+ $(this).parent('.fc-timegrid-event-harness').css('pointer-events', 'none');
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+ dateStr: function(_dateTime) {
|
|
|
|
+ return _dateTime.getFullYear() + "-" +
|
|
|
|
+ ("0"+(_dateTime.getMonth()+1)).slice(-2) + "-" +
|
|
|
|
+ ("0" + _dateTime.getDate()).slice(-2);
|
|
|
|
+ },
|
|
|
|
+ timeStr: function(_dateTime) {
|
|
|
|
+ return ("0" + _dateTime.getHours()).slice(-2) + ":" +
|
|
|
|
+ ("0" + _dateTime.getMinutes()).slice(-2);
|
|
|
|
+ },
|
|
|
|
+ eventMovedOrResized: function (info) {
|
|
|
|
+ let self = this;
|
|
|
|
+ if(!window.confirm('Are you sure?')) {
|
|
|
|
+ this.refreshEvents();
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ let date = this.dateStr(info.event.start);
|
|
|
|
+ let startTime = this.timeStr(info.event.start);
|
|
|
|
+ let endTime = null;
|
|
|
|
+ if(info.event.end) {
|
|
|
|
+ endTime = this.timeStr(info.event.end);
|
|
|
|
+ }
|
|
|
|
+ $.post('/api/appointment/updateDateAndTime', {
|
|
|
|
+ uid: info.event.extendedProps.appointmentUid,
|
|
|
|
+ date: date,
|
|
|
|
+ startTime: startTime,
|
|
|
|
+ endTime: endTime,
|
|
|
|
+ timeZone: this.timezone,
|
|
|
|
+ }, function(_data) {
|
|
|
|
+ self.refreshEvents();
|
|
|
|
+ if(_data) {
|
|
|
|
+ if(!_data.success) {
|
|
|
|
+ toastr.error(_data.message);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else {
|
|
|
|
+ toastr.error('Unable to update the appointment!');
|
|
|
|
+ }
|
|
|
|
+ }, 'json');
|
|
|
|
+ },
|
|
|
|
+ refreshEvents: function() {
|
|
|
|
+ this.calendar.refetchEvents();
|
|
|
|
+ },
|
|
|
|
+ showAddAppointmentModal: function() {
|
|
|
|
+ // setup model data
|
|
|
|
+ /*this.inProgress = false;
|
|
|
|
+ let startTime = this.timeStr(this.selectedSlot.start);
|
|
|
|
+ let endTime = this.timeStr(this.selectedSlot.end);
|
|
|
|
+ this.newAppointment.clientUid = -1; // TODO
|
|
|
|
+ this.newAppointment.proUid = '';
|
|
|
|
+ this.newAppointment.referringProUid = '';
|
|
|
|
+ this.newAppointment.date = this.dateStr(this.selectedSlot.start);
|
|
|
|
+ this.newAppointment.startTime = startTime === '00:00' ? '' : startTime;
|
|
|
|
+ this.newAppointment.endTime = (this.selectedSlot.allDay || endTime === '00:00') ? '' : endTime;
|
|
|
|
+ this.newAppointment.timeZone = this.timezone;
|
|
|
|
+ this.newAppointment.title = '';
|
|
|
|
+ this.newAppointment.description = '';
|
|
|
|
+ Vue.nextTick(function() {
|
|
|
|
+ $('#addApptPro').find('option').prop('selected', false);
|
|
|
|
+ $('#addApptPro').trigger('change');
|
|
|
|
+ showStagPopup('client-add-appointment');
|
|
|
|
+ $('#addApptPro').select2('open');
|
|
|
|
+ });*/
|
|
|
|
+ },
|
|
|
|
+ addAppointment: function() {
|
|
|
|
+ let form = $('#newApptForm');
|
|
|
|
+ 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 book appointment!');
|
|
|
|
+ }
|
|
|
|
+ self.inProgress = false;
|
|
|
|
+ hideMask();
|
|
|
|
+ }, 'json');
|
|
|
|
+ },
|
|
|
|
+ showEditAppointmentModal: function() {
|
|
|
|
+ // setup model data
|
|
|
|
+ /*this.inProgress = false;
|
|
|
|
+ this.editAppointment.uid = this.selectedEvent.extendedProps.appointmentUid;
|
|
|
|
+ this.editAppointment.clientName = this.selectedEvent.extendedProps.clientName;
|
|
|
|
+ this.editAppointment.proUid = this.selectedEvent.extendedProps.proUid;
|
|
|
|
+ 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;
|
|
|
|
+ Vue.nextTick(function() {
|
|
|
|
+ $('#editApptPro').trigger('change');
|
|
|
|
+ showStagPopup('client-edit-appointment');
|
|
|
|
+ });*/
|
|
|
|
+ },
|
|
|
|
+ updateAppointment: function() {
|
|
|
|
+ let form = $('#editApptForm');
|
|
|
|
+ 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');
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ mounted: function() {
|
|
|
|
+ this.init();
|
|
|
|
+ @if($pro->pro_type == 'ADMIN')
|
|
|
|
+ if(localStorage.stagProCalendarTZ) {
|
|
|
|
+ this.timezone = localStorage.stagProCalendarTZ;
|
|
|
|
+ Vue.nextTick(function() {
|
|
|
|
+ $('#eventTz').trigger('change');
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ if(localStorage.stagProCalendarProIds) {
|
|
|
|
+ this.proIds = JSON.parse(localStorage.stagProCalendarProIds);
|
|
|
|
+ Vue.nextTick(function() {
|
|
|
|
+ $('#eventPros').trigger('change');
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ @endif
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ addMCInitializer('patient-calendar', init);
|
|
|
|
+ })();
|
|
|
|
+ </script>
|
|
|
|
+@endsection
|