'ticket_category_id', 'priority' => 'ticket_priority_id', 'severity' => 'ticket_severity_id', 'status' => 'ticket_status_id', 'ticket_owner' => 'ticket_owner_id', 'owner' => 'owned_by', 'account_name' => 'account_id', 'contact_name' => 'contact_id', ]; /** Columns that are always shown regardless of field settings. */ private static array $alwaysVisible = ['id', 'created_at', 'updated_at']; public static function availableQueryFilters() { $labelMap = []; if (Auth::check() && Schema::hasTable('ticket_field_settings')) { $service = app(TicketFieldSettingsService::class); $labelMap = $service->getFieldLabelMap(Auth::user()); } $getLabel = function ($fieldKey, $defaultTranslation) use ($labelMap) { return $labelMap[$fieldKey] ?? trans($defaultTranslation); }; return [ [ 'id' => 'accounts.name', 'label' => $getLabel('account_id', 'tickets::tickets.form.account_name'), 'type' => 'string', ], [ 'id' => 'contacts.full_name', 'label' => $getLabel('contact_id', 'tickets::tickets.form.contact_name'), 'type' => 'string', ], [ 'id' => 'tickets.name', 'label' => $getLabel('name', 'tickets::tickets.form.name'), 'type' => 'string', ], [ 'id' => 'tickets.ticket_priority_id', 'label' => $getLabel('ticket_priority_id', 'tickets::tickets.form.ticket_priority_id'), 'type' => 'integer', 'input' => 'select', 'multiple' => true, 'plugin' => 'select2', 'plugin_config' => [ 'multiple' => 'multiple', 'width' => '300px', ], 'values' => TicketPriority::pluck('name', 'id'), 'operators' => [ 'in', 'not_in', 'is_null', 'is_not_null' ] ], [ 'id' => 'tickets.ticket_owner_id', 'label' => $getLabel('ticket_owner_id', 'tickets::tickets.form.ticket_owner_id'), 'type' => 'integer', 'input' => 'select', 'multiple' => true, 'plugin' => 'select2', 'plugin_config' => [ 'multiple' => 'multiple', 'width' => '300px', ], 'values' => FormHelper::shareWithChoises(), 'operators' => [ 'in', 'not_in', 'is_null', 'is_not_null' ] ], [ 'id' => 'tickets.ticket_status_id', 'label' => $getLabel('ticket_status_id', 'tickets::tickets.form.ticket_status_id'), 'type' => 'integer', 'input' => 'select', 'multiple' => true, 'plugin' => 'select2', 'plugin_config' => [ 'multiple' => 'multiple', 'width' => '300px', ], 'values' => TicketStatus::pluck('name', 'id'), 'operators' => [ 'in', 'not_in', 'is_null', 'is_not_null' ] ], [ 'id' => 'tickets.ticket_severity_id', 'label' => $getLabel('ticket_severity_id', 'tickets::tickets.form.ticket_severity_id'), 'type' => 'integer', 'input' => 'select', 'multiple' => true, 'plugin' => 'select2', 'plugin_config' => [ 'multiple' => 'multiple', 'width' => '300px', ], 'values' => TicketSeverity::pluck('name', 'id'), 'operators' => [ 'in', 'not_in', 'is_null', 'is_not_null' ] ], [ 'id' => 'tickets.ticket_category_id', 'label' => $getLabel('ticket_category_id', 'tickets::tickets.form.ticket_category_id'), 'type' => 'integer', 'input' => 'select', 'multiple' => true, 'plugin' => 'select2', 'plugin_config' => [ 'multiple' => 'multiple', 'width' => '300px', ], 'values' => TicketCategory::pluck('name', 'id'), 'operators' => [ 'in', 'not_in', 'is_null', 'is_not_null' ] ], [ 'id' => 'tickets.due_date', 'label' => $getLabel('due_date', 'tickets::tickets.form.due_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' => 'tickets.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' => 'tickets.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() ] ], ]; } public static function availableColumns() { return [ 'id' => [ 'data' => 'id', 'title' => trans('tickets::tickets.form.id'), 'data_type' => 'text', 'filter_type' => 'text', ], 'ticket_num' => [ 'data' => 'ticket_num', 'title' => trans('tickets::tickets.form.ticket_num'), 'data_type' => 'text', 'filter_type' => 'text', ], 'name' => [ 'data' => 'name', 'title' => trans('tickets::tickets.form.name'), 'data_type' => 'text', 'filter_type' => 'text', ], 'category' => [ 'name' => 'tickets_dict_category.name', 'data' => 'category', 'title' => trans('tickets::tickets.form.category'), 'data_type' => 'text', 'filter_type' => 'text' ], 'priority' => [ 'name' => 'tickets_dict_priority.name', 'data' => 'priority', 'title' => trans('tickets::tickets.form.priority'), 'data_type' => 'text', 'filter_type' => 'text' ], 'ticket_owner' => [ 'name' => 'users.name', 'data' => 'ticket_owner', 'title' => trans('tickets::tickets.form.ticket_owner_id'), 'data_type' => 'text', 'filter_type' => 'text' ], 'severity' => [ 'name' => 'tickets_dict_severity.name', 'data' => 'severity', 'title' => trans('tickets::tickets.form.severity'), 'data_type' => 'text', 'filter_type' => 'text' ], 'status' => [ 'name' => 'tickets_dict_status.name', 'data' => 'status', 'title' => trans('tickets::tickets.form.status'), 'data_type' => 'text', 'filter_type' => 'text' ], 'account_name' => [ 'name' => 'accounts.name', 'data' => 'account_name', 'title' => trans('tickets::tickets.form.account_name'), 'data_type' => 'text', 'filter_type' => 'text' ], 'contact_name' => [ 'name' => 'contacts.full_name', 'data' => 'contact_name', 'title' => trans('tickets::tickets.form.contact_name'), 'data_type' => 'text', 'filter_type' => 'text' ], 'due_date' => [ 'data' => 'due_date', 'title' => trans('tickets::tickets.form.due_date'), 'data_type' => 'date', 'filter_type' => 'bap_date_range_picker' ], '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', ], '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() ] ]; } 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->filterColumn('owner', function ($query, $keyword) { DataTableHelper::queryOwner($query, $keyword, 'tickets'); }); $dataTable->editColumn('priority', function ($record) { $text = "$record->priority"; return DataTableHelper::applyLink($text, route(self::SHOW_URL_ROUTE, $record->id)); }); $dataTable->editColumn('severity', function ($record) { $text = "$record->severity"; return DataTableHelper::applyLink($text, route(self::SHOW_URL_ROUTE, $record->id)); }); $dataTable->filterColumn('created_at', function ($query, $keyword) { $dates = DataTableHelper::getDatesForFilter($keyword); if ($dates != null) { $query->whereBetween('tickets.created_at', [$dates[0], $dates[1]]); } }); $dataTable->filterColumn('due_date', function ($query, $keyword) { $dates = DataTableHelper::getDatesForFilter($keyword); if ($dates != null) { $query->whereBetween('tickets.due_date', [$dates[0], $dates[1]]); } }); $dataTable->filterColumn('updated_at', function ($query, $keyword) { $dates = DataTableHelper::getDatesForFilter($keyword); if ($dates != null) { $query->whereBetween('tickets.updated_at', [$dates[0], $dates[1]]); } }); // Register filter callbacks for each visible custom_data field if (Auth::check() && Schema::hasColumn('tickets', 'custom_data')) { $service = app(TicketFieldSettingsService::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(tickets.custom_data, ?))) BETWEEN ? AND ?', [$jsonPath, $dates[0], $dates[1]] ); } return; } $q->whereRaw( "JSON_UNQUOTE(JSON_EXTRACT(tickets.custom_data, ?)) LIKE ?", [$jsonPath, "%{$keyword}%"] ); }); } } return $dataTable; } /** * Get query source of dataTable. * * * @return \Illuminate\Database\Eloquent\Builder */ public function query(Ticket $model) { $query = $model->with('owner') ->leftJoin('tickets_dict_category', 'tickets.ticket_category_id', '=', 'tickets_dict_category.id') ->leftJoin('tickets_dict_priority', 'tickets.ticket_priority_id', '=', 'tickets_dict_priority.id') ->leftJoin('tickets_dict_severity', 'tickets.ticket_severity_id', '=', 'tickets_dict_severity.id') ->leftJoin('tickets_dict_status', 'tickets.ticket_status_id', '=', 'tickets_dict_status.id') ->leftJoin('accounts', 'tickets.account_id', '=', 'accounts.id') ->leftJoin('contacts', 'tickets.contact_id', '=', 'contacts.id') ->leftJoin('users', 'tickets.ticket_owner_id', '=', 'users.id') ->newQuery()->select([ 'tickets.*', 'tickets_dict_category.name as category', 'tickets_dict_priority.id as priority_id', 'tickets_dict_priority.name as priority', 'tickets_dict_priority.color as priority_color', 'tickets_dict_severity.id as severity_id', 'tickets_dict_severity.name as severity', 'tickets_dict_severity.color as severity_color', 'tickets_dict_status.name as status', 'accounts.name as account_name', 'contacts.full_name as contact_name', 'users.name as ticket_owner', ]) ->orderBy('tickets.created_at', 'DESC'); // Add JSON extractions for visible custom_data fields if (Auth::check() && Schema::hasColumn('tickets', 'custom_data')) { $service = app(TicketFieldSettingsService::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(tickets.custom_data, '{$jsonPath}')) as `{$col}`") ); } } if (!empty($this->filterRules)) { $queryBuilderParser = new QueryBuilderParser(); return $queryBuilderParser->parse($this->filterRules, $query); } 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()) ->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 ]); } protected function getColumns() { if (!empty($this->advancedView)) { $result = []; if ($this->allowSelect) { $result = $this->btnCheck_selection; } return $result + $this->advancedView; } $allColumns = self::availableColumns(); // Append any visible custom_data fields as additional columns if (Auth::check() && Schema::hasColumn('tickets', 'custom_data')) { $service = app(TicketFieldSettingsService::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, ]; } } // Apply field-settings visibility, labels, and ordering if (Auth::check() && Schema::hasTable('ticket_field_settings')) { $service = app(TicketFieldSettingsService::class); $user = Auth::user(); $visibleKeys = $service->getVisibleFieldKeys($user); $orderMap = $service->getFieldOrderMap($user); $labelMap = $service->getFieldLabelMap($user); $filtered = []; foreach ($allColumns as $colKey => $colDef) { if (strpos($colKey, self::CUSTOM_COL_PREFIX) === 0) { $filtered[$colKey] = $colDef; continue; } if (in_array($colKey, self::$alwaysVisible, true)) { $filtered[$colKey] = $colDef; continue; } $fieldKey = self::$columnToFieldMap[$colKey] ?? $colKey; if (in_array($fieldKey, $visibleKeys, true)) { 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) { $pinFirst = ['id' => -1]; $pinLast = array_fill_keys(['created_at', 'updated_at'], 999000); $resolveOrder = function ($col) use ($orderMap, $pinFirst, $pinLast) { if (isset($pinFirst[$col])) return $pinFirst[$col]; 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); }); $allColumns = $filtered; } $result = []; if ($this->allowSelect) { $result = $this->btnCheck_selection; } if ($this->allowUnlink) { $result = $this->btnUnlink; } if ($this->allowQuickEdit) { $result = $result + $this->btnQuick_edit; } return $result + $allColumns; } }