Browse Source

Merge branch 'dev' of rav.triplestart.com:jmudaka/stagfe2 into dev

= 3 years ago
parent
commit
1b7c72db38

+ 4 - 4
app/Http/Controllers/ClauseArgController.php

@@ -27,15 +27,15 @@ class ClauseArgController extends Controller
         return $this->pass();
     }
     public function update(Request $request) {
-        $clauseArg = ClauseArg::where('uid', $request->input('uid'))->first();
+        $clauseArg = ClauseArg::where('id', $request->input('id'))->first();
         if(!$clauseArg) return $this->fail('Clause arg not found!');
-        $clauseArg->arg_text = $request->input('arg_text');
-        $clauseArg->field_type = $request->input('field_type');
+        $clauseArg->arg_text = $request->input('argText');
+        $clauseArg->field_type = $request->input('fieldType');
         $clauseArg->save();
         return $this->pass();
     }
     public function remove(Request $request) {
-        $clauseArg = ClauseArg::where('uid', $request->input('uid'))->first();
+        $clauseArg = ClauseArg::where('id', $request->input('id'))->first();
         if(!$clauseArg) return $this->fail('Clause arg not found!');
         DB::select("delete from clause_arg where id = {$clauseArg->id}");
         return $this->pass();

+ 492 - 4
app/Http/Controllers/StatTreeController.php

@@ -2,6 +2,8 @@
 
 namespace App\Http\Controllers;
 
+use App\Models\Pro;
+use App\Models\StatTreeLineClauseArg;
 use App\Models\StatTreeLineReportColumn;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Validator;
@@ -17,7 +19,7 @@ class StatTreeController extends Controller
 {
     public function list()
     {
-        $statTrees = StatTree::all();
+        $statTrees = StatTree::orderByRaw('is_template DESC NULLS LAST')->orderBy('name')->get();
         return view('app.stat-tree.stat-trees.list', compact('statTrees'));
     }
 
@@ -56,6 +58,17 @@ class StatTreeController extends Controller
         $statTree->name = $name;
         $statTree->model = $model;
         $statTree->slug = $slug;
+
+        $statTree->is_template = (bool)$request->input('isTemplate');
+
+        if($request->input('proUid')) {
+            $pro = Pro::where('uid', $request->input('proUid'))->first();
+            if($pro) {
+                $statTree->pro_id = $pro->id;
+            }
+        }
+        $statTree->pro_scope_clause = $request->input('proScopeClause');
+
         $statTree->save();
 
         return $this->pass($statTree->uid);
@@ -67,13 +80,50 @@ class StatTreeController extends Controller
         $statTree->name = $request->input('name');
         $statTree->model = $request->input('model');
         $statTree->slug = $request->input('slug');
+        $statTree->is_template = (bool)$request->input('isTemplate');
+        if($request->input('proUid')) {
+            $pro = Pro::where('uid', $request->input('proUid'))->first();
+            if($pro) {
+                $statTree->pro_id = $pro->id;
+            }
+        }
+        $statTree->pro_scope_clause = $request->input('proScopeClause');
         $statTree->save();
         return $this->pass();
     }
 
+    public function instantiate(Request $request, StatTree $statTree) {
+        if(!$statTree->is_template) {
+            return $this->fail("State tree is not a template!");
+        }
+        $pro = Pro::where('uid', $request->input('proUid'))->first();
+        if(!$pro) {
+            return $this->fail("Pro not found!");
+        }
+        $instance = $statTree->instantiateForPro($pro);
+        return $this->pass($instance->uid);
+    }
+
+    public function clone(Request $request, StatTree $statTree) {
+        $instance = $statTree->clone($request->input('name'));
+        return $this->pass($instance->uid);
+    }
+
+    private function traverseLines($_lines, &$result) {
+        foreach ($_lines as $line) {
+            $result[] = $line;
+            if(count($line->children)) {
+                $this->traverseLines($line->children, $result);
+            }
+        }
+    }
+
     public function edit(Request $request, StatTree $statTree) {
-        $clauses = Clause::where('model', $statTree->model)->orderBy('position_index')->get();
-        return view('app.stat-tree.stat-trees.sub.edit', compact('statTree', 'clauses'));
+        $linesFlat = [];
+        if($request->input('multi-pro')) {
+            $this->traverseLines($statTree->rootLines, $linesFlat);
+        }
+        return view('app.stat-tree.stat-trees.sub.edit', compact('statTree', 'linesFlat'));
     }
 
     public function delete(Request $request){
@@ -82,7 +132,7 @@ class StatTreeController extends Controller
         ]);
         $id = $request->get('id');
         $statTree = StatTree::where('id', $id)->first();
-        
+
     }
 
     public function replaceAllLines(Request $request){
@@ -143,6 +193,25 @@ class StatTreeController extends Controller
                 $statTreeLineClause->detail_json = json_encode(['model' => $model]);
                 $statTreeLineClause->stat_tree_id = $statTree->id;
                 $statTreeLineClause->save();
+
+                // check if the clause has any clauseArgs, if yes, create stat-tree-line-clause-arg and value records for them
+                if(count($clause->clauseArgs)) {
+                    foreach ($clause->clauseArgs as $clauseArg) {
+                        $statTreeLineClauseArg = new StatTreeLineClauseArg();
+                        $nextId = DB::select("select nextval('stat_tree_line_clause_arg_id_seq')");
+                        $statTreeLineClauseArg->id = $nextId[0]->nextval;
+                        $statTreeLineClauseArg->stat_tree_line_clause_id = $statTreeLineClause->id;
+                        $statTreeLineClauseArg->clause_arg_id = $clauseArg->id;
+                        $statTreeLineClauseArg->access_level = 'ADMIN';
+                        $statTreeLineClauseArg->stat_tree_id = $statTree->id;
+                        $statTreeLineClauseArg->default_value = null;
+
+                        // TODO: need to copy clause arg values from parent node for all except last clause
+
+                        $statTreeLineClauseArg->save();
+                    }
+                }
+
                 $allClauses[] = $cell;
 
                 // fill report columns (if last clause)
@@ -197,4 +266,423 @@ class StatTreeController extends Controller
         DB::commit();
         return $this->pass();
     }
+
+    public function clausesJSON(Request $request, StatTree $statTree) {
+        $clauses = Clause::where('model', $statTree->model)->orderBy('position_index')->get();
+        $nodes = [];
+        foreach ($clauses as $clause) {
+
+            $children = [];
+
+            // clause text child
+            $children[] = [
+                "text" => $clause->clause_text,
+                "icon" => "fa fa-laptop-code text-primary text-sm",
+                "state" => [
+                    "opened" => false,
+                    "disabled" => false,
+                    "selected" => false,
+                ],
+                "li_attr" => [
+                    "type" => "clause_text"
+                ],
+                "a_attr" => [
+                    "title" => $clause->clause_text,
+                ],
+                "data" => [
+                    "type" => "clause_text"
+                ]
+            ];
+
+            // clause arg children
+            $argsSystem = [];
+            foreach ($clause->clauseArgs as $clauseArg) {
+                $children[] = [
+                    "text" => $clauseArg->arg_text . '<span class="text-secondary text-sm ml-2">' . $clauseArg->field_type . '</span>',
+                    "icon" => "fa fa-cubes text-info",
+                    "state" => [
+                        "opened" => false,
+                        "disabled" => false,
+                        "selected" => false,
+                    ],
+                    "data" => [
+                        "type" => "clause_arg",
+                        "id" => $clauseArg->id,
+                        "uid" => $clauseArg->uid,
+                        "argText" => $clauseArg->arg_text,
+                        "fieldType" => $clauseArg->field_type,
+                        "clauseId" => $clause->id
+                    ]
+                ];
+                $argsSystem[] = [
+                    "arg_text" => $clauseArg->arg_text,
+                    "field_type" => $clauseArg->field_type,
+                    "default_value" => null,
+                    "access_level" => null
+                ];
+            }
+
+            $nodes[] = [
+                "text" => $clause->label,
+                "state" => [
+                    "opened" => false,
+                    "disabled" => false,
+                    "selected" => false,
+                ],
+                "children" => $children,
+                "data" => [
+                    "type" => "clause",
+                    "id" => $clause->id,
+                    "uid" => $clause->uid,
+                    "model" => $clause->model,
+                    "question" => $clause->question,
+                    "answer" => $clause->answer,
+                    "label" => $clause->label,
+                    "clauseText" => $clause->clause_text,
+                    "clauseId" => $clause->id,
+                    "args" => $argsSystem
+                ],
+                "a_attr" => [
+                    "title" => $clause->clause_text,
+                ],
+            ];
+        }
+
+        return json_encode($nodes);
+    }
+
+    private function cleanupClause($clauseText)
+    {
+        //Dont include empty clauses, i.e ()
+        preg_match('#\((.*?)\)#', $clauseText, $match);
+        $content = @$match[1];
+        if (!$content || empty($content)) return null;
+        return $content;
+    }
+
+    private function applyStatTreeLineQueryClauses(StatTreeLine $statTreeLine, $proUid = false)
+    {
+        $model = $statTreeLine->statTree->model;
+        $clauses = [];
+        foreach ($statTreeLine->lineClauses as $lineClause) {
+            $clauseText = $lineClause->clause->clause_text;
+
+            // apply arg values
+            foreach ($lineClause->clause->clauseArgs as $clauseArg) {
+
+                $value = null;
+                foreach ($lineClause->statTreeLineClauseArgs as $lineClauseArg) {
+                    if($lineClauseArg->clause_arg_id === $clauseArg->id) {
+                        $value = $lineClauseArg->default_value;
+                    }
+                }
+
+                if(!is_null($value)) {
+                    $clauseText = str_replace(
+                        ':' . $clauseArg->arg_text,                         // search for :xxx
+                        "'" . $value . "'::" . $clauseArg->field_type,      // replace with '$value'::$field_type
+                        $clauseText);
+                }
+            }
+
+            $isValid = $this->cleanupClause($clauseText);
+            if ($isValid) {
+                array_push($clauses, $clauseText);
+            }
+        }
+
+        // if stat tree bound to a pro, apply pro_scope_clause
+        if(!$proUid) {
+            if($statTreeLine->statTree->pro && $statTreeLine->statTree->pro_scope_clause) {
+                $clauses[] = str_replace('@PRO_ID', $statTreeLine->statTree->pro->id, $statTreeLine->statTree->pro_scope_clause);
+            }
+        }
+        else {
+            $mvPro = Pro::where('uid', $proUid)->first();
+            if($statTreeLine->statTree->pro_scope_clause) {
+                $clauses[] = str_replace('@PRO_ID', $mvPro->id, $statTreeLine->statTree->pro_scope_clause);
+            }
+            $query = 'SELECT COUNT(*) FROM '.$model.' WHERE '. implode(" AND ", $clauses);
+        }
+
+        $query = 'SELECT COUNT(*) FROM '.$model.' WHERE '. implode(" AND ", $clauses);
+        try {
+            $result = DB::select($query);
+        }
+        catch (\Exception $ex) {
+            $result = 'error';
+        }
+        return $result;
+    }
+
+    public function linesJSON(Request $request, StatTree $statTree) {
+
+        // refresh counts
+        $lines = $statTree->lines;
+        foreach ($lines as $line) {
+            $query = $this->applyStatTreeLineQueryClauses($line);
+            if ($query && $query !== 'error') {
+                $line->last_refresh_count = $query[0]->count;
+            }
+            else {
+                $line->last_refresh_count = -1;
+            }
+            $line->save();
+        }
+
+        $nodes = [];
+        foreach ($statTree->rootLines as $rootLine) {
+            $nodes[] = $this->lineObject($rootLine);
+        }
+        return json_encode($nodes);
+    }
+
+    public function getCountsForPro(Request $request) {
+        $statTreeUid = $request->get('uid');
+        if (!$statTreeUid) return $this->fail('No specified stat tree!');
+        $statTree = StatTree::where('uid',  $statTreeUid)->first();
+        $lines = $statTree->lines;
+        $result = [];
+        foreach ($lines as $line) {
+            $query = $this->applyStatTreeLineQueryClauses($line, $request->input('proUid'));
+            if ($query && $query !== 'error') {
+                $result[$line->uid] = $query[0]->count;
+            }
+            else {
+                $result[$line->uid] = -1;
+            }
+        }
+        return json_encode($result);
+    }
+
+    private function lineObject(StatTreeLine  $line) {
+
+        // columns
+        $columns = [];
+        foreach ($line->reportColumns as $column) {
+            $columns[] = [
+                "label" => $column->label,
+                "display_key" => $column->display_key
+            ];
+        }
+
+        // clause
+        $lineClause = $line->displayLineClause();
+        $args = [];
+        foreach($lineClause->statTreeLineClauseArgs as $stlcArg) {
+            if($stlcArg->clauseArg) {
+                $args[] = [
+                    "arg_text" => $stlcArg->clauseArg->arg_text,
+                    "field_type" => $stlcArg->clauseArg->field_type,
+                    "default_value" => $stlcArg->default_value,
+                    "access_level" => $stlcArg->access_level
+                ];
+            }
+        }
+        $clause = [
+            "clause_id" => $lineClause->clause_id,
+            "clause_label" => $lineClause->clause_label,
+            "position_index" => $lineClause->position_index,
+            "args" => $args
+        ];
+
+        $children = [];
+        foreach ($line->children as $child) {
+            $children[] = $this->lineObject($child);
+        }
+        return [
+            "text" => '<span class="stat-tree-anchor">' . $line->displayLabel() . '</span>' .
+                '<span class="ml-2 text-secondary line-count-label">(' .
+                (is_null($line->last_refresh_count) ? '…' :
+                    ($line->last_refresh_count === -1 ?
+                        '<span class="text-danger text-sm font-weight-bold"><i class="fa fa-exclamation-triangle"></i> Query error / missing arg values</span>' :
+                        $line->last_refresh_count)) .
+                ')</span>',
+            "state" => [
+                "opened" => true,
+                "disabled" => false,
+                "selected" => false,
+            ],
+            "children" => $children,
+            "data" => [
+                "type" => "stat_tree_line",
+                "id" => $line->id,
+                "uid" => $line->uid,
+                "displayLabel" => $line->displayLabel(),
+                "lastRefreshCount" => $line->last_refresh_count,
+                "treeOrderPositionIndex" => $line->tree_order_position_index,
+                "columns" => $columns,
+                "clause" => $clause
+            ],
+            "a_attr" => [
+                "title" => $line->displayLineClause()->clause->clause_text
+            ]
+        ];
+    }
+
+    public function replaceAllLinesJSON(Request $request) {
+
+        $statTree = StatTree::where('uid', $request->get('uid'))->first();
+        if(!$statTree) return $this->fail("Stat tree does not exist!");
+
+        $lines = json_decode($request->get('data'));
+
+        DB::beginTransaction();
+
+        // cleanup junk
+        DB::statement("DELETE FROM stat_tree_line WHERE stat_tree_id = :stat_tree_id", ['stat_tree_id' => $statTree->id]);
+        DB::statement("DELETE FROM stat_tree_line_clause WHERE stat_tree_id = :stat_tree_id", ['stat_tree_id' => $statTree->id]);
+        DB::statement("DELETE FROM stat_tree_line_clause_arg WHERE stat_tree_id = :stat_tree_id", ['stat_tree_id' => $statTree->id]);
+        DB::statement("DELETE FROM stat_tree_line_clause_arg_value WHERE stat_tree_id = :stat_tree_id", ['stat_tree_id' => $statTree->id]);
+        DB::statement("DELETE FROM stat_tree_line_report_column WHERE stat_tree_id = :stat_tree_id", ['stat_tree_id' => $statTree->id]);
+
+        // process
+        for ($i=0; $i<count($lines); $i++) {
+            $result = $this->saveStatTreeLine($lines[$i], $i, null, $statTree);
+            if($result !== TRUE) {
+                DB::rollBack();
+                return $result;
+            }
+        }
+
+        DB::commit();
+        return $this->pass();
+    }
+
+    private function saveStatTreeLine($line, $position, $parentLine, $statTree) {
+
+        // saved tree line
+        $nextId = DB::select("select nextval('stat_tree_line_id_seq')");
+        $statTreeLine = new StatTreeLine;
+        $statTreeLine->id = $nextId[0]->nextval;
+        $statTreeLine->uid = Uuid::uuid4();
+        $statTreeLine->stat_tree_id = $statTree->id;
+        $statTreeLine->tree_order_position_index = $position;
+        $statTreeLine->last_refresh_count = null;
+        $statTreeLine->tsv_text_for_report_columns = null;
+        $statTreeLine->parent_stat_tree_line_id = $parentLine ? $parentLine->id : null;
+        $statTreeLine->save();
+
+        // -- clauses -- START
+
+        // copy parent line clauses
+        $maxParentClausePositionIndex = 0;
+        if($parentLine && count($parentLine->lineClauses)) {
+            foreach ($parentLine->lineClauses as $parentLineClause) {
+                $nextId = DB::select("select nextval('stat_tree_line_clause_id_seq')");
+                $statTreeLineClause = new StatTreeLineClause();
+                $statTreeLineClause->id = $nextId[0]->nextval;
+                $statTreeLineClause->uid = Uuid::uuid4();
+                $statTreeLineClause->stat_tree_line_id = $statTreeLine->id; // this stat tree line
+                $statTreeLineClause->clause_id = $parentLineClause->clause_id;
+                $statTreeLineClause->clause_label = $parentLineClause->clause_label;
+                $statTreeLineClause->position_index = $parentLineClause->position_index;
+                if($statTreeLineClause->position_index > $maxParentClausePositionIndex) {
+                    $maxParentClausePositionIndex = $statTreeLineClause->position_index;
+                }
+                $statTreeLineClause->stat_tree_id = $statTree->id;
+                $statTreeLineClause->save();
+
+                // copy clause args for parent line clauses
+                foreach ($parentLineClause->statTreeLineClauseArgs as $parentLineClauseArg) {
+                    $statTreeLineClauseArg = new StatTreeLineClauseArg();
+                    $nextId = DB::select("select nextval('stat_tree_line_clause_arg_id_seq')");
+                    $statTreeLineClauseArg->id = $nextId[0]->nextval;
+                    $statTreeLineClauseArg->stat_tree_line_clause_id = $statTreeLineClause->id; // this line cause
+                    $statTreeLineClauseArg->clause_arg_id = $parentLineClauseArg->clause_arg_id;
+                    $statTreeLineClauseArg->default_value = $parentLineClauseArg->default_value;
+                    $statTreeLineClauseArg->access_level = $parentLineClauseArg->access_level;
+                    $statTreeLineClauseArg->stat_tree_id = $statTree->id;
+                    $statTreeLineClauseArg->save();
+                }
+            }
+        }
+
+        // create own line clause (from clause.clause_label)
+        $clause = Clause::where('label', $line->clause->clause_label)->where('model', 'ilike', $statTree->model)->first();
+        if(!$clause) {
+            return $this->fail('No clause record found for ' . $line->clause->clause_label);
+        }
+        $nextId = DB::select("select nextval('stat_tree_line_clause_id_seq')");
+        $statTreeLineClause = new StatTreeLineClause();
+        $statTreeLineClause->id = $nextId[0]->nextval;
+        $statTreeLineClause->uid = Uuid::uuid4();
+        $statTreeLineClause->stat_tree_line_id = $statTreeLine->id; // this stat tree line
+        $statTreeLineClause->clause_id = $clause->id;
+        $statTreeLineClause->clause_label = $line->clause->clause_label;
+        $statTreeLineClause->position_index = $maxParentClausePositionIndex + 1;
+        $statTreeLineClause->stat_tree_id = $statTree->id;
+        $statTreeLineClause->save();
+
+        // create args for own line clause
+        foreach ($clause->clauseArgs as $clauseArg) {
+            $statTreeLineClauseArg = new StatTreeLineClauseArg();
+            $nextId = DB::select("select nextval('stat_tree_line_clause_arg_id_seq')");
+            $statTreeLineClauseArg->id = $nextId[0]->nextval;
+            $statTreeLineClauseArg->stat_tree_line_clause_id = $statTreeLineClause->id;
+            $statTreeLineClauseArg->clause_arg_id = $clauseArg->id;
+            $statTreeLineClauseArg->default_value = null;
+            $statTreeLineClauseArg->access_level = 'ADMIN';
+            for ($j=0; $j<count($line->clause->args); $j++) { // find the arg matching text and type from line->args and use that
+                if($line->clause->args[$j]->arg_text === $clauseArg->arg_text &&
+                    $line->clause->args[$j]->field_type === $clauseArg->field_type) {
+                    $statTreeLineClauseArg->default_value = $line->clause->args[$j]->default_value;
+                    $statTreeLineClauseArg->access_level = $line->clause->args[$j]->access_level;
+                    break;
+                }
+            }
+            $statTreeLineClauseArg->stat_tree_id = $statTree->id;
+            $statTreeLineClauseArg->save();
+        }
+
+        // -- clauses -- END
+
+
+        // columns
+        for ($i=0; $i<count($line->columns); $i++) {
+            $column = new StatTreeLineReportColumn();
+            $nextId = DB::select("select nextval('stat_tree_line_report_column_id_seq')");
+            $column->id = $nextId[0]->nextval;
+            $column->uid = Uuid::uuid4();
+            $column->stat_tree_line_id = $statTreeLine->id;
+            $column->label = $line->columns[$i]->label;
+            $column->display_key = $line->columns[$i]->display_key;
+            $column->position_index = $i;
+            $column->stat_tree_id = $statTree->id;
+            $column->save();
+        }
+
+        // child lines
+        for ($i=0; $i<count($line->children); $i++) {
+            $result = $this->saveStatTreeLine($line->children[$i], $i, $statTreeLine, $statTree);
+            if($result !== TRUE) {
+                DB::rollBack();
+                return $result;
+            }
+        }
+
+        return TRUE;
+    }
+
+    public function refreshTreeCountQueries(Request $request)
+    {
+        $statTreeID = $request->get('statTreeID');
+        if (!$statTreeID) return $this->fail('No specified stat tree!');
+
+        $statTree = StatTree::where('id', $statTreeID)->first();
+        if (!$statTree) return $this->fail('Invalid stat tree!');
+        $lines = $statTree->lines;
+        foreach ($lines as $line) {
+            $query = $this->applyStatTreeLineQueryClauses($line, $request);
+            if ($query && $query !== 'error') {
+                $line->last_refresh_count = $query[0]->count;
+            }
+            else {
+                $line->last_refresh_count = -1;
+            }
+            $line->save();
+        }
+
+        return $this->pass();
+    }
 }

