Jelajahi Sumber

Calendar v2 [wip]

Vijayakrishnan 4 tahun lalu
induk
melakukan
a81d814510

+ 4 - 0
app/Http/Controllers/PatientController.php

@@ -199,4 +199,8 @@ class PatientController extends Controller
     public function manageAppointment(Request $request, Client $patient, Appointment $appointment) {
         return view('app.patient.manage-appointment', compact('patient', 'appointment'));
     }
+
+    public function calendar(Request $request, Client $patient) {
+        return view('app.patient.appointment-calendar', compact('patient'));
+    }
 }

+ 7 - 0
app/Models/Pro.php

@@ -21,6 +21,13 @@ class Pro extends Model
         return $name;
     }
 
+    public function initials() {
+        $characters = [];
+        if(!empty($this->name_first)) $characters[] = $this->name_first[0];
+        if(!empty($this->name_last)) $characters[] = $this->name_last[0];
+        return strtoupper(implode("", $characters));
+    }
+
     public function cmBills()
     {
         return $this->hasMany(Bill::class, 'cm_pro_id');

+ 44 - 0
public/css/style.css

@@ -867,3 +867,47 @@ body .node input[type="number"] {
 .client-rs-contents p {
     margin-bottom: 0.25rem;
 }
+
+.pro-option {
+    position: relative;
+    display: block;
+    padding-left: 30px;
+}
+.pro-option.pro-option-selected {
+    display: inline-block;
+    padding-left: 25px;
+}
+.pro-option .pro-option-initials {
+    position: absolute;
+    left: 5px;
+    top: 2px;
+    font-size: 10px;
+    border-radius: 100%;
+    height: 20px;
+    width: 20px;
+    line-height: 20px;
+    text-align: center;
+    font-weight: 400;
+}
+.pro-option.pro-option-selected .pro-option-initials {
+    left: 3px;
+    top: 0;
+    font-size: 10px;
+    border-radius: 100%;
+    height: 18px;
+    width: 18px;
+    line-height: 19px;
+    text-align: center;
+    font-weight: 400;
+}
+span.pro-selection {
+    padding: 0 5px;
+    height: 18px;
+    display: inline-block;
+    border-top-right-radius: 3px;
+    border-bottom-right-radius: 3px;
+}
+.select2-selection__choice__display {
+    padding: 0 !important;
+    overflow: hidden !important;
+}

+ 355 - 0
resources/views/app/patient/appointment-calendar.blade.php

@@ -0,0 +1,355 @@
+@extends ('layouts.patient')
+
+@section('inner-content')
+
+    <?php
+    $palette = [
+        ["bc" => '#522e92', "fc" => "#fff"],
+        ["bc" => '#111e6c', "fc" => "#fff"],
+        ["bc" => '#003152', "fc" => "#fff"],
+        ["bc" => '#1034a6', "fc" => "#fff"],
+        ["bc" => '#0f52ba', "fc" => "#fff"],
+        ["bc" => '#447684', "fc" => "#fff"],
+        ["bc" => '#d86700', "fc" => "#fff"],
+        ["bc" => '#643c07', "fc" => "#fff"],
+        ["bc" => '#ff3f3f', "fc" => "#fff"],
+        ["bc" => '#ffa395', "fc" => "#222"],
+        ["bc" => '#6450ff', "fc" => "#fff"],
+        ["bc" => '#8ec7f4', "fc" => "#222"],
+        ["bc" => '#522e92', "fc" => "#fff"],
+        ["bc" => '#111e6c', "fc" => "#fff"],
+        ["bc" => '#003152', "fc" => "#fff"],
+        ["bc" => '#1034a6', "fc" => "#fff"],
+        ["bc" => '#0f52ba', "fc" => "#fff"],
+        ["bc" => '#447684', "fc" => "#fff"],
+        ["bc" => '#d86700', "fc" => "#fff"],
+        ["bc" => '#643c07', "fc" => "#fff"],
+        ["bc" => '#ff3f3f', "fc" => "#fff"],
+        ["bc" => '#ffa395', "fc" => "#222"],
+        ["bc" => '#6450ff', "fc" => "#fff"],
+        ["bc" => '#8ec7f4', "fc" => "#222"],
+    ];
+    ?>
+
+    <link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-beta.1/dist/css/select2.min.css" rel="stylesheet" />
+    <script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-beta.1/dist/js/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 class="d-flex align-items-center pb-2">
+        <h4 class="font-weight-bold m-0 font-size-16">
+            Calendar for {{ $patient->displayName() }}
+        </h4>
+    </div>
+    <hr class="mt-2 mb-3">
+    <div class="d-flex align-items-start appt-form">
+        {{--<form action="/api/appointment/{{ $appointment && $appointment->uid ? 'update' : 'create' }}" method="post" class="appt-form-col pr-2">
+            @if($appointment && $appointment->uid)
+                <input name="uid" type="hidden" value="{{ $appointment->uid }}">
+            @else
+                <input name="clientUid" type="hidden" value="{{ $patient->uid }}">
+            @endif
+            <div class="mb-3">
+                <label class="text-secondary mb-1">Patient</label>
+                <div class="font-weight-bold">{{ $patient->displayName() }}</div>
+            </div>
+            <div class="mb-3">
+                <label class="text-secondary mb-1">Pro *</label>
+                <select name="proUid" class="form-control form-control-sm">
+                    <option value="">-- select --</option>
+                    @foreach($pros as $iPro)
+                        <option value="{{$iPro->uid}}" {{ $iPro->uid === $pro->uid ? 'selected' : '' }}>{{$iPro->displayName()}}</option>
+                    @endforeach
+                </select>
+            </div>
+            <div class="mb-3">
+                <label class="text-secondary mb-1">Timezone *</label>
+                <select name="timeZone" class="form-control form-control-sm" required>
+                    <option value=""> --select-- </option>
+                    <option {{ ($appointment && $appointment->timezone === 'EASTERN') || (!$appointment || !$appointment->uid) ? 'selected' : '' }}
+                            value="EASTERN">Eastern</option>
+                    <option {{ $appointment && $appointment->timezone === 'CENTRAL' ? 'selected' : '' }}
+                            value="CENTRAL">Central</option>
+                    <option {{ $appointment && $appointment->timezone === 'MOUNTAIN' ? 'selected' : '' }}
+                            value="MOUNTAIN">Mountain</option>
+                    <option {{ $appointment && $appointment->timezone === 'PACIFIC' ? 'selected' : '' }}
+                            value="PACIFIC">Pacific</option>
+                    <option {{ $appointment && $appointment->timezone === 'ALASKA' ? 'selected' : '' }}
+                            value="ALASKA">Alaska</option>
+                    <option {{ $appointment && $appointment->timezone === 'HAWAII' ? 'selected' : '' }}
+                            value="HAWAII">Hawaii</option>
+                    <option {{ $appointment && $appointment->timezone === 'PUERTO_RICO' ? 'selected' : '' }}
+                            value="PUERTO_RICO">Puerto Rico</option>
+                </select>
+            </div>
+            <div class="mb-3">
+                <label class="text-secondary mb-1">Date *</label>
+                <input name="date" class="form-control form-control-sm" type="date"
+                       value="{{ $appointment && $appointment->raw_date ? $appointment->raw_date : date('Y-m-d') }}"
+                       required>
+            </div>
+            <div class="mb-3">
+                <label class="text-secondary mb-1">Start Time *</label>
+                <input name="startTime" class="form-control form-control-sm" type="time"
+                       value="{{ $appointment && $appointment->raw_start_time ? $appointment->raw_start_time : '' }}"
+                       required>
+            </div>
+            <div class="mb-3">
+                <label class="text-secondary mb-1">End Time</label>
+                <input name="endTime" class="form-control form-control-sm" type="time"
+                       value="{{ $appointment && $appointment->raw_end_time ? $appointment->raw_end_time : '' }}">
+            </div>
+            <div class="mb-3">
+                <label class="text-secondary mb-1">Title</label>
+                <input name="title" class="form-control form-control-sm" type="text"
+                       value="{{ $appointment && $appointment->title ? $appointment->title : '' }}"
+                       placeholder="(optional)">
+            </div>
+            <div class="mb-3">
+                <label class="text-secondary mb-1">Description</label>
+                <textarea name="description" class="form-control form-control-sm"
+                          type="text"
+                          placeholder="(optional)"
+                >{{ $appointment && $appointment->description ? $appointment->description : '' }}</textarea>
+            </div>
+            <div class="mb-3">
+                <button class="btn btn-sm btn-primary d-block w-100 font-weight-bold apply-appt-button" disabled>
+                    @if($appointment && $appointment->uid)
+                        Update Appointment
+                    @else
+                        Book Appointment
+                    @endif
+                </button>
+            </div>
+        </form>--}}
+        <form class="appt-form-col">
+            <div class="mb-3">
+                <label class="mb-1 text-secondary">Show appointments for:</label>
+                <select id="eventPros" name="proUid" class="form-control form-control-sm" multiple>
+                    <?php
+                    $proIndex = 0;
+                    $proMeta = [];
+                    ?>
+                    @foreach($pros as $iPro)
+                        <option value="{{$iPro->uid}}" {{ $iPro->uid === $pro->uid ? 'selected' : '' }}
+                                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"],
+                                "initials" => $iPro->initials()
+                            ];
+                            $proIndex++;
+                            if($proIndex >= count($palette)) $proIndex = 0;
+                        ?>
+                    @endforeach
+                </select>
+            </div>
+            <div class="mb-3">
+                <label class="mb-1 text-secondary">Show appointments in:</label>
+                <select id="eventTz" name="timeZone" class="form-control form-control-sm" required>
+                    <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>
+        </form>
+        <div class="appt-calendar-col pl-3">
+            <div class="stag-fc-container">
+
+            </div>
+        </div>
+    </div>
+    <script>
+        (function() {
+            let calendarObject = null;
+            let proMeta = <?= json_encode($proMeta) ?>;
+            function initSelect2() {
+                $('#eventTz').select2({
+                    templateResult: function(_state) {
+                        return $('<span class="mcp-theme-1"><span>' + _state.text + '</span></span>');
+                    }
+                });
+                $('#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>');
+                    }
+                });
+            }
+            function initCalendar() {
+                calendarObject = new FullCalendar.Calendar($('.stag-fc-container')[0], {
+                    headerToolbar: {
+                        left: 'prev,next today',
+                        center: 'title',
+                        right: 'dayGridMonth,timeGridWeek,timeGridDay'
+                    },
+                    initialDate: '{{ date('Y-m-d') }}',
+                    editable: false,
+                    navLinks: true,
+                    dayMaxEvents: false,
+                    events: function(info, successCallback, failureCallback) {
+                        let proUid = $('.appt-form [name="proUid"]').val();
+                        if(!proUid || !proUid.length) {
+                            successCallback([]); // no events if no pro selected
+                            return;
+                        }
+                        let timeZone = $('.appt-form [name="timeZone"]').val();
+                        if(!timeZone) {
+                            successCallback([]); // no events if no tz selected
+                            return;
+                        }
+                        console.log(proUid)
+                        $.get('/api/appointment/getAllAppointmentsForPro' +
+                            '?proUid=' + proUid +
+                            '&start=' + info.startStr.substr(0, 10) +
+                            '&end=' + info.endStr.substr(0, 10) +
+                            '&timeZone=' + timeZone, function(_data) {
+                            // $.get('/api/appointment/getAllAppointmentsForPro?start=1990-01-01&end=2025-01-01&timeZone=CENTRAL', function(_data) {
+                            if(_data && _data.success) {
+                                let events = _data.data;
+                                for(let e in events) {
+                                    if(events.hasOwnProperty(e) && proMeta[events[e].proUid]) {
+                                        // events[e].backgroundColor =  'yellow'; // proMeta[events[e].proUid].bc;
+                                        events[e].borderColor = proMeta[events[e].proUid].bc;
+                                        // events[e].textColor = 'yellow'; // proMeta[events[e].proUid].fc;
+                                    }
+                                }
+                                successCallback(events);
+                            }
+                            else {
+                                failureCallback('Unable to fetch appointments!');
+                            }
+                        }, 'json');
+                    },
+                    loading: function(bool) {
+
+                    },
+                    dateClick: function(info) {
+                        $('.appt-form td.stag-selected').removeClass('stag-selected');
+                        $(info.dayEl).addClass('stag-selected');
+                        $('.appt-form [name="date"]').val(info.dateStr.substr(0, 10));
+                        $('.apply-appt-button').prop('disabled', false);
+                    },
+                    eventContent: function(arg) {
+
+                    },
+                    eventClassNames: function(arg) {
+                        {{--@if($appointment && $appointment->uid)
+                            if(arg.event.extendedProps.appointmentUid === '{{ $appointment->uid }}') {
+                                return ['stag-current-appt'];
+                            }
+                        @endif--}}
+                    }
+                });
+                calendarObject.render();
+
+            }
+            function init() {
+
+                initSelect2();
+                initCalendar();
+
+                $(document).on('change', '.appt-form [name="proUid"], .appt-form [name="timeZone"]', function() {
+                    calendarObject.refetchEvents();
+                });
+
+                $(document).on('change', '.appt-form [name="date"]', function() {
+                    $('.appt-form td.stag-selected').removeClass('stag-selected');
+                    if(!$(this).val()) return;
+                    calendarObject.gotoDate($(this).val());
+                    $('.fc-day[data-date="' + $(this).val() + '"]').addClass('stag-selected');
+                });
+
+                $('.appt-form [name="date"]').trigger('change');
+
+                $('form.appt-form-col').on('submit', function() {
+                    let form = $(this);
+                    if(!form[0].checkValidity()) {
+                        form[0].reportValidity();
+                        return false;
+                    }
+                    $('.apply-appt-button').prop('disabled', true);
+                    $('.appt-form [name="startTime"]').val($('.appt-form [name="startTime"]').val().substr(0, 5));
+                    $('.appt-form [name="endTime"]').val($('.appt-form [name="endTime"]').val().substr(0, 5));
+                    $.post(form.attr('action'), form.serialize(), function(_data) {
+                        if(_data && _data.success) {
+                            toastr.success('Appointment updated successfully');
+                            $('.apply-appt-button').prop('disabled', true);
+                            calendarObject.refetchEvents();
+                        }
+                        else {
+                            if(_data && _data.message) {
+                                toastr.error(_data.message);
+                            }
+                            else {
+                                toastr.error('Unable to update appointment!');
+                            }
+                        }
+                    }, 'json');
+                    return false;
+                });
+
+                $(document).on('change input paste', '.appt-form-col *', function() {
+                    updateCalendarTitle();
+                    $('.apply-appt-button').prop('disabled', false);
+                });
+
+                function updateCalendarTitle() {
+                    $('.stag-calendar-header-extra').remove();
+                    if(!$('.appt-form [name="proUid"]').val() || !$('.appt-form [name="timeZone"]').val()) {
+                        $('.fc-toolbar-title').parent().addClass('mb-3').removeClass('text-center');
+                        return;
+                    }
+                    else {
+                        $('.fc-toolbar-title').parent().removeClass('mb-3').addClass('text-center');
+                    }
+                    let html = '<div class="stag-calendar-header-extra d-inline-flex">' +
+                        '<span class="text-secondary">Pro:</span>&nbsp;' +
+                        '<span class="font-weight-bold mr-4">' + $('.appt-form [name="proUid"] option:selected').text() + '</span>' +
+                        '<span class="text-secondary">Times in</span>&nbsp;' +
+                        '<span class="font-weight-bold">' + $('.appt-form [name="timeZone"] option:selected').text() + '</span>' +
+                        '</div>';
+                    $(html).insertAfter('.fc-toolbar-title');
+                }
+
+                updateCalendarTitle();
+            }
+            addMCInitializer('patient-manage-appointment', init);
+        })();
+    </script>
+@endsection

