'lead_id', 'contact_name' => 'contact_id', ]; /** Columns that are always shown regardless of field settings. */ private static array $alwaysVisible = ['created_at', 'agent_name', 'survey', 'gln']; // ── Filter definitions ──────────────────────────────────────────────────── public static function availableQueryFilters(): array { $labelMap = []; if (Auth::check() && Schema::hasTable('call_log_field_settings')) { $service = app(CallLogFieldSettingsService::class); $labelMap = $service->getFieldLabelMap(Auth::user()); } $getLabel = function ($fieldKey, $defaultTranslation) use ($labelMap) { return $labelMap[$fieldKey] ?? trans($defaultTranslation); }; return [ [ 'id' => 'call_logs.phone_number', 'label' => $getLabel('phone_number', 'calllogs::calllogs.form.phone_number'), 'type' => 'string', ], [ 'id' => 'call_logs.subject', 'label' => $getLabel('subject', 'calllogs::calllogs.form.subject'), 'type' => 'string', ], [ 'id' => 'call_logs.status', 'label' => $getLabel('status', 'calllogs::calllogs.form.status'), 'type' => 'string', ], [ 'id' => 'call_logs.source', 'label' => $getLabel('source', 'calllogs::calllogs.form.source'), 'type' => 'string', ], [ 'id' => 'call_logs.queue', 'label' => $getLabel('queue', 'calllogs::calllogs.form.queue'), 'type' => 'string', ], [ 'id' => 'call_logs.outcome', 'label' => $getLabel('outcome', 'calllogs::calllogs.form.outcome'), 'type' => 'string', ], [ 'id' => 'call_logs.notes', 'label' => $getLabel('notes', 'calllogs::calllogs.form.notes'), 'type' => 'string', ], [ 'id' => 'call_logs.linkedid', 'label' => $getLabel('linkedid', 'calllogs::calllogs.form.linkedid'), 'type' => 'string', ], [ 'id' => 'leads.full_name', 'label' => $getLabel('lead_id', 'calllogs::calllogs.form.lead_name'), 'type' => 'string', ], [ 'id' => 'contacts.full_name', 'label' => $getLabel('contact_id', 'calllogs::calllogs.form.contact_name'), 'type' => 'string', ], ]; } // ── Columns ─────────────────────────────────────────────────────────────── public static function availableColumns(): array { return [ 'call_date' => [ 'data' => 'call_date', 'title' => trans('calllogs::calllogs.form.call_date'), 'data_type' => 'datetime', 'filter_type' => 'bap_date_range_picker', ], 'phone_number' => [ 'data' => 'phone_number', 'title' => trans('calllogs::calllogs.form.phone_number'), 'data_type' => 'text', 'filter_type' => 'text', ], 'gln' => [ 'data' => 'gln', 'title' => 'GLN', 'data_type' => 'text', 'filter_type' => 'text', ], 'direction' => [ 'data' => 'direction', 'title' => trans('calllogs::calllogs.form.direction'), 'data_type' => 'text', 'filter_type' => 'text', ], 'queue' => [ 'data' => 'queue', 'title' => trans('calllogs::calllogs.form.queue'), 'data_type' => 'text', 'filter_type' => 'text', ], 'status' => [ 'data' => 'status', 'title' => trans('calllogs::calllogs.form.status'), 'data_type' => 'text', 'filter_type' => 'text', ], 'source' => [ 'data' => 'source', 'title' => trans('calllogs::calllogs.form.source'), 'data_type' => 'text', 'filter_type' => 'text', ], 'outcome' => [ 'data' => 'outcome', 'title' => trans('calllogs::calllogs.form.outcome'), 'data_type' => 'text', 'filter_type' => 'text', ], 'survey' => [ 'data' => 'survey', 'title' => trans('calllogs::calllogs.form.survey'), 'data_type' => 'text', 'filter_type' => 'text', 'searchable' => false, 'orderable' => false, ], 'subject' => [ 'data' => 'subject', 'title' => trans('calllogs::calllogs.form.subject'), 'data_type' => 'text', 'filter_type' => 'text', ], 'lead_name' => [ 'name' => 'leads.full_name', 'data' => 'lead_name', 'title' => trans('calllogs::calllogs.form.lead_name'), 'data_type' => 'text', 'filter_type' => 'text', ], 'contact_name' => [ 'name' => 'contacts.full_name', 'data' => 'contact_name', 'title' => trans('calllogs::calllogs.form.contact_name'), 'data_type' => 'text', 'filter_type' => 'text', ], 'follow_up_date' => [ 'data' => 'follow_up_date', 'title' => trans('calllogs::calllogs.form.follow_up_date'), 'data_type' => 'text', 'filter_type' => 'text', ], 'agent_name' => [ 'name' => 'users.name', 'data' => 'agent_name', 'title' => trans('calllogs::calllogs.form.agent'), 'data_type' => 'text', 'filter_type' => 'text', 'orderable' => false, ], ]; } protected function setFilterDefinition(): void { $this->filterDefinition = self::availableQueryFilters(); } // ── DataTable build ─────────────────────────────────────────────────────── public function dataTable($query): EloquentDataTable { $dataTable = new EloquentDataTable($query); $this->applyLinks($dataTable, self::SHOW_URL_ROUTE); $dataTable->filterColumn('call_date', function ($q, $keyword) { $dates = DataTableHelper::getDatesForFilter($keyword); if ($dates) { $q->whereBetween('call_logs.call_date', [$dates[0], $dates[1]]); } }); $dataTable->filterColumn('follow_up_date', function ($q, $keyword) { $q->whereDate('call_logs.follow_up_date', $keyword); }); $dataTable->editColumn('survey', function ($row) { $answers = $row->survey; if (is_string($answers)) { $answers = json_decode($answers, true); } if (!is_array($answers)) { return ''; } $formatted = []; $questionCount = (int) CompanySettings::get('SURVEY_QUESTIONS_COUNT', 1); $questionCount = max(1, min(5, $questionCount)); for ($question = 1; $question <= $questionCount; $question++) { $answer = $answers['Q' . $question] ?? null; if (is_scalar($answer) && trim((string) $answer) !== '') { $formatted[] = trim((string) $answer); } } if (count($formatted) === 1) { return $formatted[0]; } return $formatted ? '(' . implode(', ', $formatted) . ')' : ''; }); // Register filter callbacks for each visible custom_data field if (Auth::check() && Schema::hasColumn('call_logs', 'custom_data')) { $service = app(CallLogFieldSettingsService::class); foreach ($service->getVisibleCustomFieldsForDatatable(Auth::user()) as $field) { $col = self::CUSTOM_COL_PREFIX . $field['key']; $jsonPath = '$.' . $field['key']; $isDateField = DataTableHelper::isDateCustomFieldType($field['type'] ?? null); $dataTable->filterColumn($col, function ($q, $keyword) use ($jsonPath, $isDateField) { if ($isDateField) { // Date range picker — keyword is " - " $dates = DataTableHelper::getDatesForFilter($keyword); if ($dates != null) { $q->whereRaw( 'DATE(JSON_UNQUOTE(JSON_EXTRACT(call_logs.custom_data, ?))) BETWEEN ? AND ?', [$jsonPath, $dates[0], $dates[1]] ); } return; } $q->whereRaw( "JSON_UNQUOTE(JSON_EXTRACT(call_logs.custom_data, ?)) LIKE ?", [$jsonPath, "%{$keyword}%"] ); }); } } return $dataTable; } // ── Query ───────────────────────────────────────────────────────────────── public function query(CallLog $model) { $query = $model->newQuery() ->leftJoin('leads', 'call_logs.lead_id', '=', 'leads.id') ->leftJoin('contacts', 'call_logs.contact_id', '=', 'contacts.id') ->leftJoin('users', 'call_logs.user_id', '=', 'users.id') ->select( 'call_logs.*', 'leads.full_name as lead_name', 'contacts.full_name as contact_name', 'users.name as agent_name' ) ->orderBy('call_logs.created_at', 'DESC'); // Add JSON extractions for visible custom_data fields if (Auth::check() && Schema::hasColumn('call_logs', 'custom_data')) { $service = app(CallLogFieldSettingsService::class); foreach ($service->getVisibleCustomFieldsForDatatable(Auth::user()) as $field) { $col = self::CUSTOM_COL_PREFIX . $field['key']; $jsonPath = '$.' . $field['key']; $query->addSelect( DB::raw("JSON_UNQUOTE(JSON_EXTRACT(call_logs.custom_data, '{$jsonPath}')) as `{$col}`") ); } } $user = auth()->user(); if ($user->hasRole('admin')) { // unrestricted } elseif ($user->hasRole('user')) { $query->where(function ($q) use ($user) { $q->where('call_logs.user_id', $user->id) ->orWhereNull('call_logs.user_id'); }); $query->whereIn('call_logs.direction', ['inbound', 'outbound']); } elseif ($user->hasRole('company_manager') || $user->access_to_all_entity == true) { $query->where(function ($q) use ($user) { $q->where('users.company_id', $user->company_id) ->orWhereNull('call_logs.user_id'); }); } else { $query->where(function ($q) use ($user) { $q->where('call_logs.user_id', $user->id) ->orWhereNull('call_logs.user_id'); }); } if (!empty($this->filterRules)) { $queryBuilderParser = new QueryBuilderParser(); return $queryBuilderParser->parse($this->filterRules, $query); } return $query; } // ── HTML builder ────────────────────────────────────────────────────────── public function html() { return $this->builder() ->columns($this->getColumns()) ->setTableAttribute('class', 'table table-hover') ->parameters([ 'dom' => 'lBfrtip', 'responsive' => false, 'stateSave' => true, 'filterDefinitions' => $this->getFilterDefinition(), 'filterRules' => $this->filterRules, 'headerFilters' => true, 'buttons' => DataTableHelper::buttons(), 'regexp' => true, ]); } protected function getColumns(): array { $allColumns = self::availableColumns(); // Append any visible custom_data fields as additional columns if (Auth::check() && Schema::hasColumn('call_logs', 'custom_data')) { $service = app(CallLogFieldSettingsService::class); foreach ($service->getVisibleCustomFieldsForDatatable(Auth::user()) as $field) { $col = self::CUSTOM_COL_PREFIX . $field['key']; $allColumns[$col] = [ 'data' => $col, 'title' => $field['label'], 'data_type' => 'text', 'filter_type' => DataTableHelper::isDateCustomFieldType($field['type'] ?? null) ? 'bap_date_range_picker' : 'text', 'searchable' => true, ]; } } // Without auth or settings table, return the full static list if (!Auth::check() || !Schema::hasTable('call_log_field_settings')) { return $allColumns; } $service = app(CallLogFieldSettingsService::class); $user = Auth::user(); $visibleKeys = $service->getVisibleFieldKeys($user); $orderMap = $service->getFieldOrderMap($user); $labelMap = $service->getFieldLabelMap($user); $filtered = []; foreach ($allColumns as $colKey => $colDef) { // Custom columns are already filtered by getVisibleCustomFieldsForDatatable if (strpos($colKey, self::CUSTOM_COL_PREFIX) === 0) { $filtered[$colKey] = $colDef; continue; } // Some columns are always shown (not managed by field settings) if (in_array($colKey, self::$alwaysVisible, true)) { $filtered[$colKey] = $colDef; continue; } // Resolve the field-settings key for this column $fieldKey = self::$columnToFieldMap[$colKey] ?? $colKey; if (in_array($fieldKey, $visibleKeys, true)) { // Apply label override from settings if (isset($labelMap[$fieldKey])) { $colDef['title'] = $labelMap[$fieldKey]; } $filtered[$colKey] = $colDef; } } // Sort columns by field order from settings uksort($filtered, function ($a, $b) use ($orderMap) { $pinLast = array_fill_keys(self::$alwaysVisible, 999000); $resolveOrder = function ($col) use ($orderMap, $pinLast) { if (isset($pinLast[$col])) return $pinLast[$col]; if (strpos($col, self::CUSTOM_COL_PREFIX) === 0) { $key = substr($col, strlen(self::CUSTOM_COL_PREFIX)); } else { $key = self::$columnToFieldMap[$col] ?? $col; } return $orderMap[$key] ?? 99999; }; return $resolveOrder($a) <=> $resolveOrder($b); }); return $filtered; } }