# Ordenação



# Introdução

Esta feature foi desenvolvida para fornecer um **sistema de ordenação flexível e reutilizável** para os módulos do painel.  
O objetivo é simplificar a implementação de ordenações, padronizando a forma como os registros são organizados e garantindo consistência entre diferentes módulos, sem a necessidade de reescrever a lógica de movimentação ou atualização de posições.

O sistema suporta **ordenções simples**, que envolvem apenas registros do mesmo módulo, permitindo que o usuário reorganize os itens de forma direta e intuitiva.  
Além disso, ele também é capaz de lidar com **cenários mais complexos**, envolvendo múltiplos níveis de relacionamento entre modelos diferentes. Por exemplo, em um módulo que gerencia seções e produtos, é possível ordenar os produtos **dentro de uma seção específica** (*seção → produto*). Nesse caso, o sistema primeiro aplica filtros para selecionar apenas os registros do nível superior (a seção) e, em seguida, realiza a ordenação dos registros do nível inferior (os produtos).

Outro ponto importante é o suporte a **ordenação recursiva dentro da própria model**, útil em casos onde um registro pode ter outro registro do mesmo tipo como “pai”. Esse recurso permite criar estruturas hierárquicas complexas, como categorias e subcategorias, menus com itens filhos ou qualquer outro cenário em que a ordenação precise percorrer múltiplos níveis da mesma tabela, gerando uma **estrutura de ordenação potencialmente infinita**.

Com essa abordagem, qualquer módulo do painel pode adotar o sistema de ordenação de forma rápida e confiável, aproveitando toda a lógica de backend e frontend já pronta, garantindo consistência, performance e facilidade de manutenção.

# Model e Registros

Para que o sistema de ordenação funcione corretamente, cada **model** envolvida deve seguir uma estrutura mínima obrigatória.  
Abaixo estão descritas as configurações e implementações necessárias para garantir a compatibilidade com a feature.

---

### 1. **Trait obrigatória**

Toda model que utilizar o sistema de ordenação **deve incluir a trait**:

<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary" id="bkmrk-use-app%5Ctraits%5Csorta"><div class="overflow-y-auto p-4" dir="ltr">**use App\\Traits\\SortableTrait;**</div></div>Essa trait contém os métodos e comportamentos essenciais para manipular a posição dos registros, incluindo funções de ordenação, reordenação após exclusões e cálculo da próxima posição disponível.

---

### 2. **Evento <span style="text-decoration: underline;">deleted</span> com <span style="text-decoration: underline;">reorder()</span>**

É obrigatório que a model implemente o evento **deleted**, chamando o método **reorder()** após a exclusão de um registro.  
Isso garante que a ordenação seja recalculada automaticamente, mantendo a sequência correta dos itens.

**Exemplo:**

```php
protected static function boot()
    {
        parent::boot();

        static::deleted(function ($model) {
            $model->reorder(); // executa após deletar com sucesso
        });
    }
```

> **Observação:**  
> Se a ordenação possuir níveis (ex: seção → produto), é necessário passar um *filtro* via **Closure** como terceiro parâmetro na função **reorder()**, indicando o escopo onde a reordenação deve ocorrer (por exemplo, dentro da mesma seção).

---

### 3. **Relacionamentos entre níveis**

Quando o módulo utiliza **ordenação com múltiplos níveis**, cada nível adicional deve possuir uma **relação "belongsTo"** com o nível imediatamente anterior.  
Essa relação é obrigatória para que o sistema saiba a qual contexto o registro pertence e consiga filtrar corretamente antes de aplicar a ordenação.

**Exemplo:**  
Na model do **nível 3**, deve existir uma relação com o **nível 2**:

```
public function nivel2() { return $this->belongsTo(NivelDoi::class, 'nivel_2_id'); }
```

---

### 4. **Colunas obrigatórias na tabela**

A tabela da model deve conter as seguintes colunas:

