agentsclimarketplace

Unopim datagrid

Skill unopim/agent-skills/skills/unopim-datagrid

Agent skills for UnoPim — domain-specific context for AI agents (Claude Code, Cursor, Windsurf) working in an UnoPim PIM codebase. Install: npx skills add unopim/agent-skills

Install
npx -y skills add unopim/agent-skills --skill unopim-datagrid

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Implement Unopim DataGrid classes for listing records in admin panels with search, filter, sort, actions (edit/delete), and mass actions. Covers DataGrid subdirectory structure (DataGrids/Section/), prepareQueryBuilder with DB::table (prefix added by Laravel), addColumn with correct closure patterns, addAction with function callbacks for url, addMassAction, bouncer() permission checks, and Blade integration. Use this skill when creating a DataGrid for credentials, mappings, export history, or any Unopim module listing page.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

11.7 KB, ~2.6k tokens by cl100k_base, as published. Nobody here has run it

Unopim DataGrid

Overview

All DataGrid patterns are derived from the WooCommerce connector reference: DataGrids/Credential/CredentialDataGrid.php.

Critical rules:

  • DataGrid lives in a subdirectory: DataGrids/{Section}/{Name}DataGrid.php
  • Methods use PHPDoc @return only — no PHP return type hints on methods
  • closure callbacks in columns use PHP arrow functions fn ($row) =>
  • url callbacks in actions use regular function: function ($row) { return ...; }
  • Table names without prefix in DB::table() — Laravel adds DB_PREFIX
  • Use bouncer()->hasPermission() for action permission checks
  • Status badges: class="label-active" (true) and class="label-info text-gray-600 dark:text-gray-300" (false)
  • Comments: match Laravel core — no inline comments that narrate what the code does. The closure/url/permission patterns above are documented in this skill, not repeated as comments in generated code. Comment only a non-obvious why, one line.

Admin UI rule (critical):

  • If the listing page includes form/filter controls in Blade, use UnoPim form components.
  • Do not generate raw <select>, <input>, <textarea>, or <label> controls when x-admin::form.* components exist.
  • Keep labels/placeholders/messages in translations.

1. Class Structure

<?php
// src/DataGrids/Credential/CredentialDataGrid.php
//                 ^^^^^^^^^ — always a subdirectory named after the entity

namespace Webkul\{ModuleName}\DataGrids\Credential;

use Illuminate\Support\Facades\DB;
use Webkul\DataGrid\DataGrid;

class CredentialDataGrid extends DataGrid
{
    /**
     * Prepare query builder.
     *
     * @return \Illuminate\Database\Query\Builder
     */
    public function prepareQueryBuilder()
    {
        // Use DB::table() without prefix — never Eloquent model here
        $queryBuilder = DB::table('{module}_credentials')
            ->select(
                'id',
                'label',
                'apiUrl',
                'consumerKey',
                'status',
                'created_at',
            );

        return $queryBuilder;
    }

    /**
     * Add columns.
     *
     * @return void
     */
    public function prepareColumns()
    {
        $this->addColumn([
            'index'      => 'label',
            'label'      => trans('{module-name}::app.credentials.datagrid.label'),
            'type'       => 'string',
            'searchable' => true,
            'filterable' => true,
            'sortable'   => true,
        ]);

        $this->addColumn([
            'index'      => 'apiUrl',
            'label'      => trans('{module-name}::app.credentials.datagrid.api-url'),
            'type'       => 'string',
            'searchable' => true,
            'filterable' => true,
            'sortable'   => true,
        ]);

        $this->addColumn([
            'index'      => 'consumerKey',
            'label'      => trans('{module-name}::app.credentials.datagrid.consumer-key'),
            'type'       => 'string',
            'searchable' => false,
            'filterable' => false,
            'sortable'   => false,
        ]);

        $this->addColumn([
            'index'      => 'status',
            'label'      => trans('{module-name}::app.credentials.datagrid.status'),
            'type'       => 'boolean',
            'searchable' => true,
            'filterable' => true,
            'sortable'   => false,
            'closure'    => fn ($row) => $row->status
                ? '<span class="label-active">' . trans('admin::app.common.yes') . '</span>'
                : '<span class="label-info text-gray-600 dark:text-gray-300">' . trans('admin::app.common.no') . '</span>',
        ]);
    }

    /**
     * Prepare actions.
     *
     * @return void
     */
    public function prepareActions()
    {
        if (bouncer()->hasPermission('{module-slug}.credentials.edit')) {
            $this->addAction([
                'icon'   => 'icon-edit',
                'title'  => trans('admin::app.catalog.attributes.index.datagrid.edit'),
                'method' => 'GET',
                'url'    => function ($row) {
                    return route('{module-slug}.credentials.edit', $row->id);
                },
            ]);
        }

        if (bouncer()->hasPermission('{module-slug}.credentials.delete')) {
            $this->addAction([
                'icon'   => 'icon-delete',
                'title'  => trans('admin::app.catalog.attributes.index.datagrid.delete'),
                'method' => 'DELETE',
                'url'    => function ($row) {
                    return route('{module-slug}.credentials.destroy', $row->id);
                },
            ]);
        }
    }

