Browse Source

RTM protocol builder (wip)

Vijayakrishnan 2 năm trước cách đây
mục cha
commit
3b0afdc8cc

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

@@ -715,4 +715,8 @@ class PatientController extends Controller
         $insuranceCoverageHistory = ClientPrimaryCoverage::where('client_id', $patient->id)->orderBy('created_at', 'DESC')->get();
         return view('app.patient.insurance-coverage-history', compact('patient', 'insuranceCoverageHistory'));
     }
+
+    public function protocolBuilder(Request $request, Client $patient) {
+        return view('app.patient.rtm.protocol-builder', compact('patient'));
+    }
 }

+ 1 - 1
config/app.php

@@ -65,7 +65,7 @@ return [
 
     'hrm2_url' => env('HRM2_URL'),
 
-    'asset_version' => 105,
+    'asset_version' => 106,
 
 
     'temp_dir' => env('TEMP_DIR'),

+ 34 - 0
config/rtm.php

@@ -0,0 +1,34 @@
+<?php
+
+return [
+    'exercises' => [
+        [
+            'name' => 'Bench Press',
+            'description' => 'Description of bench press goes here. Not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset.',
+            'image' => 'https://cdn.mos.cms.futurecdn.net/pLaRi5jXSHDKu6WRydetBo-970-80.jpg.webp',
+            'video' => 'https://www.youtube.com/watch?v=SCVCLChPQFY',
+            'props' => [
+                'Weekly', 'Daily', 'Sets', 'Reps'
+            ]
+        ],
+        [
+            'name' => 'Squats',
+            'description' => 'Description of squats goes here. Not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset.',
+            'image' => 'https://media.self.com/photos/5ea9bc77bb9c6b75996c7e91/4:3/w_640,c_limit/squats_woman_exercise.jpg',
+            'video' => 'https://www.youtube.com/watch?v=xqvCmoLULNY',
+            'props' => [
+                'Weekly', 'Daily', 'Sets', 'Reps'
+            ]
+        ],
+        [
+            'name' => 'Walking',
+            'description' => 'Description of walking goes here. Not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset.',
+            'image' => 'https://i0.wp.com/post.greatist.com/wp-content/uploads/sites/2/2020/04/GRT-female-walking-street-1296x728-header.jpg?w=1575',
+            'video' => 'https://www.youtube.com/watch?v=35G7AW78kxo',
+            'props' => [
+                'Weekly', 'Daily', 'Duration', 'Intensity'
+            ]
+        ],
+
+    ]
+];

+ 12 - 0
public/css/style.css

@@ -514,6 +514,9 @@ body>nav.navbar {
     min-height: 35px !important;
     max-height: 35px !important;
 }
+.mcp-theme-1 .image-w90px {
+    max-width: 90px;
+}
 .mcp-theme-1 .outline-0 {
     outline: none !important;
     box-shadow: none !important;
@@ -3737,3 +3740,12 @@ table.v-top th {
 .mcp-theme-1 .red-measurement {
     color: red !important;
 }
+.mcp-theme-1 .rtm-prop-input {
+    border-radius: 0 !important;
+    outline: 0 !important;
+    border: 0 !important;
+    border-bottom: 1px solid #aaa !important;
+    box-shadow: none !important;
+    height: 20px !important;
+    padding-left: 3px;
+}

+ 9 - 14
resources/views/app/patient/partials/rtm-msk.blade.php

@@ -434,20 +434,15 @@
                         @if($patient->rtm_msk_protocol_detail_json)
                             <span class="mr-2">{{$patient->rtm_msk_protocol_detail_json}}</span>
                         @endif
-                        <div moe relative>
-                            <a href="#" start show>Edit</a>
-                            <form url="/api/clientRtm/updateRtmMskProtocolDetailJson">
-                                <input type="hidden" name="uid" value="{{$patient->uid}}">
-                                <div class="mb-2">
-                                    <label class="text-secondary text-sm mb-1">Protocol</label>
-                                    <textarea class="form-control form-control-sm" name="rtmMskProtocolDetailJson">{{$patient->rtm_msk_protocol_detail_json}}</textarea>
-                                </div>
-                                <div class="d-flex align-items-center">
-                                    <button class="btn btn-sm btn-primary mr-2" submit>Save</button>
-                                    <button class="btn btn-sm btn-default mr-2 border" cancel>Cancel</button>
-                                </div>
-                            </form>
-                        </div>
+                        <a href="{{ route('protocol-builder', ['patient' => $patient]) }}"
+                           native target="_blank"
+                           open-in-stag-popup
+                           popup-style="overflow-visible stag-popup-md"
+                           update-parent
+                           mc-initer="protocol-builder-{{$patient->uid}}"
+                           title="Protocol Builder">
+                            Edit
+                        </a>
                     </div>
                 </div>
             </div>

+ 131 - 0
resources/views/app/patient/rtm/protocol-builder.blade.php

@@ -0,0 +1,131 @@
+<?php
+$parsed = null;
+if($patient->rtm_msk_protocol_detail_json) {
+    try {
+        $parsed = json_decode($patient->rtm_msk_protocol_detail_json);
+    }
+    catch (Exception $e) {}
+}
+?>
+<div class="popup-content-container px-3 min-height-300px" id="protocol-builder-{{$patient->uid}}">
+    <div class="d-flex align-items-baseline mb-2">
+        <b>Program Notes</b>
+        <div class="flex-grow-1" v-if="!programNotesEditMode">
+            <span class="ml-2" v-if="protocol.program_notes">@{{protocol.program_notes}}</span>
+            <a href="#" class="ml-2" v-on:click.prevent="programNotesEditMode = !programNotesEditMode">Edit</a>
+        </div>
+        <div class="flex-grow-1 d-inline-flex align-items-stretch" v-if="programNotesEditMode">
+            <input type="text" class="form-control form-control-sm ml-2" v-model="protocol.program_notes">
+            <button class="btn btn-sm btn-primary ml-2" v-on:click.prevent="programNotesEditMode = !programNotesEditMode">Done</button>
+        </div>
+    </div>
+    <div class="d-flex align-items-baseline mb-2">
+        <b>Exercises</b>
+        <div class="ml-2 position-relative">
+            <a href="#" v-on:click.prevent="showLibrary = !showLibrary">Add From Library</a>
+            <div v-if="showLibrary" class="position-absolute px-2 pt-2 border bg-white min-width-500px">
+                <p class="mb-2 text-nowrap text-secondary d-flex align-items-baseline">
+                    <span class="font-weight-bold">Exercises Library</span>
+                    <span class="text-sm ml-2">(click to add)</span>
+                    <a href="#" class="ml-auto text-secondary" v-on:click.prevent="showLibrary = !showLibrary"><i class="fa fa-times-circle"></i></a>
+                </p>
+                <div class="max-height-400px overflow-auto">
+                    <div v-for="(exercise, index) in library"
+                         class="p-2 border mb-2 on-hover-aliceblue c-pointer"
+                         v-on:click.prevent="addActivity(exercise)">
+                        <div class="d-flex align-items-baseline">
+                            <img v-if="exercise.image" :src="exercise.image" :alt="exercise.name" class="image-w90px mr-2 align-self-start">
+                            <div>
+                                <div class="font-weight-bold">@{{exercise.name}}</div>
+                                <div class="text-secondary text-sm">@{{exercise.description}}</div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div v-if="!protocol.activities || !protocol.activities.length" class="text-secondary min-height-300px">No exercises defined yet!</div>
+    <div v-else class="min-height-300px">
+        <div v-for="(activity, activityIndex) in protocol.activities">
+            <div class="d-flex align-items-baseline p-2 border mb-2">
+                <img v-if="activity.image" :src="activity.image" :alt="activity.name" class="image-w90px mr-2 align-self-start">
+                <div class="flex-grow-1">
+                    <div class="font-weight-bold">@{{activity.name}}</div>
+                    <div class="d-flex align-items-baseline mt-2">
+                        <div v-for="(prop, propIndex) in activity.props" class="d-inline-flex align-items-end mr-3">
+                            <label class="d-inline-flex align-items-end m-0">
+                                <input type="checkbox" v-model="activity.props[propIndex].index" class="align-self-end mb-1">
+                                <span class="ml-2">@{{activity.props[propIndex].name}}</span>
+                            </label>
+                            <input type="text" class="form-control form-control-sm min-width-unset width-70px ml-2 rtm-prop-input" v-model="activity.props[propIndex].value">
+                        </div>
+                    </div>
+                </div>
+                <a href="#" class="text-danger on-hover-opaque" v-on:click.prevent="protocol.activities.splice(activityIndex, 1)">Remove</a>
+            </div>
+        </div>
+    </div>
+    <div class="d-flex align-items-baseline my-3">
+        <button class="btn btn-sm btn-primary font-weight-bold" v-on:click.prevent="saveProtocol()">Save</button>
+        <button class="btn btn-sm btn-default border bg-white text-dark ml-2" onclick="return closeStagPopup()">Close</button>
+    </div>
+</div>
+<?php
+    $data = [
+        'program_notes' => @$parsed->program_notes ?: '',
+        'activities' => @$parsed->activities && count($parsed->activities) ? $parsed->activities : []
+    ];
+?>
+<script>
+    (function() {
+        function init() {
+            new Vue({
+                'el': '#protocol-builder-{{$patient->uid}}',
+                delimiters: ['@{{', '}}'],
+                data: {
+                    programNotesEditMode: false,
+                    showLibrary: false,
+                    protocol: {!! json_encode($data) !!},
+                    library: {!! json_encode(config('rtm.exercises')) !!}
+                },
+                methods: {
+                    addActivity: function(_exercise) {
+                        let activity = {
+                            name: _exercise.name,
+                            description: _exercise.description,
+                            image: _exercise.image,
+                            video: _exercise.video,
+                            props: [],
+                        };
+                        for(let i = 0; i < _exercise.props.length; i++) {
+                            activity.props.push({
+                                name: _exercise.props[i],
+                                value: '',
+                                include: true,
+                            });
+                        }
+                        this.protocol.activities.push(activity);
+                        this.showLibrary = false;
+                    },
+                    saveProtocol: function() {
+                        $.post('/api/clientRtm/updateRtmMskProtocolDetailJson', {
+                            uid: '{{$patient->uid}}',
+                            rtmMskProtocolDetailJson: JSON.stringify(this.protocol)
+                        }, _data => {
+                            if(!hasResponseError(_data)) {
+                                toastr.success('Protocol saved!');
+                            }
+                        }, 'json');
+                    }
+                },
+                mounted: function () {
+                    Vue.nextTick(() => {
+                        initMoes();
+                    });
+                }
+            })
+        }
+        addMCInitializer('protocol-builder-{{$patient->uid}}', init, '#protocol-builder-{{$patient->uid}}');
+    }).call(window);
+</script>

+ 1 - 0
routes/web.php

@@ -616,6 +616,7 @@ Route::middleware('pro.auth')->group(function () {
 
     });
 
+    Route::get('/protocol-builder/{patient}', 'PatientController@protocolBuilder')->name('protocol-builder');
     Route::get('/point/edit-hpi/{note}/{point}', 'NoteController@editHPI')->name('point-edit-hpi');
     Route::get('/point/hpi-log/{point}', 'NoteController@hpiLog')->name('point-hpi-log');
     Route::get('/point/review-log/{point}', 'NoteController@reviewLog')->name('point-review-log');