<div class="_tableContainer_1rjym_1" id="bkmrk-coluna-tipo-obrigato"><div class="group _tableWrapper_1rjym_13 flex w-fit flex-col-reverse" tabindex="-1"><table class="w-fit min-w-(--thread-content-width)" data-end="2596" data-start="2101" style="width: 100%;"><thead data-end="2148" data-start="2101"><tr data-end="2148" data-start="2101"><th data-col-size="sm" data-end="2110" data-start="2101" style="width: 15.9714%;">Coluna</th><th data-col-size="sm" data-end="2117" data-start="2110" style="width: 13.7068%;">Tipo</th><th data-col-size="lg" data-end="2135" data-start="2117" style="width: 42.9043%;">Obrigatoriedade</th><th data-col-size="md" data-end="2148" data-start="2135" style="width: 27.4176%;">Descrição</th></tr></thead><tbody data-end="2596" data-start="2198"><tr data-end="2307" data-start="2198"><td data-col-size="sm" data-end="2212" data-start="2198" style="width: 15.9714%;">**order**</td><td data-col-size="sm" data-end="2224" data-start="2212" style="width: 13.7068%;">**integer**</td><td data-col-size="lg" data-end="2242" data-start="2224" style="width: 42.9043%;">**Obrigatória**</td><td data-col-size="md" data-end="2307" data-start="2242" style="width: 27.4176%;">Define a posição do registro dentro do contexto da ordenação.</td></tr><tr data-end="2442" data-start="2308"><td data-col-size="sm" data-end="2326" data-start="2308" style="width: 15.9714%;">**parent\_id**</td><td data-col-size="sm" data-end="2338" data-start="2326" style="width: 13.7068%;">**integer**</td><td data-col-size="lg" data-end="2442" data-start="2338" style="width: 42.9043%;">Obrigatória **somente** quando o tipo de ordenação é **recursiva** (entre registros da mesma model).</td><td data-col-size="md" style="width: 27.4176%;"> </td></tr><tr data-end="2596" data-start="2443"><td data-col-size="sm" data-end="2464" data-start="2443" style="width: 15.9714%;">**&lt;nome\_da\_fk&gt;**</td><td data-col-size="sm" data-end="2476" data-start="2464" style="width: 13.7068%;">**integer**</td><td data-col-size="lg" data-end="2596" data-start="2476" style="width: 42.9043%;">Obrigatória **quando existe mais de um nível** (não recursiva). Representa a foreign key que liga ao nível anterior.</td><td data-col-size="md" style="width: 27.4176%;"> </td></tr></tbody></table>

</div></div>> **Exemplo:**
> 
> - Em uma ordenação recursiva (ex: categorias com subcategorias), use **parent\_id**.
> - Em uma ordenação com múltiplos níveis (ex: seção → produto), use a FK correspondente, como **secao\_id**.
> - Em uma ordenação simples (sem níveis), nenhuma FK adicional é necessária.

# Configuração no controller

Para habilitar o sistema de ordenação em um módulo, é necessário implementar no **controller** uma função pública chamada **orderConfig()**  
Essa função é responsável por retornar duas arrays principais que controlam todo o comportamento da ordenação: **$generalConfig e $configLevels**.

#### <span style="color: rgb(0, 0, 0);">**$generalConfig**</span>

A array **$generalConfig** contém configurações gerais da ordenação.  
Atualmente, possui as seguintes propriedades possíveis:

```
$generalConfig = ['selfGroupedRecords' => false];
```

<table class="w-fit min-w-(--thread-content-width)" data-end="926" data-start="690" id="bkmrk-propriedade-tipo-des" style="width: 100%; height: 94.8126px;"><thead data-end="724" data-start="690"><tr data-end="724" data-start="690" style="height: 47.5938px;"><th data-col-size="sm" data-end="704" data-start="690" style="width: 20.143%; height: 47.5938px;">Propriedade</th><th data-col-size="sm" data-end="711" data-start="704" style="width: 11.4469%; height: 47.5938px;">Tipo</th><th data-col-size="lg" data-end="724" data-start="711" style="width: 68.4101%; height: 47.5938px;">Descrição</th></tr></thead><tbody data-end="926" data-start="762"><tr><td style="width: 20.143%;">**view**</td><td style="width: 11.4469%;">**string | null**</td><td style="width: 68.4101%;">Para alterar a view que será exibida a ordenação, pode ser passado um nome de rota (por exemplo, "*panel.layouts.modules.order*")

