Sfoglia il codice sorgente

Stat-tree: tree-view

Vijayakrishnan 3 anni fa
parent
commit
549ebc4952

+ 32 - 0
app/Http/Controllers/StatTreeController.php

@@ -28,6 +28,10 @@ class StatTreeController extends Controller
     {
         return view('app.stat-tree.stat-trees.sub.dashboard', compact('statTree'));
     }
+    public function dashboard2(StatTree $statTree)
+    {
+        return view('app.stat-tree.stat-trees.sub.dashboard2', compact('statTree'));
+    }
 
     public function create(Request $request)
     {
@@ -66,6 +70,9 @@ class StatTreeController extends Controller
     }
 
     public function replaceAllLines(Request $request){
+
+        $parents = [];
+
         $statTreeID = (int) $request->get('statTreeID');
         $data = $request->get('data');
         $rows = json_decode($data, true);
@@ -89,6 +96,7 @@ class StatTreeController extends Controller
 
             DB::statement("DELETE FROM stat_tree_line_clause WHERE stat_tree_line_id = :stat_tree_line_id", ['stat_tree_line_id' => $statTreeLine->id]);
 
+            $allClauses = [];
             for($i = 0; $i < count($row); $i++){
                 $cell = $row[$i];
                 if(!$cell || empty($cell)) continue;
@@ -108,7 +116,31 @@ class StatTreeController extends Controller
                 $statTreeLineClause->position_index = $i;
                 $statTreeLineClause->detail_json = json_encode(['model' => $model]);
                 $statTreeLineClause->save();
+                $allClauses[] = $cell;
+            }
+            $parents[implode("|", $allClauses)] = $statTreeLine;
 
+            // if child, find and set parent
+            $nonEmpty = [];
+            for($i = 0; $i < count($row); $i++){
+                if($row[$i] && !empty($row[$i])) $nonEmpty[] = $row[$i];
+            }
+            if(count($nonEmpty) > 1) {
+                $parentClauses = [];
+                for($i = 0; $i < count($nonEmpty) - 1; $i++){
+                    $cell = $nonEmpty[$i];
+                    $clause = Clause::where('label', $cell)->where('model', 'ilike', $model)->first();
+                    if(!$clause){
+                        DB::rollBack();
+                        return $this->fail('No clause record found for ' . $cell);
+                    }
+                    $parentClauses[] = $clause->label;
+                }
+                $parentClauses = implode("|", $parentClauses);
+                if(@$parents[$parentClauses]) {
+                    $statTreeLine->parent_stat_tree_line_id = $parents[$parentClauses]->id;
+                    $statTreeLine->save();
+                }
             }
         }
         DB::commit();

+ 12 - 0
app/Models/StatTreeLine.php

@@ -16,4 +16,16 @@ class StatTreeLine extends Model
     public function statTree(){
         return $this->hasOne(StatTree::class, 'id', 'stat_tree_id');
     }
+    public function children(){
+        return $this->hasMany(StatTreeLine::class, 'parent_stat_tree_line_id', 'id');
+    }
+    public function displayLabel() {
+        $lastStatTreeLineClause = StatTreeLineClause::where('stat_tree_line_id', $this->id)
+            ->orderBy('position_index', 'DESC')
+            ->first();
+        if($lastStatTreeLineClause) {
+            return $lastStatTreeLineClause->clause_label;
+        }
+        return '-';
+    }
 }

+ 6 - 0
public/css/style.css

@@ -2730,4 +2730,10 @@ table.stag-compact-grid>tbody>tr>td [if-grid-view] {
     font-weight: bold;
     text-decoration: underline;
     margin-left: 10px;
+}
+.stat-tree-view {
+    padding: 0.5rem;
+}
+.stat-tree-view .stat-tree-node {
+    padding: 0.1rem 0.3rem;
 }

+ 2 - 2
resources/views/app/stat-tree/stat-trees/sub/dashboard.blade.php

