diff --git a/.cursor/rules/laravel-blade-forms.mdc b/.cursor/rules/laravel-blade-forms.mdc new file mode 100644 index 00000000..ae1677ae --- /dev/null +++ b/.cursor/rules/laravel-blade-forms.mdc @@ -0,0 +1,55 @@ +--- +description: Rapport form Blade templates — resolution order, patterns, guardrails +globs: resources/views/form/**/*.blade.php +alwaysApply: false +--- + +# Form views (`resources/views/form`) + +## View selection (must match `FormController::deliverform`) + +Resolution order for **form** links: + +1. `form.{partition}.{form_id}` — Blade at `form/{partition}/{form_id}.blade.php` (controller uses `is_file` on a deploy-specific base path; if checks fail, overrides are skipped). +2. `form.{form_id}` — `form/{form_id}.blade.php` at folder root. +3. `form.common-enrollmentform` if `$formdata->form_type === 'enrollment_form'`. +4. `form.common-planexecution` if `$formdata->form_type === 'plan_vs_execution'`. +5. Else `form.common`. + +**List** (`link_type == 'list'`): `form.{partition}.list-{form_id}` → `form.list-{form_id}` → `form.{partition}.list` → `form.list`. History views use similar `View::exists` patterns in the same controller. + +If a new Blade’s name/path does not match this cascade, it will not load. + +## Default implementation + +- Prefer copying or extending **`form.common`**: `@foreach ($questions as $question)` and `@switch ($question->type)` (`hidden`, `text`, `email`, `tel`, `date`, `number`, `textarea`, `dropdown`, `radio`, `radiometrix`, `checkbox`, `file`, `multitextbox`). +- Use **`$question->code`** for `name` / `id` (`id="field-{{ $question->code }}"`). Checkbox names use `[]` where existing templates do. +- Preserve **`$questions`**, **`$form` / `$formdata`**, **`$form_id`**, **`$id`**, and optional **`$mode`**, **`$demo`**, **`$additional`** — do not rename without updating the controller and all templates. + +## Layout and assets + +- Reuse `@include('layouts.shared/title-meta', ...)`, `layouts.shared/head-css`, `layouts.shared/footer-script`, existing SweetAlert2 paths, `#pageloader`, and Bootstrap card / `account-pages` structure used in sibling blades. + +## Submit behaviour + +- Standard submit form: `id="report-form"`, `method="POST"`, `action="/form/submit"`, `enctype="multipart/form-data"`, `@csrf`, hidden `form_id`, optional hidden `id`. Client-side submit typically POSTs `FormData` to **`/form/ajaxsubmit`** with SweetAlert2 feedback — match `form.common` unless the page is explicitly non-submit (e.g. list-only). + +### Required submit handler + notifications (SweetAlert2) + +- For any submit-capable form Blade (`link_type == 'form'`), prefer using the exact `$('#report-form').on('submit', function(event){ ... })` pattern from `form.common`: + - prevent default submit + - show/hide `#pageloader` + - `POST` `new FormData($('#report-form')[0])` to `/form/ajaxsubmit` +- Use **SweetAlert2 (`Swal.fire`)** for all user-facing success/error messages in JS (no `alert()`, no console-only messages). + +## Partition / client overrides + +- Client-specific templates live under **`form/rapport-{client}/`**. File name must be **`{form_id}.blade.php`** (or the `list-` / `-history` variants the controller checks) so Laravel’s `form.{partition}.{form_id}` resolves. + +## Do not + +- Add a new stack (SPA framework, new global layout) or new submit URLs without **routes** and **`FormController`** changes. +- Remove approval / read-only behaviour (`is_approval`, file upload vs preview) when copying from `common` unless the task explicitly requires it. +- Refactor unrelated modules or shared layouts “while you’re here”. + +When unsure, open **`form.common`** and the closest sibling template (e.g. **`form.common-planexecution`**, **`form.list`**) and mirror their structure. diff --git a/app/Exports/ZenovaReportExport.php b/app/Exports/ZenovaReportExport.php new file mode 100644 index 00000000..d98d0a10 --- /dev/null +++ b/app/Exports/ZenovaReportExport.php @@ -0,0 +1,153 @@ +groups = $groups; + $this->body = $body; + $this->totals = $totals; + } + + public function array(): array + { + $groupRow = []; + $labelRow = []; + foreach ($this->groups as $group) { + $first = true; + foreach ($group['columns'] as $column) { + $groupRow[] = $first ? $group['label'] : ''; + $labelRow[] = $column['label']; + $first = false; + } + } + + $rows = [$groupRow, $labelRow]; + foreach ($this->body as $line) { + $rows[] = $line; + } + if (!empty($this->totals)) { + $rows[] = $this->totals; + } + + return $rows; + } + + public function registerEvents(): array + { + return [ + AfterSheet::class => function (AfterSheet $event) { + $sheet = $event->sheet->getDelegate(); + $rows = $this->array(); + $rowCount = max(1, count($rows)); + $colCount = 0; + foreach ($this->groups as $group) { + $colCount += count($group['columns']); + } + if ($colCount < 1) { + return; + } + + $lastCol = Coordinate::stringFromColumnIndex($colCount); + $colors = [ + 'demo' => 'FFF2CC', + 'day' => 'DDEBF7', + 'yesterday' => 'E2EFDA', + 'today' => 'C6EFCE', + 'mtd' => 'FCE4D6', + 'ytd' => 'D9E2F3', + ]; + + $sheet->getStyle('A1:' . $lastCol . $rowCount)->applyFromArray([ + 'font' => [ + 'name' => 'Calibri', + 'size' => 11, + ], + 'borders' => [ + 'allBorders' => [ + 'borderStyle' => Border::BORDER_THIN, + 'color' => ['rgb' => 'B0B0B0'], + ], + ], + 'alignment' => [ + 'vertical' => Alignment::VERTICAL_CENTER, + ], + ]); + + $col = 1; + foreach ($this->groups as $group) { + $span = count($group['columns']); + $start = Coordinate::stringFromColumnIndex($col); + $end = Coordinate::stringFromColumnIndex($col + $span - 1); + if ($span > 1) { + $sheet->mergeCells($start . '1:' . $end . '1'); + } + $sheet->getStyle($start . '1:' . $end . '2')->applyFromArray([ + 'font' => [ + 'bold' => true, + 'color' => ['rgb' => '000000'], + ], + 'fill' => [ + 'fillType' => Fill::FILL_SOLID, + 'startColor' => ['rgb' => $colors[$group['tone']] ?? 'FFFFFF'], + ], + 'alignment' => [ + 'horizontal' => Alignment::HORIZONTAL_CENTER, + 'vertical' => Alignment::VERTICAL_CENTER, + 'wrapText' => true, + ], + ]); + + foreach ($group['columns'] as $offset => $column) { + $letter = Coordinate::stringFromColumnIndex($col + $offset); + $width = $column['type'] === 'text' ? 24 : 16; + $sheet->getColumnDimension($letter)->setWidth($width); + if ($rowCount < 3) { + continue; + } + $dataRange = $letter . '3:' . $letter . $rowCount; + if ($column['type'] === 'text') { + $sheet->getStyle($dataRange)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_LEFT); + } elseif ($column['type'] === 'percent') { + $sheet->getStyle($dataRange)->getNumberFormat()->setFormatCode('0.00"%"'); + $sheet->getStyle($dataRange)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT); + } else { + $sheet->getStyle($dataRange)->getNumberFormat()->setFormatCode('#,##0'); + $sheet->getStyle($dataRange)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT); + } + } + + $col += $span; + } + + $sheet->getRowDimension(1)->setRowHeight(22); + $sheet->getRowDimension(2)->setRowHeight(36); + $sheet->freezePane('A3'); + + if (!empty($this->totals)) { + $sheet->getStyle('A' . $rowCount . ':' . $lastCol . $rowCount)->applyFromArray([ + 'font' => ['bold' => true], + 'fill' => [ + 'fillType' => Fill::FILL_SOLID, + 'startColor' => ['rgb' => 'F2F2F2'], + ], + ]); + } + }, + ]; + } +} diff --git a/app/Helpers/ZenovaReportAccess.php b/app/Helpers/ZenovaReportAccess.php new file mode 100644 index 00000000..442076a4 --- /dev/null +++ b/app/Helpers/ZenovaReportAccess.php @@ -0,0 +1,43 @@ +domain ?? '') !== 'zenova') { + return false; + } + + return count(array_intersect(self::roleIds($user->roles ?? []), self::ROLE_IDS)) > 0; + } + + private static function roleIds($roles): array + { + if (is_string($roles)) { + $decoded = json_decode($roles, true); + $roles = is_array($decoded) ? $decoded : []; + } + if (!is_array($roles)) { + return []; + } + + $ids = []; + foreach ($roles as $role) { + if (is_scalar($role) && (string) $role !== '') { + $ids[] = (string) $role; + } + } + + return $ids; + } +} diff --git a/app/Http/Controllers/ActionsheetController.php b/app/Http/Controllers/ActionsheetController.php new file mode 100644 index 00000000..344ecb25 --- /dev/null +++ b/app/Http/Controllers/ActionsheetController.php @@ -0,0 +1,235 @@ +middleware('auth'); + $this->_service = 'formreminders'; + $this->_module = 'action_sheet'; + $this->_viewname = 'actionsheet'; + $this->_title = 'Action Sheets'; + $this->_base_url = '/actionsheet'; + } + + protected function get_list_fields() + { + return [ + 'actions' => ['title' => 'Actions', 'buttons' => ['edit', 'delete']], + 'title' => ['title' => 'Title', 'type' => 'text'], + 'period' => ['title' => 'Period', 'type' => 'text'], + 'interval' => ['title' => 'Interval', 'type' => 'text'], + 'sort_order' => ['title' => 'Sort Order', 'type' => 'number'], + 'show_popup' => ['title' => 'Show Popup', 'type' => 'text'], + 'status' => ['title' => 'Status', 'type' => 'select'], + ]; + } + + protected function get_form_fields($type, $values) + { + $roles = $this->getlambda('get', 'ums', 'roles', 'dropdown'); + $roles_dd = []; + foreach (($roles->data->items ?? []) as $role) { + $roles_dd[$role->id] = $role->name; + } + + $metrics = $this->getlambda('get', 'metric', 'metric', 'get_all'); + $metrics_dd = []; + foreach (($metrics->data->items ?? []) as $metric) { + $name = $metric->name ?? ($metric->title ?? $metric->id); + $metrics_dd[$metric->id] = $name; + } + + return [ + 'id' => ['title' => '', 'type' => 'hidden'], + 'title' => ['title' => 'Title', 'type' => 'text', 'required' => true], + 'sort_order' => ['title' => 'Sort Order', 'type' => 'number', 'required' => false], + 'entity_id' => ['title' => 'Entity ID', 'type' => 'text', 'required' => false], + 'status' => ['title' => 'Status', 'type' => 'select', 'required' => true, 'values' => ['active' => 'Active', 'disabled' => 'Disabled'], 'default' => 'active'], + 'interval_start' => ['title' => 'Interval Start', 'type' => 'date', 'required' => true], + 'interval_end' => ['title' => 'Interval End', 'type' => 'date', 'required' => true], + 'period' => ['title' => 'Period', 'type' => 'select', 'required' => true, 'values' => [ + 'daily' => 'Daily', + 'weekly' => 'Weekly', + 'monthly' => 'Monthly', + 'yearly' => 'Yearly', + 'custom' => 'Custom', + 'overall' => 'Overall', + ], 'default' => 'monthly'], + 'custom_start_date' => ['title' => 'Custom Start Date', 'type' => 'date', 'required' => false], + 'custom_end_date' => ['title' => 'Custom End Date', 'type' => 'date', 'required' => false], + 'visible_roles' => ['title' => 'Visible Roles', 'type' => 'multiselect', 'values' => $roles_dd, 'required' => true], + 'reporting_roles' => ['title' => 'Reporting Roles', 'type' => 'multiselect', 'values' => $roles_dd, 'required' => true], + 'model_name' => ['title' => 'Model', 'type' => 'select', 'required' => true, 'values' => [ + 'metric_aggregation' => 'Metric Aggregation', + 'rapport_roles_leaderboard' => 'Roles Leaderboard', + 'rapport_metric_views' => 'Metric Views', + 'rapport_campaign_view' => 'Campaign View', + 'rapport_milestones' => 'Milestones', + ], 'default' => 'metric_aggregation'], + 'aggregation_type' => ['title' => 'Aggregation Type', 'type' => 'select', 'required' => true, 'values' => ['sum' => 'Sum', 'count' => 'Count', 'avg' => 'Average'], 'default' => 'sum'], + 'show_aggregation_data' => ['title' => 'Show Aggregation Data', 'type' => 'select', 'required' => true, 'values' => ['login' => 'Login hierarchy', 'metric' => 'All reporting roles'], 'default' => 'login'], + 'leaderboard_chart' => ['title' => 'Leaderboard Chart', 'type' => 'text', 'required' => false], + 'leaderboard_type' => ['title' => 'Leaderboard Type', 'type' => 'text', 'required' => false], + 'metrics' => ['title' => 'Metrics', 'type' => 'sheet_metrics', 'required' => false, 'fullwidth' => true, 'metrics' => $metrics_dd], + 'html_format' => ['title' => 'HTML Format', 'type' => 'textarea', 'required' => false, 'fullwidth' => true], + 'show_popup' => ['title' => 'Show Popup', 'type' => 'select', 'required' => true, 'values' => ['yes' => 'Yes', 'no' => 'No'], 'default' => 'no'], + 'repeat_popup' => ['title' => 'Repeat Popup', 'type' => 'select', 'required' => true, 'values' => ['yes' => 'Yes', 'no' => 'No'], 'default' => 'no'], + 'repeat_value' => ['title' => 'Repeat Value', 'type' => 'number', 'required' => false, 'default' => 1], + 'repeat_period' => ['title' => 'Repeat Period', 'type' => 'select', 'required' => false, 'values' => ['hourly' => 'Hourly', 'daily' => 'Daily', 'weekly' => 'Weekly', 'monthly' => 'Monthly'], 'default' => 'daily'], + ]; + } + + protected function _beforeform($type, $values) + { + if ($type !== 'edit' || !is_object($values)) { + return $values; + } + + $toArray = function ($value) { + if (!isset($value) || $value === null || $value === '') { + return []; + } + if (is_array($value)) { + return array_values(array_filter($value, function ($item) { + return $item !== '' && $item !== null; + })); + } + if (is_object($value)) { + return array_values((array) $value); + } + return array_values(array_filter(explode('#', (string) $value), function ($item) { + return $item !== ''; + })); + }; + + $values->repeat_popup = $values->repeat_popup ?? 'no'; + $values->repeat_value = $values->repeat_value ?? 1; + $values->repeat_period = $values->repeat_period ?? 'daily'; + + $values->visible_roles = $toArray($values->visible_roles ?? null); + $values->reporting_roles = $toArray($values->reporting_roles ?? null); + + $parts = explode('_', (string) ($values->interval ?? ''), 2); + $values->interval_start = $parts[0] ?? ''; + $values->interval_end = $parts[1] ?? ''; + + $aggregation = json_decode(json_encode($values->aggregation ?? new \stdClass()), true) ?: []; + $values->aggregation_type = $aggregation['aggregation_type'] ?? 'sum'; + $values->show_aggregation_data = $aggregation['show_aggregation_data'] ?? 'login'; + $values->leaderboard_chart = $aggregation['leaderboard_chart'] ?? ''; + $values->leaderboard_type = $aggregation['leaderboard_type'] ?? ''; + + $metrics = json_decode(json_encode($values->metrics ?? []), true) ?: []; + $values->metrics = array_values(array_map(function ($row) use ($toArray) { + if (!is_array($row)) { + $row = []; + } + $row['metric_ids'] = $toArray($row['metric_ids'] ?? []); + return $row; + }, $metrics)); + + return $values; + } + + protected function _beforesave($type, $request) + { + $data = $request->except('_token'); + + if ($type === 'edit') { + if (empty($data['id'])) { + $data['id'] = $request->query('id') ?: $request->input('id'); + } + } else { + unset($data['id']); + } + + $normalizeList = function ($value) { + if (!isset($value) || $value === null || $value === '') { + return []; + } + if (!is_array($value)) { + $value = explode('#', (string) $value); + } + return array_values(array_filter($value, function ($item) { + return $item !== '' && $item !== null; + })); + }; + + $data['sort_order'] = isset($data['sort_order']) && $data['sort_order'] !== '' ? (int) $data['sort_order'] : 0; + $data['entity_id'] = $data['entity_id'] ?? ''; + $data['status'] = $data['status'] ?? 'active'; + $data['show_popup'] = $data['show_popup'] ?? 'no'; + $data['repeat_popup'] = $data['repeat_popup'] ?? 'no'; + $data['repeat_value'] = isset($data['repeat_value']) && $data['repeat_value'] !== '' ? (int) $data['repeat_value'] : 1; + $data['repeat_period'] = $data['repeat_period'] ?? 'daily'; + $data['html_format'] = $data['html_format'] ?? ''; + $data['interval'] = $this->toIsoDate($data['interval_start'] ?? '') . '_' . $this->toIsoDate($data['interval_end'] ?? ''); + $data['custom_start_date'] = $this->toIsoDate($data['custom_start_date'] ?? ''); + $data['custom_end_date'] = $this->toIsoDate($data['custom_end_date'] ?? ''); + + $data['visible_roles'] = $normalizeList($data['visible_roles'] ?? []); + $data['reporting_roles'] = $normalizeList($data['reporting_roles'] ?? []); + + $data['aggregation'] = [ + 'aggregation_type' => $data['aggregation_type'] ?? 'sum', + 'show_aggregation_data' => $data['show_aggregation_data'] ?? 'login', + 'leaderboard_chart' => $data['leaderboard_chart'] ?? '', + 'leaderboard_type' => $data['leaderboard_type'] ?? '', + ]; + + $metrics = []; + foreach (($data['metrics'] ?? []) as $row) { + if (!is_array($row)) { + continue; + } + $id = trim((string) ($row['id'] ?? '')); + $title = trim((string) ($row['title'] ?? '')); + $label = trim((string) ($row['label'] ?? '')); + $metricIds = $normalizeList($row['metric_ids'] ?? []); + if ($id === '' && $title === '' && $label === '' && empty($metricIds)) { + continue; + } + $metrics[] = [ + 'id' => $id, + 'title' => $title, + 'label' => $label, + 'metric_ids' => $metricIds, + ]; + } + $data['metrics'] = $metrics; + + unset( + $data['interval_start'], + $data['interval_end'], + $data['aggregation_type'], + $data['show_aggregation_data'], + $data['leaderboard_chart'], + $data['leaderboard_type'] + ); + + return $data; + } + + protected function toIsoDate($value) + { + $value = trim((string) $value); + if ($value === '') { + return ''; + } + foreach (['Y-m-d', 'm-d-Y', 'm/d/Y', 'd-m-Y'] as $format) { + try { + $date = Carbon::createFromFormat($format, $value); + if ($date !== false) { + return $date->format('Y-m-d'); + } + } catch (\Exception $e) { + } + } + return $value; + } +} diff --git a/app/Http/Controllers/BannerController.php b/app/Http/Controllers/BannerController.php index b504da6d..062a6b05 100644 --- a/app/Http/Controllers/BannerController.php +++ b/app/Http/Controllers/BannerController.php @@ -49,15 +49,15 @@ class BannerController extends Controller $forms_dd[$form->id] = $form->title; } - $forms = $this->getlambda('get', 'form', 'survey', 'dropdown'); - foreach($forms->data->items as $form){ - $forms_dd[$form->id] = $form->title . ' (Survey)'; - } + // $forms = $this->getlambda('get', 'form', 'survey', 'dropdown'); + // foreach($forms->data->items as $form){ + // $forms_dd[$form->id] = $form->title . ' (Survey)'; + // } - $quizs = $this->getlambda('get', 'form', 'quiz', 'dropdown'); - foreach($quizs->data->items as $form){ - $forms_dd[$form->id] = $form->name . ' (Quiz)'; - } + // $quizs = $this->getlambda('get', 'form', 'quiz', 'dropdown'); + // foreach($quizs->data->items as $form){ + // $forms_dd[$form->id] = $form->name . ' (Quiz)'; + // } return [ 'title' => ['title' => 'Title', 'type' => 'text', 'required' => true], @@ -74,7 +74,7 @@ class BannerController extends Controller 'status' => ['title' => 'Status', 'type' => 'select', 'required' => true, 'values' => ['active' => 'Active', 'disabled' => 'Disabled']], 'has_cta' => ['title' => 'Has CTA', 'type' => 'yesno', 'required' => true, 'fullwidth' => true], - 'target_type' => ['title' => 'Target Type', 'type' => 'select', 'values' => ['internal_action' => 'Internal Action', 'form' => 'Web Form', 'survey' => 'Surveys', 'quiz' => 'Quizs', 'doctorlist' => 'Doctor List', 'external' => 'External Webpage', 'external_with_token' => 'External Webpage with Token'] ], + 'target_type' => ['title' => 'Target Type', 'type' => 'select', 'values' => ['internal_action' => 'Internal Action', 'form' => 'Web Form', 'doctorlist' => 'Doctor List', 'external' => 'External Webpage', 'external_with_token' => 'External Webpage with Token'] ], 'form_id' => ['title' => 'Form', 'type' => 'select', 'values' => $forms_dd ], 'external_url' => ['title' => 'External Url / Internal Action', 'type' => 'text'], diff --git a/app/Http/Controllers/FormController.php b/app/Http/Controllers/FormController.php index d38a9be7..c6d0f615 100644 --- a/app/Http/Controllers/FormController.php +++ b/app/Http/Controllers/FormController.php @@ -358,6 +358,17 @@ class FormController extends Controller } } + $generatedFiles = $submission->formdata->genareted_image ?? null; + if (is_object($generatedFiles)) { + $generatedFiles = array_values((array) $generatedFiles); + } + if (is_array($generatedFiles) && count($generatedFiles)) { + $imageFormId = $submission->formdata->form_id ?? $form_id; + $row['generated_image_url'] = collect($generatedFiles)->filter()->map(function ($file) use ($imageFormId) { + return asset('storage/personalized/'.$imageFormId.'/'.$file); + })->implode(', '); + } + if (isset($formdata->workflow) && $formdata->workflow == 'yes') { $row['workflow_action_date'] = $this->formatWorkflowActionDate($submission); $row['approval_history'] = $this->formatApprovalHistory($submission); @@ -594,6 +605,9 @@ class FormController extends Controller $token = DB::table('users')->select("access_token")->where("domain", $domain)->where("permissions", 'like', '%contact_create%')->orderBy('updated_at', 'desc')->skip(0)->take(1)->get(); //print_r(['form_id' => $form_id, 'id' => $id ]); exit; LambdaRequest::getrequest('form', 'formentity/deletesubmission', ['form_id' => $form_id, 'id' => $id ], $token[0]->access_token); + if ($form_id === InceptNebAllocation::FORM_ID) { + (new InceptNebAllocation())->releaseBySubmission($id); + } return response()->json(['success' => true, 'message' => 'Successfully Deleted !!!']); } @@ -1141,6 +1155,7 @@ class FormController extends Controller 'ecme_dr', 'rtm_dr', 'absi_camp', + 'h1_cardio_conference', ]; foreach ($contactAttributes as $attr) { if (isset($post[$attr]) && $post[$attr] !== '') { @@ -5747,6 +5762,9 @@ class FormController extends Controller $inceptUsageId = null; $inceptAllocation = null; if (($formdata->form_id ?? '') === InceptNebAllocation::FORM_ID) { + if (!empty($post['id'])) { + return response()->json(['status' => 'failure', 'message' => 'Editing is not allowed']); + } $inceptAllocation = new InceptNebAllocation(); try { $inceptUsageId = $inceptAllocation->reserveFromSubmission($post, $formdata); @@ -7768,6 +7786,8 @@ class FormController extends Controller Log::debug(print_r($post, true)); Log::debug(print_r($formdata, true)); + $this->syncNapMysqlFromApproval($request, $post, $finfo); + return response()->json($post); } @@ -7812,6 +7832,45 @@ class FormController extends Controller return response()->json($post); } + protected function syncNapMysqlFromApproval(Request $request, array $post, $response): void + { + $napFormId = '1750914801-153443b9-94d0-4dcb-b3f3-4fa11588a0a9'; + if (($post['form_id'] ?? '') !== $napFormId) { + return; + } + if (!isset($response->status) || $response->status !== 'success') { + return; + } + $napStatus = $response->data->nap_status ?? ''; + $submissionId = $post['submission_id'] ?? $post['id'] ?? ''; + if ($napStatus === '' || $submissionId === '') { + return; + } + + $record = DB::table('neblogistic')->where('submission_id', $submissionId)->first(); + if (!$record) { + return; + } + + $update = ['status' => $napStatus]; + $now = date('Y-m-d H:i:s'); + if ($napStatus === 'Rejected') { + $update['rejected_at'] = $now; + $reason = trim((string) ($post['reason'] ?? '')); + if ($reason !== '') { + $update['rejected_reason'] = $reason; + } + } elseif (preg_match('/^L([1-3]) Approved$/', $napStatus, $match)) { + $update['l' . $match[1] . '_approved_at'] = $now; + } elseif ($napStatus === 'Approved') { + $update['approved_at'] = $now; + } + + $old = json_decode(json_encode($record), true); + DB::table('neblogistic')->where('submission_id', $submissionId)->update($update); + NeblogisticAuditService::logUpdate($record->id, $old, $update, null, $request); + } + protected function isValidActingApproverUserId($userId): bool { if ($userId === null || $userId === '') { @@ -8281,6 +8340,11 @@ class FormController extends Controller return Excel::download(new InvoicesExport($exceldata), $formdata->title.'.xlsx'); } + if ($form_id === InceptNebAllocation::FORM_ID) { + $inceptService = new InceptNebAllocation(); + $data['incept_neb'] = $inceptService->withOrgTotals([], $formdata); + } + if (\View::exists('form.'.$formdata->partition.'.'.$formdata->form_id.'-history')) { return view('form.'.$formdata->partition.'.'.$formdata->form_id.'-history')->with($data); } elseif (\View::exists('form.'.$formdata->form_id.'-history')) { @@ -8344,7 +8408,7 @@ class FormController extends Controller if ($empId === '' && !empty($formdata->user_id)) { $empId = $service->resolveEmpIdFromUser($formdata->user_id); } - return ['incept_neb' => $service->summaryForEmployee($empId)]; + return ['incept_neb' => $service->withOrgTotals($service->summaryForEmployee($empId), $formdata)]; break; default: @@ -8933,6 +8997,7 @@ class FormController extends Controller ); if (isset($response->status) && $response->status === 'success') { + $this->syncNapMysqlFromApproval($request, $post, $response); $workflowStatus = 'unapproved'; $approvalRecordStatus = $action === 'approve' ? 'approve' : 'reject'; if (!empty($post['entity_id'])) { diff --git a/app/Http/Controllers/FormactionController.php b/app/Http/Controllers/FormactionController.php index 84720e6a..c17391c1 100644 --- a/app/Http/Controllers/FormactionController.php +++ b/app/Http/Controllers/FormactionController.php @@ -70,17 +70,17 @@ class FormactionController extends Controller $forms_dd[$form->id] = $form->title; } - $forms = $this->getlambda('get', 'form', 'survey', 'dropdown'); - foreach($forms->data->items as $form){ - $forms_dd[$form->id] = $form->title . ' (Survey)'; - } + // $forms = $this->getlambda('get', 'form', 'survey', 'dropdown'); + // foreach($forms->data->items as $form){ + // $forms_dd[$form->id] = $form->title . ' (Survey)'; + // } - $quizs = $this->getlambda('get', 'form', 'quiz', 'dropdown'); - foreach($quizs->data->items as $form){ - $forms_dd[$form->id] = $form->name . ' (Quiz)'; - } + // $quizs = $this->getlambda('get', 'form', 'quiz', 'dropdown'); + // foreach($quizs->data->items as $form){ + // $forms_dd[$form->id] = $form->name . ' (Quiz)'; + // } - $target_types = ['form' => 'Web Form', 'survey' => 'Surveys', 'quiz' => 'Quizs', 'doctorlist' => 'Doctor List', 'external' => 'External Webpage', 'internal_action' => 'Internal Action', 'external_with_token' => 'External Webpage with Token', ]; + $target_types = ['form' => 'Web Form', 'doctorlist' => 'Doctor List', 'external' => 'External Webpage', 'internal_action' => 'Internal Action', 'external_with_token' => 'External Webpage with Token', ]; $permissions = json_decode(auth()->user()->permissions); if(in_array('rcpa_reports', $permissions)) { @@ -141,6 +141,7 @@ class FormactionController extends Controller $actions = $this->getlambda('get', 'form', 'formaction', 'list'); //echo '
'; print_r($actions); exit;
         // For all actions, replace external form links with the localhost link
