MAD Lab
/
Tutor > Módulos visuais > Planilha (sheet)

Planilha (sheet)

Módulos visuais

mad-sheet mad-sheet-col
Combo com cascade e busca

mad-sheet zero PHP: categoria filtra produto por linha (depends-on + search).

row(s) filled error(s)
#
Σ
resources/views/tutor/examples/modulos-sheet/cascade.blade.php
<mad-sheet model="TutorDemoSheetLancamento" database="business" rows-min="4">
    <mad-sheet-col field="categoria_id" type="combo" model="TutorDemoSheetCategoria"
                   display="nome" label="Categoria" required width="200px" />
    {{-- Cascade por linha: muda a categoria da linha e o produto da MESMA
         linha limpa; busca server-side por causa do depends-on. --}}
    <mad-sheet-col field="produto_id" type="combo" model="TutorDemoSheetProduto"
                   display="nome" label="Produto" required width="220px"
                   search depends-on="categoria_id" depends-column="categoria_id" />
    <mad-sheet-col field="quantidade" type="number" label="Qtd" total="sum"
                   decimals="0" min="0" step="1" required />
</mad-sheet>
Lote completo (combo, número, moeda, data, calculado)

Todos os tipos de mad-sheet-col + quick register na célula + total persistido via beforeSaveRow.

row(s) filled error(s)
#
Σ
resources/views/tutor/examples/modulos-sheet/lote-completo.blade.php
@php
    // Partial de <x-tutor-demo> = escopo Blade ISOLADO — `$that` não
    // atravessa esse aninhamento sozinho (ver lição em
    // tutor.examples.grids-formatacao.agrupamento). Reabre aqui porque
    // beforeSaveRow (recalcular o total no servidor) só existe no HOST de
    // verdade (TutorModulosSheet, subclasse de MadSheet).
    $that = \Mad\Component\MadRenderContext::getComponent();
@endphp
<mad-sheet model="TutorDemoSheetLancamento" database="business" rows-min="6" max-rows="500">
    <mad-sheet-col field="categoria_id" type="combo" model="TutorDemoSheetCategoria"
                   display="nome" label="Categoria" required width="170px" />
    <mad-sheet-col field="produto_id" type="combo" model="TutorDemoSheetProduto"
                   display="nome" label="Produto" required width="220px"
                   search depends-on="categoria_id" depends-column="categoria_id">
        <mad-quick-form action="TutorModulosSheet::onQuickAddProduto" label="Adicionar"
                        message="Produto não encontrado — cadastre na hora:">
            <mad-input-field name="nome" label="Nome" required />
        </mad-quick-form>
    </mad-sheet-col>
    <mad-sheet-col field="quantidade" type="number" label="Qtd" total="sum"
                   decimals="0" min="0" step="1" required />
    <mad-sheet-col field="valor" type="money" label="Valor unit." total="sum"
                   width="150px" prefix="R$" min="0" />
    {{-- Coluna calculada: readonly, display-only — persistida no
         beforeSaveRow (TutorModulosSheet::beforeSaveRow). --}}
    <mad-sheet-col field="total" type="money" label="Total" total="sum"
                   width="150px" compute="{quantidade} * {valor}" />
    {{-- :default = expressão PHP; default="{{ '{{ }}' }}" NÃO funciona aqui
         (o compiler da tag roda antes do Blade). --}}
    <mad-sheet-col field="data" type="date" label="Data" :default="date('Y-m-d')"
                   width="150px" min="2020-01-01" />
    <mad-sheet-col field="obs" label="Observação" placeholder="opcional" maxlength="120" />
</mad-sheet>
app/control/Tutor/TutorModulosSheet.php
/**
 * `compute` é display-only (nunca persiste) — recalcula o total no
 * servidor antes de gravar. Contrato: SheetDevView::enrichComputedTotal.
 */
protected function beforeSaveRow(array $row, int $index): array
{
    $row['total'] = round(((float) ($row['quantidade'] ?? 0)) * ((float) ($row['valor'] ?? 0)), 2);

    return $row;
}
Código-fonte completo da página
app/control/Tutor/TutorModulosSheet.php
<?php

namespace App\Control\Tutor;

use App\Models\TutorDemo\TutorDemoSheetCategoria;
use App\Models\TutorDemo\TutorDemoSheetProduto;
use Illuminate\Support\Facades\Schema;
use Mad\Sheet\MadSheet;

