thedevdojo / voyager

Voyager - The Missing Laravel Admin
https://voyager.devdojo.com
MIT License
11.81k stars 2.67k forks source link

[Feature] filtration and exporting feature #4216

Open omarkadd opened 5 years ago

omarkadd commented 5 years ago

Version information

Description of feature

datatables provide a multiple filtration to the table which cannot be done in voyager. filtration could re-draw tables as the user need.(like the images attached) adding printing to tables and exporting as excel or pdf or word

Proposed solution

add support for multiple filtration and advanced search add exporting feature to the tables

Alternatives considered

providing a clear way to do it manually

Additional context

maxresdefault (1) maxresdefault

fletch3555 commented 5 years ago

PRs welcome

omarkadd commented 5 years ago

as for me iam not really good enough to pull this myself so if any one else would help that would be great

ikramulmustafa commented 5 years ago

any update regarding this feature of filtration? @fletch3555

fletch3555 commented 5 years ago

No update. Nobody has built it yet

damms005 commented 5 years ago

It is a good thing that there are now thoughts in this direction, because it was an absolute no no when I brought it up in the slack channel

I hope this comes up soon. I wish to have some more time to do a PR for this.

However, maybe a palliative I created for myself may be useful for someone in the mean time: https://github.com/damms005/laravel-model-export

rsecor commented 4 years ago

Is there any update on having associated data being searchable? And should this be merged with #3595 ?

vyyytenis commented 4 years ago

Hi, I made a quick fix for multiple field search, It does work, but it needs work. I'll put a bit more code that it would be easier to see where what goes. WORKS ONLY WITH SERVER SIDE PAGINATION, AND BRAKES NON SERVER SIDE PAGINATION Look for: ------->>>>>

