浏览代码

Tickets v1 (wip)

Vijayakrishnan 4 年之前
父节点
当前提交
f0f3f6a732

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

@@ -314,4 +314,10 @@ class PatientController extends Controller
         $pros = $this->pros;
         return view('app.patient.flowsheets', compact('patient', 'pros', 'filter'));
     }
+
+    public function tickets(Request $request, Client $patient, $filter = 'all') {
+        $pros = $this->pros;
+        $allPros = Pro::all();
+        return view('app.patient.tickets', compact('patient', 'pros', 'filter', 'allPros'));
+    }
 }

+ 57 - 0
public/css/style.css

@@ -967,6 +967,63 @@ body .node input[type="number"] {
     margin-right: 1.5rem;
     margin-left: auto;
 }
+
+/* slide-in stag-popups */
+.stag-popup.stag-slide {
+    display: block;
+    background: center center no-repeat scroll rgba(0, 0, 0, 0);
+    pointer-events: none;
+    overflow: hidden;
+}
+.stag-popup.stag-slide.show {
+    pointer-events: all;
+}
+.stag-popup.stag-slide>form {
+    position: absolute;
+    top: 0;
+    height: 100% !important;
+    overflow-y: auto;
+    border-radius: 0;
+    border-top: 0;
+    border-bottom: 0;
+    border-right: 0;
+    transition: right 0.3s ease;
+    width: 0;
+}
+.stag-popup.stag-slide.stag-popup-sm>form {
+    width: 500px;
+    right: -500px;
+}
+.stag-popup.stag-slide.stag-popup-md>form {
+    width: 632pt;
+    right: -632pt;
+}
+.stag-popup.stag-slide.show>form {
+    right: 0;
+}
+
+/* asana style ticket management */
+.pro-initials {
+    font-size: 10px;
+    border-radius: 100%;
+    height: 24px;
+    width: 24px;
+    text-align: center;
+    font-weight: 400;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    background-color: #6457f9;
+    color: #fff;
+    font-size: 10px !important;
+}
+.tickets-table tbody tr {
+    transition: background-color 0.2s ease;
+}
+.tickets-table tbody tr:hover {
+    background-color: rgba(0,0,0,.04);
+}
+
 .no-scroll {
     /*overflow: hidden;*/
 }

+ 506 - 0
resources/views/app/patient/tickets.blade.php

