890 lines
55 KiB
PHP
890 lines
55 KiB
PHP
@extends('layouts.vertical', ["page_title"=> "Add & Edit Campaigns"])
|
||
|
||
@section('css')
|
||
<link href="{{asset('assets/libs/select2/select2.min.css')}}" rel="stylesheet" type="text/css" />
|
||
<link href="{{asset('assets/libs/multiselect/multiselect.min.css')}}" rel="stylesheet" type="text/css" />
|
||
<link href="{{ URL::asset('assets/libs/flatpickr/flatpickr.min.css')}}" rel="stylesheet" type="text/css" />
|
||
<link href="{{ URL::asset('assets/libs/dropify/dropify.min.css')}}" rel="stylesheet" type="text/css" />
|
||
@endsection
|
||
|
||
@section('content')
|
||
<div class="container-fluid">
|
||
@if(session()->has('message'))
|
||
<div class="alert alert-success mt-3">
|
||
{{ session()->get('message') }}
|
||
</div>
|
||
@endif
|
||
<div class="row mt-3">
|
||
<div class="col-xl-12">
|
||
<div class="card">
|
||
<form method="post" enctype="multipart/form-data">
|
||
@csrf
|
||
<div class="card-body">
|
||
<div class="row">
|
||
<div class="col-md-6 mb-3">
|
||
<h4 class="page-title">Add / Edit {{$title}}</h4>
|
||
</div>
|
||
<div class="col-md-6 mb-3">
|
||
<div class="float-end">
|
||
<a href="{{$base_url}}s" class="btn btn-dark waves-effect waves-light">Back</a>
|
||
<button type="submit" class="btn btn-success waves-effect waves-light">Submit</button>
|
||
</div>
|
||
</div>
|
||
|
||
@foreach($fields as $field => $props)
|
||
@if($props['type'] != 'hidden')
|
||
<div class="col-md-{{isset($props['fullwidth'])?'12':'6'}} mb-3">
|
||
<label for="field-{{$field}}" class="form-label">{{$props['title']}} {!!isset($props['required'])&&$props['required'] ? '<span class="text-danger">*</span>':''!!} </label>
|
||
@endif
|
||
|
||
@switch($props['type'])
|
||
@case('hidden')
|
||
<input type="hidden" id="field-{{$field}}" name="{{$field}}" value="{{$mode=='edit'&&isset($item->{$field})?$item->{$field}:''}}">
|
||
@break
|
||
@case('text')
|
||
@case('email')
|
||
@case('number')
|
||
@case('tel')
|
||
<input type="{{$props['type']}}" id="field-{{$field}}" name="{{$field}}" class="form-control" placeholder="{{ $props['placeholder'] ?? 'Enter '.$props['title'] }}" value="{{$mode=='edit'&&isset($item->{$field})?$item->{$field}:''}}" {{isset($props['required'])&&$props['required']?'required':'' }}>
|
||
@break
|
||
@case('date')
|
||
<input type="text" id="field-{{$field}}" name="{{$field}}" class="form-control datepicker" placeholder="Enter {{$props['title']}}" value="{{$mode=='edit'&&isset($item->{$field})?$item->{$field}:''}}" {{isset($props['required'])&&$props['required']?'required':'' }}>
|
||
@break
|
||
@case('datetime')
|
||
<input type="text" id="field-{{$field}}" name="{{$field}}" class="form-control datetimepicker" placeholder="Enter {{$props['title']}}" value="{{$mode=='edit'&&isset($item->{$field})?$item->{$field}:''}}" {{isset($props['required'])&&$props['required']?'required':'' }}>
|
||
@break
|
||
@case('textarea')
|
||
<textarea id="field-{{$field}}" name="{{$field}}" class="form-control" placeholder="Enter {{$props['title']}}" {{isset($props['required'])&&$props['required']?'required':'' }}>{{$mode=='edit'&&isset($item->{$field})?$item->{$field}:''}}</textarea>
|
||
@break
|
||
@case('select')
|
||
@php
|
||
$__selected = (isset($item->{$field}) && $item->{$field} !== '' && $item->{$field} !== null)
|
||
? $item->{$field}
|
||
: ($props['default'] ?? null);
|
||
@endphp
|
||
<select class="form-control" id="field-{{$field}}" name="{{$field}}" {{isset($props['required'])&&$props['required']?'required':'' }}>
|
||
<option value=""> -- Select {{$props['title']}} -- </option>
|
||
@foreach($props['values'] as $k => $v)
|
||
<option value="{{$k}}" {{ (string)$__selected === (string)$k ? 'selected="selected"':''}}>{{$v}}</option>
|
||
@endforeach
|
||
</select>
|
||
@break
|
||
@case('multiselect')
|
||
@php
|
||
$__selectedList = (isset($item->{$field}) && is_array($item->{$field}) && count($item->{$field}))
|
||
? $item->{$field}
|
||
: ($props['default'] ?? []);
|
||
@endphp
|
||
<select class="form-control select2-multiple" data-toggle="select2" data-width="100%" multiple="multiple" data-placeholder="Choose {{$props['title']}}" id="field-{{$field}}" name="{{$field}}[]" {{isset($props['required'])&&$props['required']?'required':'' }}>
|
||
<option value=""> -- Select {{$props['title']}} -- </option>
|
||
@foreach($props['values'] as $k => $v)
|
||
<option value="{{$k}}" {{is_array($__selectedList) && in_array($k, $__selectedList) ? 'selected="selected"':''}}>{{$v}}</option>
|
||
@endforeach
|
||
</select>
|
||
@break
|
||
@case('image')
|
||
<div class="mt-3" id="file_wrapper">
|
||
<input type="file" name="{{$field}}" class="dropify" data-default-file="{{$mode=='edit'&&isset($item->{$field})?$item->{$field}:''}}" data-allowed-file-extensions="png jpg jpeg gif" />
|
||
<p class="text-muted text-center mt-2 mb-0">500MB Max File size</p>
|
||
</div>
|
||
@break
|
||
@case('multiimage')
|
||
<div class="mt-3" id="file_wrapper">
|
||
<input type="file" name="{{$field}}[]" multiple class="dropify" data-allowed-file-extensions="png jpg jpeg gif" />
|
||
<p class="text-muted text-center mt-2 mb-0">500MB Max File size</p>
|
||
</div>
|
||
@break
|
||
@case('file')
|
||
<div class="mt-3" id="file_wrapper">
|
||
<input type="file" name="{{$field}}" class="dropify" data-default-file="{{$mode=='edit'?$item->{$field}:''}}" data-allowed-file-extensions="png jpg jpeg gif mp4 pdf mp3 zip csv" />
|
||
<p class="text-muted text-center mt-2 mb-0">1GB Max File size</p>
|
||
</div>
|
||
@break
|
||
@case('ranking_attributes')
|
||
@php
|
||
$rankingRows = [];
|
||
if ($mode === 'edit') {
|
||
$rawRanking = $item->ranking_attributes ?? null;
|
||
if (is_string($rawRanking) && $rawRanking !== '') {
|
||
$decodedRanking = json_decode($rawRanking, true);
|
||
$rawRanking = is_array($decodedRanking) ? $decodedRanking : [];
|
||
}
|
||
if (is_object($rawRanking)) {
|
||
$rawRanking = json_decode(json_encode($rawRanking), true);
|
||
}
|
||
if (is_array($rawRanking)) {
|
||
foreach ($rawRanking as $rankRow) {
|
||
if (is_object($rankRow)) {
|
||
$rankRow = (array) $rankRow;
|
||
}
|
||
if (!is_array($rankRow)) {
|
||
continue;
|
||
}
|
||
$rowCode = trim((string) ($rankRow['code'] ?? ''));
|
||
$rowValue = array_key_exists('value', $rankRow)
|
||
? trim((string) $rankRow['value'])
|
||
: '';
|
||
if ($rowCode === '') {
|
||
continue;
|
||
}
|
||
$rankingRows[] = [
|
||
'code' => $rowCode,
|
||
'value' => $rowValue,
|
||
];
|
||
}
|
||
}
|
||
}
|
||
$rankingAttrMeta = $props['attributes_meta'] ?? [];
|
||
$optionAttrTypes = ['select', 'multiselect', 'checkbox', 'radio'];
|
||
@endphp
|
||
<div class="col-md-12">
|
||
<p class="text-muted small mb-2">Filter ranking by user attributes. Add multiple filters; each needs an attribute and value.</p>
|
||
<button type="button" class="btn btn-info btn-xs float-end" id="btn-add-ranking-attribute">Add Filter</button>
|
||
</div>
|
||
<div id="ranking-attributes-wrapper" class="col-md-12 mt-2">
|
||
@if(count($rankingRows) > 0)
|
||
<div class="row mb-2 ranking-attributes-header">
|
||
<div class="col-md-5"><label class="form-label">Attribute</label></div>
|
||
<div class="col-md-5"><label class="form-label">Value</label></div>
|
||
<div class="col-md-2"></div>
|
||
</div>
|
||
@endif
|
||
@foreach($rankingRows as $rankRow)
|
||
@php
|
||
$rowCode = $rankRow['code'];
|
||
$rowValue = $rankRow['value'];
|
||
$rowMeta = $rankingAttrMeta[$rowCode] ?? null;
|
||
$useOptions = $rowMeta
|
||
&& in_array(($rowMeta['type'] ?? ''), $optionAttrTypes, true)
|
||
&& !empty($rowMeta['options']);
|
||
@endphp
|
||
<div class="row mb-2 ranking-attribute-row">
|
||
<div class="col-md-5">
|
||
<select class="form-control ranking-attr-code" name="ranking_attribute_code[]">
|
||
<option value=""> -- Select Attribute -- </option>
|
||
@if($rowCode !== '' && !isset($props['attributes'][$rowCode]))
|
||
<option value="{{ $rowCode }}" selected>{{ $rowCode }}</option>
|
||
@endif
|
||
@foreach(($props['attributes'] ?? []) as $attrCode => $attrLabel)
|
||
<option value="{{ $attrCode }}" {{ (string)$rowCode === (string)$attrCode ? 'selected' : '' }}>{{ $attrLabel }}</option>
|
||
@endforeach
|
||
</select>
|
||
</div>
|
||
<div class="col-md-5 ranking-attr-value-wrap">
|
||
@if($useOptions)
|
||
<select class="form-control ranking-attr-value" name="ranking_attribute_value[]">
|
||
<option value=""> -- Select Value -- </option>
|
||
@if($rowValue !== '' && !isset($rowMeta['options'][$rowValue]))
|
||
<option value="{{ $rowValue }}" selected>{{ $rowValue }}</option>
|
||
@endif
|
||
@foreach($rowMeta['options'] as $optVal => $optLabel)
|
||
<option value="{{ $optVal }}" {{ (string)$rowValue === (string)$optVal ? 'selected' : '' }}>{{ $optLabel }}</option>
|
||
@endforeach
|
||
</select>
|
||
@else
|
||
<input type="text" class="form-control ranking-attr-value" name="ranking_attribute_value[]" value="{{ $rowValue }}" placeholder="Enter value">
|
||
@endif
|
||
</div>
|
||
<div class="col-md-2">
|
||
<button type="button" class="btn btn-danger waves-effect waves-light btn-remove-ranking-attribute"><i class="mdi mdi-close"></i></button>
|
||
</div>
|
||
</div>
|
||
@endforeach
|
||
</div>
|
||
@break
|
||
@case('mapping')
|
||
<div>
|
||
<button type="button" class="btn btn-info btn-xs float-end" id="btn-add-mapping">Add Metric</button>
|
||
</div>
|
||
<table class="table mb-0" id="table-metrics">
|
||
<thead>
|
||
<tr>
|
||
<th>Metric</th>
|
||
<th>Display</th>
|
||
<th>Sort</th>
|
||
<th>Weight</th>
|
||
<th>Label Text</th>
|
||
<th>Unit Type</th>
|
||
<th>Rules</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@if($mode=='edit' && isset($item->{$field}) && is_array($item->{$field}))
|
||
@foreach($item->{$field} as $map)
|
||
@php
|
||
$mapRules = is_object($map) ? ($map->rules ?? []) : ($map['rules'] ?? []);
|
||
if (is_string($mapRules)) {
|
||
$mapRules = json_decode($mapRules, true) ?: [];
|
||
} elseif (!is_array($mapRules)) {
|
||
$mapRules = json_decode(json_encode($mapRules), true) ?: [];
|
||
}
|
||
$mapRulesCount = is_array($mapRules) ? count($mapRules) : 0;
|
||
@endphp
|
||
<tr>
|
||
<td>
|
||
<select class="form-control metric-select" name="metric[]">
|
||
<option value=""> -- Select Metric -- </option>
|
||
@foreach($props['metrics'] as $k => $v)
|
||
<option value="{{$k}}" {{isset($map->metric) && $map->metric == $k ? 'selected="selected"':''}}>{{$v}}</option>
|
||
@endforeach
|
||
</select>
|
||
</td>
|
||
<td><input type="text" name="display_unit[]" class="form-control" value="{{isset($map->display)?$map->display:''}}"></td>
|
||
<td><input type="number" name="metric_sort[]" class="form-control" value="{{isset($map->sort)?$map->sort:0}}"></td>
|
||
<td><input type="number" name="weight[]" class="form-control" step="any" value="{{isset($map->weight)?$map->weight:1}}"></td>
|
||
<td><input type="text" name="label_text[]" class="form-control" value="{{isset($map->label_text)?$map->label_text:'units'}}"></td>
|
||
<td>
|
||
<select class="form-control" name="unit_type[]">
|
||
<option value="sum" {{ (!isset($map->unit_type) || $map->unit_type === 'sum') ? 'selected' : '' }}>Sum</option>
|
||
<option value="average" {{ (isset($map->unit_type) && $map->unit_type === 'average') ? 'selected' : '' }}>Average</option>
|
||
</select>
|
||
</td>
|
||
<td>
|
||
<input type="hidden" name="metric_rules[]" class="metric-rules-input" value='@json($mapRules)'>
|
||
<input type="hidden" name="metric_rules_interval[]" class="metric-rules-interval-input" value="{{ $map->rules_interval ?? 'overall' }}">
|
||
<input type="hidden" name="metric_rules_custom_start[]" class="metric-rules-custom-start-input" value="{{ $map->rules_custom_start ?? '' }}">
|
||
<input type="hidden" name="metric_rules_custom_end[]" class="metric-rules-custom-end-input" value="{{ $map->rules_custom_end ?? '' }}">
|
||
<button type="button" class="btn btn-sm {{ $mapRulesCount > 0 ? 'btn-success' : 'btn-outline-info' }} btn-metric-rules">
|
||
Rules{{ $mapRulesCount > 0 ? ' ('.$mapRulesCount.')' : '' }}
|
||
</button>
|
||
</td>
|
||
<th><button type="button" class="btn btn-danger waves-effect waves-light btn-remove"><i class="mdi mdi-close"></i></button></th>
|
||
</tr>
|
||
@endforeach
|
||
@endif
|
||
</tbody>
|
||
</table>
|
||
@break
|
||
@case('combined_metrics')
|
||
<div><button type="button" class="btn btn-info btn-xs float-end" id="btn-add-combined-metric">Add Metric</button></div>
|
||
<table class="table mb-0" id="table-combined">
|
||
<thead>
|
||
<tr>
|
||
<th width="10%">Type</th>
|
||
<th width="10%">Title</th>
|
||
<th width="28%">Include Metrics</th>
|
||
<th width="8%">Weight</th>
|
||
<th width="10%">Label Text</th>
|
||
<th width="10%">Unit Type</th>
|
||
<th width="8%">Rules</th>
|
||
<th width="5%"></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@php
|
||
$combinedMetricsRows = [];
|
||
if ($mode == 'edit' && isset($item->{$field}) && $item->{$field} !== '' && $item->{$field} !== '[]') {
|
||
$decoded = json_decode($item->{$field}, true);
|
||
$combinedMetricsRows = is_array($decoded) ? $decoded : [];
|
||
}
|
||
@endphp
|
||
@if(!empty($combinedMetricsRows))
|
||
@foreach($combinedMetricsRows as $i => $map)
|
||
@php
|
||
if (is_object($map)) {
|
||
$map = json_decode(json_encode($map), true);
|
||
}
|
||
if (!is_array($map)) {
|
||
$map = [];
|
||
}
|
||
$combinedRules = $map['rules'] ?? [];
|
||
if (is_string($combinedRules)) {
|
||
$combinedRules = json_decode($combinedRules, true) ?: [];
|
||
} elseif (!is_array($combinedRules)) {
|
||
$combinedRules = json_decode(json_encode($combinedRules), true) ?: [];
|
||
}
|
||
$combinedRulesCount = is_array($combinedRules) ? count($combinedRules) : 0;
|
||
$combinedRulesInterval = $map['rules_interval'] ?? 'overall';
|
||
$combinedRulesCustomStart = $map['rules_custom_start'] ?? '';
|
||
$combinedRulesCustomEnd = $map['rules_custom_end'] ?? '';
|
||
if (empty($map['rules_interval']) && !empty($combinedRules[0]['interval'])) {
|
||
$combinedRulesInterval = $combinedRules[0]['interval'];
|
||
$combinedRulesCustomStart = $combinedRules[0]['custom_start'] ?? '';
|
||
$combinedRulesCustomEnd = $combinedRules[0]['custom_end'] ?? '';
|
||
}
|
||
@endphp
|
||
<tr>
|
||
<td>
|
||
<select class="form-control" name="combined_metrics[type][]" required>
|
||
<option value=""> -- Select Type -- </option>
|
||
<option value="prescribers" {{($map['type'] ?? '') == 'prescribers' ? 'selected':''}}>Prescribers (no of)</option>
|
||
<option value="unique_new" {{($map['type'] ?? '') == 'unique_new' ? 'selected':''}}>Unique New Prescribers (launch)</option>
|
||
<option value="combination" {{($map['type'] ?? '') == 'combination' ? 'selected':''}}>Combination (Total)</option>
|
||
</select>
|
||
</td>
|
||
<td><input type="text" name="combined_metrics[title][]" class="form-control" value="{{$map['title'] ?? ''}}"></td>
|
||
<td>
|
||
<select class="form-control select2-multiple" data-toggle="select2" data-width="100%" multiple="multiple" data-placeholder="Choose Metrics" name="combined_metrics[fields][{{$i}}][]">
|
||
<option value=""> -- Select Multiple Metrics -- </option>
|
||
@foreach($fields['metrics']['metrics'] as $k => $v)
|
||
<option value="{{$k}}" {{isset($map['fields']) && is_array($map['fields']) && in_array($k, $map['fields']) ? 'selected="selected"':''}}>{{$v}}</option>
|
||
@endforeach
|
||
</select>
|
||
</td>
|
||
<td><input type="number" name="combined_metrics[weight][]" class="form-control" step="any" value="{{$map['weight'] ?? 1}}"></td>
|
||
<td><input type="text" name="combined_metrics[label_text][]" class="form-control" value="{{$map['label_text'] ?? 'units'}}"></td>
|
||
<td>
|
||
<select class="form-control" name="combined_metrics[unit_type][]">
|
||
<option value="sum" {{ (!isset($map['unit_type']) || $map['unit_type'] === 'sum') ? 'selected' : '' }}>Sum</option>
|
||
<option value="average" {{ (isset($map['unit_type']) && $map['unit_type'] === 'average') ? 'selected' : '' }}>Average</option>
|
||
</select>
|
||
</td>
|
||
<td>
|
||
<input type="hidden" name="combined_metrics[rules][]" class="metric-rules-input" value='@json($combinedRules)'>
|
||
<input type="hidden" name="combined_metrics[rules_interval][]" class="metric-rules-interval-input" value="{{ $combinedRulesInterval }}">
|
||
<input type="hidden" name="combined_metrics[rules_custom_start][]" class="metric-rules-custom-start-input" value="{{ $combinedRulesCustomStart }}">
|
||
<input type="hidden" name="combined_metrics[rules_custom_end][]" class="metric-rules-custom-end-input" value="{{ $combinedRulesCustomEnd }}">
|
||
<button type="button" class="btn btn-sm {{ $combinedRulesCount > 0 ? 'btn-success' : 'btn-outline-info' }} btn-metric-rules">
|
||
Rules{{ $combinedRulesCount > 0 ? ' ('.$combinedRulesCount.')' : '' }}
|
||
</button>
|
||
</td>
|
||
<th><button type="button" class="btn btn-danger waves-effect waves-light btn-remove-combined"><i class="mdi mdi-close"></i></button></th>
|
||
</tr>
|
||
@endforeach
|
||
@endif
|
||
</tbody>
|
||
</table>
|
||
@break
|
||
@endswitch
|
||
|
||
@if($props['type'] != 'hidden')
|
||
</div>
|
||
@endif
|
||
@endforeach
|
||
|
||
<div class="col-md-12 mb-3">
|
||
<div class="float-end">
|
||
<a href="{{$base_url}}s" class="btn btn-dark waves-effect waves-light">Back</a>
|
||
<button type="submit" class="btn btn-success waves-effect waves-light">Submit</button>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal fade" id="metric-rules-modal" tabindex="-1" aria-labelledby="metric-rules-modal-label" aria-hidden="true">
|
||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="metric-rules-modal-label">Metric Calculation Rules</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p class="text-muted mb-3">Metric: <strong id="metric-rules-modal-metric-name"></strong></p>
|
||
|
||
<div class="card mb-3" id="rules-interval-section">
|
||
<div class="card-body py-2">
|
||
<div class="row align-items-end">
|
||
<div class="col-md-4">
|
||
<label class="form-label mb-1">Interval</label>
|
||
<select class="form-control form-control-sm" id="metric-rules-interval">
|
||
<option value="overall">Overall</option>
|
||
<option value="monthly">Monthly</option>
|
||
<option value="weekly">Weekly</option>
|
||
<option value="daily">Daily</option>
|
||
<option value="custom">Custom</option>
|
||
</select>
|
||
</div>
|
||
<div class="col-md-8" id="metric-rules-custom-dates" style="display:none;">
|
||
<div class="row">
|
||
<div class="col-6">
|
||
<label class="form-label mb-1">Start Date</label>
|
||
<input type="text" class="form-control form-control-sm" id="metric-rules-custom-start" placeholder="Start date">
|
||
</div>
|
||
<div class="col-6">
|
||
<label class="form-label mb-1">End Date</label>
|
||
<input type="text" class="form-control form-control-sm" id="metric-rules-custom-end" placeholder="End date">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
@foreach(['Linear', 'Staggered', 'Milestone'] as $ruleType)
|
||
<div class="card mb-3 rule-section" data-rule-type="{{ $ruleType }}">
|
||
<div class="card-header py-2">
|
||
<div class="form-check mb-0">
|
||
<input type="checkbox" class="form-check-input rule-enabled" id="rule-enabled-{{ strtolower($ruleType) }}">
|
||
<label class="form-check-label fw-semibold" for="rule-enabled-{{ strtolower($ruleType) }}">{{ $ruleType }}</label>
|
||
</div>
|
||
</div>
|
||
<div class="card-body rule-section-body py-2">
|
||
<div class="table-responsive">
|
||
<table class="table table-sm mb-2 rule-ranges">
|
||
<thead>
|
||
<tr>
|
||
<th>Start</th>
|
||
<th>End</th>
|
||
<th>Weight</th>
|
||
<th width="50"></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody></tbody>
|
||
</table>
|
||
</div>
|
||
<button type="button" class="btn btn-sm btn-outline-primary btn-add-range" data-rule-type="{{ $ruleType }}">Add Range</button>
|
||
@if($ruleType === 'Linear')
|
||
<small class="text-muted d-block mt-2">Linear: points = units × weight when units ≥ start</small>
|
||
@elseif($ruleType === 'Staggered')
|
||
<small class="text-muted d-block mt-2">Staggered: adds weight when start ≤ units ≤ end</small>
|
||
@else
|
||
<small class="text-muted d-block mt-2">Milestone: adds weight when units ≥ start (cumulative)</small>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
@endforeach
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Cancel</button>
|
||
<button type="button" class="btn btn-primary" id="btn-save-metric-rules">Save Rules</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
@endsection
|
||
|
||
@section('script')
|
||
<script src="{{asset('assets/libs/select2/select2.min.js')}}"></script>
|
||
<script src="{{asset('assets/libs/multiselect/multiselect.min.js')}}"></script>
|
||
<script src="{{ URL::asset('assets/libs/flatpickr/flatpickr.min.js')}}"></script>
|
||
<script src="{{ URL::asset('assets/libs/dropify/dropify.min.js')}}"></script>
|
||
<script type="text/javascript">
|
||
$(document).ready(function(){
|
||
$('[data-toggle="select2"]').select2();
|
||
function toggleUserAttributesField() {
|
||
var show = $('#field-show_user_attributes').val() === 'yes';
|
||
$('#field-user_attributes').closest('.mb-3').toggle(show);
|
||
}
|
||
toggleUserAttributesField();
|
||
$('#field-show_user_attributes').on('change', toggleUserAttributesField);
|
||
$('.dropify').dropify({
|
||
messages: {
|
||
'default': 'Drag and drop a file here or click',
|
||
'replace': 'Drag and drop or click to replace',
|
||
'remove': 'Remove',
|
||
'error': 'Ooops, something wrong appended.'
|
||
},
|
||
error: {
|
||
'fileSize': 'The file size is too big (500MB max).'
|
||
}
|
||
});
|
||
|
||
$('.datepicker').flatpickr({
|
||
dateFormat: "m/d/Y"
|
||
});
|
||
$('.datetimepicker').flatpickr({
|
||
enableTime: true,
|
||
dateFormat: "m/d/Y H:i"
|
||
});
|
||
|
||
function initMetricSelect2($context) {
|
||
($context || $('#table-metrics')).find('select.metric-select').each(function() {
|
||
let $select = $(this);
|
||
|
||
if ($select.hasClass('select2-hidden-accessible')) {
|
||
$select.select2('destroy');
|
||
}
|
||
|
||
$select.select2({
|
||
width: '100%',
|
||
placeholder: '-- Select Metric --',
|
||
allowClear: true
|
||
});
|
||
});
|
||
}
|
||
|
||
initMetricSelect2();
|
||
|
||
function bindRemove() {
|
||
$('.btn-remove').off('click').on('click', function(){
|
||
$(this).closest('tr').remove();
|
||
});
|
||
}
|
||
|
||
bindRemove();
|
||
|
||
const RULE_TYPES = ['Linear', 'Staggered', 'Milestone'];
|
||
let $activeRulesRow = null;
|
||
let metricRulesModal = null;
|
||
|
||
function initRuleDatepickers() {
|
||
['#metric-rules-custom-start', '#metric-rules-custom-end'].forEach(function(selector) {
|
||
const el = document.querySelector(selector);
|
||
if (!el) {
|
||
return;
|
||
}
|
||
if (el._flatpickr) {
|
||
el._flatpickr.destroy();
|
||
}
|
||
flatpickr(el, { dateFormat: 'm/d/Y' });
|
||
});
|
||
}
|
||
|
||
function toggleCustomDatesModal() {
|
||
const isCustom = $('#metric-rules-interval').val() === 'custom';
|
||
$('#metric-rules-custom-dates').toggle(isCustom);
|
||
}
|
||
|
||
function getRulesIntervalFromRow($row) {
|
||
return {
|
||
interval: $row.find('.metric-rules-interval-input').val() || 'overall',
|
||
custom_start: $row.find('.metric-rules-custom-start-input').val() || '',
|
||
custom_end: $row.find('.metric-rules-custom-end-input').val() || '',
|
||
};
|
||
}
|
||
|
||
function setRulesIntervalOnRow($row, intervalData) {
|
||
$row.find('.metric-rules-interval-input').val(intervalData.interval || 'overall');
|
||
$row.find('.metric-rules-custom-start-input').val(intervalData.custom_start || '');
|
||
$row.find('.metric-rules-custom-end-input').val(intervalData.custom_end || '');
|
||
}
|
||
|
||
function loadIntervalIntoModal(intervalData) {
|
||
$('#metric-rules-interval').val(intervalData.interval || 'overall');
|
||
$('#metric-rules-custom-start').val(intervalData.custom_start || '');
|
||
$('#metric-rules-custom-end').val(intervalData.custom_end || '');
|
||
toggleCustomDatesModal();
|
||
initRuleDatepickers();
|
||
}
|
||
|
||
function collectIntervalFromModal() {
|
||
const interval = $('#metric-rules-interval').val() || 'overall';
|
||
return {
|
||
interval: interval,
|
||
custom_start: interval === 'custom' ? ($('#metric-rules-custom-start').val() || '') : '',
|
||
custom_end: interval === 'custom' ? ($('#metric-rules-custom-end').val() || '') : '',
|
||
};
|
||
}
|
||
|
||
function parseRulesJson($row) {
|
||
const $input = $row.find('.metric-rules-input');
|
||
let raw = $input.val() || '[]';
|
||
if (typeof raw === 'string') {
|
||
raw = raw.trim();
|
||
}
|
||
try {
|
||
let parsed = JSON.parse(raw);
|
||
if (typeof parsed === 'string') {
|
||
parsed = JSON.parse(parsed);
|
||
}
|
||
return Array.isArray(parsed) ? parsed : [];
|
||
} catch (e) {
|
||
return [];
|
||
}
|
||
}
|
||
|
||
function getMetricNameFromRow($row) {
|
||
const name = $row.find('select.metric-select option:selected').text().trim();
|
||
if (name && name !== '-- Select Metric --') {
|
||
return name;
|
||
}
|
||
const title = $row.find('input[name*="combined_metrics[title]"]').val();
|
||
if (title && String(title).trim() !== '') {
|
||
return String(title).trim();
|
||
}
|
||
const typeName = $row.find('select[name*="combined_metrics[type]"] option:selected').text().trim();
|
||
return typeName && typeName !== '-- Select Type --' ? typeName : 'Metric';
|
||
}
|
||
|
||
function renderRangeRow(range) {
|
||
range = range || { start: 0, end: 0, weight: 0 };
|
||
return `<tr class="range-row">
|
||
<td><input type="number" class="form-control form-control-sm range-start" step="any" value="${range.start ?? 0}"></td>
|
||
<td><input type="number" class="form-control form-control-sm range-end" step="any" value="${range.end ?? 0}"></td>
|
||
<td><input type="number" class="form-control form-control-sm range-weight" step="any" value="${range.weight ?? 0}"></td>
|
||
<td><button type="button" class="btn btn-sm btn-danger btn-remove-range"><i class="mdi mdi-close"></i></button></td>
|
||
</tr>`;
|
||
}
|
||
|
||
function toggleRuleSection(ruleType) {
|
||
const $section = $(`.rule-section[data-rule-type="${ruleType}"]`);
|
||
const enabled = $section.find('.rule-enabled').is(':checked');
|
||
$section.find('.rule-section-body').toggle(enabled);
|
||
}
|
||
|
||
function loadRulesIntoModal(rules) {
|
||
RULE_TYPES.forEach(function(ruleType) {
|
||
const rule = rules.find(function(r) { return r.rule_type === ruleType; });
|
||
let ranges = rule && rule.ranges ? rule.ranges : [];
|
||
if (!Array.isArray(ranges)) {
|
||
ranges = Object.values(ranges);
|
||
}
|
||
const $section = $(`.rule-section[data-rule-type="${ruleType}"]`);
|
||
$section.find('.rule-enabled').prop('checked', ranges.length > 0);
|
||
const $tbody = $section.find('.rule-ranges tbody');
|
||
$tbody.empty();
|
||
if (ranges.length > 0) {
|
||
ranges.forEach(function(range) {
|
||
$tbody.append(renderRangeRow(range));
|
||
});
|
||
} else {
|
||
$tbody.append(renderRangeRow());
|
||
}
|
||
toggleRuleSection(ruleType);
|
||
});
|
||
}
|
||
|
||
function collectRulesFromModal() {
|
||
const rules = [];
|
||
RULE_TYPES.forEach(function(ruleType) {
|
||
const $section = $(`.rule-section[data-rule-type="${ruleType}"]`);
|
||
if (!$section.find('.rule-enabled').is(':checked')) {
|
||
return;
|
||
}
|
||
const ranges = [];
|
||
$section.find('.rule-ranges tbody tr.range-row').each(function() {
|
||
ranges.push({
|
||
start: parseFloat($(this).find('.range-start').val()) || 0,
|
||
end: parseFloat($(this).find('.range-end').val()) || 0,
|
||
weight: parseFloat($(this).find('.range-weight').val()) || 0,
|
||
});
|
||
});
|
||
if (ranges.length > 0) {
|
||
rules.push({
|
||
rule_type: ruleType,
|
||
ranges: ranges,
|
||
});
|
||
}
|
||
});
|
||
return rules;
|
||
}
|
||
|
||
function updateRulesButtonState($row) {
|
||
const rules = parseRulesJson($row);
|
||
const $btn = $row.find('.btn-metric-rules');
|
||
if (rules.length > 0) {
|
||
$btn.removeClass('btn-outline-info').addClass('btn-success').text('Rules (' + rules.length + ')');
|
||
} else {
|
||
$btn.removeClass('btn-success').addClass('btn-outline-info').text('Rules');
|
||
}
|
||
}
|
||
|
||
function initRulesButtons() {
|
||
$('#table-metrics tbody tr, #table-combined tbody tr').each(function() {
|
||
updateRulesButtonState($(this));
|
||
});
|
||
}
|
||
|
||
initRulesButtons();
|
||
|
||
$(document).on('click', '.btn-metric-rules', function() {
|
||
$activeRulesRow = $(this).closest('tr');
|
||
$('#metric-rules-modal-metric-name').text(getMetricNameFromRow($activeRulesRow));
|
||
loadRulesIntoModal(parseRulesJson($activeRulesRow));
|
||
loadIntervalIntoModal(getRulesIntervalFromRow($activeRulesRow));
|
||
if (!metricRulesModal) {
|
||
metricRulesModal = new bootstrap.Modal(document.getElementById('metric-rules-modal'));
|
||
}
|
||
metricRulesModal.show();
|
||
});
|
||
|
||
$(document).on('change', '.rule-enabled', function() {
|
||
toggleRuleSection($(this).closest('.rule-section').data('rule-type'));
|
||
});
|
||
|
||
$(document).on('change', '#metric-rules-interval', function() {
|
||
toggleCustomDatesModal();
|
||
});
|
||
|
||
$(document).on('click', '.btn-add-range', function() {
|
||
const ruleType = $(this).data('rule-type');
|
||
const $tbody = $(`.rule-section[data-rule-type="${ruleType}"] .rule-ranges tbody`);
|
||
$tbody.append(renderRangeRow());
|
||
});
|
||
|
||
$(document).on('click', '.btn-remove-range', function() {
|
||
const $tbody = $(this).closest('tbody');
|
||
if ($tbody.find('tr.range-row').length <= 1) {
|
||
$(this).closest('tr.range-row').find('input').val(0);
|
||
return;
|
||
}
|
||
$(this).closest('tr.range-row').remove();
|
||
});
|
||
|
||
$('#btn-save-metric-rules').on('click', function() {
|
||
if (!$activeRulesRow) {
|
||
return;
|
||
}
|
||
const rules = collectRulesFromModal();
|
||
$activeRulesRow.find('.metric-rules-input').val(JSON.stringify(rules));
|
||
setRulesIntervalOnRow($activeRulesRow, collectIntervalFromModal());
|
||
updateRulesButtonState($activeRulesRow);
|
||
if (metricRulesModal) {
|
||
metricRulesModal.hide();
|
||
}
|
||
});
|
||
|
||
$('#btn-add-mapping').on('click', function(){
|
||
$('table#table-metrics tbody').append(`<tr>
|
||
<td>
|
||
<select class="form-control metric-select" name="metric[]">
|
||
<option value=""> -- Select Metric -- </option>
|
||
@foreach($fields['metrics']['metrics'] as $k => $v)
|
||
<option value="{{$k}}">{{$v}}</option>
|
||
@endforeach
|
||
</select>
|
||
</td>
|
||
<td><input type="text" name="display_unit[]" class="form-control" value=""></td>
|
||
<td><input type="number" name="metric_sort[]" class="form-control" value="0"></td>
|
||
<td><input type="number" name="weight[]" class="form-control" step="any" value="1"></td>
|
||
<td><input type="text" name="label_text[]" class="form-control" value="units"></td>
|
||
<td>
|
||
<select class="form-control" name="unit_type[]">
|
||
<option value="sum" selected>Sum</option>
|
||
<option value="average">Average</option>
|
||
</select>
|
||
</td>
|
||
<td>
|
||
<input type="hidden" name="metric_rules[]" class="metric-rules-input" value="[]">
|
||
<input type="hidden" name="metric_rules_interval[]" class="metric-rules-interval-input" value="overall">
|
||
<input type="hidden" name="metric_rules_custom_start[]" class="metric-rules-custom-start-input" value="">
|
||
<input type="hidden" name="metric_rules_custom_end[]" class="metric-rules-custom-end-input" value="">
|
||
<button type="button" class="btn btn-sm btn-outline-info btn-metric-rules">Rules</button>
|
||
</td>
|
||
<th><button type="button" class="btn btn-danger waves-effect waves-light btn-remove"><i class="mdi mdi-close"></i></button></th>
|
||
</tr>`);
|
||
initMetricSelect2($('table#table-metrics tbody tr').last());
|
||
bindRemove();
|
||
});
|
||
|
||
let combinedMetricCount = 100;
|
||
$('#btn-add-combined-metric').on('click', function(){
|
||
$('table#table-combined tbody').append(`<tr>
|
||
<td>
|
||
<select class="form-control" name="combined_metrics[type][${combinedMetricCount}]" required>
|
||
<option value=""> -- Select Type -- </option>
|
||
<option value="prescribers">Prescribers (no of)</option>
|
||
<option value="unique_new">Unique New Prescribers (launch)</option>
|
||
<option value="combination">Combination (Total)</option>
|
||
</select>
|
||
</td>
|
||
<td><input type="text" name="combined_metrics[title][${combinedMetricCount}]" class="form-control" value=""></td>
|
||
<td>
|
||
<select class="form-control select2-multiple" data-toggle="select2" data-width="100%" multiple="multiple" data-placeholder="Choose Metrics" name="combined_metrics[fields][${combinedMetricCount}][]">
|
||
<option value=""> -- Select Multiple Metrics -- </option>
|
||
@foreach($fields['metrics']['metrics'] as $k => $v)
|
||
<option value="{{$k}}">{{$v}}</option>
|
||
@endforeach
|
||
</select>
|
||
</td>
|
||
<td><input type="number" name="combined_metrics[weight][${combinedMetricCount}]" class="form-control" step="any" value="1"></td>
|
||
<td><input type="text" name="combined_metrics[label_text][${combinedMetricCount}]" class="form-control" value="units"></td>
|
||
<td>
|
||
<select class="form-control" name="combined_metrics[unit_type][${combinedMetricCount}]">
|
||
<option value="sum" selected>Sum</option>
|
||
<option value="average">Average</option>
|
||
</select>
|
||
</td>
|
||
<td>
|
||
<input type="hidden" name="combined_metrics[rules][${combinedMetricCount}]" class="metric-rules-input" value="[]">
|
||
<input type="hidden" name="combined_metrics[rules_interval][${combinedMetricCount}]" class="metric-rules-interval-input" value="overall">
|
||
<input type="hidden" name="combined_metrics[rules_custom_start][${combinedMetricCount}]" class="metric-rules-custom-start-input" value="">
|
||
<input type="hidden" name="combined_metrics[rules_custom_end][${combinedMetricCount}]" class="metric-rules-custom-end-input" value="">
|
||
<button type="button" class="btn btn-sm btn-outline-info btn-metric-rules">Rules</button>
|
||
</td>
|
||
<th><button type="button" class="btn btn-danger waves-effect waves-light btn-remove-combined"><i class="mdi mdi-close"></i></button></th>
|
||
</tr>`);
|
||
$('[data-toggle="select2"]').select2();
|
||
combinedMetricCount++;
|
||
});
|
||
|
||
$(document).on('click', '.btn-remove-combined', function(){
|
||
$(this).closest('tr').remove();
|
||
});
|
||
|
||
const rankingAttrMeta = @json($fields['ranking_attributes']['attributes_meta'] ?? []);
|
||
const rankingAttrOptions = @json($fields['ranking_attributes']['attributes'] ?? []);
|
||
const rankingOptionTypes = ['select', 'multiselect', 'checkbox', 'radio'];
|
||
|
||
function escapeHtml(str) {
|
||
return String(str)
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"');
|
||
}
|
||
|
||
function buildRankingAttrCodeSelect(selectedCode) {
|
||
let html = '<select class="form-control ranking-attr-code" name="ranking_attribute_code[]"><option value=""> -- Select Attribute -- </option>';
|
||
Object.keys(rankingAttrOptions).forEach(function(code) {
|
||
const selected = code === selectedCode ? ' selected' : '';
|
||
html += '<option value="' + escapeHtml(code) + '"' + selected + '>' + escapeHtml(rankingAttrOptions[code]) + '</option>';
|
||
});
|
||
html += '</select>';
|
||
return html;
|
||
}
|
||
|
||
function buildRankingAttrValueField(code, selectedValue) {
|
||
const meta = rankingAttrMeta[code];
|
||
const useOptions = meta
|
||
&& rankingOptionTypes.indexOf(meta.type) !== -1
|
||
&& meta.options
|
||
&& Object.keys(meta.options).length > 0;
|
||
|
||
if (useOptions) {
|
||
let html = '<select class="form-control ranking-attr-value" name="ranking_attribute_value[]"><option value=""> -- Select Value -- </option>';
|
||
Object.keys(meta.options).forEach(function(optVal) {
|
||
const selected = String(optVal) === String(selectedValue || '') ? ' selected' : '';
|
||
html += '<option value="' + escapeHtml(optVal) + '"' + selected + '>' + escapeHtml(meta.options[optVal]) + '</option>';
|
||
});
|
||
html += '</select>';
|
||
return html;
|
||
}
|
||
|
||
return '<input type="text" class="form-control ranking-attr-value" name="ranking_attribute_value[]" value="' + escapeHtml(selectedValue || '') + '" placeholder="Enter value">';
|
||
}
|
||
|
||
function syncRankingAttrValue($row, keepValue) {
|
||
const code = $row.find('.ranking-attr-code').val() || '';
|
||
const currentValue = keepValue
|
||
? ($row.find('.ranking-attr-value').val() || '')
|
||
: '';
|
||
$row.find('.ranking-attr-value-wrap').html(buildRankingAttrValueField(code, currentValue));
|
||
}
|
||
|
||
function appendRankingAttributeRow(code, value) {
|
||
if ($('#ranking-attributes-wrapper .ranking-attributes-header').length === 0) {
|
||
$('#ranking-attributes-wrapper').prepend(`
|
||
<div class="row mb-2 ranking-attributes-header">
|
||
<div class="col-md-5"><label class="form-label">Attribute</label></div>
|
||
<div class="col-md-5"><label class="form-label">Value</label></div>
|
||
<div class="col-md-2"></div>
|
||
</div>
|
||
`);
|
||
}
|
||
const rowHtml = `<div class="row mb-2 ranking-attribute-row">
|
||
<div class="col-md-5">${buildRankingAttrCodeSelect(code || '')}</div>
|
||
<div class="col-md-5 ranking-attr-value-wrap">${buildRankingAttrValueField(code || '', value || '')}</div>
|
||
<div class="col-md-2">
|
||
<button type="button" class="btn btn-danger waves-effect waves-light btn-remove-ranking-attribute"><i class="mdi mdi-close"></i></button>
|
||
</div>
|
||
</div>`;
|
||
$('#ranking-attributes-wrapper').append(rowHtml);
|
||
}
|
||
|
||
$('#btn-add-ranking-attribute').on('click', function() {
|
||
appendRankingAttributeRow('', '');
|
||
});
|
||
|
||
$(document).on('click', '.btn-remove-ranking-attribute', function() {
|
||
$(this).closest('.ranking-attribute-row').remove();
|
||
if ($('#ranking-attributes-wrapper .ranking-attribute-row').length === 0) {
|
||
$('#ranking-attributes-wrapper .ranking-attributes-header').remove();
|
||
}
|
||
});
|
||
|
||
$(document).on('change', '.ranking-attr-code', function() {
|
||
syncRankingAttrValue($(this).closest('.ranking-attribute-row'), false);
|
||
});
|
||
});
|
||
</script>
|
||
@endsection
|
||
|