@@ -87,8 +87,8 @@
                                 <table class="table table-bordered table-hover">
                                     <tbody>
                                         <tr v-for="(row, rowIndex) in rows">
-                                            <td v-for="(column, columnIndex) in columns[rowIndex]">
-                                                <input class="border-0" type="text" v-model="columns[rowIndex][columnIndex]" @keyup="updateColumns(rowIndex, columnIndex)">
+                                            <td v-for="(column, columnIndex) in columns[rowIndex]" class="p-0">
+                                                <input class="border-0 form-control p-1" type="text" v-model="columns[rowIndex][columnIndex]" @keyup="updateColumns(rowIndex, columnIndex)">
                                             </td>
                                         </tr>
                                     </tbody>

+ 302 - 0
resources/views/app/stat-tree/stat-trees/sub/dashboard2.blade.php

@@ -0,0 +1,302 @@
+@extends('app.stat-tree.stat-trees.single')
+@section('page')
+<?php
+function renderStatTreeLineNode($line) {
+    echo '<div class="stat-tree-node">' .
+        $line->displayLabel() .
+        '&nbsp;&nbsp;<span class="text-secondary">(' . (is_null($line->last_refresh_count) ? '-' : $line->last_refresh_count) . ')</span>' .
+        '</div>';
+    if(count($line->children)) {
+        echo '<div class="pl-4">';
+        foreach($line->children as $child) {
+            renderStatTreeLineNode($child);
+        }
+        echo '</div>';
+    }
+}
+?>
+<div id="statTreeViewPage">
+    <div id="statTreeView" class="row">
+        @if(count($statTree->lines))
+            <div class="col-12">
+                <div class="d-flex align-items-center justify-content-between mb-2">
+                    <h6 class="font-weight-bold">Tree</h6>
+                </div>
+                <div class="stat-tree-view border mb-3">
+                    @foreach($statTree->lines as $line)
+                        @if(!$line->parent_stat_tree_line_id)
+                            <?php renderStatTreeLineNode($line); ?>
+                        @endif
+                    @endforeach
+                </div>
+            </div>
+            <div class="col-12">
+                <div class="d-flex align-items-center justify-content-between mb-2">
+                    <h6 class="font-weight-bold">Lines</h6>
+                    <div>
+                        <button @click="refreshTreeCountQueries" class="btn btn-sm btn-danger text-white">
+                            <span v-if="!refreshing"><i class="fas fa-sync-alt"></i> Refresh Counts</span>
+                            <span v-else><i class="fas fa-circle-notch fa-spin"></i> Refreshing...</span>
+                        </button>
+                    </div>
+                </div>
+                <div class="table-responsive">
+                    <table class="table table-condensed border p-0 m-0">
+                        <thead class="bg-light">
+                            <tr>
+                                <th class="border-bottom-0">#</th>
+                                <th class="border-bottom-0">Model</th>
+                                <th class="border-bottom-0">Clauses</th>
+                                <th class="border-bottom-0">Query</th>
+                                <th class="border-bottom-0">Count</th>
+                                <th class="border-bottom-0">Actions</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            @foreach($statTree->lines as $line)
+                            <tr>
+                                <td>
+                                    <a href="{{ route('practice-management.statTreeLines.view.dashboard', $line) }}" target="_blank">
+                                        {{ $line->tree_order_position_index }}
+                                    </a>
+                                </td>
+                                <td>{{ ucwords($line->statTree->model) }}</td>
+                                <td>
+                                    @foreach($line->lineClauses as $c)
+                                    | {{ $c->clause_label }}
+                                    @endforeach
+                                </td>
+                                <td>
+                                    SELECT COUNT(*) FROM {{ $statTree->model }} WHERE
+                                    @foreach($line->lineClauses as $c)
+                                    {{ $c->clause->clause_text ?? '' }} @if(!$loop->last) AND @endif
+                                    @endforeach
+                                </td>
+                                <td>
+                                    <span id="line-{{ $line->id }}">{{ $line->last_refresh_count }}</span>
+                                </td>
+                                <td>
+                                    <a href="#" data-id="{{$line->id}}" refresh-line-query native>Refresh</a>
+                                </td>
+                            </tr>
+                            @endforeach
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+        @endif
+
+        <div class="col-12">
+            <h6 class="font-weight-bold bg-light p-3 mt-3">Replace Stat Tree Lines</h6>
+            <div class="row">
+                <div class="col-md-6">
+                    <div class="bg-white p-3">
+                        <h6 class="font-weight-bold">TSV</h6>
+                        <div class="input-group">
+                            <textarea v-model="content" class="form-control" rows="10" @keyup="splitContents"></textarea>
+                        </div>
+                    </div>
+                </div>
+                <div class="col-md-6">
+                    <div class="bg-white p-3">
+                        <h6 class="font-weight-bold">JSON Structure</h6>
+                        <textarea class="form-control" rows="10">@{{jsonStructure}}</textarea>
+                    </div>
+                </div>
+            </div>
+            <template v-if="rows.length">
+                <div class="row my-3">
+                    <div class="col">
+                        <div class="bg-white p-3">
+                            <h6 class="font-weight-bold">Table output:</h6>
+                            <div class="table-responsive">
+                                <table class="table table-bordered table-hover">
+                                    <tbody>
+                                        <tr v-for="(row, rowIndex) in rows">
+                                            <td v-for="(column, columnIndex) in columns[rowIndex]" class="p-0">
+                                                <input class="border-0 form-control p-1" type="text" v-model="columns[rowIndex][columnIndex]" @keyup="updateColumns(rowIndex, columnIndex)">
+                                            </td>
+                                        </tr>
+                                    </tbody>
+                                </table>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </template>
+            <div class="row">
+                <div class="col-md-12">
+                    <div class="px-3">
+                        <button type="button" class="btn btn-sm btn-primary" @click="submit">Submit</button>
+                        <button type="button" class="btn btn-sm btn-secondary" @click="generateAutoFilledJson">Fill</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+    </div>
+</div>
+
+<script>
+    (function() {
+        function init() {
+            new Vue({
+                el: '#statTreeView',
+                data: {
+                    content: '',
+                    rowSplitter: '\n',
+                    columnSplitter: '\t',
+                    rows: [],
+                    columns: [],
+                    maxColumn: 0,
+                    jsonStructure: '',
+                    isAutoFilled: false,
+                    refreshing: false,
+                    refreshingLine: false
+                },
+                delimiters: ['@{{', '}}'],
+                methods: {
+                    updateColumns(rowIndex, columnIndex) {
+                        try {
+                            this.rows[rowIndex] = this.columns[rowIndex].join(this.columnSplitter);
+                            this.content = this.rows.join('\n');
+                        } catch (error) {
+                            console.error(error);
+                            this.rows = [];
+                            this.content = '';
+                        }
+                        this.updateJsonStructure();
+                    },
+                    splitContents() {
+                        this.isAutoFilled = false;
+                        this.maxColumn = 0;
+                        try {
+                            this.rows = this.content.split(this.rowSplitter);
+                            this.rows.map((row, index) => {
+                                this.columns[index] = row.split(this.columnSplitter);
+                                const length = this.columns[index].length;
+                                if (length > this.maxColumn) {
+                                    this.maxColumn = length
+                                }
+                                const emptyFields = this.maxColumn - length;
+                                for (ii = 0; ii < emptyFields; ii++) {
+                                    this.columns[index].push('');
+
+                                }
+                            });
+                        } catch (error) {
+                            console.error(error);
+                            this.rows = [];
+                        }
+                        this.updateJsonStructure();
+                    },
+                    updateJsonStructure() {
+                        try {
+                            this.jsonStructure = JSON.stringify(this.columns, null, '\t');
+                        } catch (error) {
+                            console.error(error);
+                            this.jsonStructure = JSON.stringify([], null, '\t');
+                        }
+                    },
+                    numberOfTabs: function (text) {
+                        var count = 0;
+                        var index = 0;
+                        while (text.charAt(index++) === "\t") {
+                            count++;
+                        }
+                        return count;
+                    },
+                    generateAutoFilledJson: function () {
+                        if(this.isAutoFilled) return;
+                        var string = tsvToArray.getAutoFilledTabContent(this.content);
+                        this.content = string;
+                        this.splitContents();
+                        this.isAutoFilled = true;
+                    },
+                    submit: function () {
+                        var data = JSON.stringify(this.columns);
+                        $.post('{{ route("practice-management.api.statTree.replaceAllLines") }}', {
+                            statTreeID: "{{ $statTree->id }}",
+                            data
+                        }, function (response) {
+                            if (response.success) {
+                                location.href = "{{ route('practice-management.statTrees.view.dashboard', $statTree) }}";
+                            } else {
+                                alert(response.message);
+                            }
+                        }, 'json');
+                    },
+                    refreshTreeCountQueries: function (evt) {
+                        evt.preventDefault();
+                        var self = this;
+                        if (self.refreshing) return;
+
+                        self.refreshing = true;
+                        $.post("{{ route('practice-management.api.statTree.refreshTreeCountQueries') }}", {
+                            statTreeID: "{{ $statTree->id }}"
+                        }, function (response) {
+                            self.refreshing = false;
+                            if (response.success) {
+                                fastLoad("{{ route('practice-management.statTrees.view.dashboard', $statTree) }}");
+                                //fastReload()
+
+                            } else {
+                                toastr.error(response.message);
+                            }
+                        }, 'json');
+                    },
+                    initRefreshLineQuery: function () {
+                        var self = this;
+
+                        $('[refresh-line-query]').click(function () {
+                            var element = $(this);
+                            var id = element.data('id');
+                            if (self.refreshingLine) return;
+                            self.refreshingLine = true;
+                            element.html('<i class="fas fa-circle-notch fa-spin"></i> Refreshing...');
+                            $.post("{{ route('practice-management.api.statTreeLine.refreshCountQuery') }}", {
+                                statTreeLineID: id
+                            }, function (response) {
+                                self.refreshingLine = false;
+                                if (response.success) {
+                                    $('#line-' + id).text(response.data);
+                                    toastr.success('Updated value: ' + response.data);
+                                } else {
+                                    toastr.error(reasponse.message);
+                                }
+                                element.html('Refresh');
+                            }, 'json');
+                        });
+                    },
+                    init: function () {
+                        this.initRefreshLineQuery();
+                    }
+                },
+                mounted: function () {
+                    this.init();
+                },
+                updated: function () {
+                    var self = this;
+                    self.$nextTick(function () {
+
+                    });
+                },
+                watch: {
+                    maxColumn(maxColumn) {
+                        this.columns.map((column, index) => {
+                            const emptyFields = maxColumn - column.length;
+                            if (emptyFields) {
+                                for (ii = 0; ii < emptyFields; ii++) {
+                                    this.columns[index].push('');
+                                }
+                            }
+                        })
+                    }
+                }
+            });
+        }
+        addMCInitializer('statTreeView', init, '#statTreeViewPage')
+    }).call(window);
+</script>
+
+@endsection

+ 2 - 1
routes/web.php

@@ -317,7 +317,8 @@ Route::middleware('pro.auth')->group(function () {
             Route::get('', 'StatTreeController@list')->name('list');
             Route::get('create', 'StatTreeController@createPage')->name('createPage');
             Route::name('view.')->prefix('view/{statTree}')->group(function () {
-                Route::get('', 'StatTreeController@dashboard')->name('dashboard');
+                Route::get('', 'StatTreeController@dashboard2')->name('dashboard');
+                Route::get('old', 'StatTreeController@dashboard')->name('dashboard2');
             });
         });
         Route::name('statTreeLines.')->prefix('stat-tree-lines/')->group(function () {