@@ -0,0 +1,506 @@
+@extends ('layouts.patient')
+@section('inner-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>
+    <div id="ticketsApp" v-cloak>
+        <div class="d-flex align-items-end pb-3">
+            <h4 class="font-weight-bold m-0 font-size-14">Tickets</h4>
+            <select class="ml-auto max-width-300px form-control form-control-sm"
+                    onchange="fastLoad('/patients/view/{{$patient->uid}}/tickets/' + this.value, true, false, false)">
+                <option value="open" {{ $filter === 'open' ? 'selected' : '' }}>Open tickets</option>
+                <option value="closed" {{ $filter === 'closed' ? 'selected' : '' }}>Closed tickets</option>
+                <option value="all" {{ $filter === 'all' ? 'selected' : '' }}>All tickets</option>
+            </select>
+        </div>
+
+        <div>
+            <div class="d-flex align-items-center pb-2">
+                <h4 class="font-weight-bold m-0 text-secondary font-size-14"><i class="fa fa-prescription mr-2"></i>ERx</h4>
+                <a class="py-0 font-weight-normal c-pointer btn btn-sm btn-info text-white rounded-0 mx-3"
+                   v-on:click.prevent="showErxPopup()">Add</a>
+            </div>
+            <table class="table table-sm tickets-table mb-0">
+                <tbody>
+                <tr v-for="(item, index) in ticketsByType.erx" :class="item.is_open ? '' : 'opacity-60 bg-light'">
+                    <td class="px-2 py-2 c-pointer" v-on:click.prevent="showErxPopup(item)">
+                        <div class="d-flex align-items-center">
+                            <div class="pro-initials text-uppercase"
+                                 :title="allProsFlat['pro_' + item.ordering_pro_id].displayedName"
+                                 :style="'background-color: ' + allProsFlat['pro_' + item.ordering_pro_id].colors.bc + '; color: ' + allProsFlat['pro_' + item.ordering_pro_id].colors.fc + ';'">
+                                @{{allProsFlat['pro_' + item.ordering_pro_id].displayedInitials}}
+                            </div>
+                            <div class="flex-grow-1 d-inline-flex ml-2 flex-wrap">
+                                <span class="font-weight-bold text-dark font-size-13">@{{item.data.medication}}</span>
+                                <span class="d-inline-flex align-items-center" v-if="item.data.strength">
+                                    <span class="mx-2 text-secondary">•</span>
+                                    <span>@{{item.data.strength}}</span>
+                                </span>
+                                <span class="d-inline-flex align-items-center" v-if="item.data.route">
+                                    <span class="mx-2 text-secondary">•</span>
+                                    <span>@{{item.data.route}}</span>
+                                </span>
+                                <span class="d-inline-flex align-items-center" v-if="item.data.frequency">
+                                    <span class="mx-2 text-secondary">•</span>
+                                    <span>@{{item.data.frequency}}</span>
+                                </span>
+                                <!--
+                                <span class="d-inline-flex align-items-center" v-if="item.data.dispense">
+                                    <span class="mx-2 text-secondary">•</span>
+                                    <span>Dispense:</span> @{{item.data.dispense}}
+                                    <span class="text-secondary ml-1" v-html="inWords(item.data.dispense)"></span>
+                                </span>
+                                <span class="d-inline-flex align-items-center" v-if="item.data.refills">
+                                    <span class="mx-2 text-secondary">•</span>
+                                    <span><span>Refills:</span> @{{item.data.refills}}</span>
+                                </span>
+                                <span class="d-inline-flex align-items-center" v-if="item.data.purpose">
+                                    <span class="mx-2 text-secondary">•</span>
+                                    <span><span>Purpose:</span> @{{item.data.purpose}}</span>
+                                </span>
+                                -->
+                                <span class="text-nowrap ml-auto" v-html="pharmacy(item.data)"></span>
+                            </div>
+                        </div>
+                        <!--
+                        <div>
+                            <span class="text-secondary text-sm mt-1">Created:</span>
+                            @{{ item.created_at }}
+                        </div>
+                        -->
+                    </td>
+                    {{--@include('app.patient.tickets.ticket_vue_collab_column')--}}
+                    {{--<td class="px-2">
+                        <a class="mr-2 c-pointer" v-on:click.prevent="showPopup('erx-popup', item)">Edit</a>
+                        <a class="mr-2 c-pointer" v-if="item.is_open" v-on:click.prevent="closeItem(item)">Close</a>
+                        <a class="mr-2 c-pointer" v-if="!item.is_open" v-on:click.prevent="openItem(item)">Open</a>
+
+                        @include('app.patient.partials.ticket_action_links')
+                    </td>--}}
+                </tr>
+                </tbody>
+            </table>
+            <div class="stag-popup stag-popup-sm stag-slide mcp-theme-1" stag-popup-key="erx-popup">
+                <form method="POST" action="">
+                    <h3 class="stag-popup-title mb-2">
+                        <span>@{{ erxPopupMode === 'add' ? 'Add ERx Item' : 'Edit ERx Item' }}</span>
+                        <a href="#" class="ml-auto text-secondary"
+                           onclick="return closeStagPopup()"><i class="fa fa-times-circle"></i></a>
+                    </h3>
+                    <div class="row mb-2">
+                        <div class="col-6">
+                            <label class="text-sm text-secondary mb-1">Medication</label>
+                            <input required type="text" data-field="medication"
+                                   v-model="erxPopupItem.data.medication" class="form-control form-control-sm">
+                        </div>
+                        <div class="col-6">
+                            <label class="text-sm text-secondary mb-1">Strength</label>
+                            <input type="text" data-field="strength"
+                                   v-model="erxPopupItem.data.strength" class="form-control form-control-sm">
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col-6">
+                            <label class="text-sm text-secondary mb-1">Frequency</label>
+                            <input type="text" v-model="erxPopupItem.data.frequency" class="form-control form-control-sm"
+                                   data-option-list="frequency-options">
+                            <div id="frequency-options" class="data-option-list">
+                                <div>Once a day</div>
+                                <div>Twice a day</div>
+                            </div>
+                        </div>
+                        <div class="col-6">
+                            <label class="text-sm text-secondary mb-1">Route</label>
+                            <input required type="text" v-model="erxPopupItem.data.route" class="form-control form-control-sm"
+                                   data-option-list="route-options">
+                            <div id="route-options" class="data-option-list">
+                                <div>PO (by mouth)</div>
+                                <div>PR (per rectum)</div>
+                                <div>IM (intramuscular)</div>
+                                <div>IV (intravenous)</div>
+                                <div>ID (intradermal)</div>
+                                <div>IN (intranasal)</div>
+                                <div>TP (topical)</div>
+                                <div>SL (sublingual)</div>
+                                <div>BUCC (buccal)</div>
+                                <div>IP (intraperitoneal)</div>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col-6">
+                            <label class="text-sm text-secondary mb-1">Dispense Amount</label>
+                            <input required type="number" v-model="erxPopupItem.data.dispense" class="form-control form-control-sm">
+                        </div>
+                        <div class="col-6">
+                            <label class="text-sm text-secondary mb-1">Refills</label>
+                            <input type="number" v-model="erxPopupItem.data.refills" class="form-control form-control-sm">
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col-12">
+                            <label class="text-sm text-secondary mb-1">Purpose</label>
+                            <input required type="text" data-field="icd" v-model="erxPopupItem.data.purpose" class="form-control form-control-sm">
+                        </div>
+                    </div>
+                    <hr class="mt-3 mb-2">
+
+                    <div class="row mb-2">
+                        <div class="col-12">
+                            <label class="text-sm text-secondary mb-1 font-weight-bold">Preferred Pharmacy</label>
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col-12">
+                            <label class="text-sm text-secondary mb-1">Business Name</label>
+                            <input type="text" autocomplete="donotdoit" id="pharmacy-search" v-model="erxPopupItem.data.pharmacyName" class="form-control form-control-sm">
+                            <div class="suggestions-outer pharmacy-suggestions position-absolute d-none"></div>
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col-6">
+                            <label class="text-sm text-secondary mb-1">City</label>
+                            <input type="text" v-model="erxPopupItem.data.pharmacyCity" class="form-control form-control-sm">
+                        </div>
+                        <div class="col-6">
+                            <label class="text-sm text-secondary mb-1">State</label>
+                            <input type="text" v-model="erxPopupItem.data.pharmacyState" class="form-control form-control-sm">
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col-12">
+                            <label class="text-sm text-secondary mb-1">Address Memo</label>
+                            <input type="text" v-model="erxPopupItem.data.pharmacyAddressMemo" class="form-control form-control-sm">
+                        </div>
+                    </div>
+                    <div class="row mb-2">
+                        <div class="col-6">
+                            <label class="text-sm text-secondary mb-1">Phone</label>
+                            <input type="text" v-model="erxPopupItem.data.pharmacyPhone" class="form-control form-control-sm">
+                        </div>
+                        <div class="col-6">
+                            <label class="text-sm text-secondary mb-1">Fax</label>
+                            <input type="text" v-model="erxPopupItem.data.pharmacyFax" class="form-control form-control-sm">
+                        </div>
+                    </div>
+
+                    <div class="d-flex align-items-center justify-content-center mt-3">
+                        <button type="button" class="btn btn-sm btn-primary mr-2" v-on:click.prevent="savePopupItem()">Submit</button>
+                        <button type="button" class="btn btn-sm btn-default border" onclick="return closeStagPopup()">Cancel</button>
+                    </div>
+                </form>
+            </div>
+            @include('app.patient.partials.ticket_update_pro_form',['ticketType'=>'erx'])
+        </div>
+
+    </div>
+
+    <script>
+        (function() {
+            <?php
+            $tickets = $patient->tickets;
+            if ($filter !== 'all') {
+                $tickets = $tickets->filter(function ($_item) use ($filter) {
+                    return ($filter === 'open' ? $_item->is_open : !$_item->is_open);
+                });
+            }
+            $ticketsByType = [
+                "erx" => [],
+                "equipment" => [],
+                "lab" => [],
+                "imaging" => [],
+            ];
+            foreach ($tickets as $ticket) {
+                $ticket->data = json_decode($ticket->data);
+                $ticket->created_at = friendly_date_time($ticket->created_at);
+                $ticketsByType[$ticket->category][] = $ticket;
+            }
+
+            $allProsFlat = [];
+            $paletteIndex = 0;
+            foreach ($allPros as $allPro) {
+                $allPro->displayedName = $allPro->displayName();
+                $allPro->displayedInitials = $allPro->initials();
+                $allProsFlat["pro_" . $allPro->id] = $allPro;
+                $allProsFlat["pro_" . $allPro->id]['colors'] = $palette[$paletteIndex++];
+                if($paletteIndex >= count($palette)) $paletteIndex = 0;
+            }
+            ?>
+            function init() {
+                window.ticketsApp = new Vue({
+                    el: '#ticketsApp',
+                    delimiters: ['@{{', '}}'],
+                    data: {
+                        tickets: {!! json_encode($tickets) !!},
+
+                        ticketsByType: {!! json_encode($ticketsByType) !!},
+
+                        // erx
+                        erxPopupMode: 'add',
+                        erxPopupItem: {
+                            uid: '',
+                            is_open: true,
+                            data: {
+                                medication: '',
+                                strength: '',
+                                amount: '',
+                                route: '',
+                                frequency: '',
+                                dispense: '',
+                                refills: '',
+                                purpose: '',
+                                pharmacyName: '',
+                                pharmacyCity: '',
+                                pharmacyState: '',
+                                pharmacyAddressMemo: '',
+                                pharmacyPhone: '',
+                                pharmacyFax: '',
+                            }
+                        },
+
+                        // lab
+                        labPopupMode: 'add',
+                        labPopupItem: {
+                            uid: '',
+                            is_open: true,
+                            tests: [''],
+                            icds: [''],
+                            memo: '',
+                        },
+
+                        // common
+                        allPros: {!! json_encode($allPros) !!},
+                        allProsFlat: {!! json_encode($allProsFlat) !!},
+                        proToUpdate: '',
+                        proTypes: ['Assigned', 'Manager', 'Initiating', 'Ordering'],
+                        newProUid:''
+                    },
+                    methods: {
+
+                        // erx
+                        showErxPopup: function(_item) {
+                            closeStagPopup();
+                            this.erxPopupMode = _item ? 'edit' : 'add';
+                            this.erxPopupItem = _item ? JSON.parse(JSON.stringify(_item)) : {
+                                uid: '',
+                                is_open: true,
+                                data: {
+                                    medication: '',
+                                    strength: '',
+                                    amount: '',
+                                    route: '',
+                                    frequency: '',
+                                    dispense: '',
+                                    refills: '',
+                                    purpose: '',
+                                    pharmacyName: '',
+                                    pharmacyCity: '',
+                                    pharmacyState: '',
+                                    pharmacyAddressMemo: '',
+                                    pharmacyPhone: '',
+                                    pharmacyFax: '',
+                                }
+                            };
+                            showStagPopup('erx-popup');
+                        },
+                        saveErxPopupItem: function() {
+                            let form = $('#ticketsApp form').first();
+                            if(!form[0].checkValidity()) {
+                                form[0].reportValidity();
+                                return false;
+                            }
+
+                            showMask();
+                            let payload = {};
+                            if(this.erxPopupMode === 'add') {
+                                payload.clientUid = '{{ $patient->uid }}';
+                                payload.category = 'erx';
+                                payload.assignedProUid = '{{ $pro->uid  }}';
+                                payload.managerProUid = '{{ $pro->uid  }}';
+                                payload.orderingProUid = '{{ $pro->uid  }}';
+                                payload.initiatingProUid = '{{ $pro->uid  }}';
+                                payload.data = JSON.stringify(this.erxPopupItem);
+                            }
+                            else {
+                                payload.uid = this.erxPopupItem.uid;
+                                payload.newData = JSON.stringify(this.erxPopupItem);
+                            }
+
+                            $.post(
+                                '/api/ticket/' + (this.erxPopupMode === 'add' ? 'create' : 'updateData'),
+                                payload,
+                                function(_data) {
+                                    console.log(_data);
+                                    fastReload();
+                                },
+                                'json');
+
+                            return false;
+                        },
+
+                        // lab
+
+                        // imaging
+
+                        // equipment
+
+                        // common
+                        closeItem: function(_item) {
+                            showMask();
+                            $.post('/api/ticket/close', {
+                                uid: _item.uid
+                            }, function(_data) {
+                                fastReload();
+                            });
+                        },
+                        openItem: function(_item) {
+                            showMask();
+                            $.post('/api/ticket/open', {
+                                uid: _item.uid
+                            }, function(_data) {
+                                fastReload();
+                            });
+                        },
+                        initRxAutoSuggest: function() {
+                            let self = this;
+                            $('#ticketsApp input[type="text"][data-field="medication"]:not([ac-initialized])').each(function() {
+                                let elem = this,
+                                    randPart = Math.ceil(Math.random() * 1000000),
+                                    dynID = 'rx-' + randPart;
+                                $(elem).attr('id', dynID);
+                                var strengthElem = $(elem).closest('.stag-popup').find('[data-field="strength"]')[0],
+                                    dynStrengthsID = 'rx-' + randPart + '-strengths';
+                                $(strengthElem).attr('id', dynStrengthsID);
+                                $(strengthElem).attr('rx-id', dynID);
+                                new window.Def.Autocompleter.Prefetch(dynStrengthsID, []);
+                                new window.Def.Autocompleter.Search(dynID,
+                                    'https://clinicaltables.nlm.nih.gov/api/rxterms/v3/search?ef=STRENGTHS_AND_FORMS');
+                                window.Def.Autocompleter.Event.observeListSelections(dynID, function() {
+                                    var autocomp = elem.autocomp, acData = autocomp.getSelectedItemData();
+                                    var strengths = acData[0].data['STRENGTHS_AND_FORMS'];
+                                    if (strengths) {
+                                        strengthElem.autocomp.setListAndField(strengths, '');
+                                    }
+                                    self.erxPopupItem.data.medication = $(elem).val();
+                                });
+                                window.Def.Autocompleter.Event.observeListSelections(dynStrengthsID, function() {
+                                    var autocomp = elem.autocomp, acData = autocomp.getSelectedItemData();
+                                    self.erxPopupItem.data.strength = $(strengthElem).val();
+                                });
+                                $(elem).attr('ac-initialized', 1);
+                                $(strengthElem).attr('ac-initialized', 1);
+                            });
+                        },
+                        initICDAutoSuggest: function() {
+                            let self = this;
+                            $('#ticketsApp input[type="text"][data-field="icd"]:not([ac-initialized])').each(function() {
+                                var elem = this,
+                                    dynID = 'icd-' + Math.ceil(Math.random() * 1000000),
+                                    vueIndex = $(this).attr('data-index');
+                                $(elem).attr('id', dynID);
+                                new window.Def.Autocompleter.Search(dynID,
+                                    'https://clinicaltables.nlm.nih.gov/api/icd10cm/v3/search?sf=code,name&ef=name', {
+                                        tableFormat: true,
+                                        valueCols: [0],
+                                        colHeaders: ['Code', 'Name'],
+                                    }
+                                );
+                                window.Def.Autocompleter.Event.observeListSelections(dynID, function() {
+                                    let autocomp = elem.autocomp, acData = autocomp.getSelectedItemData();
+                                    self.erxPopupItem.data.purpose = acData[0].code + ' - ' + acData[0].data.name;
+                                    return false;
+                                });
+                                $(elem).attr('ac-initialized', 1);
+                            });
+                        },
+                        inWords: function (num) {
+                            try {
+                                num = +num;
+                                var a = ['','one ','two ','three ','four ', 'five ','six ','seven ','eight ','nine ','ten ','eleven ','twelve ','thirteen ','fourteen ','fifteen ','sixteen ','seventeen ','eighteen ','nineteen '];
+                                var b = ['', '', 'twenty','thirty','forty','fifty', 'sixty','seventy','eighty','ninety'];
+                                if ((num = num.toString()).length > 3) return 'overflow';
+                                let n = ('000000000' + num).substr(-9).match(/^(\d{2})(\d{2})(\d{2})(\d{1})(\d{2})$/);
+                                if (!n) return; var str = '';
+                                str += (n[1] != 0) ? (a[Number(n[1])] || b[n[1][0]] + ' ' + a[n[1][1]]) + 'crore ' : '';
+                                str += (n[2] != 0) ? (a[Number(n[2])] || b[n[2][0]] + ' ' + a[n[2][1]]) + 'lakh ' : '';
+                                str += (n[3] != 0) ? (a[Number(n[3])] || b[n[3][0]] + ' ' + a[n[3][1]]) + 'thousand ' : '';
+                                str += (n[4] != 0) ? (a[Number(n[4])] || b[n[4][0]] + ' ' + a[n[4][1]]) + 'hundred ' : '';
+                                str += (n[5] != 0) ? ((str != '') ? 'and ' : '') + (a[Number(n[5])] || b[n[5][0]] + ' ' + a[n[5][1]]) : '';
+                                return str ? '(' + $.trim(str) + ')' : '';
+                            }
+                            catch (e) {
+                                return '';
+                            }
+                        },
+                        pharmacy: function(_item) {
+                            return [
+                                _item.pharmacyName,
+                                _item.pharmacyCity,
+                                /*_item.pharmacyState,
+                                _item.pharmacyAddressMemo,
+                                _item.pharmacyPhone,
+                                _item.pharmacyFax,*/
+                            ].filter(Boolean).join(', ');
+                        },
+                        @include('app.patient.tickets.pharmacy-suggest')
+                        @include('app.patient.tickets.ticket_vue_methods',['ticketType'=>'erx'])
+                    },
+                    mounted: function () {
+                        this.initRxAutoSuggest();
+                        this.initICDAutoSuggest();
+                        initFastLoad($('#ticketsApp'));
+                        this.initPharmacySearch();
+                    }
+                })
+            }
+            addMCInitializer('patient-tickets', init, '#ticketsApp');
+        })();
+    </script>
+
+@endsection

+ 98 - 0
resources/views/app/patient/tickets/pharmacy-suggest.blade.php

@@ -0,0 +1,98 @@
+initPharmacySearch: function () {
+    let self = this;
+    const debounce = (func, wait) => {
+        let timeout;
+        return function executedFunction(...args) {
+            const later = () => {
+                clearTimeout(timeout);
+                func(...args);
+            };
+            clearTimeout(timeout);
+            timeout = setTimeout(later, wait);
+        };
+    };
+    var lastTerm = '';
+    var returnedFunction = debounce(function () {
+        var term = $('#pharmacy-search').val();
+        if (!!term && lastTerm !== term) {
+            $.get('/pharmacy-suggest?term=' + $.trim(term), function (_data) {
+                $('.suggestions-outer.pharmacy-suggestions').html(_data).removeClass('d-none');
+            });
+            lastTerm = term;
+        } else {
+            $('.suggestions-outer.pharmacy-suggestions').addClass('d-none');
+        }
+    }, 250);
+    $('#pharmacy-search')
+        .on('keydown', function (e) {
+            var term = $.trim($('#pharmacy-search').val());
+            var activeItem = $('.suggestions-outer.pharmacy-suggestions .suggest-item.active');
+            switch (e.which) {
+                case 27:
+                    $('.suggestions-outer.pharmacy-suggestions').addClass('d-none');
+                    return false;
+                case 38:
+                    if (activeItem.prev().length) {
+                        activeItem.prev()
+                            .addClass('active')
+                            .siblings().removeClass('active');
+                        activeItem = $('.suggestions-outer.pharmacy-suggestions .suggest-item.active');
+                        if (activeItem.length) {
+                            activeItem[0].scrollIntoView();
+                        }
+                    }
+                    return false;
+                case 40:
+                    if (activeItem.next().length) {
+                        activeItem.next()
+                            .addClass('active')
+                            .siblings().removeClass('active');
+                        activeItem = $('.suggestions-outer.pharmacy-suggestions .suggest-item.active');
+                        if (activeItem.length) {
+                            activeItem[0].scrollIntoView();
+                        }
+                    }
+                    return false;
+                case 13:
+                    if (activeItem.length) {
+                        activeItem.first().click();
+                    }
+                    return false;
+                default:
+                    if (!!term) {
+                        $('.suggestions-outer.pharmacy-suggestions')
+                            .html('<span class="d-block no-suggest-items">Searching...</span>')
+                            .removeClass('d-none');
+                        returnedFunction();
+                    } else {
+                        $('.suggestions-outer.pharmacy-suggestions').addClass('d-none');
+                    }
+                    break;
+            }
+        })
+        .on('keypress', function (e) {
+            var term = $.trim($('#pharmacy-search').val());
+            if (!!term) {
+                $('.suggestions-outer.pharmacy-suggestions')
+                    .html('<span class="d-block no-suggest-items">Searching...</span>')
+                    .removeClass('d-none');
+                returnedFunction();
+            } else {
+                $('.suggestions-outer.pharmacy-suggestions').addClass('d-none');
+            }
+        });
+    $(document).on('click', '.suggest-item.pharmacy-suggest[data-target-uid]', function () {
+        $('#pharmacy-search').val('');
+        $('.suggestions-outer.pharmacy-suggestions').addClass('d-none');
+        self.applySuggestion(this);
+        return false;
+    });
+},
+applySuggestion: function(_elem) {
+    this.popupItem.pharmacyName = $(_elem).attr('data-pharmacyName');
+    this.popupItem.pharmacyCity = $(_elem).attr('data-pharmacyCity');
+    this.popupItem.pharmacyState = $(_elem).attr('data-pharmacyState');
+    this.popupItem.pharmacyAddressMemo = $(_elem).attr('data-pharmacyAddressMemo');
+    this.popupItem.pharmacyPhone = $(_elem).attr('data-pharmacyPhone');
+    this.popupItem.pharmacyFax = $(_elem).attr('data-pharmacyFax');
+},

+ 3 - 0
resources/views/app/patient/tickets/ticket_action_links.blade.php

@@ -0,0 +1,3 @@
+<a class="btn btn-sm btn-primary text-white font-weight-bold mr-2 c-pointer" v-if="!item.is_entry_error" v-on:click.prevent="setIsEntryErrorToTrue(item)">Mark As Entry Error</a>
+<a class="btn btn-sm btn-primary text-white font-weight-bold mr-2 c-pointer" v-if="item.is_entry_error" v-on:click.prevent="setIsEntryErrorToFalse(item)">Undo Mark As Entry Error</a>
+

+ 22 - 0
resources/views/app/patient/tickets/ticket_update_pro_form.blade.php

@@ -0,0 +1,22 @@
+<div class="stag-popup stag-popup-sm mcp-theme-1" stag-popup-key="{{$ticketType}}-pro-update-popup">
+    <form method="POST" class="overflow-visible">
+        <h3 class="stag-popup-title mb-2">
+            <span>Update @{{ proToUpdate}} Pro</span>
+            <a href="#" class="ml-auto text-secondary"
+                onclick="return closeStagPopup()"><i class="fa fa-times-circle"></i></a>
+        </h3>
+        <div class="form-group mb-2">
+            <label class="text-sm text-secondary mb-1">@{{proToUpdate}} Pro</label>
+            <select v-model="newProUid" class="form-control" provider-search>
+                <option value="">-- select ---</option>
+                @foreach($pros as $pro)
+                <option value="{{$pro->uid}}">{{$pro->name_first}} {{$pro->name_last}}</option>
+                @endforeach
+            </select>
+        </div>
+        <div class="d-flex align-items-center justify-content-center mt-3">
+            <button type="button" class="btn btn-sm btn-primary mr-2" v-on:click.prevent="updateTicketPro()">Submit</button>
+            <button type="button" class="btn btn-sm btn-default border" onclick="return closeStagPopup()">Cancel</button>
+        </div>
+    </form>
+</div>

+ 84 - 0
resources/views/app/patient/tickets/ticket_vue_collab_card.blade.php

@@ -0,0 +1,84 @@
+<div class="card bg-light">
+    <div class="card-body">
+        <span class="d-flex align-items-center mb-2" v-if="item.assigned_pro_id">
+            <span class="text-secondary text-sm width-70px">Assigned:</span>
+            <b>@{{proNameFromId(item.assigned_pro_id)}}</b>
+            <span class="d-inline-flex" v-if="item.manager_pro_id === {{$pro->id}}">
+                <span class="mx-2 opacity-60 text-secondary">•</span>
+                <a class="on-hover-opaque c-pointer" v-on:click.prevent="showProUpdatePopup('other-popup', 'Assigned', item)">
+                    <i class="fa fa-edit"></i>
+                </a>
+            </span>
+            <span class="d-inline-flex" v-if="item.assigned_pro_id === {{$pro->id}}">
+                <span class="mx-2 opacity-60 text-secondary">•</span>
+                <span v-if="item.has_assigned_pro_signed" class="text-success">
+                    <i class="fa fa-check"></i>
+                    Signed
+                    <a class="ml-2 c-pointer" v-on:click.prevent="undoSignAsAssignedPro(item)">Undo</a>
+                </span>
+                <a v-if="!item.has_assigned_pro_signed" class="c-pointer"
+                   v-on:click.prevent="signAsAssignedPro(item)">Sign</a>
+            </span>
+        </span>
+        <span class="d-flex align-items-center mb-2" v-if="item.manager_pro_id">
+            <span class="text-secondary text-sm width-70px">Manager:</span>
+            <b>@{{proNameFromId(item.manager_pro_id)}}</b>
+            <span class="d-inline-flex" v-if="item.manager_pro_id === {{$pro->id}}">
+                <span class="mx-2 opacity-60 text-secondary">•</span>
+                <a class="on-hover-opaque c-pointer" v-on:click.prevent="showProUpdatePopup('other-popup', 'Manager', item)">
+                    <i class="fa fa-edit"></i>
+                </a>
+            </span>
+            <span class="d-inline-flex" v-if="item.manager_pro_id === {{$pro->id}}">
+                <span class="mx-2 opacity-60 text-secondary">•</span>
+                <span v-if="item.has_manager_pro_signed" class="text-success">
+                    <i class="fa fa-check"></i>
+                    Signed
+                    <a class="ml-2 c-pointer" v-on:click.prevent="undoSignAsManagerPro(item)">Undo</a>
+                </span>
+                <a v-if="!item.has_manager_pro_signed" class="c-pointer"
+                   v-on:click.prevent="signAsManagerPro(item)">Sign</a>
+            </span>
+        </span>
+        <span class="d-flex align-items-center mb-2" v-if="item.ordering_pro_id">
+            <span class="text-secondary text-sm width-70px">Ordering:</span>
+            <b>@{{proNameFromId(item.ordering_pro_id)}}</b>
+            <span class="d-inline-flex" v-if="item.manager_pro_id === {{$pro->id}}">
+                <span class="mx-2 opacity-60 text-secondary">•</span>
+                <a class="on-hover-opaque c-pointer" v-on:click.prevent="showProUpdatePopup('other-popup', 'Ordering', item)">
+                    <i class="fa fa-edit"></i>
+                </a>
+            </span>
+            <span class="d-inline-flex" v-if="item.ordering_pro_id === {{$pro->id}}">
+                <span class="mx-2 opacity-60 text-secondary">•</span>
+                <span v-if="item.has_ordering_pro_signed" class="text-success">
+                    <i class="fa fa-check"></i>
+                    Signed
+                    <a class="ml-2 c-pointer" v-on:click.prevent="undoSignAsOrderingPro(item)">Undo</a>
+                </span>
+                <a v-if="!item.has_ordering_pro_signed" class="c-pointer"
+                   v-on:click.prevent="signAsOrderingPro(item)">Sign</a>
+            </span>
+        </span>
+        <span class="d-flex align-items-center" v-if="item.initiating_pro_id">
+            <span class="text-secondary text-sm width-70px">Initiating:</span>
+            <b>@{{proNameFromId(item.initiating_pro_id)}}</b>
+            <span class="d-inline-flex" v-if="item.manager_pro_id === {{$pro->id}}">
+                <span class="mx-2 opacity-60 text-secondary">•</span>
+                <a class="on-hover-opaque c-pointer" v-on:click.prevent="showProUpdatePopup('other-popup', 'Initiating', item)">
+                    <i class="fa fa-edit"></i>
+                </a>
+            </span>
+            <span class="d-inline-flex" v-if="item.initiating_pro_id === {{$pro->id}}">
+                <span class="mx-2 opacity-60 text-secondary">•</span>
+                <span v-if="item.has_initiating_pro_signed" class="text-success">
+                    <i class="fa fa-check"></i>
+                    Signed
+                    <a class="ml-2 c-pointer" v-on:click.prevent="undoSignAsInitiatingPro(item)">Undo</a>
+                </span>
+                <a v-if="!item.has_initiating_pro_signed" class="c-pointer"
+                   v-on:click.prevent="signAsInitiatingPro(item)">Sign</a>
+            </span>
+        </span>
+    </div>
+</div>

+ 126 - 0
resources/views/app/patient/tickets/ticket_vue_methods.blade.php

@@ -0,0 +1,126 @@
+
+showProUpdatePopup: function(_name, _proToUpdate, _item) {
+    closeStagPopup();
+    this.proToUpdate = _proToUpdate
+    this.popupItem =JSON.parse(JSON.stringify(_item))
+    let self = this;
+    Vue.nextTick(function() {
+        showStagPopup('{{$ticketType}}-pro-update-popup', true);
+    });
+},
+
+updateTicketPro: function() {
+    let self  = this;
+    let form = $('#{{$ticketType}}App form').first();
+    if(!form[0].checkValidity()) {
+        form[0].reportValidity();
+        return false;
+    }
+
+    showMask();
+    let payload = this.popupItem;
+    payload.newProUid = self.newProUid;
+
+    $.post(
+        '/api/ticket/update' + self.proToUpdate +'Pro',
+        payload,
+        function(_data) {
+            console.log(_data);
+            fastReload();
+        },
+        'json');
+
+    return false;
+},
+
+
+
+setIsEntryErrorToTrue: function(_item){
+    showMask();
+    $.post('/api/ticket/setIsEntryErrorToTrue', {
+        uid:_item.uid
+    }, function(_data){
+        fastReload();
+    });
+},
+
+setIsEntryErrorToFalse: function(_item){
+    showMask();
+    $.post('/api/ticket/setIsEntryErrorToFalse', {
+        uid:_item.uid
+    }, function(_data){
+        fastReload();
+    })
+},
+
+signAsAssignedPro: function(_item){
+    showMask();
+    $.post('/api/ticket/signAsAssignedPro', {
+        uid:_item.uid
+    }, function(_data){
+        fastReload();
+    })
+},
+
+undoSignAsAssignedPro: function(_item){
+    showMask();
+    $.post('/api/ticket/undoSignAsAssignedPro', {
+        uid:_item.uid
+    }, function(_data){
+        fastReload();
+    })
+},
+
+signAsManagerPro: function(_item){
+    showMask();
+    $.post('/api/ticket/signAsManagerPro', {
+        uid:_item.uid
+    }, function(_data){
+        fastReload();
+    })
+},
+
+undoSignAsManagerPro: function(_item){
+    showMask();
+    $.post('/api/ticket/undoSignAsManagerPro', {
+        uid:_item.uid
+    }, function(_data){
+        fastReload();
+    })
+},
+
+signAsOrderingPro: function(_item){
+    showMask();
+    $.post('/api/ticket/signAsOrderingPro', {
+        uid:_item.uid
+    }, function(_data){
+        fastReload();
+    })
+},
+
+undoSignAsOrderingPro: function(_item){
+    showMask();
+    $.post('/api/ticket/undoSignAsOrderingPro', {
+        uid:_item.uid
+    }, function(_data){
+        fastReload();
+    })
+},
+
+signAsInitiatingPro: function(_item){
+    showMask();
+    $.post('/api/ticket/signAsInitiatingPro', {
+        uid:_item.uid
+    }, function(_data){
+        fastReload();
+    })
+},
+
+undoSignAsInitiatingPro: function(_item){
+    showMask();
+    $.post('/api/ticket/undoSignAsInitiatingPro', {
+        uid:_item.uid
+    }, function(_data){
+        fastReload();
+    })
+}

+ 6 - 8
resources/views/layouts/patient.blade.php

@@ -53,8 +53,6 @@
                             <a class="nav-link d-flex align-items-center {{ strpos($routeName, 'patients.view.action-items') === 0 ? 'active' : '' }}"
                                native onclick="return false">
                                 <span class="text-dark">ERx/Orders</span>
-                                {{--<i class="fa if-collapsed fa-chevron-down text-secondary ml-auto mr-1"></i>
-                                <i class="fa if-not-collapsed fa-chevron-up text-secondary ml-auto mr-1"></i>--}}
                             </a>
                             <ul class="m-0 p-0 nav-child-list">
                                 <li class="nav-item">
@@ -73,12 +71,12 @@
                                     <a class="nav-link {{ strpos($routeName, 'patients.view.action-items-equipment') === 0 ? 'active' : '' }}"
                                        href="{{ route('patients.view.action-items-equipment', ['patient' => $patient]) }}">Equipment</a>
                                 </li>
-                                {{--<li class="nav-item">
-                                    <a class="nav-link {{ strpos($routeName, 'patients.view.action-items-other') === 0 ? 'active' : '' }}"
-                                       href="{{ route('patients.view.action-items-other', ['patient' => $patient]) }}">Other</a>
-                                </li>--}}
                             </ul>
                         </li>
+                        <li class="nav-item">
+                            <a class="nav-link {{ strpos($routeName, 'patients.view.tickets') === 0 ? 'active' : '' }}"
+                               href="{{ route('patients.view.patient-tickets', ['patient' => $patient]) }}">Tickets</a>
+                        </li>
                         <li class="nav-item">
                             <a class="nav-link {{ strpos($routeName, 'patients.view.allergies') === 0 ? 'active' : '' }}"
                                href="{{ route('patients.view.allergies', ['patient' => $patient]) }}">Allergies</a>
@@ -290,7 +288,7 @@
                                                                 <input type="hidden" name="uid" value="{{$patient->uid}}">
                                                                 <div class="mb-2">
                                                                     <label class="text-secondary text-sm">Remove MCP Pro</label>
-                                                                    
+
                                                                 </div>
                                                                 <div>
                                                                     <button submit class="btn btn-sm btn-primary mr-1">Submit
@@ -404,7 +402,7 @@
                                                         <input type="hidden" name="uid" value="{{$patient->uid}}">
                                                         <div class="mb-2">
                                                             <label class="text-secondary text-sm">Remove Physician Pro</label>
-                                                            
+
                                                         </div>
                                                         <div>
                                                             <button submit class="btn btn-sm btn-primary mr-1">Submit

+ 3 - 0
routes/web.php

@@ -143,6 +143,9 @@ Route::middleware('pro.auth')->group(function () {
 
         // flowsheets
         Route::get('flowsheets/{filter?}', 'PatientController@flowsheets')->name('flowsheets');
+
+        // tickets
+        Route::get('tickets/{filter?}', 'PatientController@tickets')->name('patient-tickets');
     });
 
     // pro dashboard events (ajax)