</td></tr><tr data-end="926" data-start="762" style="height: 47.2188px;"><td class="align-left" data-col-size="sm" data-end="789" data-start="762" style="width: 20.143%; height: 47.2188px;"> **selfGroupedRecords**</td><td class="align-left" data-col-size="sm" data-end="798" data-start="789" style="width: 11.4469%; height: 47.2188px;">**bool**</td><td data-col-size="lg" data-end="926" data-start="798" style="width: 68.4101%; height: 47.2188px;">Define se a ordenação será **recursiva dentro do mesmo módulo** (por exemplo, quando um registro pode ter outro como “pai”).

</td></tr></tbody></table>

#### **<span style="color: rgb(0, 0, 0);">$configLevels</span>**

A array **$configLevels** é onde é definida toda a **lógica de ordenação**.  
Cada item dentro dela representa um **nível de ordenação** — ou seja:

- Se for uma ordenação **simples** (sem níveis adicionais ou recursivos), haverá **apenas um nível**.
- Se for uma ordenação **hierárquica** (por exemplo, *seção → produto*), haverá **um nível para cada relação**.

> **Observações:**
> 
> - A **ordem dos itens na array** define a hierarquia dos níveis.  
>     O **primeiro item** representa o **primeiro nível** da ordenação, o **segundo item** o **segundo nível**, e assim por diante.
> - A chave de cada nível deve ser um **identificador em camelCase**, de preferência o nome do módulo.

Cada nível da configuração deve conter as seguintes propriedades:

```
 $configLevels = [

            'levelOne' => [
                'label' => 'Nivel 1',
                'model' => SubmodulesTest::class,
                'sortable' => true,
                'labelBtnNextLevel' => 'Ordernar itens do nível 1',
                'breadcrumbColumnName' => 'name',
                'order' => ['column' => 'order', 'type' => 'ASC'],
                'AdditionalQuery' => function ($q) { $q->where('id', 1); },
                'columnsToList' => [
                    [
                        'name' => 'active',
                        'label' => 'Status'
                    ],
                    [
                        'name' => 'id',
                        'label' => '#'
                    ],
                    [
                        'name' => 'name',
                        'label' => 'Título'
                    ],
                ],
            ],
            'levelTwo' => [
                'label' => 'Nivel 2',
                'model' => NivelDoi::class,
                'sortable' => true,
                'order' => ['column' => 'order', 'type' => 'ASC'],
                'breadcrumbColumnName' => 'name',
                'columnsToList' => [
                    [
                        'name' => 'active',
                        'label' => 'Status'
                    ],
                    [
                        'name' => 'id',
                        'label' => '#'
                    ],
                    [
                        'name' => 'name',
                        'label' => 'Título'
                    ],
                ],
            ],
            // Adicione mais níveis aqui, se necessário
        ];
```