+ 31 - 38
app/Http/Controllers/StatTreeLineController.php

@@ -36,11 +36,6 @@ class StatTreeLineController extends Controller
         return view('app.stat-tree.stat-tree-lines.single', compact('statTreeLine', 'response'));
     }
 
-    public function reportColumns(Request $request, StatTreeLine $line)
-    {
-        return view('app.stat-tree.stat-tree-lines.report-columns', compact('line'));
-    }
-
     public function columnSuggest(Request $request) {
         $term = $request->input('term') ? trim($request->input('term')) : '';
         if (empty($term)) return '';
@@ -108,25 +103,6 @@ class StatTreeLineController extends Controller
         return view('app.stat-tree.stat-tree-lines.view-data', compact('line', 'rows', 'columns'));
     }
 
-    public function refreshTreeCountQueries(Request $request)
-    {
-        $statTreeID = $request->get('statTreeID');
-        if (!$statTreeID) return $this->fail('No specified stat tree!');
-
-        $statTree = StatTree::where('id', $statTreeID)->first();
-        if (!$statTree) return $this->fail('Invalid stat tree!');
-        $lines = $statTree->lines;
-        foreach ($lines as $line) {
-            $query = $this->applyStatTreeLineQueryClauses($line);
-            if ($query) {
-                $line->last_refresh_count = $query[0]->count;
-                $line->save();
-            }
-        }
-
-        return $this->pass();
-    }
-
     public function refreshCountQuery(Request $request)
     {
         $statTreeLineID = $request->get('statTreeLineID');
@@ -177,33 +153,50 @@ class StatTreeLineController extends Controller
     protected function queryStatTreeLineData(StatTreeLine $statTreeLine, $selectColumns)
     {
 
-
-
         $model = $statTreeLine->statTree->model;
-        // $query = null;
-        // if (strcasecmp($model, 'client') == 0) {
-        //     $query = Client::query();
-        // }
-        // if (strcasecmp($model, 'pro') == 0) {
-        //     $query = Pro::query();
-        // }
-        // if (!$query) return null;
-
-
-
 
         $clauses = [];
         foreach ($statTreeLine->lineClauses as $lineClause) {
             $clauseText = $lineClause->clause->clause_text;
+
+            // apply arg values
+            foreach ($lineClause->clause->clauseArgs as $clauseArg) {
+
+                $value = null;
+                foreach ($lineClause->statTreeLineClauseArgs as $lineClauseArg) {
+                    if($lineClauseArg->clause_arg_id === $clauseArg->id) {
+                        $value = $lineClauseArg->default_value;
+                    }
+                }
+
+                if(!is_null($value)) {
+                    $clauseText = str_replace(
+                        ':' . $clauseArg->arg_text,                         // search for :xxx
+                        "'" . $value . "'::" . $clauseArg->field_type,      // replace with '$value'::$field_type
+                        $clauseText);
+                }
+            }
+
             $isValid = $this->cleanupClause($clauseText);
             if ($isValid) {
                 array_push($clauses, $clauseText);
             }
         }
 
+        // if stat tree bound to a pro, apply pro_scope_clause
+        if($statTreeLine->statTree->pro && $statTreeLine->statTree->pro_scope_clause) {
+            $clauses[] = str_replace('@PRO_ID', $statTreeLine->statTree->pro->id, $statTreeLine->statTree->pro_scope_clause);
+        }
+
         $query = 'SELECT ' . implode(", ", $selectColumns) . ' FROM '.$model.' WHERE '. implode(" AND ", $clauses);
 
-        return DB::select($query);
+        try {
+            $result = DB::select($query);
+        }
+        catch (\Exception $ex) {
+            $result = 'error';
+        }
+        return $result;
     }
 
     protected function cleanupClause($clauseText)

+ 55 - 1
app/Models/StatTree.php

@@ -4,6 +4,8 @@ namespace App\Models;
 
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\DB;
+use Ramsey\Uuid\Uuid;
 
 class StatTree extends Model
 {
@@ -16,11 +18,63 @@ class StatTree extends Model
         return 'uid';
     }
 
+    public function pro(){
+        return $this->hasOne(Pro::class, 'id', 'pro_id');
+    }
+
     public function lines(){
         return $this->hasMany(StatTreeLine::class, 'stat_tree_id', 'id')->orderBy('id', 'ASC');
     }
 
     public function rootLines(){
-        return $this->hasMany(StatTreeLine::class, 'stat_tree_id', 'id')->whereNull('parent_stat_tree_line_id')->orderBy('id', 'ASC');
+        return $this->hasMany(StatTreeLine::class, 'stat_tree_id', 'id')->whereNull('parent_stat_tree_line_id')->orderBy('tree_order_position_index', 'ASC');
+    }
+
+    public function instantiateForPro(Pro $pro) {
+
+        // create stat tree
+        $instance = new StatTree();
+        $nextId = DB::select("select nextval('stat_tree_id_seq')");
+        $instance->id = $nextId[0]->nextval;
+        $instance->uid = Uuid::uuid4();
+        $instance->name = $this->name;
+        $instance->model = $this->model;
+        $instance->slug = $this->slug . ':' . $pro->uid;
+        $instance->is_template = false;
+        $instance->pro_id = $pro->id;
+        $instance->pro_scope_clause = $this->pro_scope_clause;
+        $instance->save();
+
+        // create tree lines
+        foreach ($this->rootLines as $line) {
+            $line->copy(null, $instance);
+        }
+
+        return $instance;
+
+    }
+
+    public function clone($name) {
+
+        // create stat tree
+        $instance = new StatTree();
+        $nextId = DB::select("select nextval('stat_tree_id_seq')");
+        $instance->id = $nextId[0]->nextval;
+        $instance->uid = Uuid::uuid4();
+        $instance->name = $name;
+        $instance->model = $this->model;
+        $instance->slug = $this->slug . '-' . rand(100, 999);
+        $instance->is_template = $this->is_template;
+        $instance->pro_id = $this->pro_id;
+        $instance->pro_scope_clause = $this->pro_scope_clause;
+        $instance->save();
+
+        // create tree lines
+        foreach ($this->rootLines as $line) {
+            $line->copy(null, $instance);
+        }
+
+        return $instance;
+
     }
 }

+ 90 - 1
app/Models/StatTreeLine.php

@@ -4,6 +4,8 @@ namespace App\Models;
 
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Facades\DB;
+use Ramsey\Uuid\Uuid;
 
 class StatTreeLine extends Model
 {
@@ -16,7 +18,8 @@ class StatTreeLine extends Model
     }
 
     public function lineClauses(){
-        return $this->hasMany(StatTreeLineClause::class, 'stat_tree_line_id', 'id');
+        return $this->hasMany(StatTreeLineClause::class, 'stat_tree_line_id', 'id')
+            ->orderBy('position_index');
     }
     public function statTree(){
         return $this->hasOne(StatTree::class, 'id', 'stat_tree_id');
@@ -41,6 +44,15 @@ class StatTreeLine extends Model
         }
         return '-';
     }
+    public function displayLineClause() {
+        $lastStatTreeLineClause = StatTreeLineClause::where('stat_tree_line_id', $this->id)
+            ->orderBy('position_index', 'DESC')
+            ->first();
+        if($lastStatTreeLineClause) {
+            return $lastStatTreeLineClause;
+        }
+        return null;
+    }
     public function columnsJSON() {
         $columns = [];
         foreach ($this->reportColumns as $column) {
@@ -51,4 +63,81 @@ class StatTreeLine extends Model
         }
         return json_encode($columns);
     }