/**
 * TutorModulosSheet — página "Planilha (sheet)" do MAD Lab (catálogo
 * modulos_sheet). Cobre: mad-sheet, mad-sheet-col.
 *
 * O demo pequeno (cascade.blade.php) NÃO reabre `$that` — cai num
 * MadSheetStandalone novo por request (zero PHP, igual ao example_basic do
 * mad_props). O demo grande (lote-completo.blade.php) reabre `$that` via
 * MadRenderContext::getComponent() pra usar o HOST de verdade (esta classe),
 * já que o hook `beforeSaveRow` só existe em subclasse. Diferente do
 * mad-org-chart, o MadSheet NÃO cacheia dados carregados por instância (é
 * entry-first — nasce vazio), então múltiplos <mad-sheet> no mesmo host não
 * têm o bug de "roots desatualizados"; mesmo assim só o ÚLTIMO tag
 * renderizado fica com o `shCfgKey` válido pro round-trip do wire (Salvar/
 * Validar) — por isso o demo grande é sempre o último da página.
 */
class TutorModulosSheet extends MadSheet
{
    protected static string $wrapper = self::INTERNAL;

    protected string $model    = 'TutorDemoSheetLancamento';
    protected string $database = 'business';

    public function mount(array $params = []): void
    {
        $this->ensureFixture();
    }

    /**
     * `compute` é display-only (nunca persiste) — recalcula o total no
     * servidor antes de gravar. Contrato: SheetDevView::enrichComputedTotal.
     */
    protected function beforeSaveRow(array $row, int $index): array
    {
        $row['total'] = round(((float) ($row['quantidade'] ?? 0)) * ((float) ($row['valor'] ?? 0)), 2);

        return $row;
    }

    /**
     * Quick register do combo de produto (MadQuickRegisterService), chamado
     * pelo <mad-quick-form action="TutorModulosSheet::onQuickAddProduto">
     * dentro da coluna produto_id. Recebe {term, fields, field_name}; retorna
     * {value, label[, toast]}.
     */
    public static function onQuickAddProduto(array $params = []): array
    {
        $nome = trim((string) ($params['fields']['nome'] ?? $params['term'] ?? ''));
        if ($nome === '') {
            throw new \RuntimeException('Informe o nome do produto.');
        }

        $categoriaId = (int) ($params['fields']['categoria_id'] ?? 1);
        $produto     = TutorDemoSheetProduto::query()->create([
            'nome'         => $nome,
            'categoria_id' => $categoriaId > 0 ? $categoriaId : 1,
        ]);

        return ['value' => (string) $produto->id, 'label' => $nome, 'toast' => 'Produto cadastrado.'];
    }

    // ── Fixture ──────────────────────────────────────────────────────────

    private function ensureFixture(): void
    {
        $schema = Schema::connection('business');

        if (!$schema->hasTable('tutor_sheet_categorias')) {
            $schema->create('tutor_sheet_categorias', function ($t) {
                $t->increments('id');
                $t->string('nome');
            });
        }
        if (!$schema->hasTable('tutor_sheet_produtos')) {
            $schema->create('tutor_sheet_produtos', function ($t) {
                $t->increments('id');
                $t->string('nome');
                $t->integer('categoria_id')->default(0);
            });
        }
        if (!$schema->hasTable('tutor_sheet_lancamentos')) {
            $schema->create('tutor_sheet_lancamentos', function ($t) {
                $t->increments('id');
                $t->integer('categoria_id')->default(0);
                $t->integer('produto_id')->default(0);
                $t->decimal('quantidade', 12, 2)->default(0);
                $t->decimal('valor', 12, 2)->default(0);
                $t->decimal('total', 14, 2)->default(0);
                $t->string('data', 10)->nullable();
                $t->string('obs')->nullable();
            });
        }

        if (TutorDemoSheetCategoria::query()->count() === 0) {
            TutorDemoSheetCategoria::query()->insert([
                ['id' => 1, 'nome' => 'Bebidas'],
                ['id' => 2, 'nome' => 'Limpeza'],
            ]);
        }
        if (TutorDemoSheetProduto::query()->count() === 0) {
            TutorDemoSheetProduto::query()->insert([
                ['id' => 1, 'nome' => 'Água mineral 500ml', 'categoria_id' => 1],
                ['id' => 2, 'nome' => 'Suco de laranja 1L',  'categoria_id' => 1],
                ['id' => 3, 'nome' => 'Detergente neutro',   'categoria_id' => 2],
                ['id' => 4, 'nome' => 'Água sanitária 1L',   'categoria_id' => 2],
            ]);
        }
    }

    protected function view(): string|array
    {
        return ['tutor.modulos.sheet', []];
    }
}
resources/views/tutor/modulos/sheet.blade.php
<x-tutor-page page="modulos_sheet">
    <x-tutor-demo demo="tutor.examples.modulos-sheet.cascade"
        title="Combo com cascade e busca" desc="mad-sheet zero PHP: categoria filtra produto por linha (depends-on + search)." />

    <x-tutor-demo demo="tutor.examples.modulos-sheet.lote-completo"
        title="Lote completo (combo, número, moeda, data, calculado)" desc="Todos os tipos de mad-sheet-col + quick register na célula + total persistido via beforeSaveRow."
        php="TutorModulosSheet::beforeSaveRow" />
</x-tutor-page>