+        $token = Auth::user()->access_token ?? '';
         if (isset($actions->data->actions) && is_array($actions->data->actions)) {
             foreach ($actions->data->actions as &$action) {
                 if (isset($action->url) && is_string($action->url)) {
@@ -148,6 +149,9 @@ class FormactionController extends Controller
                         "https://stage.buildrapport.co/",
                         "https://form.buildrapport.co/"
                     ], "http://127.0.0.1:8000/", $action->url);
+                    if ($this->actionNeedsAuthToken($action)) {
+                        $action->url = $this->withExternalAuthToken($action->url, $token);
+                    }
                 }
             }
             unset($action); // break reference
@@ -156,6 +160,65 @@ class FormactionController extends Controller
         return view('form.list-console')->with(['actions' => $actions->data->actions]);
     }
 
+    protected function actionNeedsAuthToken($action) {
+        if (isset($action->target) && $action->target === 'external_with_token') {
+            return true;
+        }
+        $url = isset($action->url) ? $action->url : '';
+        return is_string($url) && strpos($url, '/tambola') !== false;
+    }
+
+    protected function withExternalAuthToken($url, $token) {
+        if (!is_string($url) || $url === '' || !is_string($token) || $token === '') {
+            return $url;
+        }
+
+        if (preg_match('/(?:authorization|token)=TOKEN(?=&|$)/', $url)) {
+            return str_replace('TOKEN', rawurlencode($token), $url);
+        }
+        if (preg_match('/[?&]TOKEN(?=&|$)/', $url)) {
+            return preg_replace('/([?&])TOKEN(?=&|$)/', '$1authorization=' . rawurlencode($token), $url, 1);
+        }
+
+        $parts = parse_url($url);
+        if ($parts === false) {
+            return $url;
+        }
+        $query = [];
+        if (!empty($parts['query'])) {
+            parse_str($parts['query'], $query);
+        }
+        $existing = '';
+        if (isset($query['authorization']) && is_string($query['authorization']) && $query['authorization'] !== '') {
+            $existing = $query['authorization'];
+        } elseif (isset($query['token']) && is_string($query['token']) && $query['token'] !== '') {
+            $existing = $query['token'];
+        }
+        if ($existing !== '') {
+            return $url;
+        }
+
+        unset($query['authorization'], $query['token']);
+        $query['authorization'] = $token;
+
+        $rebuilt = '';
+        if (!empty($parts['scheme'])) {
+            $rebuilt .= $parts['scheme'] . '://';
+        }
+        if (!empty($parts['host'])) {
+            $rebuilt .= $parts['host'];
+        }
+        if (!empty($parts['port'])) {
+            $rebuilt .= ':' . $parts['port'];
+        }
+        $rebuilt .= isset($parts['path']) ? $parts['path'] : '';
+        $rebuilt .= '?' . http_build_query($query);
+        if (!empty($parts['fragment'])) {
+            $rebuilt .= '#' . $parts['fragment'];
+        }
+        return $rebuilt;
+    }
+
     public function consoleshow(Request $request) {
         $form_id = $request->input('id', 0);
         $finfo = LambdaRequest::postrequest('form', 'formentity/getformdefination', ['form_id' => $form_id, 'user_id' => Auth::user()->user_id, 'partition' => 'rapport-'.Auth::user()->domain ]);
diff --git a/app/Http/Controllers/IvareportController.php b/app/Http/Controllers/IvareportController.php
index f0e0201e..ebdd53b4 100644
--- a/app/Http/Controllers/IvareportController.php
+++ b/app/Http/Controllers/IvareportController.php
@@ -107,6 +107,21 @@ class IvareportController extends Controller
     private const COLOR_META = ['bg' => 'E7E6E6', 'fg' => '000000', 'class' => 'iva-th-meta'];
     private const COLOR_SKU = ['bg' => 'FFC000', 'fg' => '000000', 'class' => 'iva-th-sku'];
 
+    private const SESSION_TOKEN_KEY = 'iva_report_token';
+
+    private const TOKEN_ACTIONS = [
+        'mobile',
+        'mobileplanner',
+        'mobilekamwise',
+        'mobiledrbrandwise',
+        'mobileskuwise',
+        'export',
+    ];
+
+    private ?string $accessToken = null;
+
+    private ?object $tokenUser = null;
+
     private const PLANNER_BRAND_COLORS = [
         'Materna HMG' => ['bg' => 'E7E6E6', 'fg' => '000000'],
         'Ciscure' => ['bg' => 'F8CBAD', 'fg' => '000000'],
@@ -129,11 +144,24 @@ class IvareportController extends Controller
 
     public function __construct()
     {
-        $this->middleware('auth');
+        // Routes are /ivareport/{action} -> action(), so except() cannot key off mobile* methods.
         $this->middleware(function ($request, $next) {
+            $action = (string) $request->route('action', '');
+            if (in_array($action, self::TOKEN_ACTIONS, true)) {
+                return $next($request);
+            }
+
+            if (!auth()->check()) {
+                if ($request->expectsJson()) {
+                    abort(401);
+                }
+                return redirect()->guest(route('login'));
+            }
+
             if ((auth()->user()->domain ?? '') !== self::DOMAIN) {
                 abort(403);
             }
+
             return $next($request);
         });
         $this->_service = 'reports';
@@ -169,8 +197,58 @@ class IvareportController extends Controller
         return $this->renderReport($request, 'sku_wise', 'skuwise', 'SKU wise Rate');
     }
 
+    public function mobile(Request $request)
+    {
+        $this->assertIvaToken($request);
+        $token = $this->accessToken ?? '';
+        $shared = array_filter([
+            'authorization' => $token,
+            'month' => $request->input('month'),
+            'filter_hier' => $request->input('filter_hier', []),
+            'selected_brand' => $request->input('selected_brand', []),
+        ], function ($value) {
+            return $value !== null && $value !== '' && $value !== [];
+        });
+        $query = $shared ? '?' . http_build_query($shared) : '';
+
+        return view('ivareport.mobile', [
+            'title' => 'Custom Dashboards',
+            'reports' => [
+                ['label' => 'Planner Data', 'url' => '/ivareport/mobileplanner' . $query],
+                ['label' => 'KAM wise details', 'url' => '/ivareport/mobilekamwise' . $query],
+                ['label' => 'Dr & Brand Wise details', 'url' => '/ivareport/mobiledrbrandwise' . $query],
+                ['label' => 'SKU wise Rate', 'url' => '/ivareport/mobileskuwise' . $query],
+            ],
+        ]);
+    }
+
+    public function mobileplanner(Request $request)
+    {
+        $this->assertIvaToken($request);
+        return $this->renderReport($request, 'planner_data', 'planner', 'Planner Data', true);
+    }
+
+    public function mobilekamwise(Request $request)
+    {
+        $this->assertIvaToken($request);
+        return $this->renderReport($request, 'kam_wise', 'kamwise', 'KAM wise details', true);
+    }
+
+    public function mobiledrbrandwise(Request $request)
+    {
+        $this->assertIvaToken($request);
+        return $this->renderReport($request, 'dr_and_brandwise', 'drbrandwise', 'Dr & Brand Wise details', true);
+    }
+
+    public function mobileskuwise(Request $request)
+    {
+        $this->assertIvaToken($request);
+        return $this->renderReport($request, 'sku_wise', 'skuwise', 'SKU wise Rate', true);
+    }
+
     public function export(Request $request)
     {
+        $this->assertWebOrToken($request);
         $typeMap = [
             'planner' => ['api' => 'planner_data', 'file' => 'planner-data'],
             'kamwise' => ['api' => 'kam_wise', 'file' => 'kam-wise-details'],
@@ -209,7 +287,7 @@ class IvareportController extends Controller
         return Excel::download(new IvaStyledExport($export), $typeMap[$type]['file'] . '-' . $monthSlug . '.xlsx');
     }
 
-    private function renderReport(Request $request, string $reportType, string $view, string $title)
+    private function renderReport(Request $request, string $reportType, string $view, string $title, bool $isMobile = false)
     {
         $filters = $this->resolveFilters($request, $reportType);
         $payload = $this->fetchReport(
@@ -218,17 +296,23 @@ class IvareportController extends Controller
             $filters['selected_brands'],
             $filters['hierarchy_ids']
         );
+        $token = $isMobile ? ($this->accessToken ?? '') : null;
+        $filterQuery = $this->filterQuery($filters, $token);
 
         $viewData = [
             'title' => $title,
             'activeTab' => $view,
+            'isMobile' => $isMobile,
+            'token' => $token ?? '',
+            'formAction' => $isMobile ? $this->mobileReportPath($view) : '',
+            'mobileBackUrl' => $isMobile ? '/ivareport/mobile' . ($filterQuery ? '?' . $filterQuery : '') : '',
             'months' => $this->availableMonths(),
             'selectedMonth' => $filters['month'],
-            'filter_hier' => $this->loadHierarchies(),
+            'filter_hier' => $this->loadHierarchies($isMobile ? $this->tokenUser : null),
             'selectedHier' => $filters['hierarchy_ids'],
             'brandOptions' => $this->brandOptions($reportType),
             'selectedBrands' => $filters['selected_brands'],
-            'filterQuery' => $this->filterQuery($filters),
+            'filterQuery' => $filterQuery,
             'error' => $payload['error'],
             'rows' => [],
             'ivaFmt' => function ($val, $isPct = false) {
@@ -307,20 +391,119 @@ class IvareportController extends Controller
         return self::KAM_BRANDS;
     }
 
-    private function filterQuery(array $filters): string
+    private function filterQuery(array $filters, ?string $token = null): string
     {
-        return http_build_query([
+        $query = [
             'month' => $filters['month'],
             'filter_hier' => $filters['hierarchy_ids'],
             'selected_brand' => $filters['selected_brands'],
-        ]);
+        ];
+        if ($token) {
+            $query['authorization'] = $token;
+        }
+
+        return http_build_query($query);
+    }
+
+    private function mobileReportPath(string $view): string
+    {
+        return '/ivareport/mobile' . $view;
+    }
+
+    private function assertWebOrToken(Request $request): void
+    {
+        $user = auth()->user();
+        if ($user) {
+            if (($user->domain ?? '') !== self::DOMAIN) {
+                abort(403);
+            }
+            return;
+        }
+
+        $this->assertIvaToken($request);
+    }
+
+    private function assertIvaToken(Request $request): void
+    {
+        $token = $this->resolveAccessToken($request);
+        if ($token === null) {
+            abort(401, 'Access token is required');
+        }
+
+        $user = $this->getUserFromToken($token);
+        if (!$user || ($user->domain ?? '') !== self::DOMAIN) {
+            abort(403);
+        }
+
+        $this->accessToken = $token;
+        $this->tokenUser = $user;
+    }
+
+    private function resolveAccessToken(Request $request): ?string
+    {
+        $candidates = [
+            $request->query('authorization'),
+            $request->query('token'),
+            $request->header('Authorization'),
+        ];
+        $token = '';
+        foreach ($candidates as $candidate) {
+            $candidate = trim((string) ($candidate ?? ''));
+            if (stripos($candidate, 'Bearer ') === 0) {
+                $candidate = trim(substr($candidate, 7));
+            }
+            if ($candidate !== '') {
+                $token = $candidate;
+                break;
+            }
+        }
+        if ($token === '' && $request->session()->has(self::SESSION_TOKEN_KEY)) {
+            $token = (string) $request->session()->get(self::SESSION_TOKEN_KEY);
+        }
+
+        if ($token !== '') {
+            $request->session()->put(self::SESSION_TOKEN_KEY, $token);
+        }
+
+        return $token !== '' ? $token : null;
+    }
+
+    private function getUserFromToken(string $token): ?object
+    {
+        $parts = explode('.', $token);
+        if (count($parts) < 2) {
+            return null;
+        }
+
+        $payload = json_decode(base64_decode(strtr($parts[1], '-_', '+/')));
+        if (!is_object($payload)) {
+            return null;
+        }
+
+        $user = $payload->user ?? null;
+        if (!is_object($user)) {
+            return null;
+        }
+        if (!isset($user->permissions) && isset($payload->permissions)) {
+            $user->permissions = $payload->permissions;
+        }
+
+        return $user;
+    }
+
+    private function apiGet(string $service, string $url, array $data, int $timeout = 30)
+    {
+        if ($this->accessToken) {
+            return LambdaRequest::getrequest($service, $url, $data, $this->accessToken, $timeout);
+        }
+
+        return LambdaRequest::signedrequest('get', $service, $url, $data, $timeout);
     }
 
     private function fetchReport(string $reportType, string $month, array $brands, array $hierIds): array
     {
         try {
-            $response = LambdaRequest::signedrequest(
-                'get',
+            $response = $this->apiGet(
                 'reports',
                 'iva_reports/brand_reports',
                 [
@@ -359,17 +542,22 @@ class IvareportController extends Controller
         return ['report' => $report, 'metric_config' => $metricConfig, 'error' => null];
     }
 
-    private function loadHierarchies(): array
+    private function loadHierarchies(?object $user = null): array
     {
-        $permissions = json_decode(auth()->user()->permissions ?? '[]');
+        $user = $user ?? auth()->user();
+        $permissions = $user->permissions ?? '[]';
+        if (is_string($permissions)) {
+            $permissions = json_decode($permissions, true);
+        } elseif (is_object($permissions)) {
+            $permissions = json_decode(json_encode($permissions), true);
+        }
         if (!is_array($permissions)) {
             $permissions = [];
         }
         $hierarchyId = in_array('all_data_access', $permissions, true) ? 'root' : [];
 
         try {
-            $response = LambdaRequest::signedrequest(
-                'get',
+            $response = $this->apiGet(
                 'ums',
                 'hierarchy/hierarchy_below_me',
                 [
diff --git a/app/Http/Controllers/LeaderboardController.php b/app/Http/Controllers/LeaderboardController.php
index 1876d885..7313801a 100644
--- a/app/Http/Controllers/LeaderboardController.php
+++ b/app/Http/Controllers/LeaderboardController.php
@@ -119,6 +119,7 @@ class LeaderboardController extends Controller
         }
 
         $teams_dd = $this->teamDropdownOptions();
+        $userattr_dd = $this->getUserAttributeOptions();
 
         $managerLevels = [
             'l1manager' => 'L1 Manager',
@@ -135,6 +136,7 @@ class LeaderboardController extends Controller
             'validity_end' => ['title' => 'Validity end', 'type' => 'date', 'required' => true],
             'leaderboard_roles_type' => ['title' => 'Leaderboard Type', 'type' => 'select', 'required' => true, 'values' => ['roles' => 'Roles', 'team' => 'Team']],
             'leaderboard_team_ids' => ['title' => 'Teams', 'type' => 'multiselect', 'values' => $teams_dd, 'fullwidth' => true, 'required' => true, 'showIf' => ['leaderboard_roles_type' => 'team']],
+            'view_mode' => ['title' => 'Team View Mode', 'type' => 'select', 'required' => true, 'values' => ['versus' => 'Versus', 'podium' => 'Podium'], 'default' => 'versus', 'showIf' => ['leaderboard_roles_type' => 'team']],
             'visible_roles' => ['title' => 'Visible Roles', 'type' => 'multiselect', 'values' => $roles_dd, 'required' => true],
             'reporting_roles' => ['title' => 'Reporting Roles', 'type' => 'multiselect', 'values' => $roles_dd, 'required' => true],
             'rank_roles' => ['title' => 'Rank Roles', 'type' => 'rank_roles', 'roles' => $roles_dd, 'required' => true, 'fullwidth' => true, 'showIf' => ['leaderboard_roles_type' => 'roles']],
@@ -168,7 +170,13 @@ class LeaderboardController extends Controller
             ]],
             'show_positions' => ['title' => 'Show Positions', 'type' => 'select', 'required' => true, 'values' => ['yes' => 'Yes', 'no' => 'No']],
             'show_positions_type' => ['title' => 'Show Positions Type', 'type' => 'select', 'required' => true, 'values' => ['all' => 'All', 'loggedin' => 'Logged In']],
-            'manager_details' => ['title' => 'Manager Details', 'type' => 'multiselect', 'values' => $managerLevels],
+            'show_units_export' => ['title' => 'Show Units Export', 'type' => 'select', 'required' => true, 'values' => ['yes' => 'Yes', 'no' => 'No'], 'default' => 'yes'],
+            'show_user_attributes' => ['title' => 'Show User Attributes', 'type' => 'select', 'required' => true, 'values' => ['yes' => 'Yes', 'no' => 'No'], 'default' => 'no'],
+            'user_attributes' => ['title' => 'Include User Attributes', 'type' => 'multiselect', 'values' => $userattr_dd, 'default' => []],
+            'show_normal_metrics' => ['title' => 'Show Normal Metrics', 'type' => 'select', 'required' => true, 'values' => ['yes' => 'Yes', 'no' => 'No'], 'default' => 'yes'],
+            'show_combined_metrics' => ['title' => 'Show Combined Metrics', 'type' => 'select', 'required' => true, 'values' => ['yes' => 'Yes', 'no' => 'No'], 'default' => 'yes'],
+            'manager_details' => ['title' => 'Manager Details', 'type' => 'multiselect', 'values' => $managerLevels, 'default' => ['l1manager']],
+            'total_units_name' => ['title' => 'Total Units Name', 'type' => 'text', 'required' => false, 'default' => ''],
             'bg_color' => ['title' => 'Background Color', 'type' => 'color'],
             'bg_image' => ['title' => 'Background Image', 'type' => 'image'],
             'icon' => ['title' => 'Icon', 'type' => 'image'],
@@ -246,12 +254,25 @@ class LeaderboardController extends Controller
         if (!isset($data['leaderboard_roles_type']) || !in_array($data['leaderboard_roles_type'], ['team', 'roles'], true)) {
             $data['leaderboard_roles_type'] = 'roles';
         }
-        if ($data['leaderboard_roles_type'] !== 'team') {
+        if ($data['leaderboard_roles_type'] === 'team') {
+            if (!isset($data['view_mode']) || !in_array($data['view_mode'], ['versus', 'podium'], true)) {
+                $data['view_mode'] = 'versus';
+            }
+        } else {
             $data['leaderboard_team_ids'] = [];
+            $data['view_mode'] = '';
         }
         if (empty($data['manager_details'])) {
             $data['manager_details'] = ['l1manager'];
         }
+        foreach (['show_units_export', 'show_normal_metrics', 'show_combined_metrics'] as $flag) {
+            if (!isset($data[$flag]) || $data[$flag] === '') {
+                $data[$flag] = 'yes';
+            }
+        }
+        $data['total_units_name'] = trim((string) ($data['total_units_name'] ?? ''));
+        $data['show_user_attributes'] = ($data['show_user_attributes'] ?? 'no') === 'yes' ? 'yes' : 'no';
+        $data['user_attributes'] = $this->normalizeAttributeCodes($data['user_attributes'] ?? []);
         if (!isset($data['bg_color']) || is_array($data['bg_color'])) {
             $data['bg_color'] = '';
         }
@@ -297,7 +318,14 @@ class LeaderboardController extends Controller
                 'intervals' => [],
                 'leaderboard_roles_type' => 'roles',
                 'leaderboard_team_ids' => [],
+                'view_mode' => 'versus',
                 'manager_details' => ['l1manager'],
+                'show_units_export' => 'yes',
+                'show_user_attributes' => 'no',
+                'user_attributes' => [],
+                'show_normal_metrics' => 'yes',
+                'show_combined_metrics' => 'yes',
+                'total_units_name' => '',
                 'bg_color' => '',
                 'bg_image' => '',
                 'icon' => '',
@@ -314,9 +342,22 @@ class LeaderboardController extends Controller
             if (!isset($values->leaderboard_roles_type) || !in_array($values->leaderboard_roles_type, ['team', 'roles'], true)) {
                 $values->leaderboard_roles_type = 'roles';
             }
+            if (!isset($values->view_mode) || !in_array($values->view_mode, ['versus', 'podium'], true)) {
+                $values->view_mode = 'versus';
+            }
             if (empty($values->manager_details)) {
                 $values->manager_details = ['l1manager'];
             }
+            foreach (['show_units_export', 'show_normal_metrics', 'show_combined_metrics'] as $flag) {
+                if (!isset($values->{$flag}) || $values->{$flag} === '') {
+                    $values->{$flag} = 'yes';
+                }
+            }
+            if (!isset($values->total_units_name) || $values->total_units_name === null) {
+                $values->total_units_name = '';
+            }
+            $values->show_user_attributes = (($values->show_user_attributes ?? 'no') === 'yes') ? 'yes' : 'no';
+            $values->user_attributes = $this->normalizeAttributeCodes($values->user_attributes ?? []);
             if (!isset($values->bg_color)) {
                 $values->bg_color = '';
             }
@@ -388,21 +429,33 @@ class LeaderboardController extends Controller
                 ->withError($data['error']);
         }
 
-        $headers = ['Rank', 'User', 'Employee Code', 'Designation', 'Position', 'Score'];
+        if (!empty($data['is_team'])) {
+            $headers = ['Rank', 'Team', 'Score'];
+        } else {
+            $headers = ['Rank', 'User', 'Employee Code', 'Designation', 'Position', 'Score'];
+        }
         foreach ($data['metrics'] as $metric) {
             $headers[] = $metric['name'];
         }
 
         $exceldata = [$headers];
         foreach ($data['users'] as $index => $user) {
-            $row = [
-                $user['rank'] ?? ($index + 1),
-                $user['name'] ?? '',
-                $user['employee_code'] ?? '',
-                $user['designation'] ?? '',
-                $user['position_name'] ?? '',
-                $user['score'] ?? 0,
-            ];
+            if (!empty($data['is_team'])) {
+                $row = [
+                    $user['rank'] ?? ($index + 1),
+                    $user['name'] ?? '',
+                    $user['score'] ?? 0,
+                ];
+            } else {
+                $row = [
+                    $user['rank'] ?? ($index + 1),
+                    $user['name'] ?? '',
+                    $user['employee_code'] ?? '',
+                    $user['designation'] ?? '',
+                    $user['position_name'] ?? '',
+                    $user['score'] ?? 0,
+                ];
+            }
             foreach ($data['metrics'] as $metric) {
                 $row[] = $user['metric_values'][$metric['id']] ?? '';
             }
@@ -430,6 +483,7 @@ class LeaderboardController extends Controller
             'metrics' => [],
             'pills' => [],
             'selected_role' => $roleId,
+            'is_team' => false,
         ];
 
         if ($id === '') {
@@ -455,6 +509,11 @@ class LeaderboardController extends Controller
             $groups = array_values((array) $groups);
         }
 
+        $teamRows = $this->teamRowsFromGroups($groups);
+        if ($this->isTeamLeaderboardView($view->team_view ?? false) || count($teamRows)) {
+            return $this->teamLeaderboardViewData($view, $teamRows, $request);
+        }
+
         $selected = null;
         foreach ($groups as $group) {
             $group = is_object($group) ? $group : (object) $group;
@@ -486,6 +545,7 @@ class LeaderboardController extends Controller
                 'metrics' => [],
                 'pills' => $pills,
                 'selected_role' => $roleId,
+                'is_team' => false,
             ];
         }
 
@@ -553,6 +613,87 @@ class LeaderboardController extends Controller
             'metrics' => $metrics,
             'pills' => $pills,
             'selected_role' => $roleId,
+            'is_team' => false,
+        ];
+    }
+
+    protected function isTeamLeaderboardView($flag): bool
+    {
+        return $flag === true || $flag === 1 || $flag === '1' || $flag === 'true' || $flag === 'yes';
+    }
+
+    protected function teamRowsFromGroups(array $groups): array
+    {
+        foreach ($groups as $group) {
+            $group = is_object($group) ? $group : (object) $group;
+            $rows = $group->teams ?? null;
+            if (is_object($rows)) {
+                $rows = array_values((array) $rows);
+            }
+            if (is_array($rows) && count($rows)) {
+                return $rows;
+            }
+        }
+
+        return [];
+    }
+
+    protected function teamLeaderboardViewData($view, array $teamRows, Request $request): array
+    {
+        $periods = $this->uniquePeriodOptions($view->periods ?? []);
+        $selectedPeriod = (string) ($request->query('period') ?: ($view->interval ?? ''));
+        if ($selectedPeriod === '') {
+            $selectedPeriod = 'all';
+        }
+
+        $metrics = [];
+        $users = [];
+        foreach ($teamRows as $index => $row) {
+            $row = is_object($row) ? $row : (object) $row;
+            $team = is_object($row->team ?? null) ? $row->team : (object) ($row->team ?? []);
+            $metricValues = [];
+            $rowMetrics = $row->metrics ?? [];
+            if (is_object($rowMetrics)) {
+                $rowMetrics = array_values((array) $rowMetrics);
+            }
+            if (is_array($rowMetrics)) {
+                foreach ($rowMetrics as $metric) {
+                    $metric = is_object($metric) ? $metric : (object) $metric;
+                    $metricId = $metric->metric ?? ($metric->title ?? ('m' . count($metrics)));
+                    $metricName = $metric->name ?? ($metric->display ?? ($metric->title ?? $metricId));
+                    if ($index === 0) {
+                        $metrics[] = ['id' => $metricId, 'name' => $metricName];
+                    }
+                    $metricValues[$metricId] = $metric->value ?? 0;
+                }
+            }
+            $name = trim((string) ($team->name ?? ''));
+            $users[] = [
+                'rank' => $row->rank ?? ($index + 1),
+                'name' => $name !== '' ? $name : 'Team',
+                'avatar' => $team->icon ?? '',
+                'employee_code' => '',
+                'designation' => '',
+                'position_name' => '',
+                'score' => $row->score ?? 0,
+                'metric_values' => $metricValues,
+            ];
+        }
+
+        $periodLabel = trim(($view->firstDay ?? '') . (($view->firstDay ?? '') && ($view->lastDay ?? '') ? ' – ' : '') . ($view->lastDay ?? ''));
+
+        return [
+            'error' => '',
+            'title' => $view->name ?? 'Leaderboard',
+            'subtitle' => 'Teams',
+            'period_label' => $periodLabel,
+            'periods' => $periods,
+            'selected_period' => $selectedPeriod,
+            'users' => $users,
+            'metrics' => $metrics,
+            'pills' => [],
+            'selected_role' => '',
+            'is_team' => true,
         ];
     }
 
@@ -633,4 +774,46 @@ class LeaderboardController extends Controller
 
         return $pills;
     }
+
+    protected function getUserAttributeOptions(): array
+    {
+        $options = [];
+        $userAttrs = $this->getlambda('get', 'ums', 'userattribute', 'get_all');
+        foreach (($userAttrs->data->items ?? []) as $attr) {
+            if (($attr->status ?? 'active') === 'disabled') {
+                continue;
+            }
+            $code = $attr->code ?? '';
+            if ($code === '') {
+                continue;
+            }
+            $options[$code] = $attr->attribute ?? $code;
+        }
+
+        return $options;
+    }
+
+    protected function normalizeAttributeCodes($raw): array
+    {
+        if (is_string($raw) && $raw !== '') {
+            $decoded = json_decode($raw, true);
+            $raw = is_array($decoded) ? $decoded : explode('#', $raw);
+        }
+        if (!is_array($raw)) {
+            return [];
+        }
+
+        $codes = [];
+        foreach ($raw as $code) {
+            if (is_object($code)) {
+                $code = $code->code ?? '';
+            }
+            $code = trim((string) $code);
+            if ($code !== '') {
+                $codes[] = $code;
+            }
+        }
+
+        return array_values(array_unique($codes));
+    }
 }
diff --git a/app/Http/Controllers/MagmediaController.php b/app/Http/Controllers/MagmediaController.php
index 1c40c49e..15c5e80d 100644
--- a/app/Http/Controllers/MagmediaController.php
+++ b/app/Http/Controllers/MagmediaController.php
@@ -67,15 +67,15 @@ class MagmediaController extends Controller
             $forms_dd[$form->id] = $form->title;
         }
 
-        $forms = $this->getlambda('get', 'form', 'survey', 'dropdown');
-        foreach($forms->data->items as $form){
-            $forms_dd[$form->id] = $form->title . ' (Survey)';
-        }
+        // $forms = $this->getlambda('get', 'form', 'survey', 'dropdown');
+        // foreach($forms->data->items as $form){
+        //     $forms_dd[$form->id] = $form->title . ' (Survey)';
+        // }
 
-        $quizs = $this->getlambda('get', 'form', 'quiz', 'dropdown');
-        foreach($quizs->data->items as $form){
-            $forms_dd[$form->id] = $form->name . ' (Quiz)';
-        }
+        // $quizs = $this->getlambda('get', 'form', 'quiz', 'dropdown');
+        // foreach($quizs->data->items as $form){
+        //     $forms_dd[$form->id] = $form->name . ' (Quiz)';
+        // }
         $metrics = $this->getlambda('get', 'metric', 'metric', 'get_all');
         $metric_dd = ['NA' => 'Tracking Not Needed'];
         foreach($metrics->data->items as $metric){
@@ -85,7 +85,7 @@ class MagmediaController extends Controller
             'name' => ['title' => 'Media Name', 'type' => 'text', 'required' => true],
             'points' => ['title' => 'Points', 'type' => 'text', 'required' => true],
             'description' => ['title' => 'Media Description', 'type' => 'textarea', 'required' => true, 'fullwidth' => true],
-            'media_type' => ['title' => 'Media Types', 'type' => 'select', 'required' => true, 'values' => ['image' => 'Image', 'video' => 'Video', 'pdf' => 'PDF', 'audio' => 'Audio', 'gallery' => 'Gallery', 'form' => 'Form', 'quiz' => 'Quiz', 'survey' => 'Survey', 'external' => 'External Url',  'scorm' => 'Scorm']  ],
+            'media_type' => ['title' => 'Media Types', 'type' => 'select', 'required' => true, 'values' => ['image' => 'Image', 'video' => 'Video', 'pdf' => 'PDF', 'audio' => 'Audio', 'gallery' => 'Gallery', 'form' => 'Form', 'external' => 'External Url',  'scorm' => 'Scorm']  ],
             'status' => ['title' => 'Status', 'type' => 'select', 'required' => true, 'values' => ['active' => 'Active', 'disabled' => 'Disabled'] ],
             'form_id' => ['title' => 'Form', 'type' => 'select', 'values' => $forms_dd ],
             //'media_thumb' => ['title' => 'Media Thumb', 'type' => 'image', 'required' => true],
diff --git a/app/Http/Controllers/MetriccampaignController.php b/app/Http/Controllers/MetriccampaignController.php
index 1bad60e8..b1a67290 100644
--- a/app/Http/Controllers/MetriccampaignController.php
+++ b/app/Http/Controllers/MetriccampaignController.php
@@ -82,6 +82,7 @@ class MetriccampaignController extends Controller
 
         $userAttrs = $this->getlambda('get', 'ums', 'userattribute', 'get_all');
         $attrs_dd = [];
+        $userattr_dd = [];
         $attrs_meta = [];
         foreach (($userAttrs->data->items ?? []) as $attr) {
             if (($attr->status ?? 'active') === 'disabled') {
@@ -101,6 +102,7 @@ class MetriccampaignController extends Controller
                 $options[$optValue] = (string) ($opt['display'] ?? $optValue);
             }
             $attrs_dd[$code] = ($attr->attribute ?? $code) . ' (' . $code . ')';
+            $userattr_dd[$code] = $attr->attribute ?? $code;
             $attrs_meta[$code] = [
                 'code' => $code,
                 'attribute' => $attr->attribute ?? $code,
@@ -232,6 +234,13 @@ class MetriccampaignController extends Controller
             'status' => ['title' => 'Status', 'type' => 'select', 'required' => true, 'values' => ['active' => 'Active', 'disabled' => 'Disabled']],
             'show_positions' => ['title' => 'Show Positions', 'type' => 'select', 'required' => true, 'values' => ['yes' => 'Yes', 'no' => 'No']],
             'show_positions_type' => ['title' => 'Show Positions Type', 'type' => 'select', 'required' => true, 'values' => ['all' => 'All', 'loggedin' => 'Logged In']],
+            'show_units_export' => ['title' => 'Show Units Export', 'type' => 'select', 'required' => true, 'values' => ['yes' => 'Yes', 'no' => 'No'], 'default' => 'yes'],
+            'show_user_attributes' => ['title' => 'Show User Attributes', 'type' => 'select', 'required' => true, 'values' => ['yes' => 'Yes', 'no' => 'No'], 'default' => 'no'],
+            'user_attributes' => ['title' => 'Include User Attributes', 'type' => 'multiselect', 'values' => $userattr_dd, 'default' => []],
+            'show_normal_metrics' => ['title' => 'Show Normal Metrics', 'type' => 'select', 'required' => true, 'values' => ['yes' => 'Yes', 'no' => 'No'], 'default' => 'yes'],
+            'show_combined_metrics' => ['title' => 'Show Combined Metrics', 'type' => 'select', 'required' => true, 'values' => ['yes' => 'Yes', 'no' => 'No'], 'default' => 'yes'],
+            'manager_details' => ['title' => 'Manager Details', 'type' => 'multiselect', 'values' => ['l1manager' => 'L1 Manager', 'l2manager' => 'L2 Manager', 'l3manager' => 'L3 Manager', 'l4manager' => 'L4 Manager'], 'default' => ['l1manager']],
+            'total_units_name' => ['title' => 'Total Units Name', 'type' => 'text', 'required' => false, 'default' => ''],
             'highlight_top' => [
                 'title' => 'Highlight Top',
                 'type' => 'text',
@@ -290,18 +299,29 @@ class MetriccampaignController extends Controller
                 'showTeamRank' => 'yes',
                 'showManagerOverallRank' => 'yes',
                 'showManagerTeamRank' => 'yes',
+                'show_units_export' => 'yes',
+                'show_normal_metrics' => 'yes',
+                'show_combined_metrics' => 'yes',
             ];
             foreach ($yesNoDefaults as $field => $default) {
                 if (!isset($values->{$field}) || $values->{$field} === '' || $values->{$field} === null) {
                     $values->{$field} = $default;
                 }
             }
+            $values->show_user_attributes = (($values->show_user_attributes ?? 'no') === 'yes') ? 'yes' : 'no';
+            $values->user_attributes = $this->normalizeAttributeCodes($values->user_attributes ?? []);
             if (!isset($values->highlight_top) || $values->highlight_top === null) {
                 $values->highlight_top = '';
             }
             if (!isset($values->highlight_color) || $values->highlight_color === null) {
                 $values->highlight_color = '';
             }
+            if (!isset($values->manager_details) || !is_array($values->manager_details) || $values->manager_details === []) {
+                $values->manager_details = ['l1manager'];
+            }
+            if (!isset($values->total_units_name) || $values->total_units_name === null) {
+                $values->total_units_name = '';
+            }
             if (!isset($values->sort_order) || $values->sort_order === null || $values->sort_order === '') {
                 $values->sort_order = 0;
             }
@@ -315,6 +335,13 @@ class MetriccampaignController extends Controller
             $values->showManagerTeamRank = 'yes';
             $values->highlight_top = '';
             $values->highlight_color = '';
+            $values->show_units_export = 'yes';
+            $values->show_user_attributes = 'no';
+            $values->user_attributes = [];
+            $values->show_normal_metrics = 'yes';
+            $values->show_combined_metrics = 'yes';
+            $values->manager_details = ['l1manager'];
+            $values->total_units_name = '';
         }
         return $values;
     }
@@ -637,6 +664,18 @@ class MetriccampaignController extends Controller
             ? (int) $data['sort_order']
             : 0;
 
+        if (!isset($data['manager_details']) || !is_array($data['manager_details']) || $data['manager_details'] === []) {
+            $data['manager_details'] = ['l1manager'];
+        }
+        foreach (['show_units_export', 'show_normal_metrics', 'show_combined_metrics'] as $flag) {
+            if (!isset($data[$flag]) || $data[$flag] === '') {
+                $data[$flag] = 'yes';
+            }
+        }
+        $data['total_units_name'] = trim((string) ($data['total_units_name'] ?? ''));
+        $data['show_user_attributes'] = ($data['show_user_attributes'] ?? 'no') === 'yes' ? 'yes' : 'no';
+        $data['user_attributes'] = $this->normalizeAttributeCodes($data['user_attributes'] ?? []);
+
         unset(
             $data['metric'],
             $data['display_unit'],
@@ -654,5 +693,29 @@ class MetriccampaignController extends Controller
         );
         return $data;
     }
+
+    protected function normalizeAttributeCodes($raw): array
+    {
+        if (is_string($raw) && $raw !== '') {
+            $decoded = json_decode($raw, true);
+            $raw = is_array($decoded) ? $decoded : explode('#', $raw);
+        }
+        if (!is_array($raw)) {
+            return [];
+        }
+
+        $codes = [];
+        foreach ($raw as $code) {
+            if (is_object($code)) {
+                $code = $code->code ?? '';
+            }
+            $code = trim((string) $code);
+            if ($code !== '') {
+                $codes[] = $code;
+            }
+        }
+
+        return array_values(array_unique($codes));
+    }
 }
 
diff --git a/app/Http/Controllers/MetricviewController.php b/app/Http/Controllers/MetricviewController.php
index c7e4a796..f62333dc 100644
--- a/app/Http/Controllers/MetricviewController.php
+++ b/app/Http/Controllers/MetricviewController.php
@@ -196,23 +196,28 @@ class MetricviewController extends Controller
             'agg_type' => ['title' => 'Aggregation Type', 'type' => 'select', 'required' => true, 'values' => ['individual' => 'Individual', 'team' => 'Team'] ],
 
             'graphical_views' => ['title' => 'Graphical views', 'type' => 'multiselect', 'values' => ['progress' => 'Progress', 'percentile' => 'Percentile', 'pie' => 'Pie chart', 'bar' => 'Bar chart', 'line' => 'Line chart'] ],
-            'details_views' => ['title' => 'Details views', 'type' => 'multiselect', 'required' => true, 'values' => ['tabular' => 'Tabular', 'contacts' => 'Doctor View', 'incremental' => 'Incremental View'] ],
+            'value_views' => ['title' => 'Value views', 'type' => 'multiselect', 'values' => ['value' => 'Value'] ],
+            'details_views' => ['title' => 'Details views', 'type' => 'multiselect', 'values' => ['tabular' => 'Tabular', 'contacts' => 'Doctor View', 'incremental' => 'Incremental View'] ],
             'trend_views' => ['title' => 'Trend views', 'type' => 'multiselect', 'values' => ['bar' => 'Bar chart', 'line' => 'Line chart', 'tabular' => 'Tabular'] ],
-            'leaderboard_views' => ['title' => 'Leaderboard views', 'type' => 'multiselect', 'values' => ['leaderboard' => 'Leaderboard View'] ],
+            'leaderboard_views' => ['title' => 'My Team views', 'type' => 'multiselect', 'values' => ['leaderboard' => 'My Team View'] ],
+            'scoreboard_views' => ['title' => 'Leaderboard views', 'type' => 'multiselect', 'values' => ['leaderboard' => 'Leaderboard View'] ],
             'contacts_views' => ['title' => 'Doctor views', 'type' => 'multiselect', 'values' => ['contacts' => 'Doctor View'] ],
             'doctor_view_name' => ['title' => 'Enter Doctor View Name', 'type' => 'text', 'required' => false ],
             
             'intervals' => ['title' => 'Intervals', 'type' => 'multiselect', 'required' => true, 'values' => ['yearlycy' => 'Calender Year', 'yearlyfy' => 'Financial Year', 'monthly' => 'Monthly', 'weekly' => 'Weekly', 'daily' => 'Daily'] ],
 
             'default_interval' => ['title' => 'Default Interval', 'type' => 'select', 'required' => true, 'values' => ['month-0' => 'This Month','month-1' => 'Last Month','month-3' => 'Last 3 Months','month-6' => 'Last 6 Months','month-12' => 'Last 12 Months','day-0' => 'Today','day-1' => 'Yesterday','day-7' => 'Last 7 Days','day-30' => 'Last 30 Days','week-0' => 'This week','week-1' => 'Last week','week-2' => '-2 week','week-3' => '-3 week','week-4' => '-4 week','yearfy-0' => 'This Financial Year','yearfy-1' => 'Last Financial Year','yearcy-0' => 'This Calender Year','yearcy-1' => 'Last Calender Year'] ],
+            'default_tab' => ['title' => 'Default Tab', 'type' => 'select', 'required' => false, 'values' => ['value' => 'Value', 'trend' => 'Trend', 'details' => 'Detail', 'leaderboard' => 'My Team', 'scoreboard' => 'Leaderboard', 'contacts' => 'Doctor List', 'incremental' => 'Incremental'] ],
             'widget_sort_order' => ['title' => 'Sort Order', 'type' => 'number', 'required' => true],
             'doctor_type' => ['title' => 'Doctor Type', 'type' => 'text', 'required' => false],
             'combined_metrics' => ['title' => 'Combined Metrics (if any)', 'type' => 'combined_metrics', 'fullwidth' => true ],
 
             'report_roles' => ['title' => 'Data Report from Roles', 'type' => 'multiselect', 'values' => $roles_dd, 'required' => true],
+            'team_leaderboard_widget_roles' => ['title' => 'Team Leaderboard Widget Roles', 'type' => 'multiselect', 'values' => $roles_dd, 'required' => false],
 
             'status' => ['title' => 'Status', 'type' => 'select', 'required' => true, 'values' => ['active' => 'Active', 'disabled' => 'Disabled'] ],
             'show_positions' => ['title' => 'Show Positions', 'type' => 'select', 'required' => true, 'values' => ['yes' => 'Yes', 'no' => 'No'] ],
+            'stateheadwise_leaderboard' => ['title' => 'Stateheadwise Leaderboard', 'type' => 'select', 'required' => true, 'values' => ['yes' => 'Yes', 'no' => 'No'], 'default' => 'no'],
             'show_positions_type' => ['title' => 'Show Positions Type', 'type' => 'select', 'required' => true, 'values' => ['all' => 'All', 'loggedin' => 'Logged In'] ],
             
             'contact_attributes' => ['title' => 'Include Doctor Attributes', 'type' => 'multiselect', 'values' => $coattr_dd],
@@ -289,12 +294,14 @@ class MetricviewController extends Controller
         unset($data['metric']);
         unset($data['display_unit']);
         unset($data['weight']);
-        foreach (['contacts_views', 'graphical_views', 'trend_views', 'leaderboard_views', 'contact_attributes', 'user_attributes'] as $field) {
+        foreach (['contacts_views', 'graphical_views', 'trend_views', 'leaderboard_views', 'scoreboard_views', 'value_views', 'details_views', 'contact_attributes', 'user_attributes', 'team_leaderboard_widget_roles'] as $field) {
             if (!isset($data[$field]) || !is_array($data[$field])) {
                 $data[$field] = [];
             }
         }
+        $data['default_tab'] = trim((string) ($data['default_tab'] ?? ''));
         $data['show_user_attributes'] = ($data['show_user_attributes'] ?? 'no') === 'yes' ? 'yes' : 'no';
+        $data['stateheadwise_leaderboard'] = ($data['stateheadwise_leaderboard'] ?? 'no') === 'yes' ? 'yes' : 'no';
         $data['user_attributes'] = $this->normalizeAttributeCodes($data['user_attributes']);
         if (array_key_exists('id', $data) && ($data['id'] === null || $data['id'] === '')) {
             unset($data['id']);
@@ -309,7 +316,11 @@ class MetricviewController extends Controller
                 'sort_order' => 0,
                 'widget_sort_order' => 0,
                 'show_user_attributes' => 'no',
+                'stateheadwise_leaderboard' => 'no',
                 'user_attributes' => [],
+                'team_leaderboard_widget_roles' => [],
+                'value_views' => ['value'],
+                'default_tab' => '',
             ];
             if (($values->type ?? '') === 'verses') {
                 $values->list_chart_type = 'progressbar';
@@ -328,6 +339,14 @@ class MetricviewController extends Controller
             } else {
                 $values->report_roles = [];
             }
+            $widgetRoles = $values->team_leaderboard_widget_roles ?? [];
+            if (is_string($widgetRoles)) {
+                $values->team_leaderboard_widget_roles = $widgetRoles === '' ? [] : explode('#', $widgetRoles);
+            } elseif (is_array($widgetRoles)) {
+                $values->team_leaderboard_widget_roles = array_values($widgetRoles);
+            } else {
+                $values->team_leaderboard_widget_roles = [];
+            }
             
             if(!isset($values->combined_metrics)) {
                 $values->combined_metrics = '[]';
@@ -369,7 +388,17 @@ class MetricviewController extends Controller
                 $values->show_positions_type = 'all';
             }
             $values->show_user_attributes = (($values->show_user_attributes ?? 'no') === 'yes') ? 'yes' : 'no';
+            $values->stateheadwise_leaderboard = (($values->stateheadwise_leaderboard ?? 'no') === 'yes') ? 'yes' : 'no';
             $values->user_attributes = $this->normalizeAttributeCodes($values->user_attributes ?? []);
+            if (!isset($values->value_views)) {
+                $values->value_views = ['value'];
+            }
+            if (!isset($values->scoreboard_views)) {
+                $values->scoreboard_views = $values->leaderboard_views ?? [];
+            }
+            if (!isset($values->default_tab)) {
+                $values->default_tab = '';
+            }
             if (($values->type ?? '') === 'verses' && (!isset($values->list_chart_type) || $values->list_chart_type === '')) {
                 $values->list_chart_type = 'progressbar';
             }
diff --git a/app/Http/Controllers/MilestoneController.php b/app/Http/Controllers/MilestoneController.php
index 4563b71c..503f9beb 100644
--- a/app/Http/Controllers/MilestoneController.php
+++ b/app/Http/Controllers/MilestoneController.php
@@ -109,6 +109,8 @@ class MilestoneController extends Controller
 
         //echo '
'; print_r($coattr_dd); exit;
 
+        $userattr_dd = $this->getUserAttributeOptions();
+
         $dates_dd = ['L' => 'Last day of month'];
         $date = 1;
         while($date <= 31) {
@@ -127,6 +129,13 @@ class MilestoneController extends Controller
             'status' => ['title' => 'Status', 'type' => 'select', 'required' => true, 'values' => ['active' => 'Active', 'disabled' => 'Disabled']],
             'show_positions' => ['title' => 'Show Positions', 'type' => 'select', 'required' => true, 'values' => ['yes' => 'Yes', 'no' => 'No']],
             'show_positions_type' => ['title' => 'Show Positions Type', 'type' => 'select', 'required' => true, 'values' => ['all' => 'All', 'loggedin' => 'Logged In']],
+            'show_units_export' => ['title' => 'Show Units Export', 'type' => 'select', 'required' => true, 'values' => ['yes' => 'Yes', 'no' => 'No'], 'default' => 'yes'],
+            'show_user_attributes' => ['title' => 'Show User Attributes', 'type' => 'select', 'required' => true, 'values' => ['yes' => 'Yes', 'no' => 'No'], 'default' => 'no'],
+            'user_attributes' => ['title' => 'Include User Attributes', 'type' => 'multiselect', 'values' => $userattr_dd, 'default' => []],
+            'show_normal_metrics' => ['title' => 'Show Normal Metrics', 'type' => 'select', 'required' => true, 'values' => ['yes' => 'Yes', 'no' => 'No'], 'default' => 'yes'],
+            'show_combined_metrics' => ['title' => 'Show Combined Metrics', 'type' => 'select', 'required' => true, 'values' => ['yes' => 'Yes', 'no' => 'No'], 'default' => 'yes'],
+            'manager_details' => ['title' => 'Manager Details', 'type' => 'multiselect', 'values' => ['l1manager' => 'L1 Manager', 'l2manager' => 'L2 Manager', 'l3manager' => 'L3 Manager', 'l4manager' => 'L4 Manager'], 'default' => ['l1manager']],
+            'total_units_name' => ['title' => 'Total Units Name', 'type' => 'text', 'required' => false, 'default' => ''],
             'send_report' => ['title' => 'Send Report', 'type' => 'select', 'required' => true, 'values' => ['yes' => 'Yes', 'no' => 'No']],
             'performance_period' => ['title' => 'Performance Period', 'type' => 'select', 'required' => false, 'values' => ['daily' => 'Daily', 'weekly' => 'Weekly', 'monthly' => 'Monthly']],
             'days_or_dates' => ['title' => 'Days or Dates', 'type' => 'radio', 'required' => false, 'fullwidth' => true, 'values' => ['days' => 'Days', 'dates' => 'Dates']],
@@ -296,6 +305,18 @@ $data['custom_end_date'] = !empty($data['custom_end_date'])
 
         // $data['is_recurring'] = isset($data['is_recurring']) ? (strtolower($data['is_recurring']) === 'yes') : false;
 
+        if (!isset($data['manager_details']) || !is_array($data['manager_details']) || $data['manager_details'] === []) {
+            $data['manager_details'] = ['l1manager'];
+        }
+        foreach (['show_units_export', 'show_normal_metrics', 'show_combined_metrics'] as $flag) {
+            if (!isset($data[$flag]) || $data[$flag] === '') {
+                $data[$flag] = 'yes';
+            }
+        }
+        $data['show_user_attributes'] = ($data['show_user_attributes'] ?? 'no') === 'yes' ? 'yes' : 'no';
+        $data['user_attributes'] = $this->normalizeAttributeCodes($data['user_attributes'] ?? []);
+        $data['total_units_name'] = trim((string) ($data['total_units_name'] ?? ''));
+
         unset($data['interval_start_date']);
         unset($data['interval_end_date']);
         unset($data['metric']);
@@ -327,6 +348,19 @@ $data['custom_end_date'] = !empty($data['custom_end_date'])
     protected function _beforeform($type, $values)
     {
         if ($type == 'edit') {
+            if (!isset($values->manager_details) || !is_array($values->manager_details) || $values->manager_details === []) {
+                $values->manager_details = ['l1manager'];
+            }
+            foreach (['show_units_export', 'show_normal_metrics', 'show_combined_metrics'] as $flag) {
+                if (!isset($values->{$flag}) || $values->{$flag} === '') {
+                    $values->{$flag} = 'yes';
+                }
+            }
+            $values->show_user_attributes = (($values->show_user_attributes ?? 'no') === 'yes') ? 'yes' : 'no';
+            $values->user_attributes = $this->normalizeAttributeCodes($values->user_attributes ?? []);
+            if (!isset($values->total_units_name) || $values->total_units_name === null) {
+                $values->total_units_name = '';
+            }
 
             // split interval
             if (!empty($values->interval)) {
@@ -525,6 +559,24 @@ $data['custom_end_date'] = !empty($data['custom_end_date'])
                 $rankRoles = [];
             }
 
+            $start = $board->validity_start ?? '';
+            $end = $board->validity_end ?? '';
+            $detailsUrl = url('/leaderboard/dashboard') . '?' . http_build_query([
+                'id' => $board->id ?? '',
+            ]);
+
+            if ($this->isTeamLeaderboardView($board->team_view ?? false)) {
+                $items[] = [
+                    'id' => $board->id ?? '',
+                    'title' => $board->name ?? 'Leaderboard',
+                    'interval_label' => trim($start . ($start && $end ? ' – ' : '') . $end),
+                    'details_url' => $detailsUrl,
+                    'podium_users' => $this->podiumUsersFromTeams($rankRoles),
+                    'pills' => [],
+                ];
+                continue;
+            }
+
             $pills = [];
             foreach ($rankRoles as $index => $rankRole) {
                 $rankRole = $this->asObject($rankRole);
@@ -536,8 +588,7 @@ $data['custom_end_date'] = !empty($data['custom_end_date'])
                 }
                 $pills[] = [
                     'label' => $this->rankRolePillLabel($rankRole->title ?? '', $limit, $role->name ?? ''),
-                    'url' => url('/leaderboard/dashboard') . '?' . http_build_query([
-                        'id' => $board->id ?? '',
+                    'url' => $detailsUrl . '&' . http_build_query([
                         'role' => $roleId,
                     ]),
                     'podium_users' => $this->podiumUsersFromRankRole($rankRole),
@@ -545,15 +596,11 @@ $data['custom_end_date'] = !empty($data['custom_end_date'])
                 ];
             }
 
-            $start = $board->validity_start ?? '';
-            $end = $board->validity_end ?? '';
             $items[] = [
                 'id' => $board->id ?? '',
                 'title' => $board->name ?? 'Leaderboard',
                 'interval_label' => trim($start . ($start && $end ? ' – ' : '') . $end),
-                'details_url' => $pills[0]['url'] ?? (url('/leaderboard/dashboard') . '?' . http_build_query([
-                    'id' => $board->id ?? '',
-                ])),
+                'details_url' => $pills[0]['url'] ?? $detailsUrl,
                 'podium_users' => $pills[0]['podium_users'] ?? [],
                 'pills' => $pills,
             ];
@@ -601,6 +648,38 @@ $data['custom_end_date'] = !empty($data['custom_end_date'])
         return $users;
     }
 
+    protected function isTeamLeaderboardView($flag): bool
+    {
+        return $flag === true || $flag === 1 || $flag === '1' || $flag === 'true' || $flag === 'yes';
+    }
+
+    protected function podiumUsersFromTeams(array $rows): array
+    {
+        usort($rows, function ($a, $b) {
+            $a = $this->asObject($a);
+            $b = $this->asObject($b);
+            return ((int) ($a->rank ?? 0)) <=> ((int) ($b->rank ?? 0));
+        });
+
+        $users = [];
+        foreach (array_slice($rows, 0, 3) as $row) {
+            $row = $this->asObject($row);
+            $team = $this->asObject($row->team ?? null);
+            $name = trim((string) ($team->name ?? ''));
+            if ($name === '') {
+                $name = 'Team';
+            }
+            $users[] = [
+                'name' => $name,
+                'initials' => $this->userInitials($name),
+                'avatar' => $team->icon ?? '',
+                'score' => is_numeric($row->score ?? null) ? (0 + $row->score) : 0,
+            ];
+        }
+
+        return $users;
+    }
+
     public function dashboardLeaderboardPodium(Request $request)
     {
         $data = $this->teamReportViewData($request);
@@ -1013,4 +1092,46 @@ $data['custom_end_date'] = !empty($data['custom_end_date'])
         $ts = strtotime((string) $value);
         return $ts ? date('d M Y', $ts) : '';
     }
+
+    protected function getUserAttributeOptions(): array
+    {
+        $options = [];
+        $userAttrs = $this->getlambda('get', 'ums', 'userattribute', 'get_all');
+        foreach (($userAttrs->data->items ?? []) as $attr) {
+            if (($attr->status ?? 'active') === 'disabled') {
+                continue;
+            }
+            $code = $attr->code ?? '';
+            if ($code === '') {
+                continue;
+            }
+            $options[$code] = $attr->attribute ?? $code;
+        }
+
+        return $options;
+    }
+
+    protected function normalizeAttributeCodes($raw): array
+    {
+        if (is_string($raw) && $raw !== '') {
+            $decoded = json_decode($raw, true);
+            $raw = is_array($decoded) ? $decoded : explode('#', $raw);
+        }
+        if (!is_array($raw)) {
+            return [];
+        }
+
+        $codes = [];
+        foreach ($raw as $code) {
+            if (is_object($code)) {
+                $code = $code->code ?? '';
+            }
+            $code = trim((string) $code);
+            if ($code !== '') {
+                $codes[] = $code;
+            }
+        }
+
+        return array_values(array_unique($codes));
+    }
 }
diff --git a/app/Http/Controllers/ZenovaReportController.php b/app/Http/Controllers/ZenovaReportController.php
new file mode 100644
index 00000000..0d5389cc
--- /dev/null
+++ b/app/Http/Controllers/ZenovaReportController.php
@@ -0,0 +1,348 @@
+middleware('auth');
+        $this->middleware(function ($request, $next) {
+            if (!ZenovaReportAccess::canView()) {
+                abort(403);
+            }
+
+            return $next($request);
+        });
+        $this->_service = 'reports';
+        $this->_module = 'zenova_reports';
+        $this->_viewname = 'zenovareport';
+        $this->_title = 'Glipiq & Sitazit Dashboard';
+        $this->_base_url = '/zenovareport';
+    }
+
+    public function list(Request $request)
+    {
+        return redirect('/zenovareport/dashboard');
+    }
+
+    public function dashboard(Request $request)
+    {
+        $level = $this->level($request);
+        $built = $this->build($level);
+
+        return view('zenovareport.dashboard', [
+            'title' => $this->_title,
+            'level' => $level,
+            'groups' => $built['groups'],
+            'body' => $built['body'],
+            'totals' => $built['totals'],
+            'error' => $built['error'],
+        ]);
+    }
+
+    public function export(Request $request)
+    {
+        $level = $this->level($request);
+        $built = $this->build($level);
+
+        if ($built['error']) {
+            return redirect('/zenovareport/dashboard?level=' . $level)->with('error', $built['error']);
+        }
+
+        return Excel::download(
+            new ZenovaReportExport($built['groups'], $built['raw_body'], $built['raw_totals']),
+            'glipiq-sitazit-level-' . $level . '.xlsx'
+        );
+    }
+
+    private function level(Request $request): int
+    {
+        $level = (int) $request->input('level', 3);
+
+        return ($level >= 1 && $level <= 5) ? $level : 3;
+    }
+
+    private function build(int $level): array
+    {
+        $response = LambdaRequest::signedrequest(
+            'get',
+            'reports',
+            'zenova_reports/glipiqsitazit_report',
+            ['level' => (string) $level],
+            120
+        );
+
+        $rows = [];
+        $error = null;
+        if (!$response || ($response->status ?? '') !== 'success') {
+            $error = $response->message ?? 'Unable to load the Glipiq & Sitazit report.';
+        } else {
+            $rows = is_array($response->data ?? null) ? $response->data : [];
+        }
+
+        $groups = $this->groups($rows);
+        $columns = [];
+        foreach ($groups as $group) {
+            foreach ($group['columns'] as $column) {
+                $columns[] = $column;
+            }
+        }
+
+        $body = [];
+        $rawBody = [];
+        $rawTotals = array_fill(0, count($columns), 0);
+        foreach ($rows as $row) {
+            $line = [];
+            $rawLine = [];
+            foreach ($columns as $index => $column) {
+                $raw = $this->rawValue($row, $column);
+                if ($column['type'] !== 'text') {
+                    $rawTotals[$index] += $raw;
+                }
+                $rawLine[] = $raw;
+                $line[] = $this->formatValue($raw, $column['type']);
+            }
+            $body[] = $line;
+            $rawBody[] = $rawLine;
+        }
+
+        $totals = [];
+        $rawTotalLine = [];
+        if (!empty($rows)) {
+            $fsos = $this->sumKey($rows, 'Demography & Details', 'No. of FSOs');
+            $glipiqActive = $this->sumKey($rows, 'Demography & Details', 'Glipiq Active FSOs');
+            $sitazitActive = $this->sumKey($rows, 'Demography & Details', 'Sitazit Active FSOs');
+            foreach ($columns as $index => $column) {
+                if ($column['type'] === 'text') {
+                    $label = !empty($column['is_total']) ? 'Total' : '';
+                    $totals[] = $label;
+                    $rawTotalLine[] = $label;
+                    continue;
+                }
+                if ($column['key'] === 'Glipiq Active FSO %') {
+                    $percent = $fsos ? ($glipiqActive / $fsos) * 100 : 0;
+                    $totals[] = $this->formatValue($percent, 'percent');
+                    $rawTotalLine[] = $percent;
+                    continue;
+                }
+                if ($column['key'] === 'Sitazit Active FSO %') {
+                    $percent = $fsos ? ($sitazitActive / $fsos) * 100 : 0;
+                    $totals[] = $this->formatValue($percent, 'percent');
+                    $rawTotalLine[] = $percent;
+                    continue;
+                }
+                $totals[] = $this->formatValue($rawTotals[$index], $column['type']);
+                $rawTotalLine[] = $rawTotals[$index];
+            }
+        }
+
+        return [
+            'groups' => $groups,
+            'body' => $body,
+            'totals' => $totals,
+            'raw_body' => $rawBody,
+            'raw_totals' => $rawTotalLine,
+            'error' => $error,
+        ];
+    }
+
+    private function groups(array $rows): array
+    {
+        $nameKey = 'Name of Region';
+        foreach ($rows as $row) {
+            $demo = (array) ($row->{'Demography & Details'} ?? []);
+            foreach (array_keys($demo) as $key) {
+                if (strpos($key, 'Name of ') === 0) {
+                    $nameKey = $key;
+                    break 2;
+                }
+            }
+        }
+
+        $demoColumns = $this->geoColumns($nameKey);
+        $currentType = strtolower(preg_replace('/^Name of /', '', $nameKey));
+        if (!in_array($currentType, ['zone', 'region', 'area'], true)) {
+            $nameColumn = $this->column($nameKey, 'Demography & Details', $nameKey, 'text');
+            $nameColumn['is_total'] = true;
+            $demoColumns[] = $nameColumn;
+        }
+        foreach ([
+            'No. of FSOs' => 'number',
+            'No. of DSMs' => 'number',
+            'Glipiq Active FSOs' => 'number',
+            'Glipiq Active FSO %' => 'percent',
+            'Sitazit Active FSOs' => 'number',
+            'Sitazit Active FSO %' => 'percent',
+        ] as $key => $type) {
+            $demoColumns[] = $this->column($key, 'Demography & Details', $key, $type);
+        }
+
+        $dayMetrics = ['GLIPIQ Total Rx', 'ABSI Camp Count', 'Patients Screened Count', 'Sitazit Total RX'];
+        $periodMetrics = [
+            'GLIPIQ Total Rx',
+            'GLIPIQ New Conversion',
+            'ABSI Camp Count',
+            'Patients Screened Count',
+            'GLIPIQ Total POB Units',
+            'Sitazit Total RX',
+            'Sitazit New Conversions',
+            'Sitazit Total POB Units',
+        ];
+
+        $groups = [
+            ['label' => 'Demography & Details', 'tone' => 'demo', 'columns' => $demoColumns],
+        ];
+
+        $first = isset($rows[0]) ? $rows[0] : null;
+
+        foreach ([
+            'day5' => 'Day 5 Status',
+            'day4' => 'Day 4 Status',
+            'day3' => 'Day 3 Status',
+            'day2' => 'Day 2 Status',
+        ] as $section => $title) {
+            $groups[] = [
+                'label' => $this->periodLabel($title, $this->sectionField($first, $section, 'date')),
+                'tone' => 'day',
+                'columns' => $this->metricColumns($section, $dayMetrics),
+            ];
+        }
+
+        $groups[] = [
+            'label' => $this->periodLabel('Day 1 / Yesterday', $this->sectionField($first, 'day1', 'date')),
+            'tone' => 'yesterday',
+            'columns' => $this->metricColumns('day1', $dayMetrics),
+        ];
+        $groups[] = [
+            'label' => $this->periodLabel('Today', $this->sectionField($first, 'today', 'date')),
+            'tone' => 'today',
+            'columns' => $this->metricColumns('today', $periodMetrics),
+        ];
+        $groups[] = [
+            'label' => $this->periodLabel('MTD', $this->sectionField($first, 'mtd', 'period')),
+            'tone' => 'mtd',
+            'columns' => $this->metricColumns('mtd', $periodMetrics),
+        ];
+        $groups[] = [
+            'label' => $this->periodLabel('YTD', $this->sectionField($first, 'ytd', 'period')),
+            'tone' => 'ytd',
+            'columns' => $this->metricColumns('ytd', $periodMetrics),
+        ];
+
+        return $groups;
+    }
+
+    private function geoColumns($nameKey)
+    {
+        $currentType = preg_replace('/^Name of /', '', $nameKey);
+        $columns = [];
+        foreach (['Zone', 'Region', 'Area'] as $type) {
+            if (strcasecmp($currentType, $type) === 0) {
+                $column = $this->column($type, 'Demography & Details', $nameKey, 'text');
+                $column['is_total'] = true;
+                $columns[] = $column;
+                continue;
+            }
+            $columns[] = $this->column($type, 'parent_positions', $type, 'text');
+        }
+
+        return $columns;
+    }
+
+    private function column(string $label, string $section, string $key, string $type): array
+    {
+        return [
+            'label' => $label,
+            'section' => $section,
+            'key' => $key,
+            'type' => $type,
+        ];
+    }
+
+    private function metricColumns(string $section, array $metrics): array
+    {
+        $columns = [];
+        foreach ($metrics as $metric) {
+            $columns[] = $this->column($metric, $section, $metric, 'number');
+        }
+
+        return $columns;
+    }
+
+    private function periodLabel(string $title, string $period): string
+    {
+        return $period !== '' ? $title . ' (' . $period . ')' : $title;
+    }
+
+    private function sectionField($row, $section, $field)
+    {
+        if (!is_object($row) || !isset($row->{$section}) || !is_object($row->{$section})) {
+            return '';
+        }
+
+        $value = $row->{$section}->{$field} ?? '';
+
+        return $value === null ? '' : (string) $value;
+    }
+
+    private function rawValue($row, array $column)
+    {
+        if ($column['section'] === 'parent_positions') {
+            $parents = (array) ($row->parent_positions ?? []);
+            foreach ($parents as $key => $parentValue) {
+                if (strcasecmp((string) $key, 'Country') === 0) {
+                    continue;
+                }
+                if (strcasecmp((string) $key, $column['key']) === 0) {
+                    return ($parentValue === null || $parentValue === '') ? '' : (string) $parentValue;
+                }
+            }
+
+            return '';
+        }
+
+        $section = $row->{$column['section']} ?? null;
+        $value = is_object($section) ? ($section->{$column['key']} ?? null) : null;
+        if ($column['type'] === 'text') {
+            return ($value === null || $value === '') ? '-' : (string) $value;
+        }
+
+        return is_numeric($value) ? (float) $value : 0;
+    }
+
+    private function sumKey(array $rows, string $section, string $key): float
+    {
+        $total = 0;
+        foreach ($rows as $row) {
+            $value = $row->{$section}->{$key} ?? 0;
+            if (is_numeric($value)) {
+                $total += (float) $value;
+            }
+        }
+
+        return $total;
+    }
+
+    private function formatValue($value, $type)
+    {
+        if ($type === 'text') {
+            return (string) $value;
+        }
+        $number = (float) $value;
+        if ($type === 'percent') {
+            return number_format($number, 2) . '%';
+        }
+        if (floor($number) == $number) {
+            return number_format($number, 0);
+        }
+
+        return number_format($number, 2);
+    }
+}
diff --git a/app/Http/Requests/LambdaRequest.php b/app/Http/Requests/LambdaRequest.php
index 3030da76..e91045ea 100644
--- a/app/Http/Requests/LambdaRequest.php
+++ b/app/Http/Requests/LambdaRequest.php
@@ -34,9 +34,9 @@ class LambdaRequest
 			case self::REMOTE_ENV != 'local' && $service == 'metric' && $url == 'metric_report/view':
 				return 'https://zbmttswaqimy3nmo5wactmja3i0uosyv.lambda-url.ap-south-1.on.aws/';
 				break;
-			case self::REMOTE_ENV != 'local' && $service == 'form' && $url == 'survey/getformhistory':
-				return 'https://tvp2drn5c2jpf7dpxdwwykdez40ikfbz.lambda-url.ap-south-1.on.aws/';
-				break;
+			// case self::REMOTE_ENV != 'local' && $service == 'form' && $url == 'survey/getformhistory':
+			// 	return 'https://tvp2drn5c2jpf7dpxdwwykdez40ikfbz.lambda-url.ap-south-1.on.aws/';
+			// 	break;
 			case self::REMOTE_ENV != 'local' && $service == 'form' && $url == 'formentity/getformhistory':
 				return 'https://sgrmuxa46z7xfouezyvg3tnhnq0yptjn.lambda-url.ap-south-1.on.aws/';
 				break;
@@ -46,9 +46,9 @@ class LambdaRequest
 			case self::REMOTE_ENV != 'local' && $service == 'catalog' && $url == 'activity/nyexecutionfollowup':
 				return 'https://4sat7sjmmslqwq6nil2kxjxm6y0afoii.lambda-url.ap-south-1.on.aws/';
 				break;
-			case self::REMOTE_ENV != 'local' && $service == 'form' && $url == 'quiz/getformhistory':
-				return 'https://pc4gexzhd43hwms34p7vldwksm0zoehu.lambda-url.ap-south-1.on.aws/';
-				break;
+			// case self::REMOTE_ENV != 'local' && $service == 'form' && $url == 'quiz/getformhistory':
+			// 	return 'https://pc4gexzhd43hwms34p7vldwksm0zoehu.lambda-url.ap-south-1.on.aws/';
+			// 	break;
 			case self::REMOTE_ENV != 'local' && $service == 'magazine' && $url == 'magazine/magazine_export':
 				return 'https://bmilwmrobqef6z6pxmb7aa2tui0pcrca.lambda-url.ap-south-1.on.aws/';
 				break;
diff --git a/app/Services/InceptNebAllocation.php b/app/Services/InceptNebAllocation.php
index 82e3b5ef..8ef91e05 100644
--- a/app/Services/InceptNebAllocation.php
+++ b/app/Services/InceptNebAllocation.php
@@ -55,6 +55,70 @@ class InceptNebAllocation
         ];
     }
 
+    public function summaryAll()
+    {
+        $allocated = [
+            'robust' => (int) DB::table('incept_neb_allocations')->sum('robust_qty'),
+            'mini' => (int) DB::table('incept_neb_allocations')->sum('mini_qty'),
+            'control_d' => (int) DB::table('incept_neb_allocations')->sum('control_d_qty'),
+        ];
+        $usedByType = [];
+        $rows = DB::table('incept_neb_usage')
+            ->select('nebulizer_type', DB::raw('SUM(qty) as used_qty'))
+            ->groupBy('nebulizer_type')
+            ->get();
+        foreach ($rows as $row) {
+            $usedByType[$row->nebulizer_type] = (int) $row->used_qty;
+        }
+
+        $types = [];
+        foreach (self::TYPES as $key => $meta) {
+            $qty = (int) ($allocated[$key] ?? 0);
+            $used = (int) ($usedByType[$key] ?? 0);
+            $types[$key] = [
+                'label' => $meta['label'],
+                'allocated' => $qty,
+                'used' => $used,
+                'balance' => max(0, $qty - $used),
+            ];
+        }
+
+        return ['types' => $types];
+    }
+
+    public function viewerSeesOrgTotals($formdata)
+    {
+        $allAccess = $this->formValue($formdata, 'all_data_access');
+        if ($allAccess === true || $allAccess === 1 || $allAccess === '1' || $allAccess === 'true') {
+            return true;
+        }
+
+        $designation = '';
+        if (is_object($formdata) && isset($formdata->user_info->designation)) {
+            $designation = (string) $formdata->user_info->designation;
+        }
+        if (trim($designation) === '') {
+            $empId = $this->resolveEmpId([], is_object($formdata) ? $formdata : null);
+            if ($empId !== '') {
+                $allocation = DB::table('incept_neb_allocations')->where('emp_id', $empId)->first();
+                $designation = $allocation->designation ?? '';
+            }
+        }
+
+        return strtoupper(trim($designation)) === 'HO';
+    }
+
+    public function withOrgTotals(array $summary, $formdata)
+    {
+        if (!$this->viewerSeesOrgTotals($formdata)) {
+            return $summary;
+        }
+        $summary['show_org_totals'] = true;
+        $summary['org_totals'] = $this->summaryAll();
+
+        return $summary;
+    }
+
     public function mslUsage($mslCode)
     {
         $mslCode = trim((string) $mslCode);
@@ -222,6 +286,15 @@ class InceptNebAllocation
         DB::table('incept_neb_usage')->where('id', $usageId)->delete();
     }
 
+    public function releaseBySubmission($submissionId)
+    {
+        $submissionId = trim((string) $submissionId);
+        if ($submissionId === '') {
+            return;
+        }
+        DB::table('incept_neb_usage')->where('submission_id', $submissionId)->delete();
+    }
+
     public function typeKey($label)
     {
         $value = strtolower(trim((string) $label));
@@ -256,6 +329,18 @@ class InceptNebAllocation
         return $value;
     }
 
+    private function formValue($formdata, $key)
+    {
+        if (is_object($formdata) && isset($formdata->{$key})) {
+            return $formdata->{$key};
+        }
+        if (is_array($formdata) && array_key_exists($key, $formdata)) {
+            return $formdata[$key];
+        }
+
+        return null;
+    }
+
     private function acquireLock($name)
     {
         $name = substr($name, 0, 64);
diff --git a/resources/views/actionsheet/form.blade.php b/resources/views/actionsheet/form.blade.php
new file mode 100644
index 00000000..b273c110
--- /dev/null
+++ b/resources/views/actionsheet/form.blade.php
@@ -0,0 +1,186 @@
+@extends('layouts.vertical', ["page_title"=> "Add & Edit Action Sheets"])
+
+@section('css')
+
+@endsection
+
+@section('content')
+
+@if(session()->has('message')) +
+ {{ session()->get('message') }} +
+@endif +@if(session()->has('error')) +
+ {{ session()->get('error') }} +
+@endif +
+
+
+
+ @csrf +
+
+
+

Add / Edit {{$title}}

+
+
+
+ Back + +
+
+ @foreach($fields as $field => $props) + @if($props['type'] != 'hidden') +
+ + @endif + @switch($props['type']) + @case('hidden') + + @break + @case('text') + + @break + @case('number') + + @break + @case('date') + + @break + @case('textarea') + + @if($field === 'html_format') + Use each metric id as a placeholder, for example {Ciphaler}. + @endif + @break + @case('select') + @php + $selected = ($mode == 'edit' && isset($item->{$field}) && $item->{$field} !== '') ? $item->{$field} : ($props['default'] ?? ''); + @endphp + + @break + @case('multiselect') + + @break + @case('sheet_metrics') +
+ Metric id is the HTML placeholder key. + +
+
+ + + + + + + + + + + + @php + $sheetMetrics = ($mode == 'edit' && isset($item->metrics) && is_array($item->metrics)) ? $item->metrics : []; + @endphp + @foreach($sheetMetrics as $i => $map) + + + + + + + + @endforeach + +
IdTitleLabelMetrics
+ +
+
+ @break + @endswitch + @if($props['type'] != 'hidden') +
+ @endif + @endforeach +
+
+ Back + +
+
+
+
+
+
+
+
+
+ +@endsection + +@section('script') + + +@endsection diff --git a/resources/views/form/list-console.blade.php b/resources/views/form/list-console.blade.php index 3e0b24df..15eb3ca3 100644 --- a/resources/views/form/list-console.blade.php +++ b/resources/views/form/list-console.blade.php @@ -15,7 +15,7 @@ @endif
-
+
image

{{$action->name}}

@@ -65,8 +65,11 @@ + + + diff --git a/resources/views/form/rapport-team3/partials/doctor-chemist-chemist.blade.php b/resources/views/form/rapport-team3/partials/doctor-chemist-chemist.blade.php new file mode 100644 index 00000000..e2266d79 --- /dev/null +++ b/resources/views/form/rapport-team3/partials/doctor-chemist-chemist.blade.php @@ -0,0 +1,111 @@ +@php +$chemistTypes = ['Attached','Floating','Semi','Chain Pharmacy']; +$products = ['Asthalin Inhaler' => 'asthalin_inhaler','Asthalin Rotacap' => 'asthalin_rotacap','Aerocort Inhaler' => 'aerocort_inhaler','Aerocort Rotacap' => 'aerocort_rotacap','Aerocort Forte Rotacap' => 'aerocort_forte_rotacap']; +$therapies = [ + 'Nebulization No. of Respules per Month (all included)' => 'neb_respules', + 'Foracort Inhaler No. of Units per Month' => 'foracort_inhaler_units', + 'Foracort Rotacaps No. of Units per Month' => 'foracort_rotacaps_units', + 'Asthalin Syrup No. of Units per Month' => 'asthalin_syrup_units', + 'Levolin Syrup No. of Units per Month' => 'levolin_syrup_units', + 'Deriphyllin No. of Units per Month' => 'deriphyllin_units', + 'Beclomethasone Tablet No. of Units per Month' => 'beclomethasone_units', + 'Methylprednisolone Tablet No. of Units per Month' => 'methylprednisolone_units', + 'Prednisolone Tablet No. of Units per Month' => 'prednisolone_units', + 'Cough Syrups No. of Units per Month' => 'cough_syrups_units', +]; +$services = ['BP Monitoring' => 'chemist_bp_monitoring','Weighing Machine' => 'chemist_weighing_machine','Nebulization' => 'chemist_nebulization']; +@endphp + +
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+ +
+ @foreach($products as $label => $code) +
{{$label}}
+
+
+ + + + + + + + + + + @foreach([1, 2, 3] as $doctor) + + + + + + + @endforeach + +
NameMobileSpecialityCode
+ + +
+
+ @endforeach +
+
+ +
+ +
+ @foreach([1,2] as $n) +
+
+
+
+
+
+ @endforeach +
+
+ +
+ +
+ @foreach($therapies as $label => $code) +
+ @endforeach +
+
+ +
+ +
+ @foreach($services as $label => $code) +
+ + + +
+ @endforeach +
+
diff --git a/resources/views/form/rapport-team3/partials/doctor-chemist-doctor.blade.php b/resources/views/form/rapport-team3/partials/doctor-chemist-doctor.blade.php new file mode 100644 index 00000000..d9984765 --- /dev/null +++ b/resources/views/form/rapport-team3/partials/doctor-chemist-doctor.blade.php @@ -0,0 +1,54 @@ +@php +$degrees = ['B. Pharm','D. Pharm','M.B.B.S.','M.D.','B.A.M.S.','B.U.M.S.','B.H.M.S.','D.A.M.S.','D.H.M.S.','B.M.S.','D.M.S.','B.E.M.S.','R.M.P.','B.I.A.M.','D.I.A.M.','C.M.S.','C.M.S. & E.D.','C.C.M.P.','G.A.M.S.','B.P.T.','B.S.C.','Other']; +$specialties = ['G.P (General Physician)','R.M.P (Registered Medical Practitioner)','C.P (Consulting Physician)','Child Specialist','Pediatrician','Family Physician','Multi Speciality','Gynecologist','Urologist','Physiotherapy','Other']; +$bands = ['0-10','10-20','20-30','30-40','40-50','More than 50']; +$doctorTypes = ['Standalone Clinic','Nursing Home','Hospital','Dispensing Doctors (Doctor with Self Purchase)']; +$products = ['Asthalin Inhaler' => 'asthalin_inhaler','Asthalin Rotacap' => 'asthalin_rotacap','Aerocort Inhaler' => 'aerocort_inhaler','Aerocort Rotacap' => 'aerocort_rotacap','Aerocort Forte Rotacap' => 'aerocort_forte_rotacap']; +@endphp + +
+ +
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+
+
+ +
+ +
+
+
+
+
+ +
+ +
+ @foreach($products as $label => $code) +
+ @endforeach +
+
+ +
+ +
+

Upload one of visiting card, Rx pad or doctor board. JPG, JPEG or PNG, max 10 MB.

+
+
+
+
+
diff --git a/resources/views/form/rapport-team3/partials/incept-neb-org-totals.blade.php b/resources/views/form/rapport-team3/partials/incept-neb-org-totals.blade.php new file mode 100644 index 00000000..88a1ba4c --- /dev/null +++ b/resources/views/form/rapport-team3/partials/incept-neb-org-totals.blade.php @@ -0,0 +1,36 @@ +@php + $orgTypes = $orgTypes ?? []; +@endphp +

All employees

+
+ + + + + @foreach(['robust', 'mini', 'control_d'] as $typeKey) + + @endforeach + + + + + + @foreach(['robust', 'mini', 'control_d'] as $typeKey) + + @endforeach + + + + @foreach(['robust', 'mini', 'control_d'] as $typeKey) + + @endforeach + + + + @foreach(['robust', 'mini', 'control_d'] as $typeKey) + + @endforeach + + +
{{ $orgTypes[$typeKey]['label'] ?? ucwords(str_replace('_', ' ', $typeKey)) }}
Allocation{{ $orgTypes[$typeKey]['allocated'] ?? 0 }}
Submitted{{ $orgTypes[$typeKey]['used'] ?? 0 }}
Balance{{ $orgTypes[$typeKey]['balance'] ?? 0 }}
+
diff --git a/resources/views/form/rapport-titans/1790753526-37d85bd1-442d-4226-bacf-5f12e647373d.blade.php b/resources/views/form/rapport-titans/1790753526-37d85bd1-442d-4226-bacf-5f12e647373d.blade.php index cef77c80..c801644c 100644 --- a/resources/views/form/rapport-titans/1790753526-37d85bd1-442d-4226-bacf-5f12e647373d.blade.php +++ b/resources/views/form/rapport-titans/1790753526-37d85bd1-442d-4226-bacf-5f12e647373d.blade.php @@ -69,7 +69,7 @@ PCODE Speciality Month of Engagement - Core Value + Activity Executed as per Plan No. of Flutter Utilized @@ -89,17 +89,24 @@ @endphp - @if(isset($sub->formdata->execution_done) && $sub->formdata->execution_done == 'yes' ) Execution Done - - @else - + @if(isset($sub->formdata->execution_done) && $sub->formdata->execution_done == 'yes') + + Execution Done + + @elseif(isset($sub->workflow_status) && $sub->workflow_status == 'approved') + + + + @elseif(isset($sub->workflow_status) && $sub->workflow_status == 'rejected') - + plan_details="{{ json_encode($sub->formdata) }}"> + Edit Plan + + @endif @@ -154,10 +161,21 @@
+
+ Please ensure to add Plan for all your Core Drs which will be executed during the period Oct 2026 - Dec 2026 +
+
@csrf + + + + + + + @if ($id) @endif @@ -171,6 +189,7 @@ 'sync', 'cip', 'total', + 'no_of_patients_screened', ])) @continue @endif @@ -273,7 +292,24 @@ enctype="multipart/form-data"> @csrf + + + + + + + + + + + + + {{-- --}} @foreach ($questions as $question)
@@ -405,6 +441,70 @@ $('#form-wrapper').hide() $('#execution-form-wrapper').hide() + // Patients Screened should only appear in Execution view for LHC + $('#execution-no_of_patients_screened').closest('.col-md-12').hide(); + $('#execution-no_of_patients_screened').prop('required', false); + + // ===================================================== + // DRIVE COUNT FUNCTIONS + // ===================================================== + + // Plan Filling + function setPlanDriveCount() { + + const activity = ($('#field-activity').val() || '').trim().toUpperCase(); + + $('#drive-count-plans-lhc').val('0'); + $('#drive-count-execution-lhc').val('0'); + $('#drive-count-plans-novel').val('0'); + $('#drive-count-execution-novel').val('0'); + + if (activity === 'LHC') { + + $('#drive-count-plans-lhc').val('1'); + + } else if (activity === 'NOVEL') { + + $('#drive-count-plans-novel').val('1'); + } + } + + + // Execution + function setExecutionDriveCount() { + + const activity = ($('#execution-activity').val() || '').trim().toUpperCase(); + + $('#execution-drive-count-plans-lhc').val('0'); + $('#execution-drive-count-execution-lhc').val('0'); + $('#execution-drive-count-plans-novel').val('0'); + $('#execution-drive-count-execution-novel').val('0'); + + if (activity === 'LHC') { + + $('#execution-drive-count-execution-lhc').val('1'); + + } else if (activity === 'NOVEL') { + + $('#execution-drive-count-execution-novel').val('1'); + } + } + + + // Activity change - Plan + $('#field-activity').on('change.driveCount', function() { + setPlanDriveCount(); + }); + + + // Activity change - Execution + $('#execution-activity').on('change.driveCount', function() { + setExecutionDriveCount(); + }); + + + // Initialize Plan count + setPlanDriveCount(); $('#add-camp-btn').click(function() { @@ -417,6 +517,7 @@ }) $('.edit-btn').click(function() { + $('#execution-mode').val('yes'); $('#execution-form-wrapper').show(100, () => { $('#add-camp-btn').hide(0); }) @@ -474,6 +575,7 @@ // if(selected[k] != '') // $('#execution-'+k).val(selected[k]); } + let docid = $('#execution-contact_id option').map(function() { if ($(this).text().trim() == selected['contact_id'] || $(this).val().trim() == selected['cont_id']) return this; @@ -482,7 +584,58 @@ $('#execution-contact_id').val(docid) $('#execution-id').val(selected['id']) - $('#execution-no_of_flutter_utilized').closest('.col-md-12').after('

Prescription Generated

'); + // Set execution drive count + setExecutionDriveCount(); + + // Show Patients Screened only for LHC during Execution + const selectedActivity = ($('#execution-activity').val() || '').trim().toUpperCase(); + + if (selectedActivity === 'LHC') { + $('#execution-no_of_patients_screened') + .closest('.col-md-12') + .show(); + + $('#execution-no_of_patients_screened') + .prop('required', true); + } else { + $('#execution-no_of_patients_screened') + .closest('.col-md-12') + .hide(); + + $('#execution-no_of_patients_screened') + .prop('required', false) + .val(''); + } + + $('#execution-activity').off('change.patientsScreened').on('change.patientsScreened', function () { + + const activity = ($(this).val() || '').trim().toUpperCase(); + + if (activity === 'LHC') { + + $('#execution-no_of_patients_screened') + .closest('.col-md-12') + .show(); + + $('#execution-no_of_patients_screened') + .prop('required', true); + + } else { + + $('#execution-no_of_patients_screened') + .closest('.col-md-12') + .hide(); + + $('#execution-no_of_patients_screened') + .prop('required', false) + .val(''); + } + + }); + + $('#execution-no_of_patients_screened') + .closest('.col-md-12') + .after('

Prescription Generated

'); $('#execution-dpi,#execution-mdi,#execution-sync,#execution-cip').on('input', function() { var total=( @@ -520,6 +673,13 @@ $('#add-camp-btn').hide(0); }) $('#table-wrapper').hide() + // Restrict Edit Plan date selection to Oct 1, 2026 - Dec 31, 2026 + $('#execution-plan_date').attr('min', '2026-10-01'); + $('#execution-plan_date').attr('max', '2026-12-31'); + + // Edit Plan: Patients Screened is NOT applicable + $('#execution-no_of_patients_screened').closest('.col-md-12').hide(); + $('#execution-no_of_patients_screened').prop('required', false); @@ -531,8 +691,8 @@ console.log("Selected Doctor:", selected['contact_id']); - $('#execution-execution_as_per_plan,#execution-execution_date,#execution-no_of_flutter_utilized,#execution-dpi,#execution-mdi,#execution-sync,#execution-cip,#execution-total').parent().hide(); - $('#execution-execution_as_per_plan,#execution-execution_date,#execution-no_of_flutter_utilized,#execution-dpi,#execution-mdi,#execution-sync,#execution-cip,#execution-total').prop('required',false); + $('#execution-execution_as_per_plan,#execution-execution_date,#execution-no_of_flutter_utilized,#execution-dpi,#execution-mdi,#execution-sync,#execution-cip,#execution-total,#execution-no_of_patients_screened').parent().hide(); + $('#execution-execution_as_per_plan,#execution-execution_date,#execution-no_of_flutter_utilized,#execution-dpi,#execution-mdi,#execution-sync,#execution-cip,#execution-total,#execution-no_of_patients_screened').prop('required',false); @@ -554,6 +714,14 @@ // $('#execution-'+k).val(selected[k]); } + + + + $('#execution-activity').on('change.driveCount', function() { + setExecutionDriveCount(); + }); + + let docid = $('#execution-contact_id option').map(function() { if ($(this).text().trim() == selected['contact_id'] || $(this).val().trim() == selected['cont_id']) return this; }).attr('value') @@ -596,9 +764,17 @@ $('#execution-core_value').parent().hide(); $('#execution-core_value').prop('required',false); + + $('#execution-no_of_patients_screened').on('input', function () { + let value = $(this).val(); + // Remove everything except digits + value = value.replace(/[^0-9]/g, ''); + + $(this).val(value); + }); @@ -650,35 +826,67 @@ }) $('#report-form').on('submit', function(event) { - event.preventDefault(); - $("#pageloader").fadeIn(); - $.ajax({ - type: "POST", - url: '/form/ajaxsubmit', - data: new FormData($('#report-form')[0]), - processData: false, - contentType: false, - success: function(data) { - $("#pageloader").fadeOut(); - if (data.status == 'success') { - Swal.fire({ - title: 'Great!', - text: 'Your data is successfully submitted!', - icon: 'success' - }) - window.location.reload(); - //$('#report-form').trigger("reset"); - } else { - Swal.fire({ - icon: 'error', - title: 'Data not submitted', - text: 'Something went wrong, please try again after some time!' - }) - } - }, - }); - }); + // Get the currently selected activity + const activity = ($('#field-activity').val() || '').trim().toUpperCase(); + + // Reset all Plan/Execution counters + $('#count-plans-lhc').val('0'); + $('#count-execution-lhc').val('0'); + $('#count-plans-novel').val('0'); + $('#count-execution-novel').val('0'); + + // Set Plan counter based on selected Activity + if (activity === 'LHC') { + + $('#count-plans-lhc').val('1'); + + } else if (activity === 'NOVEL') { + + $('#count-plans-novel').val('1'); + } + + // Debug - check before submitting + console.log('Plan Activity:', activity); + console.log('Count of Plans_LHC:', $('#count-plans-lhc').val()); + console.log('Count of Plans_Novel:', $('#count-plans-novel').val()); + + event.preventDefault(); + + $("#pageloader").fadeIn(); + + $.ajax({ + type: "POST", + url: '/form/ajaxsubmit', + data: new FormData($('#report-form')[0]), + processData: false, + contentType: false, + + success: function(data) { + + $("#pageloader").fadeOut(); + + if (data.status == 'success') { + + Swal.fire({ + title: 'Great!', + text: 'Your data is successfully submitted!', + icon: 'success' + }); + + window.location.reload(); + + } else { + + Swal.fire({ + icon: 'error', + title: 'Data not submitted', + text: 'Something went wrong, please try again after some time!' + }); + } + } + }); + }); $('#field-core_value').parent().hide(); @@ -701,6 +909,8 @@ $('#field-core_value').val($('option:selected', this).attr('core_value')) }) + + $('#field-msl_code').prop('readonly', true); $('#field-speciality').prop('readonly', true); @@ -746,36 +956,135 @@ }); $('#execution-form').on('submit', function(event) { - event.preventDefault(); - $("#pageloader").fadeIn(); - $.ajax({ - type: "POST", - url: '/form/ajaxsubmit', - data: new FormData($('#execution-form')[0]), - processData: false, - contentType: false, - success: function(data) { - $("#pageloader").fadeOut(); - if (data.status == 'success') { - Swal.fire({ - title: 'Great!', - text: 'Your data is successfully submitted!', - icon: 'success' - }).then(() => { - location.reload(); // 🔥 page refresh here - }); - $('#report-form').trigger("reset"); - } else { - Swal.fire({ - icon: 'error', - title: 'Data not submitted', - text: 'Something went wrong, please try again after some time!' - }) - } - }, - }); + + const activity = ($('#execution-activity').val() || '') + .trim() + .toUpperCase(); + + const executionMode = $('#execution-mode').val(); + + /* + * Reset all counters first + */ + $('#execution-count-plans-lhc').val('0'); + $('#execution-count-execution-lhc').val('0'); + $('#execution-count-plans-novel').val('0'); + $('#execution-count-execution-novel').val('0'); + + + /* + * Only set Execution counters when + * this is actual Execution, NOT Edit Plan. + */ + if (executionMode === 'yes') { + + if (activity === 'LHC') { + + $('#execution-count-execution-lhc').val('1'); + + } else if (activity === 'NOVEL') { + + $('#execution-count-execution-novel').val('1'); + } + } + + + /* + * Patients Screened validation + */ + if (activity === 'LHC') { + + const patientsScreened = + $('#execution-no_of_patients_screened').val(); + + if ( + patientsScreened === '' || + !/^\d+$/.test(patientsScreened) + ) { + + event.preventDefault(); + + Swal.fire({ + icon: 'error', + title: 'Invalid No. of Patients Screened', + text: 'Please enter a valid whole number. Negative numbers and decimal values are not allowed.' }); + $('#execution-no_of_patients_screened').focus(); + + return false; + } + } + + + /* + * Debug before submission + */ + console.log('Execution Mode:', executionMode); + console.log('Execution Activity:', activity); + + console.log( + 'Count of Execution_LHC:', + $('#execution-count-execution-lhc').val() + ); + + console.log( + 'Count of Execution_Novel:', + $('#execution-count-execution-novel').val() + ); + + + /* + * Submit + */ + event.preventDefault(); + + $("#pageloader").fadeIn(); + + $.ajax({ + type: "POST", + url: '/form/ajaxsubmit', + data: new FormData($('#execution-form')[0]), + processData: false, + contentType: false, + + success: function(data) { + + $("#pageloader").fadeOut(); + + if (data.status == 'success') { + + Swal.fire({ + title: 'Great!', + text: 'Your data is successfully submitted!', + icon: 'success' + }).then(() => { + location.reload(); + }); + + } else { + + Swal.fire({ + icon: 'error', + title: 'Data not submitted', + text: 'Something went wrong, please try again after some time!' + }); + } + }, + + error: function() { + + $("#pageloader").fadeOut(); + + Swal.fire({ + icon: 'error', + title: 'Data not submitted', + text: 'Something went wrong, please try again after some time!' + }); + } + }); +}); + var today = new Date(); var dd = String(today.getDate()).padStart(2, '0'); var mm = String(today.getMonth() + 1).padStart(2, '0'); diff --git a/resources/views/ivareport/_filters.blade.php b/resources/views/ivareport/_filters.blade.php index 6ad34842..51702b92 100644 --- a/resources/views/ivareport/_filters.blade.php +++ b/resources/views/ivareport/_filters.blade.php @@ -5,7 +5,10 @@ $hierCount = count($selectedHier); $brandCount = count($selectedBrands); @endphp - + + @if (!empty($token)) + + @endif