    /**
     * Prepare mass actions.
     *
     * @return void
     */
    public function prepareMassActions()
    {
        if (bouncer()->hasPermission('{module-slug}.credentials.mass-delete')) {
            $this->addMassAction([
                'title'   => trans('admin::app.catalog.attributes.index.datagrid.delete'),
                'url'     => route('{module-slug}.credentials.mass_delete'),
                'method'  => 'POST',
                'options' => ['actionType' => 'delete'],
            ]);
        }

        if (bouncer()->hasPermission('{module-slug}.credentials.mass-edit')) {
            $this->addMassAction([
                'title'  => trans('admin::app.catalog.products.index.datagrid.update-status'),
                'url'    => route('{module-slug}.credentials.mass_update'),
                'method' => 'POST',
                'options' => [
                    [
                        'label' => trans('admin::app.catalog.products.index.datagrid.active'),
                        'value' => true,
                    ],
                    [
                        'label' => trans('admin::app.catalog.products.index.datagrid.disable'),
                        'value' => false,
                    ],
                ],
            ]);
        }
    }
}

2. Callback Patterns: closure vs url

LocationFunction styleWhy
addColumn closure keyArrow function fn ($row) =>Short rendering expression
addAction url keyfunction ($row) { return ...; }Must return the URL string
addMassAction url keyPlain string route(...)Static URL, no row context

Column closure (arrow function OK):

'closure' => fn ($row) => $row->status ? '<span class="label-active">Yes</span>' : '<span class="label-info ...">No</span>',

Action URL (regular function required):

'url' => function ($row) {
    return route('{module-slug}.credentials.edit', $row->id);
},

Mass action URL (static — no function):

'url' => route('{module-slug}.credentials.mass_delete'),

3. Controller Integration

public function index()
{
    if (request()->ajax()) {
        return app(CredentialDataGrid::class)->toJson();
    }

    return view('{module-name}::credentials.index');
}

4. Blade View

{{-- resources/views/credentials/index.blade.php --}}
@extends('admin::layouts.master')

@section('page_title')
    {{ __( '{module-name}::app.credentials.index.title' ) }}
@stop

@section('content-wrapper')
    <div class="flex flex-col gap-4 p-4">
        <div class="flex items-center justify-between gap-4">
            <p class="text-xl font-bold text-gray-800 dark:text-white">
                {{ __(' {module-name}::app.credentials.index.title') }}
            </p>

            @if (bouncer()->hasPermission('{module-slug}.credentials.create'))
                <a
                    href="{{ route('{module-slug}.credentials.create') }}"
                    class="primary-button"
                >
                    {{ __(' {module-name}::app.credentials.index.create-btn') }}
                </a>
            @endif
        </div>

        {{-- DataGrid component renders the table --}}
        <x-admin::datagrid :src="route('{module-slug}.credentials.index')" />
    </div>
@stop

5. MappingDataGrid Example

DataGrids for other entities follow the same pattern in their own subdirectory:

<?php
// src/DataGrids/Mapping/MappingDataGrid.php

namespace Webkul\{ModuleName}\DataGrids\Mapping;

use Illuminate\Support\Facades\DB;
use Webkul\DataGrid\DataGrid;

class MappingDataGrid extends DataGrid
{
    /**
     * @return \Illuminate\Database\Query\Builder
     */
    public function prepareQueryBuilder()
    {
        return DB::table('{module}_mappings as m')
            ->leftJoin('{module}_credentials as c', 'm.credential_id', '=', 'c.id')
            ->select(
                'm.id',
                'm.sku',
                'm.external_id',
                'c.label as credential_label',
                'm.created_at',
            );
    }

    /**
     * @return void
     */
    public function prepareColumns()
    {
        $this->addColumn([
            'index'      => 'sku',
            'label'      => trans('{module-name}::app.mappings.datagrid.sku'),
            'type'       => 'string',
            'searchable' => true,
            'filterable' => true,
            'sortable'   => true,
        ]);

        $this->addColumn([
            'index'      => 'external_id',
            'label'      => trans('{module-name}::app.mappings.datagrid.external-id'),
            'type'       => 'string',
            'searchable' => true,
            'filterable' => true,
            'sortable'   => true,
        ]);

        $this->addColumn([
            'index'      => 'credential_label',
            'label'      => trans('{module-name}::app.mappings.datagrid.credential'),
            'type'       => 'string',
            'searchable' => false,
            'filterable' => false,
            'sortable'   => false,
        ]);
    }

    /**
     * @return void
     */
    public function prepareActions()
    {
        if (bouncer()->hasPermission('{module-slug}.mappings.delete')) {
            $this->addAction([
                'icon'   => 'icon-delete',
                'title'  => trans('admin::app.catalog.attributes.index.datagrid.delete'),
                'method' => 'DELETE',
                'url'    => function ($row) {
                    return route('{module-slug}.mappings.destroy', $row->id);
                },
            ]);
        }
    }
}

6. Checklist

  • DataGrid file location: src/DataGrids/{Section}/{Name}DataGrid.php (subdirectory)
  • Extends Webkul\DataGrid\DataGrid
  • Methods have PHPDoc @return only — no PHP return type hints on methods
  • DB::table('{module}_...') — table names without hardcoded prefix
  • Column closure uses arrow function fn ($row) =>
  • Action url uses regular function ($row) { return route(...); }
  • Mass action url is plain route(...) string (no function)
  • Boolean columns use label-active (true) and label-info text-gray-600 dark:text-gray-300 (false)
  • Permission checks use bouncer()->hasPermission()
  • Controller calls app(DataGridClass::class)->toJson() for AJAX
  • Blade uses <x-admin::datagrid :src="route(...)" />

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.