route())->getName(); return is_string($routeName) && Str::startsWith($routeName, 'contacts.contacts.'); } private static function customIndexFields() { if (!self::shouldIncludeCustomIndexColumns()) { return []; } $service = app(ContactFieldSettingsService::class); $customFields = $service->getVisibleCustomFieldsForIndex(Auth::user()); $result = []; foreach ($customFields as $field) { $key = (string) ($field['key'] ?? ''); if ($key === '' || !preg_match('/^[A-Za-z0-9_]+$/', $key)) { continue; } $column = self::CUSTOM_COLUMN_PREFIX . $key; $result[$column] = [ 'column' => $column, 'key' => $key, 'label' => (string) ($field['label'] ?? Str::title(str_replace('_', ' ', $key))), 'type' => (string) ($field['type'] ?? 'text'), 'uses_column' => !empty($field['uses_column']), ]; } return $result; } private static function customIndexColumns() { $columns = []; foreach (self::customIndexFields() as $field) { $dataType = 'text'; if ($field['type'] === 'email') { $dataType = 'email'; } elseif ($field['type'] === 'date') { $dataType = 'date'; } elseif ($field['type'] === 'datetime') { $dataType = 'datetime'; } $columns[$field['column']] = [ 'data' => $field['column'], 'title' => $field['label'], 'data_type' => $dataType, 'filter_type' => DataTableHelper::isDateCustomFieldType($field['type']) ? 'bap_date_range_picker' : 'text', 'searchable' => true, ]; } return $columns; } /** * Maps datatable column keys to their corresponding field settings keys. */ private static $columnToFieldMap = [ 'source' => 'contact_source_id', 'status' => 'contact_status_id', 'owner' => 'owned_by', 'country' => 'country_id', 'language' => 'language_id', ]; private static function fieldKeyForColumn($columnKey) { return self::$columnToFieldMap[$columnKey] ?? $columnKey; } public static function availableColumns() { $allColumns = [ 'full_name' => [ 'data' => 'full_name', 'title' => trans('contacts::contacts.form.full_name'), 'data_type' => 'text', 'filter_type' => 'text', ], 'status' => [ 'name' => 'contacts_dict_status.name', 'data' => 'status', 'title' => trans('contacts::contacts.form.status'), 'data_type' => 'text', 'filter_type' => 'text', 'column_name' => 'contact_status_id' ], 'email' => [ 'data' => 'email', 'title' => trans('contacts::contacts.form.email'), 'data_type' => 'text', 'filter_type' => 'text' ], 'phone' => [ 'data' => 'phone', 'title' => trans('contacts::contacts.form.phone'), 'data_type' => 'text', 'filter_type' => 'text' ], 'mobile' => [ 'data' => 'mobile', 'title' => trans('contacts::contacts.form.mobile'), 'data_type' => 'text', 'filter_type' => 'text' ], 'country' => [ 'data' => 'country', 'name' => 'bap_country.name', 'title' => trans('contacts::contacts.form.country_id'), 'data_type' => 'text', 'filter_type' => 'text' ], 'city' => [ 'data' => 'city', 'title' => trans('contacts::contacts.form.city'), 'data_type' => 'text', 'filter_type' => 'text' ], 'language' => [ 'name' => 'customer_dict_language.name', 'data' => 'language', 'title' => trans('contacts::contacts.form.language'), 'data_type' => 'text', 'filter_type' => 'text' ], 'source' => [ 'name' => 'contacts_dict_source.name', 'data' => 'source', 'title' => trans('contacts::contacts.form.source'), 'data_type' => 'text', 'filter_type' => 'text' ], 'job_title' => [ 'data' => 'job_title', 'title' => trans('contacts::contacts.form.job_title'), 'data_type' => 'text', 'filter_type' => 'text' ], 'department' => [ 'data' => 'department', 'title' => trans('contacts::contacts.form.department'), 'data_type' => 'text', 'filter_type' => 'text' ], 'fax' => [ 'data' => 'fax', 'title' => trans('contacts::contacts.form.fax'), 'data_type' => 'text', 'filter_type' => 'text' ], 'assistant_name' => [ 'data' => 'assistant_name', 'title' => trans('contacts::contacts.form.assistant_name'), 'data_type' => 'text', 'filter_type' => 'text' ], 'assistant_phone' => [ 'data' => 'assistant_phone', 'title' => trans('contacts::contacts.form.assistant_phone'), 'data_type' => 'text', 'filter_type' => 'text' ], 'street' => [ 'data' => 'street', 'title' => trans('contacts::contacts.form.street'), 'data_type' => 'text', 'filter_type' => 'text' ], 'state' => [ 'data' => 'state', 'title' => trans('contacts::contacts.form.state'), 'data_type' => 'text', 'filter_type' => 'text' ], 'zip_code' => [ 'data' => 'zip_code', 'title' => trans('contacts::contacts.form.zip_code'), 'data_type' => 'text', 'filter_type' => 'text' ], 'account_name' => [ 'name' => 'accounts.name', 'data' => 'account_name', 'title' => trans('contacts::contacts.form.account_name'), 'data_type' => 'text', 'filter_type' => 'text' ], 'created_at' => [ 'data' => 'created_at', 'title' => trans('core::core.table.created_at'), 'data_type' => 'datetime', 'filter_type' => 'bap_date_range_picker', ], 'updated_at' => [ 'data' => 'updated_at', 'title' => trans('core::core.table.updated_at'), 'data_type' => 'datetime', 'filter_type' => 'bap_date_range_picker', ], 'birth_date' => [ 'data' => 'birth_date', 'title' => trans('contacts::contacts.table.birth_date'), 'data_type' => 'date', 'filter_type' => 'bap_date_range_picker', ], 'owner' => [ 'data' => 'owner', 'title' => trans('core::core.table.assigned_to'), 'data_type' => 'assigned_to', 'orderable' => false, 'column_name' => 'assigned_to', 'filter_type' => 'select', 'select_type' => 'select2', 'select_type_options' => [ 'theme' => "bootstrap", 'width' => '100%' ], 'filter_data' => DataTableHelper::filterOwnerDropdown() ] ]; // Append custom field columns $allColumns = array_merge($allColumns, self::customIndexColumns()); // Apply field settings visibility (column ordering for the index is // fixed by the natural order of $allColumns above, which mirrors the // desired default index layout. Custom field columns are appended.) if (Auth::check() && Schema::hasTable('contact_user_field_settings')) { try { $service = app(ContactFieldSettingsService::class); $user = Auth::user(); $visibleKeys = $service->getVisibleFieldKeys($user); $labelMap = $service->getFieldLabelMap($user); if (!empty($visibleKeys)) { $filteredColumns = []; foreach ($allColumns as $colKey => $colDef) { $fieldKey = self::fieldKeyForColumn($colKey); if (Str::startsWith($colKey, self::CUSTOM_COLUMN_PREFIX) || in_array($fieldKey, $visibleKeys, true)) { if (isset($labelMap[$fieldKey])) { $colDef['title'] = $labelMap[$fieldKey]; } $filteredColumns[$colKey] = $colDef; } } $allColumns = $filteredColumns; } } catch (\Throwable $e) { // Silently ignore — field settings not ready } } return $allColumns; } public static function availableQueryFilters() { $labelMap = []; if (Auth::check() && Schema::hasTable('contact_user_field_settings')) { $service = app(ContactFieldSettingsService::class); $labelMap = $service->getFieldLabelMap(Auth::user()); } $getLabel = function ($fieldKey, $defaultTranslation) use ($labelMap) { return $labelMap[$fieldKey] ?? trans($defaultTranslation); }; return [ [ 'id' => 'contacts.full_name', 'label' => $getLabel('full_name', 'contacts::contacts.form.full_name'), 'type' => 'string', ], [ 'id' => 'contacts.job_title', 'label' => $getLabel('job_title', 'contacts::contacts.form.job_title'), 'type' => 'string', ], [ 'id' => 'contacts.country_id', 'label' => $getLabel('country_id', 'contacts::contacts.form.country_id'), 'type' => 'integer', 'input' => 'select', 'multiple' => true, 'plugin' => 'select2', 'plugin_config' => [ 'multiple' => 'multiple', 'width' => '300px', ], 'values' => Country::pluck('name', 'id'), 'operators' => [ 'in', 'not_in', 'is_null', 'is_not_null' ] ], [ 'id' => 'bap_country.continent', 'label' => $getLabel('continent', 'contacts::contacts.form.continent'), 'type' => 'string', 'input' => 'select', 'multiple' => true, 'plugin' => 'select2', 'plugin_config' => [ 'multiple' => 'multiple', 'width' => '300px', ], 'values' => Country::pluck('continent', 'continent'), 'operators' => [ 'in', 'not_in', 'is_null', 'is_not_null' ] ], [ 'id' => 'contacts.birth_date', 'label' => $getLabel('birth_date', 'contacts::contacts.form.birth_date'), 'type' => 'date', 'input_event' => 'dp.change', 'plugin' => 'datetimepicker', 'plugin_config' => [ 'locale' => app()->getLocale(), 'calendarWeeks' => true, 'showClear' => true, 'showTodayButton' => true, 'showClose' => true, 'format' => \Modules\Platform\Core\Helper\UserHelper::userJsDateFormat() ] ], [ 'id' => 'contacts.language_id', 'label' => $getLabel('language_id', 'contacts::contacts.form.language_id'), 'type' => 'integer', 'input' => 'select', 'multiple' => true, 'plugin' => 'select2', 'plugin_config' => [ 'multiple' => 'multiple', 'width' => '300px', ], 'values' => CustomerLanguage::pluck('name', 'id'), 'operators' => [ 'in', 'not_in', 'is_null', 'is_not_null' ] ], [ 'id' => 'contacts.department', 'label' => $getLabel('department', 'contacts::contacts.form.department'), 'type' => 'string', ], [ 'id' => 'contacts.phone', 'label' => $getLabel('phone', 'contacts::contacts.form.phone'), 'type' => 'string', ], [ 'id' => 'contacts.mobile', 'label' => $getLabel('mobile', 'contacts::contacts.form.mobile'), 'type' => 'string', ], [ 'id' => 'contact_email.email', 'label' => $getLabel('email', 'contacts::contacts.form.email'), 'type' => 'string', ], [ 'id' => 'contacts.fax', 'label' => $getLabel('fax', 'contacts::contacts.form.fax'), 'type' => 'string', ], [ 'id' => 'contacts.assistant_name', 'label' => $getLabel('assistant_name', 'contacts::contacts.form.assistant_name'), 'type' => 'string', ], [ 'id' => 'contacts.assistant_phone', 'label' => $getLabel('assistant_phone', 'contacts::contacts.form.assistant_phone'), 'type' => 'string', ], [ 'id' => 'contacts.street', 'label' => $getLabel('street', 'contacts::contacts.form.street'), 'type' => 'string', ], [ 'id' => 'contacts.state', 'label' => $getLabel('state', 'contacts::contacts.form.state'), 'type' => 'string', ], [ 'id' => 'contacts.zip_code', 'label' => $getLabel('zip_code', 'contacts::contacts.form.zip_code'), 'type' => 'string', ], [ 'id' => 'contacts.city', 'label' => $getLabel('city', 'contacts::contacts.form.city'), 'type' => 'string', ], [ 'id' => 'accounts.name', 'label' => $getLabel('account_id', 'contacts::contacts.form.account_name'), 'type' => 'string', ], [ 'id' => 'contacts.contact_status_id', 'label' => $getLabel('contact_status_id', 'contacts::contacts.form.contact_status_id'), 'type' => 'integer', 'input' => 'select', 'multiple' => true, 'plugin' => 'select2', 'plugin_config' => [ 'multiple' => 'multiple', 'width' => '300px', ], 'values' => ContactStatus::pluck('name', 'id'), 'operators' => [ 'in', 'not_in', 'is_null', 'is_not_null' ] ], [ 'id' => 'people_who_bought', 'label' => $getLabel('people_who_bought', 'contacts::contacts.form.people_who_bought'), 'type' => 'integer', 'input' => 'select', 'multiple' => true, 'plugin' => 'select2', 'plugin_config' => [ 'multiple' => 'multiple', 'width' => '300px', ], 'values' => Product::pluck('name', 'id'), 'operators' => [ 'in', ], 'data' => [ 'custom' => true, ] ], [ 'id' => 'people_who_bought_product_pricing', 'label' => $getLabel('people_who_bought_product_pricing', 'contacts::contacts.form.people_who_bought_product_pricing'), 'type' => 'integer', 'input' => 'select', 'multiple' => true, 'plugin' => 'select2', 'plugin_config' => [ 'multiple' => 'multiple', 'width' => '300px', ], 'values' => PriceList::with('product')->get()->pluck('nameProduct', 'id'), 'operators' => [ 'in', ], 'data' => [ 'custom' => true, ] ], [ 'id' => 'contacts.contact_source_id', 'label' => $getLabel('contact_source_id', 'contacts::contacts.form.contact_source_id'), 'type' => 'integer', 'input' => 'select', 'multiple' => true, 'plugin' => 'select2', 'plugin_config' => [ 'multiple' => 'multiple', 'width' => '300px', ], 'values' => ContactSource::pluck('name', 'id'), 'operators' => [ 'in', 'not_in', 'is_null', 'is_not_null' ] ], [ 'id' => 'contacts.created_at', 'label' => $getLabel('created_at', 'core::core.table.created_at'), 'type' => 'date', 'input_event' => 'dp.change', 'plugin' => 'datetimepicker', 'plugin_config' => [ 'locale' => app()->getLocale(), 'calendarWeeks' => true, 'showClear' => true, 'showTodayButton' => true, 'showClose' => true, 'format' => \Modules\Platform\Core\Helper\UserHelper::userJsDateFormat() ] ], [ 'id' => 'contacts.updated_at', 'label' => $getLabel('updated_at', 'core::core.table.updated_at'), 'type' => 'date', 'input_event' => 'dp.change', 'plugin' => 'datetimepicker', 'plugin_config' => [ 'locale' => app()->getLocale(), 'calendarWeeks' => true, 'showClear' => true, 'showTodayButton' => true, 'showClose' => true, 'format' => \Modules\Platform\Core\Helper\UserHelper::userJsDateFormat() ] ], ]; } protected function setFilterDefinition() { $this->filterDefinition = self::availableQueryFilters(); } /** * Build DataTable class. * * @param mixed $query Results from query() method. * @return \Yajra\DataTables\DataTableAbstract */ public function dataTable($query) { $dataTable = new EloquentDataTable($query); $this->applyLinks($dataTable, self::SHOW_URL_ROUTE); $dataTable->editColumn('status', function ($record) { $text = "$record->status"; return DataTableHelper::applyLink($text,route(self::SHOW_URL_ROUTE,$record->id)); }); $dataTable->editColumn('phone', function ($record) { $text =""; if ($record->phone) { $text = " $record->phone"; } return DataTableHelper::applyLink($text,route(self::SHOW_URL_ROUTE,$record->id)); }); $dataTable->editColumn('email', function ($record) { return $record->contactEmails->pluck('email')->transform(function ($item, $key) use ($record) { $text = " $item "; return DataTableHelper::applyLink($text,route(self::SHOW_URL_ROUTE,$record->id)); })->implode(" "); }); $dataTable->filterColumn('email', function($query,$keyword){ $query->whereHas('contactEmails', function ($query) use ($keyword) { $query->where('email', 'LIKE', "%$keyword%"); })->get(); }); $dataTable->filterColumn('owner', function ($query, $keyword) { DataTableHelper::queryOwner($query, $keyword, 'contacts'); }); $dataTable->filterColumn('created_at', function ($query, $keyword) { $dates = DataTableHelper::getDatesForFilter($keyword); if ($dates != null) { $query->whereBetween('contacts.created_at', array($dates[0], $dates[1])); } }); $dataTable->filterColumn('updated_at', function ($query, $keyword) { $dates = DataTableHelper::getDatesForFilter($keyword); if ($dates != null) { $query->whereBetween('contacts.updated_at', array($dates[0], $dates[1])); } }); $dataTable->filterColumn('birth_date', function ($query, $keyword) { $dates = DataTableHelper::getDatesForFilter($keyword); if ($dates != null) { $query->whereBetween('contacts.birth_date', array($dates[0], $dates[1])); } }); // Custom field filter columns foreach (self::customIndexFields() as $field) { $colName = $field['column']; $isDateField = DataTableHelper::isDateCustomFieldType($field['type']); if (!empty($field['uses_column'])) { $cfKey = $field['key']; $dataTable->filterColumn($colName, function ($query, $keyword) use ($cfKey, $isDateField) { if ($isDateField) { // Date range picker — keyword is " - " $dates = DataTableHelper::getDatesForFilter($keyword); if ($dates != null) { $query->whereBetween(\DB::raw('DATE(`contacts`.`' . $cfKey . '`)'), [$dates[0], $dates[1]]); } return; } $query->where('contacts.' . $cfKey, 'LIKE', '%' . $keyword . '%'); }); } else { $jsonKey = $field['key']; $dataTable->filterColumn($colName, function ($query, $keyword) use ($jsonKey, $isDateField) { if ($isDateField) { $dates = DataTableHelper::getDatesForFilter($keyword); if ($dates != null) { $query->whereRaw( 'DATE(JSON_UNQUOTE(JSON_EXTRACT(contacts.custom_fields, ?))) BETWEEN ? AND ?', ['$.' . $jsonKey, $dates[0], $dates[1]] ); } return; } $query->whereRaw( "LOWER(JSON_UNQUOTE(JSON_EXTRACT(contacts.custom_fields, ?))) LIKE ?", ['$.' . $jsonKey, '%' . mb_strtolower($keyword) . '%'] ); }); } } return $dataTable; } /** * Get query source of dataTable. * * @return \Illuminate\Database\Eloquent\Builder */ public function query(Contact $model) { $query = $model->newQuery() ->with('owner') ->with('contactEmails') ->leftJoin('contacts_dict_status', 'contacts.contact_status_id', '=', 'contacts_dict_status.id') ->leftJoin('contacts_dict_source', 'contacts.contact_source_id', '=', 'contacts_dict_source.id') ->leftJoin('customer_dict_language', 'contacts.language_id', '=', 'customer_dict_language.id') ->leftJoin('bap_country', 'contacts.country_id', '=', 'bap_country.id') ->leftJoin('accounts', 'contacts.account_id', '=', 'accounts.id') ->select([ 'contacts.*', 'bap_country.name as country', 'contacts_dict_status.id as status_id', 'contacts_dict_status.name as status', 'contacts_dict_status.color as status_color', 'contacts_dict_source.id as source_id', 'contacts_dict_source.name as source', 'customer_dict_language.name as language', 'accounts.name as account_name', ]); // Add custom field columns foreach (self::customIndexFields() as $field) { if (!empty($field['uses_column'])) { $query->addSelect( DB::raw('`contacts`.`' . $field['key'] . '` as `' . $field['column'] . '`') ); } else { $query->addSelect( DB::raw("JSON_UNQUOTE(JSON_EXTRACT(contacts.custom_fields, '$." . $field['key'] . "')) as `" . $field['column'] . "`") ); } } if (!empty($this->filterRules)) { // Custom implementation for People Who Bought X Product $filterRules = json_decode($this->filterRules); $productIds = null; $priceListIds = null; foreach ($filterRules->rules as &$rule){ if($rule->id == 'people_who_bought'){ $productIds = $rule->value; } if($rule->id == 'people_who_bought_product_pricing'){ $priceListIds = $rule->value; } if($rule->id == 'contact_email.email'){ // filter in multi email $query->leftJoin('contact_email', 'contact_email.contact_id', '=', 'contacts.id'); } } if(!empty($productIds)) { $query->with('invoices.rows')->whereHas('invoices.rows', function ($q) use ($productIds){ $q->whereIn('product_id',$productIds); }); } if(!empty($priceListIds)) { $query->with('invoices.rows')->whereHas('invoices.rows', function ($q) use ($priceListIds){ $q->whereIn('price_list_id',$priceListIds); }); } $filterRules = json_encode($filterRules); $queryBuilderParser = new QueryBuilderParser(); $queryBuilder = $queryBuilderParser->parse($filterRules, $query); return $queryBuilder; } return $query; } /** * Optional method if you want to use html builder. * * @return \Yajra\DataTables\Html\Builder */ public function html() { return $this->builder() ->columns($this->getColumns()) ->minifiedAjax(route('contacts.contacts.index', [], false)) ->setTableAttribute('class', 'table table-hover') ->parameters([ 'dom' => 'lBfrtip', 'responsive' => false, 'stateSave' => false, 'filterDefinitions' => $this->getFilterDefinition(), 'filterRules' => $this->filterRules, 'headerFilters' => true, 'buttons' => DataTableHelper::buttons(), 'regexp' => true ]); } /** * @return array */ protected function getColumns() { if (!empty($this->advancedView)) { $result = []; if ($this->allowSelect) { $result = $this->btnCheck_selection; } return $result + $this->advancedView; } $columns = self::availableColumns(); $result = []; if ($this->allowSelect) { $result = $this->btnCheck_selection; } if ($this->allowUnlink) { $result = $this->btnUnlink; } if ($this->allowQuickEdit) { $result = $result + $this->btnQuick_edit; } $result = $result + $columns; return $result; } }