<table class="w-fit min-w-(--thread-content-width)" data-end="2450" data-start="1571" id="bkmrk-propriedade-tipo-des-1" style="width: 105%; height: 338.172px;"><thead data-end="1605" data-start="1571"><tr data-end="1605" data-start="1571" style="height: 29.7969px;"><th data-col-size="sm" data-end="1585" data-start="1571" style="width: 22.8464%; height: 29.7969px;">Propriedade</th><th data-col-size="sm" data-end="1592" data-start="1585" style="width: 12.2347%; height: 29.7969px;">Tipo</th><th data-col-size="lg" data-end="1605" data-start="1592" style="width: 64.794%; height: 29.7969px;">Descrição</th></tr></thead><tbody data-end="2450" data-start="1643"><tr data-end="1697" data-start="1643" style="height: 29.7969px;"><td class="align-left" data-col-size="sm" data-end="1657" data-start="1643" style="width: 22.8464%; height: 29.7969px;">**label**</td><td class="align-left" data-col-size="sm" data-end="1668" data-start="1657" style="width: 12.2347%; height: 29.7969px;">**string**</td><td data-col-size="lg" data-end="1697" data-start="1668" style="width: 64.794%; height: 29.7969px;">Nome descritivo do nível.</td></tr><tr data-end="1769" data-start="1698" style="height: 30.1094px;"><td data-col-size="sm" data-end="1712" data-start="1698" style="width: 22.8464%; height: 30.1094px;">**model**</td><td data-col-size="sm" data-end="1722" data-start="1712" style="width: 12.2347%; height: 30.1094px;">**Model**</td><td data-col-size="lg" data-end="1769" data-start="1722" style="width: 64.794%; height: 30.1094px;">Instância da model correspondente ao nível.</td></tr><tr data-end="1861" data-start="1770" style="height: 30.1094px;"><td data-col-size="sm" data-end="1787" data-start="1770" style="width: 22.8464%; height: 30.1094px;">**sortable**</td><td data-col-size="sm" data-end="1796" data-start="1787" style="width: 12.2347%; height: 30.1094px;">**bool**</td><td data-col-size="lg" data-end="1861" data-start="1796" style="width: 64.794%; height: 30.1094px;">Define se o nível atual pode ser ordenado (**true** / **false**).</td></tr><tr data-end="1967" data-start="1862" style="height: 46.5938px;"><td data-col-size="sm" data-end="1888" data-start="1862" style="width: 22.8464%; height: 46.5938px;">**labelBtnNextLevel**</td><td data-col-size="sm" data-end="1899" data-start="1888" style="width: 12.2347%; height: 46.5938px;">**string**

*(opcional)*

</td><td data-col-size="lg" data-end="1967" data-start="1899" style="width: 64.794%; height: 46.5938px;">Texto exibido no botão que permite avançar para o próximo nível.</td></tr><tr data-end="2058" data-start="1968" style="height: 47.2188px;"><td data-col-size="sm" data-end="1997" data-start="1968" style="width: 22.8464%; height: 47.2188px;">**breadcrumbColumnName**</td><td data-col-size="sm" data-end="2008" data-start="1997" style="width: 12.2347%; height: 47.2188px;">**string**</td><td data-col-size="lg" data-end="2058" data-start="2008" style="width: 64.794%; height: 47.2188px;">Coluna usada para exibir o nome no breadcrumb.</td></tr><tr data-end="2165" data-start="2059" style="height: 30.1094px;"><td data-col-size="sm" data-end="2073" data-start="2059" style="width: 22.8464%; height: 30.1094px;">**order**</td><td data-col-size="sm" data-end="2083" data-start="2073" style="width: 12.2347%; height: 30.1094px;">**array**</td><td data-col-size="lg" data-end="2165" data-start="2083" style="width: 64.794%; height: 30.1094px;">Define a ordenação padrão da listagem (**\['column' =&gt; 'id', 'type' =&gt; 'asc'\]**).</td></tr><tr data-end="2340" data-start="2166" style="height: 47.2188px;"><td data-col-size="sm" data-end="2187" data-start="2166" style="width: 22.8464%; height: 47.2188px;">**columnToList**</td><td data-col-size="sm" data-end="2197" data-start="2187" style="width: 12.2347%; height: 47.2188px;">**array**</td><td data-col-size="lg" data-end="2340" data-start="2197" style="width: 64.794%; height: 47.2188px;">Define as colunas exibidas na listagem de ordenação. Cada item deve conter **name** (coluna no banco) e **label** (rótulo exibido no frontend).</td></tr><tr data-end="2450" data-start="2341" style="height: 47.2188px;"><td data-col-size="sm" data-end="2365" data-start="2341" style="width: 22.8464%; height: 47.2188px;">**additionalQuery**</td><td data-col-size="sm" data-end="2390" data-start="2365" style="width: 12.2347%; height: 47.2188px;">**closure**