+    public function argsJSON() {
+        $args = [];
+        $displayLineClause = $this->displayLineClause();
+        if(!!$displayLineClause) {
+            foreach($displayLineClause->statTreeLineClauseArgs as $stlcArg) {
+                $args[] = [
+                    "uid" => $stlcArg->uid,
+                    "arg_text" => $stlcArg->clauseArg->arg_text,
+                    "field_type" => $stlcArg->clauseArg->field_type,
+                    "default_value" => $stlcArg->default_value,
+                    "access_level" => $stlcArg->access_level,
+                ];
+            }
+        }
+        return json_encode($args);
+    }
+
+    public function copy($newParentStatTreeLine, StatTree $newStatTree) {
+
+        // create line
+        $newStatTreeLine = new StatTreeLine;
+        $nextId = DB::select("select nextval('stat_tree_line_id_seq')");
+        $newStatTreeLine->id = $nextId[0]->nextval;
+        $newStatTreeLine->uid = Uuid::uuid4();
+        $newStatTreeLine->stat_tree_id = $newStatTree->id;
+        $newStatTreeLine->tree_order_position_index = $this->tree_order_position_index;
+        $newStatTreeLine->parent_stat_tree_line_id = $newParentStatTreeLine ? $newParentStatTreeLine->id : null;
+        $newStatTreeLine->save();
+
+        // line clauses
+        foreach ($this->lineClauses as $srcLineClause) {
+            $nextId = DB::select("select nextval('stat_tree_line_clause_id_seq')");
+            $newStatTreeLineClause = new StatTreeLineClause;
+            $newStatTreeLineClause->id = $nextId[0]->nextval;
+            $newStatTreeLineClause->uid = Uuid::uuid4();
+            $newStatTreeLineClause->stat_tree_line_id = $newStatTreeLine->id;
+            $newStatTreeLineClause->clause_id = $srcLineClause->clause_id;
+            $newStatTreeLineClause->clause_label = $srcLineClause->clause_label;
+            $newStatTreeLineClause->position_index = $srcLineClause->position_index;
+            $newStatTreeLineClause->stat_tree_id = $newStatTree->id;
+            $newStatTreeLineClause->save();
+
+            // line clause args
+            foreach ($srcLineClause->statTreeLineClauseArgs as $srcLineClauseArg) {
+                $newStatTreeLineClauseArg = new StatTreeLineClauseArg();
+                $nextId = DB::select("select nextval('stat_tree_line_clause_arg_id_seq')");
+                $newStatTreeLineClauseArg->id = $nextId[0]->nextval;
+                $newStatTreeLineClauseArg->stat_tree_line_clause_id = $newStatTreeLineClause->id;
+                $newStatTreeLineClauseArg->clause_arg_id = $srcLineClauseArg->clause_arg_id;
+                $newStatTreeLineClauseArg->access_level = $srcLineClauseArg->access_level;
+                $newStatTreeLineClauseArg->stat_tree_id = $newStatTree->id;
+                $newStatTreeLineClauseArg->default_value = $srcLineClauseArg->default_value;
+                $newStatTreeLineClauseArg->save();
+            }
+        }
+
+        // report columns
+        foreach ($this->reportColumns as $srcReportColumn) {
+            $newStatTreeLineReportColumn = new StatTreeLineReportColumn();
+            $nextId = DB::select("select nextval('stat_tree_line_report_column_id_seq')");
+            $newStatTreeLineReportColumn->id = $nextId[0]->nextval;
+            $newStatTreeLineReportColumn->uid = Uuid::uuid4();
+            $newStatTreeLineReportColumn->stat_tree_line_id = $newStatTreeLine->id;
+            $newStatTreeLineReportColumn->label = $srcReportColumn->label;
+            $newStatTreeLineReportColumn->position_index = $srcReportColumn->position_index;
+            $newStatTreeLineReportColumn->display_key = $srcReportColumn->display_key;
+            $newStatTreeLineReportColumn->display_function = $srcReportColumn->display_function;
+            $newStatTreeLineReportColumn->record_route_name = $srcReportColumn->record_route_name;
+            $newStatTreeLineReportColumn->stat_tree_id = $newStatTree->id;
+            $newStatTreeLineReportColumn->save();
+        }
+
+        foreach ($this->children as $child) {
+            $child->copy($newStatTreeLine, $newStatTree);
+        }
+
+    }
 }

+ 5 - 2
app/Models/StatTreeLineClause.php

@@ -11,8 +11,11 @@ class StatTreeLineClause extends Model
     public $timestamps = false;
 
     public function clause(){
-        $detailJson = json_decode($this->detail_json);
-        return $this->hasOne(Clause::class, 'label', 'clause_label')->where('model', 'ilike', $detailJson->model);
+        return $this->hasOne(Clause::class, 'id', 'clause_id');
+    }
+
+    public function statTreeLineClauseArgs() {
+        return $this->hasMany(StatTreeLineClauseArg::class, 'stat_tree_line_clause_id', 'id')->orderBy('id');
     }
 
 }

+ 17 - 0
app/Models/StatTreeLineClauseArg.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Model;
+
+class StatTreeLineClauseArg extends Model
+{
+    public $table = 'stat_tree_line_clause_arg';
+    public $timestamps = false;
+
+    public function clauseArg(){
+        return $this->hasOne(ClauseArg::class, 'id', 'clause_arg_id');
+    }
+
+}

+ 1 - 1
config/app.php

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

+ 112 - 5
public/css/style.css