+ 4 - 0
resources/views/layouts/patient.blade.php

@@ -13,6 +13,10 @@
                             <a class="nav-link {{ strpos($routeName, 'patients.view.dashboard') === 0 ? 'active' : '' }}"
                                href="{{ route('patients.view.dashboard', ['patient' => $patient]) }}">Dashboard</a>
                         </li>
+                        <li class="nav-item">
+                            <a class="nav-link {{ strpos($routeName, 'patients.view.calendar') === 0 ? 'active' : '' }}"
+                               href="{{ route('patients.view.calendar', ['patient' => $patient]) }}">Calendar</a>
+                        </li>
                         <li class="nav-item">
                             <a class="nav-link {{ strpos($routeName, 'patients.view.devices') === 0 ? 'active' : '' }}"
                                href="{{ route('patients.view.devices', ['patient' => $patient]) }}">Devices</a>

+ 3 - 0
routes/web.php

@@ -111,6 +111,9 @@ Route::middleware('pro.auth')->group(function () {
 
         // dedicated-page appointment create/edit
         Route::get('manage-appointment/{appointment?}', 'PatientController@manageAppointment')->name('manage-appointment');
+
+        // appointment calendar
+        Route::get('calendar', 'PatientController@calendar')->name('calendar');
     });
 
     // load template set