*(opcional)*

</td><td data-col-size="lg" data-end="2450" data-start="2390" style="width: 64.794%; height: 47.2188px;">Permite aplicar filtros adicionais na query de listagem.</td></tr></tbody></table>

> **Observação:**
> 
> Na propriedade **columnToList**, é possível exibir campos que venham de relações da model.  
> Para isso, basta referenciar o campo desejado como **string**, utilizando o nome da relação seguido do nome da coluna.
> 
> **Exemplo:**
> 
> ```
> ['name' => 'parent->title', 'label' => 'Seção Pai']
> ```
> 
> <div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary"><div class="sticky top-9"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2"><div class="bg-token-bg-elevated-secondary text-token-text-secondary flex items-center gap-4 rounded-sm px-2 font-sans text-xs">  
> </div></div></div><div class="overflow-y-auto p-4" dir="ltr">Nesse caso, **parent** representa o nome da relação definida na model, e **nome** é o campo que será exibido no componente de ordenação.</div></div>

### **Exemplo:**

```php
public function orderConfig()
    {
        $generalConfig = ['selfGroupedRecords' => false];

        $configLevels = [

            'author' => [
                'label' => 'Autores',
                'model' => Author::class,
                'sortable' => true,
                'order' => ['column' => 'order', 'type' => 'ASC'],
                'breadcrumbColumnName' => 'title',
                'columnsToList' => [
                    [
                        'name' => 'active',
                        'label' => 'Status'
                    ],
                    [
                        'name' => 'id',
                        'label' => '#'
                    ],
                    [
                        'name' => 'name',
                        'label' => 'Título'
                    ],
                ],
            ],
            // Adicione mais níveis aqui, se necessário
        ];

        return ['generalConfig' => $generalConfig, 'configLevels' => $configLevels];
    }
```

### **Definindo a ordem no <span style="text-decoration: underline;">store</span>**

No método **store** (ou equivalente) do controller, a coluna **order** deve ser preenchida automaticamente com o **próximo valor de ordem** disponível, utilizando o método getNextOrder() da trait.

Esse método retorna o próximo valor de posição com base no contexto atual.  
Se for necessário aplicar um filtro (por exemplo, ordenar apenas dentro de um determinado nível), o método aceita uma **Closure** como segundo parâmetro:

**Exemplo:**

Nesse caso, o sistema calcula automaticamente o próximo valor com base em todos os registros existentes no módulo atual.

```php
$model->order = $model->getNextOrder();
```

**Exemplo com filtro de nível (recomendado para estruturas hierárquicas):**

Quando a ordenação depende de outro nível — como no caso **seção → produto**, em que os produtos devem ser ordenados apenas dentro de uma determinada seção —  
é obrigatório utilizar uma **Closure** para filtrar o contexto antes de calcular a próxima posição.

```
$model->order = $model->getNextOrder(function ($query) use ($secao_id) {
    return $query->where('secao_id', $secao_id);
});
```

# Rotas e View

Para que o sistema de ordenação funcione corretamente em um módulo, é necessário definir duas **rotas principais** responsáveis por exibir e processar a ordenação.  
Essas rotas devem ser criadas dentro do **grupo de rotas do módulo** e vinculadas ao seu respectivo **controller**.

---

### **Rotas**

Os módulos que implementarem a ordenação precisam conter as seguintes rotas:

#### Descrição das rotas:

<div class="_tableContainer_1rjym_1" id="bkmrk-m%C3%A9todo-caminho-contr"><div class="group _tableWrapper_1rjym_13 flex w-fit flex-col-reverse" tabindex="-1"><table class="w-fit min-w-(--thread-content-width)" data-end="1395" data-start="964" style="width: 100%;"><thead data-end="1018" data-start="964"><tr data-end="1018" data-start="964"><th data-col-size="sm" data-end="973" data-start="964" style="width: 8.22223%;">Método</th><th data-col-size="sm" data-end="983" data-start="973" style="width: 10.133%;">Caminho</th><th data-col-size="sm" data-end="1005" data-start="983" style="width: 18.3552%;">Controller / Método</th><th data-col-size="lg" data-end="1018" data-start="1005" style="width: 63.2896%;">Descrição</th></tr></thead><tbody data-end="1395" data-start="1076"><tr data-end="1248" data-start="1076"><td data-col-size="sm" data-end="1086" data-start="1076" style="width: 8.22223%;">**GET**</td><td data-col-size="sm" data-end="1099" data-start="1086" style="width: 10.133%;">**/ordenar**</td><td data-col-size="sm" data-end="1111" data-start="1099" style="width: 18.3552%;">**order()**</td><td data-col-size="lg" data-end="1248" data-start="1111" style="width: 63.2896%;">Responsável por carregar a view principal da ordenação e inicializar o componente com base nas configurações definidas no controller.</td></tr><tr data-end="1395" data-start="1249"><td data-col-size="sm" data-end="1260" data-start="1249" style="width: 8.22223%;">**POST**</td><td data-col-size="sm" data-end="1273" data-start="1260" style="width: 10.133%;">**/ordenar**</td><td data-col-size="sm" data-end="1292" data-start="1273" style="width: 18.3552%;">**processOrder()**</td><td data-col-size="lg" data-end="1395" data-start="1292" style="width: 63.2896%;">Recebe a nova ordem enviada pelo frontend e processa a atualização dos registros no banco de dados.</td></tr></tbody></table>

</div></div>Ambas as rotas utilizam o middleware "*panel.check.module.permission:update*", garantindo que apenas usuários com permissão de **edição** possam acessar e modificar a ordem dos registros.

##### **Exemplo:**

```php
Route::get('/ordernar', [AuthorController::class, 'order'])
->name('order')->middleware('panel.check.module.permission:update');

Route::post('/ordernar', [AuthorController::class, 'processOrder'])
->name('processOrder')->middleware('panel.check.module.permission:update');
```

---

### **View**

Por padrão, o sistema de ordenação utiliza uma **view global** compartilhada entre todos os módulos, localizada em:

<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary" id="bkmrk-%22panel.layouts.modul"><div class="overflow-y-auto p-4" dir="ltr">**"panel.layouts.modules.order"**</div><div class="overflow-y-auto p-4" dir="ltr">  
</div></div>Essa view é responsável por renderizar a interface do sistema de ordenação, exibindo os registros conforme as configurações definidas no método **orderConfig()** do controller.  
Ela já contém toda a estrutura necessária para:

- Exibir os níveis de ordenação e breadcrumbs;
- Listar os registros conforme o nível atual;
- Enviar a nova ordem para o backend através da rota **processOrder**;
- Atualizar dinamicamente a interface conforme o usuário interage com os níveis.

---

#### **Personalização da View**

Embora exista uma view global, o sistema permite **substituí-la por uma view personalizada**, caso o módulo necessite de um layout ou comportamento específico.  
Para isso, basta informar o **caminho da nova view** dentro da configuração **$generalConfig** do método **orderConfig()**.

**Exemplo**:

<div class="contain-inline-size rounded-2xl relative bg-token-sidebar-surface-primary" id="bkmrk--3"><div class="sticky top-9"><div class="absolute end-0 bottom-0 flex h-9 items-center pe-2">  
</div></div><div class="overflow-y-auto p-4" dir="ltr">  
</div></div>```php
$generalConfig = [ 'selfGroupedRecords' => false, 'view' => 'panel.modules.produtos.orderCustom', ];
```

Nesse caso, o sistema utilizará a view "*panel.modules.produtos.orderCustom*" em vez da padrão "*panel.layouts.modules.order*".

> **Observação:**  
> A view personalizada deve seguir a mesma estrutura básica da view padrão — ou seja, deve conter o componente responsável por interagir com o sistema de ordenação e enviar as alterações para o backend.  
> Dessa forma, mantém-se a compatibilidade com a lógica existente, mesmo quando o layout é adaptado.