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;
}
}