@@ -198,6 +198,12 @@ body.stag_rhs_collapsed .app-right-panel {
 .mcp-theme-1 .overflow-visible {
     overflow: visible;
 }
+.mcp-theme-1 .overflow-overlay-on-hover {
+    overflow: hidden;
+}
+.mcp-theme-1 .overflow-overlay-on-hover:hover {
+    overflow: overlay;
+}
 .mcp-theme-1 .text-secondary-light {
     color: #c9ddef !important;
 }
@@ -266,6 +272,9 @@ html, body {
 body>nav.navbar {
     height: 55px;
 }
+.body-height {
+    height: calc(100vh - 55px);
+}
 .stag-content {
     height: calc(100% - 55px);
 }
@@ -414,6 +423,10 @@ body>nav.navbar {
 .mcp-theme-1 .max-width-300px {
     max-width: 300px;
 }
+.mcp-theme-1 .height-35px {
+    min-height: 35px !important;
+    max-height: 35px !important;
+}
 .mcp-theme-1 .outline-0 {
     outline: none !important;
     box-shadow: none !important;
@@ -1190,6 +1203,11 @@ body .node input[type="number"] {
     margin-right: 1.5rem;
     margin-left: auto;
 }
+.stag-popup .popup-content-container {
+    margin-top: 1rem;
+    padding-top: 1rem;
+    border-top: 1px solid #e1e1e1;
+}
 
 /* slide-in stag-popups */
 .stag-popup.stag-slide {
@@ -2736,8 +2754,12 @@ table.stag-compact-grid>tbody>tr>td [if-grid-view] {
     text-decoration: underline;
     margin-left: 10px;
 }
-.stat-tree-view {
-    border: 1px solid lightsteelblue;
+.stat-tree .v-split {
+    width: 6px;
+    margin-left: -3px;
+    margin-top: 0;
+    height: 100%;
+    border-radius: 0;
 }
 .stat-tree-view .stat-tree-node {
     padding: 0.1rem 0.3rem;
@@ -2766,8 +2788,93 @@ table.stag-compact-grid>tbody>tr>td [if-grid-view] {
 .stat-tree-view>.stat-tree-node+.stat-tree-children>.stat-tree-node+.stat-tree-children>.stat-tree-node+.stat-tree-children>.stat-tree-node+.stat-tree-children>.stat-tree-node+.stat-tree-children>.stat-tree-node+.stat-tree-children>.stat-tree-node+.stat-tree-children>.stat-tree-node+.stat-tree-children>.stat-tree-node {
     padding-left: 9.2rem;
 }
+.stat-tree-user-view .stat-tree-anchor {
+    color: rgb(13, 89, 175) !important;
+    text-decoration: none;
+}
 
 /* jstree overrides */
+.stat-tree-view .jstree-wholerow {
+    border-bottom: 1px solid #f1f1f1;
+}
+.stat-tree-view .jstree-anchor.has-drop-visualization::after {
+    content: attr(data-drop-percent);
+    margin-left: 1rem;
+    font-size: 70%;
+}
+.stat-tree-view .jstree-anchor.has-drop-visualization.drop-0-25::after {
+    color: grey;
+}
+.stat-tree-view .jstree-anchor.has-drop-visualization.drop-26-50::after {
+    color: saddlebrown;
+}
+.stat-tree-view .jstree-anchor.has-drop-visualization.drop-51-75::after {
+    color: #dc7024;
+}
+.stat-tree-view .jstree-anchor.has-drop-visualization.drop-76-100::after {
+    color: red;
+}
+.multi-pro-view .line-count-label {
+    display: none;
+}
+.multi-pro-view .multi-pro-stats-table {
+    border: 0;
+}
+.multi-pro-view .multi-pro-stats-table tr th {
+    height: 22px;
+    background: #eee;
+    line-height: 22px;
+    padding: 0 5px;
+    font-weight: normal;
+    color: #777;
+    border: 0;
+    border-left: 1px solid #e1e1e1;
+    width: 100px;
+}
+.multi-pro-view .multi-pro-stats-table tr th .pro-label {
+    max-width: 100px;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+}
+.multi-pro-view .multi-pro-stats-table tr td {
+    border-top: 0;
+    padding: 0 5px;
+    border-left: 1px solid #f1f1f1;
+    width: 100px;
+}
+.multi-pro-view .multi-pro-stats-table tr th:last-child,
+.multi-pro-view .multi-pro-stats-table tr td:last-child {
+    width: auto;
+}
+.multi-pro-view .count-label {
+    height: 24px;
+    line-height: 24px;
+    border-bottom: 1px solid #f1f1f1;
+    color: #666;
+    display: flex;
+    align-items: baseline;
+    padding-left: 4px;
+}
+.multi-pro-view .count-label .count {
+    min-width: 30px;
+    /*text-align: right;*/
+    margin-right: 5px;
+}
+.multi-pro-view .count-label .drop-percent {
+    width: 41px;
+    font-size: 80%;
+    opacity: 0.8;
+    text-align: right;
+}
+.multi-pro-view .multi-pro-view-stat-label {
+    height: 22px;
+    background: #eee;
+    line-height: 22px;
+    padding: 0 5px;
+    font-weight: bold;
+    color: #777;
+}
 body .vakata-context li>a {
     padding: 1px 5px;
     padding-right: 10px;
@@ -2783,14 +2890,14 @@ body .vakata-context li>a .vakata-contextmenu-sep {
 /*body .clauses-view li>i.jstree-icon {
     display: none;
 }*/
-body .jstree {
-    margin-left: -.5rem;
-}
 body #vakata-dnd {
     font-family: Verdana, sans-serif;
     font-size: 12px;
     background: rgba(240, 248, 255, 0.76);
 }
+body .vakata-context .vakata-context-separator>a {
+    margin: 0;
+}
 
 .phq .btn-select {
     padding: 5px 13px;

+ 1 - 1
public/v-splitter-px/v-splitter.js

@@ -49,7 +49,7 @@ function initVSplitter(_name, _left, _right, _alignTo = null) {
             var tempFinalLeft = ((_left.outerWidth() + (finalLeft - origLeft)) * 100 / totalWidth);
             tempFinalLeft = Math.round(tempFinalLeft);
             _left.css({flex: '0 0 ' + tempFinalLeft + '%'});
-            vSplit.css({left: (_left.outerWidth() * 100 / totalWidth) + '%'})
+            vSplit.css({left: tempFinalLeft + '%'}) // (_left.outerWidth() * 100 / totalWidth) + '%'})
             _right.css({flex: '0 0 ' + (100 - tempFinalLeft) + '%'});
             $('.v-split-active').removeClass('v-split-active');
             localStorage['v-split-rounded-' + _name] = tempFinalLeft;

+ 55 - 0
resources/views/app/stat-tree/stat-tree-lines/line-properties.blade.php

@@ -0,0 +1,55 @@
+<div id="line-properties-column" class="d-none">
+
+    <div class="d-flex align-items-baseline mb-2">
+        <h6 class="font-weight-bold m-0">Argument Values</h6>
+    </div>
+    <div>
+        <table class="table table-sm table-bordered table-striped">
+            <thead>
+            <tr>
+                <th class="border-bottom-0 w-35">Arg</th>
+                <th class="border-bottom-0 w-35">Value</th>
+                <th class="border-bottom-0">Access</th>
+            </tr>
+            </thead>
+            <tbody line-args>
+            </tbody>
+        </table>
+    </div>
+
+    <div class="d-flex align-items-baseline mb-2">
+        <h6 class="font-weight-bold m-0">Report Columns</h6>
+    </div>
+    <div>
+        <div class="mb-2 d-flex align-items-baseline">
+            <span class="text-secondary mr-1">Node:</span>
+            <span line-label></span>
+        </div>
+        <div class="mb-2 d-flex align-items-baseline">
+            <span class="text-secondary mr-1 text-nowrap">Quick Add:</span>
+            <div class="flex-grow-1 position-relative">
+                <input type="text"
+                       name="displayKey"
+                       class="form-control form-control-sm"
+                       placeholder="Column name"
+                       stag-suggest stag-suggest-left
+                       stag-suggest-ep="/column-suggest"
+                       stag-suggest-extra="table={{$statTree->model}}"
+                       autocomplete="donotdoit">
+            </div>
+        </div>
+        <table class="table table-sm table-bordered table-striped">
+            <thead>
+            <tr>
+                <th class="border-bottom-0 width-30px">#</th>
+                <th class="border-bottom-0 w-35">Label</th>
+                <th class="border-bottom-0">Column</th>
+                <th class="border-bottom-0 width-60px"></th>
+            </tr>
+            </thead>
+            <tbody line-columns>
+            </tbody>
+        </table>
+    </div>
+
+</div>

+ 0 - 75
resources/views/app/stat-tree/stat-tree-lines/report-columns.blade.php

@@ -1,75 +0,0 @@
-<div class="mb-2 d-flex align-items-baseline">
-    <span class="text-secondary mr-1">Node:</span>
-    <span line-label></span>
-</div>
-<div class="mb-2 d-flex align-items-baseline">
-    <span class="text-secondary mr-1 text-nowrap">Quick Add:</span>
-    <div class="flex-grow-1 position-relative">
-        <input type="text"
-               name="displayKey"
-               class="form-control form-control-sm"
-               placeholder="Column name"
-               stag-suggest stag-suggest-left
-               stag-suggest-ep="/column-suggest"
-               stag-suggest-extra="table={{$statTree->model}}"
-               autocomplete="donotdoit">
-    </div>
-</div>
-<table class="table table-sm table-bordered table-striped">
-    <thead>
-    <tr>
-        <th class="border-bottom-0 width-30px">#</th>
-        <th class="border-bottom-0 w-35">Label</th>
-        <th class="border-bottom-0">Column</th>
-        <th class="border-bottom-0 width-60px"></th>
-    </tr>
-    </thead>
-    <tbody line-columns>
-        {{--
-        @if(!count($line->reportColumns))
-            <tr>
-                <td colspan="3">Nothing yet!</td>
-            </tr>
-        @else
-            @foreach($line->reportColumns as $column)
-                <tr>
-                    <td>{{$column->position_index}}</td>
-                    <td>{{$column->label}}</td>
-                    <td>{{$column->display_key}}</td>
-                    <td class="text-nowrap">
-                        <div moe relative wide class="mr-2">
-                            <a href="#" start show><i class="text-sm fa fa-edit on-hover-opaque"></i></a>
-                            <form url="{{ route("practice-management.api.statTreeLineReportColumn.update") }}" right>
-                                @csrf
-                                <input type="hidden" name="uid" value="{{$column->uid}}">
-                                <div class="mb-2">
-                                    <label class="text-secondary text-sm mb-1">Label</label>
-                                    <input type="text" class="form-control form-control-sm" name="label" value="{{@$column->label}}">
-                                </div>
-                                <div class="mb-2">
-                                    <label class="text-secondary text-sm mb-1">Column</label>
-                                    <input type="text"
-                                           name="displayKey"
-                                           data-line-id="{{$line->id}}"
-                                           class="form-control form-control-sm"
-                                           placeholder="Column name"
-                                           stag-suggest stag-suggest-left
-                                           stag-suggest-ep="/column-suggest"
-                                           stag-suggest-extra="table={{$line->statTree->model}}"
-                                           value="{{@$column->display_key}}"
-                                           autocomplete="donotdoit">
-                                </div>
-                                <div class="d-flex align-items-center">
-                                    <button class="btn btn-sm btn-primary mr-2" type="button" submit>Save</button>
-                                    <button class="btn btn-sm btn-default mr-2 border" type="button" cancel>Cancel</button>
-                                </div>
-                            </form>
-                        </div>
-                        <a href="#" class="remove-column" data-uid="{{$column->uid}}"><i class="fa fa-trash-alt text-danger on-hover-opaque"></i></a>
-                    </td>
-                </tr>
-            @endforeach
-        @endif
-        --}}
-    </tbody>
-</table>

+ 28 - 16
resources/views/app/stat-tree/stat-tree-lines/view-data.blade.php

@@ -1,27 +1,39 @@
-<div class="mcp-theme-1 p-3 border-top mt-3">
-    @if(!count($columns))
+<div class="mcp-theme-1 popup-content-container p-3">
+    @if(@$rows === 'error')
         <div class="text-secondary">
             <i class="fa fa-exclamation-triangle"></i>
-            Report columns not configured for this report!
+            Query error / missing arg values!
         </div>
     @else
-        <table class="table table-sm table-striped table-bordered">
-            <thead>
-            <tr>
-                @foreach($columns as $column)
-                <th class="border-bottom-0 text-left">{{$column['label']}}</th>
+        @if(!count($columns))
+            <div class="text-secondary">
+                <i class="fa fa-exclamation-triangle"></i>
+                Report columns not configured for this report!
+            </div>
+        @else
+            <div class="mb-2 d-flex flex-wrap">
+                @foreach($line->lineClauses as $lineClause)
+                    <div class="border rounded px-2 py-1 mr-2 bg-light">{{$lineClause->clause_label}}</div>
                 @endforeach
-            </tr>
-            </thead>
-            <tbody>
-            @foreach($rows as $row)
+            </div>
+            <table class="table table-sm table-striped table-bordered">
+                <thead>
                 <tr>
                     @foreach($columns as $column)
-                        <td>{{ $row->{$column['as']} }}</td>
+                    <th class="border-bottom-0 text-left">{{$column['label']}}</th>
                     @endforeach
                 </tr>
-            @endforeach
-            </tbody>
-        </table>
+                </thead>
+                <tbody>
+                @foreach($rows as $row)
+                    <tr>
+                        @foreach($columns as $column)
+                            <td>{{ $row->{$column['as']} }}</td>
+                        @endforeach
+                    </tr>
+                @endforeach
+                </tbody>
+            </table>
+        @endif
     @endif
 </div>

+ 89 - 32
resources/views/app/stat-tree/stat-trees/list.blade.php

@@ -34,6 +34,20 @@
                                         <label class="text-secondary text-sm mb-1">Slug *</label>
                                         <input type="text" class="form-control form-control-sm" name="slug" required>
                                     </div>
+                                    <label class="my-3 d-flex align-items-center">
+                                        <span class="mr-2">Is Template?</span>
+                                        <input type="checkbox" name="isTemplate">
+                                    </label>
+                                    <div class="mb-2">
+                                        <label class="text-secondary text-sm mb-1">Pro</label>
+                                        <select name="proUid" class="form-control input-sm" provider-search provider-type="hcp">
+                                            <option value="">--select--</option>
+                                        </select>
+                                    </div>
+                                    <div class="mb-2">
+                                        <label class="text-secondary text-sm mb-1">Pro Scope Clause</label>
+                                        <input type="text" class="form-control form-control-sm" name="proScopeClause">
+                                    </div>
                                     <div class="d-flex align-items-center">
                                         <button class="btn btn-sm btn-primary mr-2" type="button" submit>Save</button>
                                         <button class="btn btn-sm btn-default mr-2 border" type="button" cancel>Cancel</button>
@@ -43,38 +57,81 @@
                         </div>
                     </div>
                     <div class="card-body p-0">
-                        <div class="row">
-                            <div class="col-md-12">
-                                <div class="table-responsive">
-                                    <table class="table table-condensed p-0 m-0">
-                                        <thead class="bg-light">
-                                            <tr>
-                                                <th class="border-0">ID</th>
-                                                <th class="border-0">Name</th>
-                                                <th class="border-0">Model</th>
-                                                <th class="border-0">Slug</th>
-                                                <th class="border-0"></th>
-                                            </tr>
-                                        </thead>
-                                        <tbody>
-                                            @foreach($statTrees as $statTree)
-                                            <tr>
-                                                <td>
-                                                    <a href="{{ route('practice-management.statTrees.view.dashboard', $statTree) }}">{{ $statTree->id }}</a>
-                                                </td>
-                                                <td>{{ $statTree->name }}</td>
-                                                <td>{{ $statTree->model }}</td>
-                                                <td>{{ $statTree->slug }}</td>
-                                                <td>
-                                                    <a href="{{ route('practice-management.statTrees.view.edit', $statTree) }}">Edit</a>
-                                                </td>
-                                            </tr>
-                                            @endforeach
-                                        </tbody>
-                                    </table>
-                                </div>
-                            </div>
-                        </div>
+                        <table class="table table-condensed p-0 m-0">
+                            <thead class="bg-light">
+                            <tr>
+                                <th class="border-0">ID</th>
+                                <th class="border-0">Name</th>
+                                <th class="border-0">Model</th>
+                                <th class="border-0">Slug</th>
+                                <th class="border-0">Pro</th>
+                                <th class="border-0"></th>
+                            </tr>
+                            </thead>
+                            <tbody>
+                            @foreach($statTrees as $statTree)
+                                <tr>
+                                    <td>{{ $statTree->id }}</td>
+                                    <td><a href="{{ route('practice-management.statTrees.view.edit', $statTree) }}">{{ $statTree->name }}</a>
+                                        @if($statTree->is_template)
+                                            <span class="mt-1 text-sm bg-info rounded text-white d-inline-block px-2 py-1 ml-1 font-weight-bold">TEMPLATE</span>
+                                        @endif
+                                    </td>
+                                    <td>{{ $statTree->model }}</td>
+                                    <td>{{ $statTree->slug }}</td>
+                                    <td>
+                                        @if($statTree->pro)
+                                            {{$statTree->pro->displayName()}}
+                                        @endif
+                                        @if($statTree->pro_scope_clause)
+                                            <div class="text-secondary text-sm">{{$statTree->pro_scope_clause}}</div>
+                                        @endif
+                                    </td>
+                                    <td>
+                                        <div class="d-flex align-items-baseline">
+                                            <a href="{{ route('practice-management.statTrees.view.edit', $statTree) }}">Edit</a>
+                                            @if($statTree->is_template)
+                                                <span class="mx-2 text-secondary text-sm">|</span>
+                                                <div moe relative>
+                                                    <a href="#" start show class="text-nowrap">Inst.</a>
+                                                    <form url="{{ route('practice-management.statTrees.instantiate', $statTree) }}" right redir="/practice-management/stat-trees/view/[data]/edit">
+                                                        @csrf
+                                                        <p class="mb-2 font-weight-bold">Instantiate for Pro</p>
+                                                        <div class="mb-2">
+                                                            <label class="text-sm mb-1">Pro</label>
+                                                            <select name="proUid" class="form-control input-sm" provider-search provider-type="hcp">
+                                                                <option value="">--select--</option>
+                                                            </select>
+                                                        </div>
+                                                        <div class="d-flex align-items-center">
+                                                            <button class="btn btn-sm btn-primary mr-2" type="button" submit>Save</button>
+                                                            <button class="btn btn-sm btn-default mr-2 border" type="button" cancel>Cancel</button>
+                                                        </div>
+                                                    </form>
+                                                </div>
+                                                <span class="mx-2 text-secondary text-sm">|</span>
+                                                <div moe relative>
+                                                    <a href="#" start show class="text-nowrap">Clone</a>
+                                                    <form url="{{ route('practice-management.statTrees.clone', $statTree) }}" right redir="/practice-management/stat-trees/view/[data]/edit">
+                                                        @csrf
+                                                        <p class="mb-2 font-weight-bold">Clone</p>
+                                                        <div class="mb-2">
+                                                            <label class="text-sm mb-1">Name</label>
+                                                            <input type="text" name="name" class="form-control form-control-sm" value="{{$statTree->name}} (clone)" required>
+                                                        </div>
+                                                        <div class="d-flex align-items-center">
+                                                            <button class="btn btn-sm btn-primary mr-2" type="button" submit>Save</button>
+                                                            <button class="btn btn-sm btn-default mr-2 border" type="button" cancel>Cancel</button>
+                                                        </div>
+                                                    </form>
+                                                </div>
+                                            @endif
+                                        </div>
+                                    </td>
+                                </tr>
+                            @endforeach
+                            </tbody>
+                        </table>
                     </div>
                 </div>
             </div>

+ 1148 - 367
resources/views/app/stat-tree/stat-trees/sub/edit.blade.php

@@ -1,383 +1,1164 @@
-@extends('app.stat-tree.stat-trees.single')
-@section('page')
+@extends ('layouts/template')
+@section('content')
+    <?php
+    $multiProView = !!request()->input('multi-pro');
+    $multiPros = [];
+    if(request()->input('multi-pro-uids')) {
+        $uids = explode(',', request()->input('multi-pro-uids'));
+        foreach ($uids as $uid) {
+            $multiPro = \App\Models\Pro::where('uid', $uid)->first();
+            if($multiPro) {
+                $multiPros[] = $multiPro;
+            }
+        }
+    }
+    ?>
     <link rel="stylesheet" href="/jstree/themes/default/style.min.css" />
-
-    <div id="statTreeEdit">
-        <div id="statTreeView" class="row">
-            <div class="col-3 pr-0">
-                @include('app.stat-tree.clauses-edit')
-            </div>
-            <div class="col-5 pr-0 pl-3 border-left tree-column">
-                @include('app.stat-tree.tree-edit-v2', ['slug' => $statTree->slug])
-            </div>
-            <div class="col-4 pl-3 border-left setup-column">
-                <div class="d-flex align-items-baseline mb-2">
-                    <h6 class="font-weight-bold m-0">Report Columns</h6>
-                </div>
-                <div id="report-column" class="d-none">
-                    @include('app.stat-tree.stat-tree-lines.report-columns')
+    <script src="/jstree/jstree.min.js"></script>
+    <div class="mcp-theme-1">
+        <div class="card border-0 rounded-0 d-flex flex-column body-height">
+            <div class="card-header px-2 py-3 bg-aliceblue">
+                <div class="d-flex align-items-center">
+                    <a href="{{route('practice-management.statTrees.list')}}" class="small text-decoration-none mr-3">
+                        <i class="fa fa-chevron-left"></i>
+                    </a>
+                    <div class="mr-3">
+                        <span>Name:</span>
+                        <span class="font-weight-bold" title="Slug: {{ $statTree->slug }}">{{ $statTree->name }}</span>
+                    </div>
+                    <div class="mr-3 d-inline-flex">
+                        <span class="mr-1">Model:</span>
+                        <span class="font-weight-bold max-width-200px overflow-hidden text-ellipsis" title="{{ $statTree->model }}">{{ $statTree->model }}</span>
+                    </div>
+                    @if($statTree->pro_id)
+                        <div class="mr-3">
+                            <span>Pro:</span>
+                            <span class="font-weight-bold" title="Clause: {{ $statTree->pro_scope_clause }}">{{ $statTree->pro->displayName() }}</span>
+                        </div>
+                    @endif
+                    <div moe relative wide class="">
+                        <a href="#" start show class=""><i class="fa fa-edit on-hover-opaque"></i></a>
+                        <form url="{{ route("practice-management.api.statTree.updateBasic") }}">
+                            @csrf
+                            <input type="hidden" name="uid" value="{{$statTree->uid}}">
+                            <div class="mb-2">
+                                <label class="text-secondary text-sm mb-1">Name *</label>
+                                <input type="text" class="form-control form-control-sm" name="name" value="{{$statTree->name}}" autofocus required>
+                            </div>
+                            <div class="mb-2">
+                                <label class="text-secondary text-sm mb-1">Model *</label>
+                                <input type="text" class="form-control form-control-sm" data-option-list="model-options" autocomplete="off" name="model" value="{{$statTree->model}}" required>
+                                <div id="model-options" class="data-option-list">
+                                    <?php $models = \Illuminate\Support\Facades\DB::select('SELECT distinct(model) as m FROM clause order by model'); ?>
+                                    @foreach($models as $m)
+                                        <div>{!! $m->m !!}</div>
+                                    @endforeach
+                                </div>
+                            </div>
+                            <div class="mb-2">
+                                <label class="text-secondary text-sm mb-1">Slug *</label>
+                                <input type="text" class="form-control form-control-sm" name="slug" value="{{$statTree->slug}}" required>
+                            </div>
+                            <label class="my-3 d-flex align-items-center">
+                                <span class="mr-2">Is Template?</span>
+                                <input type="checkbox" name="isTemplate" {{@$statTree->is_template ? 'checked' : ''}}>
+                            </label>
+                            <div class="mb-2">
+                                <label class="text-secondary text-sm mb-1">Pro</label>
+                                <select name="proUid" class="form-control input-sm" provider-search provider-type="hcp" data-pro-uid="{{ @$statTree->pro->uid }}">
+                                    <option value="">--select--</option>
+                                </select>
+                            </div>
+                            <div class="mb-2">
+                                <label class="text-secondary text-sm mb-1">Pro Scope Clause</label>
+                                <input type="text" class="form-control form-control-sm" name="proScopeClause" value="{{@$statTree->pro_scope_clause}}">
+                            </div>
+                            <div class="d-flex align-items-center">
+                                <button class="btn btn-sm btn-primary mr-2" type="button" submit>Save</button>
+                                <button class="btn btn-sm btn-default mr-2 border" type="button" cancel>Cancel</button>
+                            </div>
+                        </form>
+                    </div>
+                    @if(!$multiProView)
+                        <a href="{{ route('practice-management.statTrees.view.edit', $statTree) }}?multi-pro=1" class="text-decoration-none font-weight-bold text-primary ml-auto">
+                            <i class="fa fa-users mr-1"></i>
+                            Multi-Pro View
+                        </a>
+                    @else
+                        <a href="{{ route('practice-management.statTrees.view.edit', $statTree) }}" class="text-decoration-none font-weight-bold text-primary ml-auto">
+                            <i class="fa fa-edit mr-1"></i>
+                            Edit View
+                        </a>
+                    @endif
                 </div>
             </div>
-        </div>
+            <div class="card-body p-0 flex-grow-1">
+                <div id="statTreeEdit-{{$statTree->id}}" class="row m-0 h-100 stat-tree {{$multiProView ? 'multi-pro-view' : ''}}">
 
-    </div>
-    <script src="/jstree/jstree.min.js"></script>
-    <script>
-        (function() {
-            function init() {
-                $(document)
-                    .off('input change paste', '.frm-clause-add-edit input[name="question"], .frm-clause-add-edit input[name="answer"]')
-                    .on('input change paste', '.frm-clause-add-edit input[name="question"], .frm-clause-add-edit input[name="answer"]', function() {
-                        let form = $(this).closest('.frm-clause-add-edit');
-                        let label = $.trim(form.find('input[name="question"]').val()) + ' ' +
-                            $.trim(form.find('input[name="answer"]').val());
-                        form.find('input[name="label"]').val(label);
-                    });
-
-                let treePayload = '';
-
-                function selectedNode() {
-                    let selected = $('#stat-tree-view-{{$statTree->id}}>ul').jstree('get_selected', false);
-                    if(selected && selected.length) {
-                        selected = $('#' + selected[0]);
-                    }
-                    if(!selected || !selected.length) return;
-                    return selected.find('>a.jstree-anchor>.st-node').first();
-                }
-
-                function getSelectedNodeColumns() {
-                    let selected = selectedNode();
-                    let columnsField = selected.find('>[data-name="columns"]').first();
-                    let columns = columnsField.val();
-                    if(!!columns) return JSON.parse(columns);
-                    return [];
-                }
-
-                function setSelectedNodeColumns(columns) {
-                    let selected = selectedNode();
-                    let columnsField = selected.find('>[data-name="columns"]').first();
-                    columnsField.val(JSON.stringify(columns));
-                }
-
-                function getNodes(_ul, _indent, _lines) {
-                    _ul.find('>li').each(function() {
-                        let node = $(this).find('>a.jstree-anchor>.st-node');
-                        let line = _indent + node.find('>span').first().text();
-                        let columnsElem = node.find('>[data-name="columns"]').first(), columns = [];
-                        if(columnsElem && columnsElem.length && !!columnsElem.val()) {
-                            let columnsParsed = JSON.parse(columnsElem.val());
-                            for (let i = 0; i < columnsParsed.length; i++) {
-                                columns.push(columnsParsed[i].display_key+'|'+columnsParsed[i].label);
-                            }
-                            columns = columns.join(',');
-                        }
-                        line = line + '==>' + columns;
-                        _lines.push(line);
-                        $(this).find('>ul.jstree-children').each(function() {
-                            getNodes($(this), _indent + "\t", _lines);
-                        });
-                    })
-                }
-
-                function generateTextRepresentation(_e, _data) {
-
-                    $('#stat-tree-view-{{$statTree->id}}').jstree('open_all');
-                    $('#stat-tree-view-{{$statTree->id}} [data-type="meta"]').remove();
-
-                    let nodes = [];
-                    getNodes($('#stat-tree-view-{{$statTree->id}}>ul').first(), '', nodes);
-
-                    let converted = tsvToArray.getAutoFilledTabContent(nodes.join("\n"));
-
-                    let maxColumn = 0, columns = [];
-                    try {
-                        let rows = converted.split("\n");
-                        rows.map((row, index) => {
-                            columns[index] = row.split("\t");
-                            const length = columns[index].length;
-                            if (length > maxColumn) {
-                                maxColumn = length
-                            }
-                            const emptyFields = maxColumn - length;
-                            for (ii = 0; ii < emptyFields; ii++) {
-                                columns[index].push('');
+                    <!-- clauses -->
+                    <div class="col-3 p-0 h-100 clauses-column">
+
+                        <div class="d-flex flex-column h-100">
+                            <div class="d-flex align-items-center pl-2 height-35px border-bottom bg-light">
+                                <h6 class="font-weight-bold m-0 text-secondary">Available Clauses</h6>
+                                <div moe relative wide class="ml-3">
+                                    <a href="#" start show>+ Add</a>
+                                    <form url="{{ route("practice-management.api.clause.create") }}" class="frm-clause-add-edit" hook="reloadClausesTree">
+                                        @csrf
+                                        <div class="mb-2">
+                                            <label class="text-secondary text-sm mb-1">Model</label>
+                                            <input type="text" class="form-control form-control-sm" name="model" readonly value="{{$statTree->model}}">
+                                        </div>
+                                        <div class="mb-2">
+                                            <label class="text-secondary text-sm mb-1">Question</label>
+                                            <input type="text" class="form-control form-control-sm" name="question" autofocus>
+                                        </div>
+                                        <div class="mb-2">
+                                            <label class="text-secondary text-sm mb-1">Answer</label>
+                                            <input type="text" class="form-control form-control-sm" name="answer">
+                                        </div>
+                                        <div class="mb-2">
+                                            <label class="text-secondary text-sm mb-1">Label</label>
+                                            <input type="text" class="form-control form-control-sm" name="label" readonly>
+                                        </div>
+                                        <div class="mb-2">
+                                            <label class="text-secondary text-sm mb-1">Clause Text</label>
+                                            <input type="text" class="form-control form-control-sm" name="clauseText">
+                                        </div>
+                                        <div class="d-flex align-items-center">
+                                            <button class="btn btn-sm btn-primary mr-2" type="button" submit>Save</button>
+                                            <button class="btn btn-sm btn-default mr-2 border" type="button" cancel>Cancel</button>
+                                        </div>
+                                    </form>
+                                </div>
+                                <a href="#" class="clause-expand-all ml-3" title="Expand All"><i class="fa fa-angle-double-down text-secondary"></i></a>
+                                <a href="#" class="clause-collapse-all ml-2" title="Collapse All"><i class="fa fa-angle-double-up text-secondary"></i></a>
+                            </div>
+                            <div class="flex-grow-1 overflow-overlay-on-hover pl-1">
+                                <div class="overflow-auto clauses-view pb-5" id="clauses-view-{{$statTree->id}}"></div>
+                            </div>
+                        </div>
+
+                        <!-- hidden moes invoked due to context actions -->
+                        <div class="border mb-3 p-2 position-absolute" style="left: -10000px; top: -10000px;">
+                            <div class="d-inline-flex align-items-baseline">
+
+                                <!-- edit clause -->
+                                <div moe center relative wide class="ml-3 mr-2" id="edit-clause-moe">
+                                    <a href="#" start show><i class="text-sm fa fa-edit on-hover-opaque"></i></a>
+                                    <form url="{{ route("practice-management.api.clause.update") }}" class="frm-clause-add-edit" center hook="reloadClausesTree">
+                                        @csrf
+                                        <input type="hidden" name="uid">
+                                        <div class="mb-2">
+                                            <label class="text-secondary text-sm mb-1">Model</label>
+                                            <input type="text" class="form-control form-control-sm" name="model" readonly>
+                                        </div>
+                                        <div class="mb-2">
+                                            <label class="text-secondary text-sm mb-1">Question</label>
+                                            <input type="text" class="form-control form-control-sm" name="question">
+                                        </div>
+                                        <div class="mb-2">
+                                            <label class="text-secondary text-sm mb-1">Answer</label>
+                                            <input type="text" class="form-control form-control-sm" name="answer">
+                                        </div>
+                                        <div class="mb-2">
+                                            <label class="text-secondary text-sm mb-1">Label</label>
+                                            <input type="text" class="form-control form-control-sm" name="label" readonly>
+                                        </div>
+                                        <div class="mb-2">
+                                            <label class="text-secondary text-sm mb-1">Clause Text</label>
+                                            <input type="text" class="form-control form-control-sm" name="clauseText">
+                                        </div>
+                                        <div class="d-flex align-items-center">
+                                            <button class="btn btn-sm btn-primary mr-2" type="button" submit>Save</button>
+                                            <button class="btn btn-sm btn-default mr-2 border" type="button" cancel>Cancel</button>
+                                        </div>
+                                    </form>
+                                </div>
+
+                                <!-- remove clause -->
+                                <div moe relative center id="remove-clause-moe">
+                                    <a href="#" start show><i class="text-sm fa fa-trash-alt on-hover-opaque text-danger"></i></a>
+                                    <form url="{{ route("practice-management.api.clause.remove") }}" center hook="reloadClausesTree">
+                                        @csrf
+                                        <input type="hidden" name="uid">
+                                        <p>Are you sure?</p>
+                                        <div class="d-flex align-items-center">
+                                            <button class="btn btn-sm btn-danger mr-2" type="button" submit>Remove</button>
+                                            <button class="btn btn-sm btn-default mr-2 border" type="button" cancel>Cancel</button>
+                                        </div>
+                                    </form>
+                                </div>
+
+                                <!-- add clause arg -->
+                                <div moe relative center id="add-clause-arg-moe">
+                                    <a href="#" start show><i class="text-sm fa fa-trash-alt on-hover-opaque text-danger"></i></a>
+                                    <form url="{{ route("practice-management.api.clauseArg.create") }}" center hook="reloadClausesTree">
+                                        @csrf
+                                        <input type="hidden" name="clauseId">
+                                        <div class="mb-2">
+                                            <label class="text-secondary text-sm mb-1">Arg Text</label>
+                                            <input type="text" class="form-control form-control-sm" name="argText" required>
+                                        </div>
+                                        <div class="mb-2">
+                                            <label class="text-secondary text-sm mb-1">Field Type</label>
+                                            <select class="form-control form-control-sm" name="fieldType" required>
+                                                <option value="">-- select --</option>
+                                                <option value="numeric">Number</option>
+                                                <option value="date">Date</option>
+                                                <option value="text">String</option>
+                                                <option value="bool">Boolean</option>
+                                            </select>
+                                        </div>
+                                        <div class="d-flex align-items-center">
+                                            <button class="btn btn-sm btn-primary mr-2" type="button" submit>Save</button>
+                                            <button class="btn btn-sm btn-default mr-2 border" type="button" cancel>Cancel</button>
+                                        </div>
+                                    </form>
+                                </div>
+
+                                <!-- edit clause arg -->
+                                <div moe relative center id="edit-clause-arg-moe">
+                                    <a href="#" start show><i class="text-sm fa fa-trash-alt on-hover-opaque text-danger"></i></a>
+                                    <form url="{{ route("practice-management.api.clauseArg.update") }}" center hook="reloadClausesTree">
+                                        @csrf
+                                        <input type="hidden" name="id">
+                                        <div class="mb-2">
+                                            <label class="text-secondary text-sm mb-1">Arg Text</label>
+                                            <input type="text" class="form-control form-control-sm" name="argText" required>
+                                        </div>
+                                        <div class="mb-2">
+                                            <label class="text-secondary text-sm mb-1">Field Type</label>
+                                            <select class="form-control form-control-sm" name="fieldType" required>
+                                                <option value="">-- select --</option>
+                                                <option value="numeric">Number</option>
+                                                <option value="date">Date</option>
+                                                <option value="text">String</option>
+                                                <option value="bool">Boolean</option>
+                                            </select>
+                                        </div>
+                                        <div class="d-flex align-items-center">
+                                            <button class="btn btn-sm btn-primary mr-2" type="button" submit>Save</button>
+                                            <button class="btn btn-sm btn-default mr-2 border" type="button" cancel>Cancel</button>
+                                        </div>
+                                    </form>
+                                </div>
+
+                                <!-- remove clause arg -->
+                                <div moe relative center id="remove-clause-arg-moe">
+                                    <a href="#" start show><i class="text-sm fa fa-trash-alt on-hover-opaque text-danger"></i></a>
+                                    <form url="{{ route("practice-management.api.clauseArg.remove") }}" center hook="reloadClausesTree">
+                                        @csrf
+                                        <input type="hidden" name="id">
+                                        <p>Are you sure?</p>
+                                        <div class="d-flex align-items-center">
+                                            <button class="btn btn-sm btn-danger mr-2" type="button" submit>Remove</button>
+                                            <button class="btn btn-sm btn-default mr-2 border" type="button" cancel>Cancel</button>
+                                        </div>
+                                    </form>
+                                </div>
+
+
+                            </div>
+                        </div>
+
+                    </div>
+
+
+                    <!-- tree -->
+                    <div class="col-9 p-0 h-100 border-left tree-column">
+                        <div class="row m-0 h-100">
+                            <div class="{{$multiProView ? 'col-12' : 'col-6'}} tree-lhs-column border-right h-100 p-0">
+                                <div class="d-flex flex-column h-100">
+                                    <div class="d-flex align-items-center pl-2 height-35px border-bottom bg-light">
+                                        <h6 class="font-weight-bold m-0 text-secondary">{{$statTree->name}}</h6>
+                                        <span class="text-danger d-none if-changed ml-2 text-sm">(modified *)</span>
+                                        @if($multiProView)
+                                            <div moe class="ml-3">
+                                                <a href="#" start show>Add Pro</a>
+                                                <div url="/nop">
+                                                    <div class="mb-2">
+                                                        <select id="proUid" class="form-control input-sm" provider-search provider-type="hcp">
+                                                            <option value="">--select--</option>
+                                                        </select>
+                                                    </div>
+                                                    <div class="d-flex align-items-center">
+                                                        <button class="btn btn-sm btn-primary mr-2 btn-add-pro" type="button">Add</button>
+                                                        <button class="btn btn-sm btn-default mr-2 border" type="button" cancel>Cancel</button>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        @endif
+                                        <a href="#" id="refresh-counts" class="ml-3">Refresh Counts</a>
+                                        <a href="#" class="tree-expand-all ml-3" title="Expand All"><i class="fa fa-angle-double-down text-secondary"></i></a>
+                                        <a href="#" class="tree-collapse-all ml-2" title="Collapse All"><i class="fa fa-angle-double-up text-secondary"></i></a>
+                                        <div class="ml-auto mr-2 d-none if-changed">
+                                            <a href="#" class="btn btn-sm btn-primary text-white" id="btn-save-tree">Save</a>
+                                            <a href="#" class="ml-2 btn btn-sm btn-default border text-dark" onclick="return fastReload()">Reset</a>
+                                        </div>
+                                    </div>
+                                    <div class="flex-grow-1 overflow-overlay-on-hover">
+                                        @if(!$multiProView)
+                                            <div class="stat-tree-view overflow-auto min-height-300px" id="stat-tree-edit-{{$statTree->id}}"></div>
+                                        @else
+                                            <div class="d-flex align-items-start">
+                                                <div class="">
+                                                    <div class="multi-pro-view-stat-label">Stat</div>
+                                                    <div class="stat-tree-view overflow-auto" id="stat-tree-edit-{{$statTree->id}}"></div>
+                                                </div>
+                                                <div class="flex-grow-1">
+                                                    <table class="table table-sm multi-pro-stats-table">
+                                                        <thead>
+                                                        <tr>
+                                                            <th {{$statTree->pro ? 'data-pro-uid="' . $statTree->pro->uid . '"' : ''}}>
+                                                                <div class="pro-label" title="{{$statTree->pro ? $statTree->pro->name_display : 'Total'}}">{{$statTree->pro ? $statTree->pro->name_display : 'Total'}}</div>
+                                                            </th>
+                                                            @foreach($multiPros as $multiPro)
+                                                                <th data-pro-uid="{{$multiPro->uid}}" count-required>
+                                                                    <div class="pro-label" title="{{$multiPro->name_display}}">{{$multiPro->name_display}}</div>
+                                                                </th>
+                                                            @endforeach
+                                                            <th></th>
+                                                        </tr>
+                                                        </thead>
+                                                        <tbody>
+                                                        @foreach($linesFlat as $line)
+                                                            <tr data-line-uid="{{$line->uid}}">
+                                                                <td class="p-0">
+                                                                    <div class="count-label">
+                                                                        <div class="count">{{$line->last_refresh_count}}</div>
+                                                                        <div class="drop-percent">50%  ⤵</div>
+                                                                    </div>
+                                                                </td>
+                                                                @foreach($multiPros as $multiPro)
+                                                                    <td class="p-0" data-pro-uid="{{$multiPro->uid}}">
+                                                                        <div class="count-label">
+                                                                            <div class="count">0</div>
+                                                                            <div class="drop-percent">0%  ⤵</div>
+                                                                        </div>
+                                                                    </td>
+                                                                @endforeach
+                                                                <td></td>
+                                                            </tr>
+                                                        @endforeach
+                                                        </tbody>
+                                                    </table>
+                                                </div>
+                                            </div>
+                                        @endif
+                                    </div>
+                                </div>
+                            </div>
+                            @if(!$multiProView)
+                                <!-- selected line properties -->
+                                <div class="col-6 tree-rhs-column h-100 p-0">
+                                    <div class="h-100 setup-column d-none" id="line-properties-column">
+                                        <div class="d-flex flex-column h-100">
+                                            <div class="d-flex align-items-center pl-2 height-35px border-bottom bg-light">
+                                                <h6 class="font-weight-bold m-0 text-secondary">Selected Line Properties</h6>
+                                            </div>
+                                            <div class="flex-grow-1 overflow-overlay-on-hover">
+                                                <div class="mb-3 d-flex align-items-baseline p-2 border-bottom font-weight-bold">
+                                                    <span line-label></span>
+                                                </div>
+                                                <div class="d-flex align-items-baseline mb-2 px-2">
+                                                    <h6 class="font-weight-bold m-0 text-secondary">Argument Values</h6>
+                                                    <!--<a href="#" class="ml-3 text-sm" onclick="$('.parent-arg').toggle(); return false;">Toggle Parent Clause Args</a>-->
+                                                </div>
+                                                <div class="px-2">
+                                                    <table class="table table-sm table-bordered table-striped">
+                                                        <thead>
+                                                        <tr>
+                                                            <th class="border-bottom-0 w-35">Arg</th>
+                                                            <th class="border-bottom-0 w-35">Value</th>
+                                                            <th class="border-bottom-0">Access</th>
+                                                        </tr>
+                                                        </thead>
+                                                        <tbody line-args>
+                                                        </tbody>
+                                                    </table>
+                                                </div>
+                                                <div class="d-flex align-items-baseline mb-2 border-top pt-3 px-2">
+                                                    <h6 class="font-weight-bold m-0 text-secondary">Report Columns</h6>
+                                                </div>
+                                                <div class="px-2">
+                                                    <div class="mb-2 d-flex align-items-baseline">
+                                                        <span class="text-secondary mr-1 text-nowrap">Quick Add:</span>
+                                                        <div class="flex-grow-1 position-relative">
+                                                            <input type="text"
+                                                                   name="displayKey"
+                                                                   class="form-control form-control-sm"
+                                                                   placeholder="Column name"
+                                                                   stag-suggest stag-suggest-left
+                                                                   stag-suggest-ep="/column-suggest"
+                                                                   stag-suggest-extra="table={{$statTree->model}}"
+                                                                   autocomplete="off">
+                                                        </div>
+                                                    </div>
+                                                    <table class="table table-sm table-bordered table-striped">
+                                                        <thead>
+                                                        <tr>
+                                                            <th class="border-bottom-0 width-30px">#</th>
+                                                            <th class="border-bottom-0 w-35">Label</th>
+                                                            <th class="border-bottom-0">Column</th>
+                                                            <th class="border-bottom-0 width-60px"></th>
+                                                        </tr>
+                                                        </thead>
+                                                        <tbody line-columns>
+                                                        </tbody>
+                                                    </table>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                            @endif
+                        </div>
+                    </div>
+
+                </div>
+                <script>
+                    (function() {
+                        function init() {
+
+                            let linePropsColumn = $('#line-properties-column'),
+                                multiViewPros = [];
+
+                            @if(request()->input('multi-pro-uids'))
+                                multiViewPros = '{{request()->input('multi-pro-uids')}}'.split(',');
+                            @endif
+
+                            function fillAndInvokeMoe(_id, _node) {
+                                let moe = $(_id);
+                                moe.find('input[name], select[name]').each(function() {
+                                    let name = $(this).attr('name');
+                                    if(!!_node.data[name]) {
+                                        $(this).val(!!_node.data[name] ? _node.data[name] : '');
+                                    }
+                                });
+                                moe.find('a[start]').trigger('click');
                             }
-                        });
-                    } catch (error) {
-                        console.error(error);
-                        this.rows = [];
-                    }
-
-                    treePayload = JSON.stringify(columns);
-
-                }
-
-                function autoSaveChanges() {
-                    setTimeout(() => {
-                        saveStatTree();
-                    }, 0);
-                }
-
-                function saveStatTree() {
-                    generateTextRepresentation();
-                    $.post('{{ route("practice-management.api.statTree.replaceAllLines") }}', {
-                        statTreeID: "{{ $statTree->id }}",
-                        data: treePayload
-                    }, function (response) {
-                        if(!hasResponseError(response)) {
-                            //fastReload();
-                            // need to fix this!
-                            showMask();
-                            window.top.location.reload();
-                        }
-                    }, 'json');
-                }
-
-                function onDeselected(_e, _data) {
-                    let rptColumn = $('#report-column').addClass('d-none');
-                }
-
-                function onSelected(_e, _data) {
-                    let selected = selectedNode();
-
-                    let rptColumn = $('#report-column').removeClass('d-none');
-                    rptColumn.find('[line-label]').text(selected.find('>span').first().text());
-
-                    let tbody = rptColumn.find('[line-columns]');
-
-                    let columns = getSelectedNodeColumns();
-
-                    tbody.empty();
-
-                    if(columns && columns.length) {
-                        for (let i = 0; i < columns.length; i++) {
-                            $('<tr/>')
-                                .append($('<td/>').text(i + 1))
-                                .append($('<td/>').text(columns[i].label))
-                                .append($('<td/>').text(columns[i].display_key))
-                                .append(
-                                    $('<td/>')
-                                        .addClass('text-right')
-                                        .append(i > 0 ? '<a href="#" class="move-column-up mr-2" data-index="' + i + '"><i class="fa fa-arrow-up text-primary on-hover-opaque"></i></a>': '')
-                                        .append(i < columns.length - 1 ? '<a href="#" class="move-column-down mr-2" data-index="' + i + '"><i class="fa fa-arrow-down text-primary on-hover-opaque"></i></a>': '')
-                                        .append('<a href="#" class="remove-column" data-index="' + i + '"><i class="fa fa-trash-alt text-danger on-hover-opaque"></i></a>')
-                                )
-                                .appendTo(tbody);
-                        }
-                    }
-
-                }
-
-                $('#stat-tree-view-{{$statTree->id}}')
-                    //.on('changed.jstree', autoSaveChanges)
-                    .on('move_node.jstree', autoSaveChanges)
-                    .on('copy_node.jstree', autoSaveChanges)
-                    .on('select_node.jstree', onSelected)
-                    .on('deselect_node.jstree', onDeselected)
-                    .jstree({
-                        "core": {
-                            "check_callback": true,
-                            "multiple": false,
-                            "animation": 0
-                        },
-                        "contextmenu": {
-                            show_at_node: false,
-                            items: function ($node) {
-                                return {
-                                    "data": {
-                                        "label": "<span class='text-sm'>View Data</span>",
-                                        "action": function (obj) {
-                                            let uid = $(obj.reference).find('>div.stat-tree-line').attr('data-line-uid');
-                                            openDynamicStagPopup('/practice-management/stat-tree-lines/view-data/' + uid,
-                                                null,
-                                                $(obj.reference).find('>div.stat-tree-line>span').first().text(),
-                                                false,
-                                                'medium');
-                                        },
-                                    },
-                                    "remove": {
-                                        "label": "<span class='text-sm'>Remove Node</span>",
-                                        "action": function (obj) {
-                                            let id = $(obj.reference).find('>div.stat-tree-line').attr('data-line-id');
-                                            $('.remove-line-moe-' + id).find('a[start]').trigger('click');
-                                        }
+
+                            // clauses tree
+                            let ClausesTree = {
+                                el: $('#clauses-view-{{$statTree->id}}'),
+                                load: function() {
+
+                                    // destroy if existing
+                                    try {
+                                        this.el.jstree('destroy');
                                     }
+                                    catch (e) {}
+
+                                    // get data
+                                    $.get('{{ route('practice-management.statTrees.view.clausesJSON', $statTree) }}', _data => {
+
+                                        // init tree with data
+                                        this.el
+                                            .jstree({
+                                                "core": {
+                                                    "multiple": false,
+                                                    "animation": 0,
+                                                    'data': _data
+                                                },
+                                                "dnd": {
+                                                    "is_draggable": function(_node, _e) {
+                                                        return _node[0].data.type === 'clause';
+                                                    }
+                                                },
+                                                "contextmenu": {
+                                                    show_at_node: false,
+                                                    items: function (node) {
+                                                        if(node.data.type === 'clause') { // clause
+                                                            return {
+                                                                "edit": {
+                                                                    "label": "<span class='text-sm'>Edit Clause</span>",
+                                                                    "action": function (obj) {
+                                                                        fillAndInvokeMoe('#edit-clause-moe', node);
+                                                                    }
+                                                                },
+                                                                "remove": {
+                                                                    "label": "<span class='text-sm'>Remove Clause</span>",
+                                                                    "action": function (obj) {
+                                                                        fillAndInvokeMoe('#remove-clause-moe', node);
+                                                                    }
+                                                                },
+                                                                "add_arg": {
+                                                                    "label": "<span class='text-sm'>Add Clause Arg</span>",
+                                                                    "action": function (obj) {
+                                                                        fillAndInvokeMoe('#add-clause-arg-moe', node);
+                                                                    }
+                                                                }
+                                                            }
+                                                        }
+                                                        else if(node.data.type === 'clause_arg') { // clause
+                                                            return {
+                                                                "edit": {
+                                                                    "label": "<span class='text-sm'>Edit Arg</span>",
+                                                                    "action": function (obj) {
+                                                                        fillAndInvokeMoe('#edit-clause-arg-moe', node);
+                                                                    }
+                                                                },
+                                                                "remove": {
+                                                                    "label": "<span class='text-sm'>Remove Arg</span>",
+                                                                    "action": function (obj) {
+                                                                        fillAndInvokeMoe('#remove-clause-arg-moe', node);
+                                                                    }
+                                                                }
+                                                            };
+                                                        }
+                                                    },
+                                                },
+                                                "plugins": [
+                                                    "wholerow",
+                                                    "dnd",
+                                                    "contextmenu",
+                                                    "state"
+                                                ]
+                                            });
+                                    }, 'json');
                                 }
-                            },
-                        },
-                        "plugins": [
-                            "wholerow",
-                            "dnd",
-                            "contextmenu",
-                            "state"
-                        ]
-                    });
-
-                $('#clauses-view-{{$statTree->id}}')
-                    /*.on('changed.jstree', generateTextRepresentation)
-                    .on('move_node.jstree', generateTextRepresentation)
-                    .on('copy_node.jstree', generateTextRepresentation)*/
-                    .jstree({
-                        "core": {
-                            "multiple": false,
-                            "animation": 0
-                        },
-                        "dnd": {
-                            "is_draggable": function(_node, _e) {
-                                return $('#' + _node[0].id).find('>a.jstree-anchor>.clause').length === 1;
-                            }
-                        },
-                        "contextmenu": {
-                            show_at_node: false,
-                            items: function ($node) {
-                                console.log($node);
-                                if($('#' + $node.id).find('>a.jstree-anchor>.clause').length === 1) { // clause
-                                    return {
-                                        "edit": {
-                                            "label": "<span class='text-sm'>Edit Clause</span>",
-                                            "action": function (obj) {
-                                                let id = $(obj.reference).find('>div.clause').attr('data-clause-id');
-                                                $('.edit-clause-moe-' + id).find('a[start]').trigger('click');
+                            };
+
+                            @if($multiProView)
+                            (function ($, undefined) {
+                                "use strict";
+                                $.jstree.plugins.noclose = function () {
+                                    this.close_node = $.noop;
+                                };
+                            })(jQuery);
+                            @endif
+
+                            // stat tree
+                            let StatTree = {
+                                el: $('#stat-tree-edit-{{$statTree->id}}'),
+                                changed: false,
+
+                                setDirty: function(_changed = true) {
+                                    this.changed = _changed;
+                                    if(_changed) {
+                                        $('.if-changed').removeClass('d-none');
+                                    }
+                                    else {
+                                        $('.if-changed').addClass('d-none');
+                                    }
+                                },
+
+                                load: function() {
+
+                                    // destroy if existing
+                                    try {
+                                        this.el.jstree('destroy');
+                                    }
+                                    catch (e) {}
+
+                                    this.el = $('#stat-tree-edit-{{$statTree->id}}');
+
+                                    // get data
+                                    showMask();
+                                    $.get('{{ route('practice-management.statTrees.view.linesJSON', $statTree) }}', _data => {
+
+                                        // init tree with data
+                                        this.el
+                                            .on('move_node.jstree', (_e, _data) => { StatTree.dropped(_e, _data); })
+                                            .on('copy_node.jstree', (_e, _data) => { StatTree.dropped(_e, _data); })
+                                            .on('select_node.jstree', () => { StatTree.onSelected(); })
+                                            .on('deselect_node.jstree', () => { StatTree.onDeselected(); })
+                                            @if(!$multiProView)
+                                            .on('redraw.jstree', () => { StatTree.dropVisualize(); })
+                                            @endif
+                                            .jstree({
+                                                "core": {
+                                                    @if(!$multiProView)
+                                                    "check_callback": true,
+                                                    @endif
+                                                    "multiple": false,
+                                                    "animation": 0,
+                                                    'data': _data
+                                                },
+                                                @if(!$multiProView)
+                                                "dnd": {
+                                                    "is_draggable": function(_node, _e) {
+                                                        return _node[0].data && _node[0].data.type === 'stat_tree_line';
+                                                    }
+                                                },
+                                                "contextmenu": {
+                                                    show_at_node: false,
+                                                    items: function (node) {
+                                                        if(node.data.type === 'stat_tree_line') { // stat_tree_line
+                                                            return {
+
+                                                                @if(!$multiProView)
+                                                                "data": {
+                                                                    "label": "<span class='text-sm'>View Data</span>",
+                                                                    "action": function (obj) {
+                                                                        openDynamicStagPopup('/practice-management/stat-tree-lines/view-data/' + node.data.uid,
+                                                                            null,
+                                                                            node.data.displayLabel,
+                                                                            false,
+                                                                            'medium');
+                                                                    },
+                                                                    separator_after: true,
+                                                                },
+                                                                @endif
+
+                                                                "cut": {
+                                                                    "label": "<span class='text-sm'>Cut</span>",
+                                                                    "action": function (obj) {
+                                                                        let selected = StatTree.selectedNode();
+                                                                        if(selected) {
+                                                                            StatTree.el.jstree(true).cut(selected.id);
+                                                                        }
+                                                                    },
+                                                                },
+                                                                "copy": {
+                                                                    "label": "<span class='text-sm'>Copy</span>",
+                                                                    "action": function (obj) {
+                                                                        let selected = StatTree.selectedNode();
+                                                                        if(selected) {
+                                                                            StatTree.el.jstree(true).copy(selected.id);
+
+                                                                            // put into localStorage for paste_ext
+                                                                            let node = StatTree.el.jstree(true).get_json(selected.id);
+                                                                            localStorage.stPasteBuffer = JSON.stringify(StatTree.getPasteBufferForNode(node));
+
+                                                                        }
+                                                                    },
+                                                                },
+                                                                "paste": {
+                                                                    "label": "<span class='text-sm'>Paste</span>",
+                                                                    "_disabled": !StatTree.el.jstree(true).can_paste(),
+                                                                    "action": function (obj) {
+                                                                        let selected = StatTree.selectedNode();
+                                                                        if(selected && StatTree.el.jstree(true).can_paste()) {
+                                                                            StatTree.el.jstree(true).paste(selected.id, 'last');
+                                                                        }
+                                                                    },
+                                                                },
+                                                                "paste_ext": {
+                                                                    "label": "<span class='text-sm'>Paste (external)</span>",
+                                                                    "_disabled": !localStorage.stPasteBuffer,
+                                                                    "action": function (obj) {
+                                                                        let selected = StatTree.selectedNode();
+                                                                        if(selected && !!localStorage.stPasteBuffer) {
+                                                                            try {
+                                                                                let parsed = JSON.parse(localStorage.stPasteBuffer);
+                                                                                StatTree.el.jstree(true).create_node(selected.id, parsed);
+                                                                                StatTree.el.jstree(true).open_node(selected.id);
+                                                                            }
+                                                                            catch (e) {
+                                                                                console.error(e);
+                                                                                console.error('Unable to paste. Invalid buffer.')
+                                                                            }
+                                                                        }
+                                                                    },
+                                                                    separator_after: true,
+                                                                },
+                                                                "remove": {
+                                                                    "label": "<span class='text-sm'>Remove</span>",
+                                                                    "action": function (obj) {
+                                                                        let selected = StatTree.selectedNode();
+                                                                        if(selected) {
+                                                                            StatTree.el.jstree(true).delete_node(selected.id);
+                                                                            StatTree.setDirty();
+                                                                        }
+                                                                    }
+                                                                }
+                                                            }
+                                                        }
+                                                    },
+                                                },
+                                                @endif
+                                                "plugins": [
+                                                    "wholerow",
+                                                    @if(!$multiProView)
+                                                    "dnd",
+                                                    "contextmenu",
+                                                    @else
+                                                    "noclose",
+                                                    @endif
+                                                    "state"
+                                                ]
+                                            });
+                                    }, 'json').then(hideMask);
+                                },
+
+                                dropVisualize: function() {
+                                    console.log('redrawn')
+                                    function calculateDropPercent(node, parent = null) {
+                                        let realNode = StatTree.el.jstree(true).get_node(node.id);
+                                        if(!realNode.data) return;
+                                        if(parent && parent.data.lastRefreshCount !== null) {
+                                            if(node.data.lastRefreshCount !== null) {
+                                                realNode.data.dropPercent = (((parent.data.lastRefreshCount - node.data.lastRefreshCount) / parent.data.lastRefreshCount) * 100);
+                                                if(realNode.data.dropPercent) {
+                                                    let element = StatTree.el.jstree(true).get_node(node.id, true), cssClass = '';
+                                                    if(realNode.data.dropPercent > 75) {
+                                                        cssClass = 'drop-76-100';
+                                                    }
+                                                    else if(realNode.data.dropPercent > 50 && realNode.data.dropPercent <= 75) {
+                                                        cssClass = 'drop-51-75';
+                                                    }
+                                                    else if(realNode.data.dropPercent > 25 && realNode.data.dropPercent <= 50) {
+                                                        cssClass = 'drop-26-50';
+                                                    }
+                                                    else if(realNode.data.dropPercent > 0 && realNode.data.dropPercent <= 25) {
+                                                        cssClass = 'drop-0-25';
+                                                    }
+                                                    element.find('>a.jstree-anchor')
+                                                        .addClass('has-drop-visualization')
+                                                        .addClass(cssClass)
+                                                        .attr('data-drop-percent', (realNode.data.dropPercent.toFixed(1)) + '% ⤵');
+                                                }
                                             }
-                                        },
-                                        "remove": {
-                                            "label": "<span class='text-sm'>Remove Clause</span>",
-                                            "action": function (obj) {
-                                                let id = $(obj.reference).find('>div.clause').attr('data-clause-id');
-                                                $('.remove-clause-moe-' + id).find('a[start]').trigger('click');
+                                        }
+                                        for (let i = 0; i < node.children.length; i++) {
+                                            calculateDropPercent(node.children[i], node);
+                                        }
+                                    }
+                                    let raw = this.el.jstree('get_json');
+                                    for (let i = 0; i < raw.length; i++) {
+                                        calculateDropPercent(raw[i]);
+                                    }
+                                },
+
+                                dropped: function(_e, _data) {
+
+                                    if(_data.original && _data.original.data &&
+                                        _data.original.data.type === 'clause') {
+                                        _data.node.data = {
+                                            type:  "stat_tree_line",
+                                            displayLabel: _data.original.data.label,
+                                            columns:  [],
+                                            clause:  {
+                                                clause_id: _data.original.data.clauseId,
+                                                clause_label: _data.original.data.label,
+                                                args: _data.original.data.args
+                                            }
+                                        };
+
+                                        // delete args and query children (from the clauses tree)
+                                        this.el.jstree(true).delete_node(_data.node.children);
+
+                                        // open new parent to reveal dropped node
+                                        this.el.jstree(true).open_node(_data.parent);
+
+                                        // select the new node
+                                        this.el.jstree(true).deselect_all();
+                                        this.el.jstree(true).select_node(_data.node.id);
+                                    }
+                                    else if(_e.type === 'copy_node' && _data.original && _data.original.data &&
+                                        _data.original.data.type === 'stat_tree_line') {
+
+                                        function recursiveCopyData(src, target) {
+                                            src = StatTree.el.jstree(true).get_node(src);
+                                            target = StatTree.el.jstree(true).get_node(target);
+                                            target.data = JSON.parse(JSON.stringify(src.data));
+                                            if(src.children.length === target.children.length) {
+                                                for (let i = 0; i < target.children.length; i++) {
+                                                    recursiveCopyData(src.children[i], target.children[i])
+                                                }
+                                            }
+                                            else {
+                                                console.log('Child count not the same!', src.text, target.text);
+                                            }
+                                        }
+
+                                        recursiveCopyData(_data.original.id, _data.node.id);
+
+                                        // open new parent to reveal dropped node
+                                        this.el.jstree(true).open_node(_data.parent);
+
+                                        // select the new node
+                                        this.el.jstree(true).deselect_all();
+                                        this.el.jstree(true).select_node(_data.node.id);
+                                    }
+
+                                    this.setDirty();
+                                },
+
+                                save: function() {
+                                    setTimeout(() => {
+                                        $.post('{{ route("practice-management.api.statTree.replaceAllLinesJSON") }}', {
+                                            uid: "{{ $statTree->uid }}",
+                                            data: StatTree.payload()
+                                        }, function (response) {
+                                            if(!hasResponseError(response)) {
+                                                toastr.success('All changes saved!');
+                                                /*StatTree.setDirty(false);
+                                                StatTree.load();*/
+                                                fastReload();
                                             }
+                                        }, 'json');
+                                    }, 0);
+                                },
+
+                                log: function() {
+                                    let raw = this.el.jstree('get_json');
+                                    for (let i = 0; i < raw.length; i++) {
+                                        this.logNode(raw[i].id, "");
+                                    }
+                                },
+
+                                logNode: function(_id, _indent) {
+                                    let node = this.el.jstree('get_node', _id);
+                                    let s = [], css = false;
+                                    if(!node.data) css = true;
+                                    s.push($('<div/>').html(node.text).text().substr(0, 10) + '...');
+                                    s.push(node.data ? 'data:' + node.data.type: '%cdata:X');
+                                    console.log("ALIX: " + _indent + s.join('  '), css ? 'color:red' : '');
+                                    for (let i = 0; i < node.children.length; i++) {
+                                        this.logNode(node.children[i], _indent + "\t");
+                                    }
+                                },
+
+                                getPasteBufferForNode(_node) {
+                                    let children = [];
+                                    for (let i = 0; i < _node.children.length; i++) {
+                                        children.push(this.getPasteBufferForNode(_node.children[i]));
+                                    }
+                                    return {
+                                        "text": _node.text,
+                                        "state": {
+                                            "opened": true,
+                                            "disabled": false,
+                                            "selected": false,
                                         },
-                                        "add_arg": {
-                                            "label": "<span class='text-sm'>Add Clause Arg</span>",
-                                            "action": function (obj) {
-                                                let id = $(obj.reference).find('>div.clause').attr('data-clause-id');
-                                                $('.add-clause-arg-moe-' + id).find('a[start]').trigger('click');
+                                        "children": children,
+                                        "data": _node.data
+                                    };
+                                },
+
+                                payload: function() {
+                                    let raw = this.el.jstree('get_json');
+                                    let nodes = [];
+                                    for (let i = 0; i < raw.length; i++) {
+                                        nodes.push(this.nodePayload(raw[i].id));
+                                    }
+                                    return JSON.stringify(nodes);
+                                },
+
+                                nodePayload: function(_id) {
+                                    let node = this.el.jstree('get_node', _id);
+                                    let payload = {};
+                                    if(node.data.type === 'stat_tree_line') {
+                                        payload = {
+                                            displayLabel: node.data.displayLabel,
+                                            clause: node.data.clause,
+                                            columns: node.data.columns,
+                                        };
+                                        let children = [];
+                                        for (let i = 0; i < node.children.length; i++) {
+                                            children.push(this.nodePayload(node.children[i]));
+                                        }
+                                        payload.children = children;
+                                    }
+                                    return payload;
+                                },
+
+                                selectedNode: function() {
+                                    let selected = this.el.jstree('get_selected', true);
+                                    if(selected && selected.length) {
+                                        return selected[0];
+                                    }
+                                    return false;
+                                },
+
+                                getSelectedNodeClauseArgs: function() {
+                                    let selected = this.selectedNode();
+                                    if(selected) {
+                                        return selected.data.clause.args;
+                                    }
+                                    return [];
+                                },
+
+                                getSelectedNodeColumns: function() {
+                                    let selected = this.selectedNode();
+                                    if(selected) {
+                                        return selected.data.columns;
+                                    }
+                                    return [];
+                                },
+
+                                setSelectedNodeColumns: function(columns) {
+                                    let selected = this.selectedNode();
+                                    if(selected) {
+                                        selected.data.columns = columns;
+                                        this.setDirty();
+                                    }
+                                    return [];
+                                },
+
+                                onDeselected: function(_e, _data) {
+                                    linePropsColumn.addClass('d-none');
+                                },
+
+                                onSelected: function(_e, _data) {
+
+                                    @if($multiProView)
+                                    return;
+                                    @endif
+
+                                    let selected = this.selectedNode();
+
+                                    if(selected) console.log(selected.data)
+
+                                    if(!(selected && selected.data && selected.data.type === 'stat_tree_line')) return;
+
+                                    linePropsColumn.removeClass('d-none');
+                                    linePropsColumn.find('[line-label]').text(selected.data.displayLabel);
+
+                                    // fill args
+                                    let tbody = linePropsColumn.find('[line-args]');
+                                    let clause = selected.data.clause;
+                                    tbody.empty();
+                                    if(clause) {
+                                        let edit = true;
+                                        let args = clause.args ? clause.args : [];
+                                        // $('<tr/>').addClass(edit ? '' : 'opacity-60 parent-arg').append($('<td/>').attr('colspan', 3).addClass('font-weight-bold text-sm ' + (edit ? '' : 'text-secondary')).text(clause.clause_label)).appendTo(tbody);
+                                        if(!args.length) {
+                                            $('<tr/>').addClass(edit ? '' : 'opacity-60 parent-arg').append($('<td/>').attr('colspan', 3).addClass('pl-3 text-secondary text-sm').text('No args')).appendTo(tbody);
+                                        }
+                                        else {
+                                            for (let i = 0; i < args.length; i++) {
+                                                $('<tr/>')
+                                                    .addClass(edit ? '' : 'opacity-60 parent-arg')
+                                                    .append(
+                                                        $('<td/>')
+                                                            .addClass('pl-3')
+                                                            .html(args[i].arg_text + ' <span class="text-secondary text-sm">(' + args[i].field_type + ')</span>')
+                                                    )
+                                                    .append(
+                                                        $('<td/>')
+                                                            .text(args[i].default_value)
+                                                            .append(edit ? '<a href="#" class="edit-arg-value ml-2" data-index="' + i + '"><i class="fa fa-edit text-primary text-sm on-hover-opaque"></i></a>': '')
+                                                    )
+                                                    .append(
+                                                        $('<td/>')
+                                                            .text(args[i].access_level)
+                                                            .append(edit ? '<a href="#" class="edit-arg-access-level ml-2" data-index="' + i + '"><i class="fa fa-edit text-primary text-sm on-hover-opaque"></i></a>': '')
+                                                    )
+                                                    .appendTo(tbody);
                                             }
                                         }
                                     }
-                                }
-                            },
-                        },
-                        "plugins": [
-                            "wholerow",
-                            "dnd",
-                            "contextmenu"
-                        ]
-                    });
-
-                $('#report-column').find('input[stag-suggest][name="displayKey"]')
-                    .off('stag-suggest-selected')
-                    .on('stag-suggest-selected', (_e, _input, _data) => {
-                        let columns = getSelectedNodeColumns();
-                        columns.push({
-                            label: _data.label,
-                            display_key: _data.text
-                        });
-                        setSelectedNodeColumns(columns);
-                        onSelected();
-                        autoSaveChanges();
-                        $(_input).val('').focus();
-                        return false;
-                    });
-
-                $('#btn-save-tree')
-                    .off('click')
-                    .on('click', function() {
-                        saveStatTree();
-                        return false;
-                    });
-
-                $('#refresh-counts')
-                    .off('click')
-                    .on('click', function() {
-                        showMask();
-                        $.post("{{ route('practice-management.api.statTree.refreshTreeCountQueries') }}", {
-                            statTreeID: "{{ $statTree->id }}"
-                        }, function (response) {
-                            if(!hasResponseError(response)) {
-                                // fastLoad("{{ route('practice-management.statTrees.view.edit', $statTree) }}");
-                                // need to fix this!
-                                showMask();
-                                window.top.location.reload();
-                            }
-                        }, 'json');
-                        return false;
-                    });
-
-                $(document)
-                    .off('click', '.move-column-up')
-                    .on('click', '.move-column-up', function() {
-                        let columns = getSelectedNodeColumns();
-                        let index = +($(this).attr('data-index'));
-                        if(index > 0) {
-                            let x = columns[index - 1];
-                            columns[index - 1] = columns[index];
-                            columns[index] = x;
-                        }
-                        setSelectedNodeColumns(columns);
-                        onSelected();
-                        autoSaveChanges();
-                        return false;
-                    });
-
-                $(document)
-                    .off('click', '.move-column-down')
-                    .on('click', '.move-column-down', function() {
-                        let columns = getSelectedNodeColumns();
-                        let index = +($(this).attr('data-index'));
-                        if(index < columns.length - 1) {
-                            let x = columns[index + 1];
-                            columns[index + 1] = columns[index];
-                            columns[index] = x;
-                        }
-                        setSelectedNodeColumns(columns);
-                        onSelected();
-                        autoSaveChanges();
-                        return false;
-                    });
-
-                $(document)
-                    .off('click', '.remove-column')
-                    .on('click', '.remove-column', function() {
-                        let columns = getSelectedNodeColumns();
-                        columns.splice(+($(this).attr('data-index')), 1);
-                        setSelectedNodeColumns(columns);
-                        onSelected();
-                        autoSaveChanges();
-                        return false;
-                    });
-
-                $(document)
-                    .off('click', '.clause-expand-all')
-                    .on('click', '.clause-expand-all', function() {
-                        $('#clauses-view-{{$statTree->id}}').jstree('open_all');
-                        return false;
-                    });
-                $(document)
-                    .off('click', '.clause-collapse-all')
-                    .on('click', '.clause-collapse-all', function() {
-                        $('#clauses-view-{{$statTree->id}}').jstree('close_all');
-                        return false;
-                    });
-
-                $(document)
-                    .off('click', '.tree-expand-all')
-                    .on('click', '.tree-expand-all', function() {
-                        $('#stat-tree-view-{{$statTree->id}}').jstree('open_all');
-                        return false;
-                    });
-                $(document)
-                    .off('click', '.tree-collapse-all')
-                    .on('click', '.tree-collapse-all', function() {
-                        $('#stat-tree-view-{{$statTree->id}}').jstree('close_all');
-                        return false;
-                    });
-
-                initMoes();
 
-            }
-            addMCInitializer('stat-tree-edit-page', init, '#statTreeEdit')
-        }).call(window);
-    </script>
+                                    // fill columns
+                                    tbody = linePropsColumn.find('[line-columns]');
+                                    let columns = StatTree.getSelectedNodeColumns();
+                                    tbody.empty();
+                                    if(columns && columns.length) {
+                                        for (let i = 0; i < columns.length; i++) {
+                                            $('<tr/>')
+                                                .append($('<td/>').text(i + 1))
+                                                .append($('<td/>').text(columns[i].label))
+                                                .append($('<td/>').text(columns[i].display_key))
+                                                .append(
+                                                    $('<td/>')
+                                                        .addClass('text-right text-nowrap')
+                                                        .append(i > 0 ? '<a href="#" class="move-column-up mr-2" data-index="' + i + '"><i class="fa fa-arrow-up text-primary on-hover-opaque"></i></a>': '')
+                                                        .append(i < columns.length - 1 ? '<a href="#" class="move-column-down mr-2" data-index="' + i + '"><i class="fa fa-arrow-down text-primary on-hover-opaque"></i></a>': '')
+                                                        .append('<a href="#" class="remove-column" data-index="' + i + '"><i class="fa fa-trash-alt text-danger on-hover-opaque"></i></a>')
+                                                )
+                                                .appendTo(tbody);
+                                        }
+                                    }
+
+                                    initMoes();
+
+                                },
+                            };
+
+                            linePropsColumn.find('input[stag-suggest][name="displayKey"]')
+                                .off('stag-suggest-selected')
+                                .on('stag-suggest-selected', (_e, _input, _data) => {
+                                    let columns = StatTree.getSelectedNodeColumns();
+                                    columns.push({
+                                        label: _data.label,
+                                        display_key: _data.text
+                                    });
+                                    $(_input).val('').focus();
+                                    StatTree.setSelectedNodeColumns(columns);
+                                    StatTree.onSelected();
+                                    StatTree.setDirty();
+                                    return false;
+                                });
+
+                            $(document)
+                                .off('click', '.move-column-up')
+                                .on('click', '.move-column-up', function() {
+                                    let columns = StatTree.getSelectedNodeColumns();
+                                    let index = +($(this).attr('data-index'));
+                                    if(index > 0) {
+                                        let x = columns[index - 1];
+                                        columns[index - 1] = columns[index];
+                                        columns[index] = x;
+                                    }
+                                    StatTree.setSelectedNodeColumns(columns);
+                                    StatTree.onSelected();
+                                    StatTree.setDirty();
+                                    return false;
+                                });
+
+                            $(document)
+                                .off('click', '.move-column-down')
+                                .on('click', '.move-column-down', function() {
+                                    let columns = StatTree.getSelectedNodeColumns();
+                                    let index = +($(this).attr('data-index'));
+                                    if(index < columns.length - 1) {
+                                        let x = columns[index + 1];
+                                        columns[index + 1] = columns[index];
+                                        columns[index] = x;
+                                    }
+                                    StatTree.setSelectedNodeColumns(columns);
+                                    StatTree.onSelected();
+                                    StatTree.setDirty();
+                                    return false;
+                                });
+
+                            $(document)
+                                .off('click', '.remove-column')
+                                .on('click', '.remove-column', function() {
+                                    let columns = StatTree.getSelectedNodeColumns();
+                                    columns.splice(+($(this).attr('data-index')), 1);
+                                    StatTree.setSelectedNodeColumns(columns);
+                                    StatTree.onSelected();
+                                    StatTree.setDirty();
+                                    return false;
+                                });
+
+                            $(document)
+                                .off('click', '.edit-arg-value')
+                                .on('click', '.edit-arg-value', function() {
+                                    let args = StatTree.getSelectedNodeClauseArgs();
+                                    let value = window.prompt('New value:', args[+($(this).attr('data-index'))].default_value || '');
+                                    if(value !== null) {
+                                        args[+($(this).attr('data-index'))].default_value = value;
+                                        StatTree.onSelected();
+                                        StatTree.setDirty();
+                                    }
+                                    return false;
+                                });
+
+                            $(document)
+                                .off('click', '.edit-arg-access-level')
+                                .on('click', '.edit-arg-access-level', function() {
+                                    let args = StatTree.getSelectedNodeClauseArgs();
+                                    let accessLevel = window.prompt('New access level (USER, PRO or ADMIN):', args[+($(this).attr('data-index'))].access_level || '');
+                                    if(accessLevel !== null) {
+                                        args[+($(this).attr('data-index'))].access_level = accessLevel;
+                                        StatTree.onSelected();
+                                        StatTree.setDirty();
+                                    }
+                                    return false;
+                                });
 
+                            $(document)
+                                .off('click', '.clause-expand-all')
+                                .on('click', '.clause-expand-all', function() {
+                                    $('#clauses-view-{{$statTree->id}}').jstree('open_all');
+                                    return false;
+                                });
+                            $(document)
+                                .off('click', '.clause-collapse-all')
+                                .on('click', '.clause-collapse-all', function() {
+                                    $('#clauses-view-{{$statTree->id}}').jstree('close_all');
+                                    return false;
+                                });
+
+                            $(document)
+                                .off('click', '.tree-expand-all')
+                                .on('click', '.tree-expand-all', function() {
+                                    $('#stat-tree-edit-{{$statTree->id}}').jstree('open_all');
+                                    return false;
+                                });
+                            $(document)
+                                .off('click', '.tree-collapse-all')
+                                .on('click', '.tree-collapse-all', function() {
+                                    $('#stat-tree-edit-{{$statTree->id}}').jstree('close_all');
+                                    return false;
+                                });
+
+                            $(document)
+                                .off('click', '#btn-save-tree')
+                                .on('click', '#btn-save-tree', function() {
+                                    StatTree.save();
+                                    return false;
+                                });
+
+                            $(document)
+                                .off('click', '.log-tree')
+                                .on('click', '.log-tree', function() {
+                                    StatTree.log();
+                                    return false;
+                                });
+
+                            $(document)
+                                .off('click', '.btn-add-pro')
+                                .on('click', '.btn-add-pro', function() {
+                                    multiViewPros.push($(this).closest('[moe]').find('#proUid').val());
+                                    fastLoad('{{ route('practice-management.statTrees.view.edit', $statTree) }}?multi-pro=1&multi-pro-uids=' + multiViewPros.join(','));
+                                    return false;
+                                });
+
+                            $(document)
+                                .off('input change paste', '.frm-clause-add-edit input[name="question"], .frm-clause-add-edit input[name="answer"]')
+                                .on('input change paste', '.frm-clause-add-edit input[name="question"], .frm-clause-add-edit input[name="answer"]', function() {
+                                    let form = $(this).closest('.frm-clause-add-edit');
+                                    let label = $.trim(form.find('input[name="question"]').val()) + ' ' +
+                                        $.trim(form.find('input[name="answer"]').val());
+                                    form.find('input[name="label"]').val(label);
+                                });
+
+                            $('#refresh-counts')
+                                .off('click')
+                                .on('click', function() {
+                                    if(StatTree.changed) {
+                                        window.alert('Cannot refresh counts while the tree has unsaved changes.');
+                                        return;
+                                    }
+                                    showMask();
+                                    $.post("{{ route('practice-management.api.statTree.refreshTreeCountQueries') }}", {
+                                        statTreeID: "{{ $statTree->id }}"
+                                    }, function (response) {
+                                        if(!hasResponseError(response)) {
+                                            fastReload();
+                                        }
+                                    }, 'json').then(hideMask);
+                                    return false;
+                                });
+
+                            @if($multiProView)
+                            $('[count-required]').each(function() {
+                                $.post('{{route('practice-management.api.statTree.getCountsForPro')}}', {
+                                    uid: '{{$statTree->uid}}',
+                                    proUid: $(this).attr('data-pro-uid')
+                                }, _data => {
+                                    for(let x in _data) {
+                                        $('.multi-pro-stats-table tr[data-line-uid="' + x + '"] td[data-pro-uid="' + $(this).attr('data-pro-uid') + '"] .count').text(_data[x]);
+                                    }
+                                }, 'json');
+                            });
+                            @endif
+
+                            ClausesTree.load();
+                            StatTree.load();
+                            initMoes();
+
+                            addMCHook('reloadClausesTree', function() {
+                                ClausesTree.load();
+                            });
+
+                            initVSplitter('stat-tree-edit-1', $('.clauses-column'), $('.tree-column'));
+                            @if(!$multiProView)
+                                initVSplitter('stat-tree-edit-2', $('.tree-lhs-column'), $('.tree-rhs-column'));
+                            @endif
+
+                        }
+                        addMCInitializer('stat-tree-edit-page', init, '#statTreeEdit-{{$statTree->id}}');
+                    }).call(window);
+                </script>
+            </div>
+        </div>
+    </div>
 @endsection

+ 7 - 0
resources/views/app/stat-tree/stat-trees/sub/view.blade.php

@@ -0,0 +1,7 @@
+@extends ('layouts/template')
+@section('content')
+    <div class="mcp-theme-1">
+        <h3 class="font-size-16 text-seondary mb-3 border-bottom p-3">{{ $statTree->name }}</h3>
+        @include('app.stat-tree.view')
+    </div>
+@endsection

+ 4 - 1
resources/views/app/stat-tree/tree-edit-v2.blade.php

@@ -6,6 +6,7 @@ if (!function_exists('renderStatTreeLineNodeEdit')) {
         <li class="jstree-open">
         <div class="d-inline-flex align-items-baseline st-node stat-tree-line" data-line-id="{{$line->id}}" data-line-uid="{{$line->uid}}">
             <textarea class="d-none" data-name="columns">{!! $line->columnsJSON() !!}</textarea>
+            <textarea class="d-none" data-name="args">{!! $line->argsJSON() !!}</textarea>
             <span>{{$line->displayLabel()}}</span>
             <span class="ml-2 text-secondary">({{(is_null($line->last_refresh_count) ? '-' : $line->last_refresh_count)}})</span>
         </div>
@@ -51,7 +52,9 @@ if (!function_exists('renderStatTreeLineNodeEdit_Moes')) {
         <?php
     }
 }
-$statTree = \App\Models\StatTree::where('slug', $slug)->first();
+if(!@$statTree && !!@$slug) {
+    $statTree = \App\Models\StatTree::where('slug', $slug)->first();
+}
 ?>
 @if(@$statTree)
     <div class="d-flex align-items-baseline mb-2">

+ 143 - 0
resources/views/app/stat-tree/view.blade.php

@@ -0,0 +1,143 @@
+{{-- resolve --}}
+@if(!@$statTree)
+    @if(!@$slug)
+        <div class="alert alert-danger m-0">Slug missing!</div>
+    @endif
+    <?php $statTree = \App\Models\StatTree::where('slug', $slug)->first(); ?>
+    @if(!@$statTree)
+        <div class="alert alert-danger m-0">Stat tree no found!</div>
+    @endif
+@endif
+<link rel="stylesheet" href="/jstree/themes/default/style.min.css" />
+<script src="/jstree/jstree.min.js"></script>
+<div id="statTreeView-{{$statTree->id}}">
+    <div class="stat-tree-view stat-tree-user-view overflow-auto min-height-300px" id="stat-tree-view-{{$statTree->id}}"></div>
+</div>
+<script>
+    (function() {
+        function init() {
+
+            (function ($, undefined) {
+                "use strict";
+                $.jstree.plugins.noclose = function () {
+                    this.close_node = $.noop;
+                };
+            })(jQuery);
+
+            // stat tree
+            let StatTree = {
+                el: $('#stat-tree-view-{{$statTree->id}}'),
+                changed: false,
+
+                onSelected: function(_e, _data) {
+                    @if(!@$reportTarget)
+                        openDynamicStagPopup('/practice-management/stat-tree-lines/view-data/' + _data.node.data.uid,
+                            null,
+                            _data.node.data.displayLabel,
+                            false,
+                            'medium');
+                    @else
+                        $.get('/practice-management/stat-tree-lines/view-data/' + _data.node.data.uid, _data => {
+                            $('{{$reportTarget}}').html(_data);
+                        });
+                    @endif
+                },
+
+                load: function() {
+
+                    // destroy if existing
+                    try {
+                        this.el.jstree('destroy');
+                    }
+                    catch (e) {}
+
+                    this.el = $('#stat-tree-view-{{$statTree->id}}');
+
+                    // get data
+                    showMask();
+                    $.get('{{ route('practice-management.statTrees.view.linesJSON', $statTree) }}', _data => {
+
+                        // init tree with data
+                        this.el
+                            .on('redraw.jstree', () => { StatTree.dropVisualize(); })
+                            .on('select_node.jstree', (_e, _data) => { StatTree.onSelected(_e, _data); })
+                            .jstree({
+                                "core": {
+                                    "multiple": false,
+                                    "animation": 0,
+                                    'data': _data
+                                },
+                                "contextmenu": {
+                                    show_at_node: false,
+                                    items: function (node) {
+                                        if(node.data.type === 'stat_tree_line') { // stat_tree_line
+                                            return {
+                                                "data": {
+                                                    "label": "<span class='text-sm'>View Data</span>",
+                                                    "action": function (obj) {
+                                                        openDynamicStagPopup('/practice-management/stat-tree-lines/view-data/' + node.data.uid,
+                                                            null,
+                                                            node.data.displayLabel,
+                                                            false,
+                                                            'medium');
+                                                    }
+                                                },
+                                            }
+                                        }
+                                    },
+                                },
+                                "plugins": [
+                                    "wholerow",
+                                    "contextmenu",
+                                    "noclose"
+                                ]
+                            });
+                    }, 'json').then(hideMask);
+                },
+
+                dropVisualize: function() {
+                    console.log('redrawn')
+                    function calculateDropPercent(node, parent = null) {
+                        let realNode = StatTree.el.jstree(true).get_node(node.id);
+                        if(!realNode.data) return;
+                        if(parent && parent.data.lastRefreshCount !== null) {
+                            if(node.data.lastRefreshCount !== null) {
+                                realNode.data.dropPercent = (((parent.data.lastRefreshCount - node.data.lastRefreshCount) / parent.data.lastRefreshCount) * 100);
+                                if(realNode.data.dropPercent) {
+                                    let element = StatTree.el.jstree(true).get_node(node.id, true), cssClass = '';
+                                    if(realNode.data.dropPercent > 75) {
+                                        cssClass = 'drop-76-100';
+                                    }
+                                    else if(realNode.data.dropPercent > 50 && realNode.data.dropPercent <= 75) {
+                                        cssClass = 'drop-51-75';
+                                    }
+                                    else if(realNode.data.dropPercent > 25 && realNode.data.dropPercent <= 50) {
+                                        cssClass = 'drop-26-50';
+                                    }
+                                    else if(realNode.data.dropPercent > 0 && realNode.data.dropPercent <= 25) {
+                                        cssClass = 'drop-0-25';
+                                    }
+                                    element.find('>a.jstree-anchor')
+                                        .addClass('has-drop-visualization')
+                                        .addClass(cssClass)
+                                        .attr('data-drop-percent', (realNode.data.dropPercent.toFixed(1)) + '% ⤵');
+                                }
+                            }
+                        }
+                        for (let i = 0; i < node.children.length; i++) {
+                            calculateDropPercent(node.children[i], node);
+                        }
+                    }
+                    let raw = this.el.jstree('get_json');
+                    for (let i = 0; i < raw.length; i++) {
+                        calculateDropPercent(raw[i]);
+                    }
+                },
+            };
+
+            StatTree.load();
+
+        }
+        addMCInitializer('stat-tree-view-{{$statTree->id}}', init, '#statTreeView-{{$statTree->id}}');
+    }).call(window);
+</script>

+ 7 - 3
routes/web.php

@@ -327,16 +327,19 @@ Route::middleware('pro.auth')->group(function () {
             Route::name('view.')->prefix('view/{statTree}')->group(function () {
                 Route::get('', 'StatTreeController@dashboard2')->name('dashboard');
                 Route::get('edit', 'StatTreeController@edit')->name('edit');
+                Route::get('clausesJSON', 'StatTreeController@clausesJSON')->name('clausesJSON');
+                Route::get('linesJSON', 'StatTreeController@linesJSON')->name('linesJSON');
 
                 Route::get('old', 'StatTreeController@dashboard')->name('dashboard2');
             });
+            Route::post('instantiate/{statTree}', 'StatTreeController@instantiate')->name('instantiate');
+            Route::post('clone/{statTree}', 'StatTreeController@clone')->name('clone');
         });
         Route::name('statTreeLines.')->prefix('stat-tree-lines/')->group(function () {
             Route::get('', 'StatTreeLineController@list')->name('list');
             Route::name('view.')->prefix('view/{statTreeLine}')->group(function () {
                 Route::get('', 'StatTreeLineController@dashboard')->name('dashboard');
             });
-            Route::post('report-columns/{line}', 'StatTreeLineController@reportColumns')->name('report-columns');
             Route::get('view-data/{line}', 'StatTreeLineController@viewData')->name('view-data');
         });
 
@@ -365,12 +368,13 @@ Route::middleware('pro.auth')->group(function () {
                 Route::post('update-basic', 'StatTreeController@updateBasic')->name('updateBasic');
                 Route::post('refresh-count', 'StatTreeController@refreshCount')->name('refreshCount');
                 Route::post('replace-all-lines', 'StatTreeController@replaceAllLines')->name('replaceAllLines');
-                Route::post('refresh-tree-count-queries', 'StatTreeLineController@refreshTreeCountQueries')->name('refreshTreeCountQueries');
+                Route::post('replace-all-lines-json', 'StatTreeController@replaceAllLinesJSON')->name('replaceAllLinesJSON');
+                Route::post('refresh-tree-count-queries', 'StatTreeController@refreshTreeCountQueries')->name('refreshTreeCountQueries');
+                Route::post('get-counts-for-pro', 'StatTreeController@getCountsForPro')->name('getCountsForPro');
             });
 
             //Stat Tree Line
             Route::name('statTreeLine.')->prefix('stat-tree-line/')->group(function () {
-                Route::post('replace-all-report-columns', 'StatTreeLineController@replaceAllReportColumns')->name('replaceAllReportColumns');
                 Route::post('refresh-count-query', 'StatTreeLineController@refreshCountQuery')->name('refreshCountQuery');
                 Route::post('create', 'StatTreeLineController@create')->name('create');
                 Route::post('remove', 'StatTreeLineController@remove')->name('remove');