class VoyagerBaseController extends BaseVoyagerBaseController
{
    public function index(Request $request)
    {
        // GET THE SLUG, ex. 'posts', 'pages', etc.
        $slug = $this->getSlug($request);

        // GET THE DataType based on the slug
        $dataType = Voyager::model('DataType')->where('slug', '=', $slug)->first();

        // Check permission
        $this->authorize('browse', app($dataType->model_name));

        $getter = $dataType->server_side ? 'paginate' : 'get';

//        $search = (object) ['value' => $request->get('s'), 'key' => $request->get('key'), 'filter' => $request->get('filter')];

        $searchNames = [];
        if ($dataType->server_side) {
            $searchable = SchemaManager::describeTable(app($dataType->model_name)->getTable())->pluck('name')->toArray();
            $dataRow = Voyager::model('DataRow')->whereDataTypeId($dataType->id)->get();
            foreach ($searchable as $key => $value) {
                $field = $dataRow->where('field', $value)->first();
                $displayName = ucwords(str_replace('_', ' ', $value));
                if ($field !== null) {
                    $displayName = $field->getTranslatedAttribute('display_name');
                }
                $searchNames[$value] = $displayName;
            }
        }

        $orderBy = $request->get('order_by', $dataType->order_column);
        $sortOrder = $request->get('sort_order', $dataType->order_direction);
        $usesSoftDeletes = false;
        $showSoftDeleted = false;

        // Next Get or Paginate the actual content from the MODEL that corresponds to the slug DataType
        if (strlen($dataType->model_name) != 0) {
            $model = app($dataType->model_name);

            if ($dataType->scope && $dataType->scope != '' && method_exists($model, 'scope'.ucfirst($dataType->scope))) {
                $query = $model->{$dataType->scope}();
            } else {
                $query = $model::select('*');
            }

            // Use withTrashed() if model uses SoftDeletes and if toggle is selected
            if ($model && in_array(SoftDeletes::class, class_uses_recursive($model)) && Auth::user()->can('delete', app($dataType->model_name))) {
                $usesSoftDeletes = true;

                if ($request->get('showSoftDeleted')) {
                    $showSoftDeleted = true;
                    $query = $query->withTrashed();
                }
            }

            // If a column has a relationship associated with it, we do not want to show that field
            $this->removeRelationshipField($dataType, 'browse');
//------->>>>>
//------->>>>>
            $search = $request->all();
            if (count($search) > 0) {
                foreach ($search as $key => $val) {
                    if (!strpos($key, 'relationship') && $val != null) $query->where($key, 'LIKE', '%' . $val . '%');
                }

//                $query->where($search->key, $search_filter, $search_value);

            }
//------->>>>>
//------->>>>>

image

This goes to bread browse.blade.php Simple form with submit

<thead>
<tr>
    <form method="get" class="form-search">

        @if($showCheckboxColumn)
            <th>
                <input type="checkbox" class="select_all">
            </th>
        @endif
//------->>>>>
//------->>>>>
 @foreach($dataType->browseRows as $key => $row)
                                                <th>
                                                    @if ($isServerSide)
                                                        <a href="{{ $row->sortByUrl($orderBy, $sortOrder) }}">
                                                            @endif
                                                            {{ $row->getTranslatedAttribute('display_name') }}
                                                            @if ($isServerSide)
                                                                @if ($row->isCurrentSortField($orderBy))
                                                                    @if ($sortOrder == 'asc')
                                                                        <i class="voyager-angle-up pull-right"></i>
                                                                    @else
                                                                        <i class="voyager-angle-down pull-right"></i>
                                                                    @endif
                                                                @endif
                                                        </a>
                                                    @endif

                                                    @if ($isServerSide)
                                                        <input type="text"   name="{{$row->field}}" value="{{ !empty($search[$row->field ]) ? $search[$row->field ] : ''}}">
                                                    @endif
                                                </th>

                                            @endforeach
//------->>>>>
//------->>>>>

        <button class="btn btn-info btn-lg" type="submit">
            <i class="voyager-search"></i>
        </button>
    </form>
    <th class="actions text-right">{{ __('voyager::generic.actions') }}</th>
</tr>
</thead>

UPDATE pagination links

{{ $dataTypeContent->appends(
     array_merge(
         $search,
         [
             'order_by' => $orderBy,
             'sort_order' => $sortOrder
         ]
     )

)->links() }}

UPDATE I store the id of the relation, so it works for me. Right now I'm building a prototype of my app, so I didnt test it fully. I will test it on the go

 if (count($search) > 0) {
                foreach ($search as $key => $val) {

                    if (
                         $val != null
                        && $key != 'page'
                        && $key != 'sort_order'
                        && $key != 'order_by'
                        && strpos($key, 'relationship')
                    ) {
                        $infoAboutColumn = $dataRow->where('type', '=','relationship')->where('field', '=', $key)->first();
                        $relationshipTableModel = $infoAboutColumn->details->model; // Model to search in for the ralation
                        $relationshipTableLabel = $infoAboutColumn->details->label; // column name in the relation table
                        $itemFromRelation = $relationshipTableModel::where($relationshipTableLabel, 'LIKE', '%' . $val .'%')->first(); // item from relation table
                        $columnNameInYoureTable = $infoAboutColumn->details->column; // Name of the column, that you are ralationing with

                        $idFromRelationTable = $itemFromRelation->id;

                        $query->where($columnNameInYoureTable, 'LIKE', '%' . $idFromRelationTable . '%');
                    } else {
                        if (
                            !strpos($key, 'relationship')
                            && $val != null
                            && $key != 'page'
                            && $key != 'sort_order'
                            && $key != 'order_by'
                        ) {
                            $query->where($key, 'LIKE', '%' . $val . '%')->get();
                        }
                    }

                }
            }
oudouss commented 4 years ago

BelongsTo ServerSide Search #3595 filtration #4216

Version information

@vyyytenis Thanks a lot for the well explained code! It was a great help so i made a few changes in my own project. It's an ugly code I KNOW but it Works with server side pagination without breaking non-server side pagination, & no changes needed in the browse blade. Hope it helps in some way..!
Steps :

  1. Use a custum controller like in Voyager-docs : Using custom Controllers

  2. In the new controller created copy index method from VoyagerBaseController

  3. In line 95 make these changes

if ($search->value != '' && $search->key && $search->filter) {
$search_filter = ($search->filter == 'equals') ? '=' : 'LIKE';
$search_value = ($search->filter == 'equals') ? $search->value : '%' . $search->value . '%';
// changes 
$searchKeyType = $dataRow->where('display_name', '=', $searchNames[$search->key])->pluck('type')->toArray();

if (in_array('relationship', $searchKeyType)) {
    $infoAboutColumn = $dataRow->where('type', '=', 'relationship')->where('display_name', '=', $searchNames[$search->key])->first();

    $relationshipTabledetails = $infoAboutColumn->details;

    $relationshipTableModel = $relationshipTabledetails->model;

    $relationshipTableLabel = $relationshipTabledetails->label;

    $relatedmodel = app($relationshipTableModel);

    $additional_attributes = $relatedmodel->additional_attributes ?? [];

    $value = $search->value;

    if (in_array($relationshipTableLabel, $additional_attributes)) {
        $idFromRelationTable = $relatedmodel->all();
        $idFromRelationTable = $idFromRelationTable->filter(function ($relatedmodel) use ($value, $relationshipTabledetails) {
            return stripos($relatedmodel->{$relationshipTabledetails->label}, $value) !== false;
        });
        $idFromRelationTable = $idFromRelationTable->pluck('id')->toArray();
    } else {
        $idFromRelationTable = $relationshipTableModel::where($relationshipTableLabel, 'LIKE', '%' . $search->value . '%')->pluck('id');
    }

    $columnNameInYourTable = $infoAboutColumn->details->column;

    $query->whereIn($columnNameInYourTable, $idFromRelationTable);
}
else{
    $query->where($search->key, $search_filter, $search_value);

 }
}
// end changes