CRUD Bootstrap 5 Mysql e Codeigniter 4 Lista de tarefas parte 4

Criando o #formulario de cadastro de tarefas

Seguindo com nosso #crud #todolist #codeigniter. Vamos criar o formulário de #cadastro de #tarefas com #controller, model e view. Validações (frontend e backend) e a tabela necessária. Você pode obter alguma ajuda ou solicitar o código fonte pelo email: contato@codesnippets.dev.br ou no Twiter @BrCodeSnippets

Para acessar o código fonte: https://www.codesnippets.dev.br/post/crud-bootstrap-5-mysql-e-codeigniter-4-lista-de-tarefas-download

Nossa tabela.

CREATE TABLE `tasks` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `name` varchar(255) NOT NULL,
 `description` text,
 `hash` varchar(50) DEFAULT NULL,
 `deadline` date DEFAULT NULL,
 `priority` tinyint(4) DEFAULT NULL,
 `status` tinyint(4) DEFAULT '0',
 `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
 `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 `deleted_at` timestamp NULL DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8

Nosso resultado será algo próximo a isso.

 

How to create a CRUD witn Codeigniter 4

 

Vamos utlizar as classes de validação. Language, Paginação Entidade.

Primeiro vamos atualizar nossas rotas.

Acesse modules/Todo/Config/Routes.php. E cole o código abaixo.

<?php

// Páginas fora do controle de login
$routes->group('/', ['namespace' => '\Modules\Auth\Controllers'], function ($routes) {
    $routes->get('', 'Auth::index');
    $routes->group('auth', function ($routes) {
        $routes->get('', 'Auth::index');
        // login e logout
        $routes->get('logout', 'Auth::logout');
        $routes->get('login', 'Auth::login');
        $routes->post('login', 'Auth::login');
    });
});

// rotas protegidas
$routes->group('todo', ['namespace' => '\Modules\Todo'], function ($routes) {
    // painel principal
    $routes->get('/', 'Dashboard\Controllers\Dashboard::index');
    $routes->get('dashboard', 'Dashboard\Controllers\Dashboard::index');
    // nossas tarefas
    $routes->group('tasks', function ($routes) {
        $routes->get('', 'Tasks\Controllers\Tasks::index');
        $routes->get('all', 'Tasks\Controllers\Tasks::all');
        $routes->get('new', 'Tasks\Controllers\Tasks::new', ['as' => 'task.new']);
        $routes->post('save', 'Tasks\Controllers\Tasks::save');

        $routes->get('edit/(:num)', 'Tasks\Controllers\Tasks::edit/$1');
        $routes->get('delete/(:num)', 'Tasks\Controllers\Tasks::delete/$1');
        $routes->get('view/(:num)', 'Tasks\Controllers\Tasks::view/$1');
        $routes->get('close/(:num)', 'Tasks\Controllers\Tasks::close/$1');
    });
});

O controller de tarefas

Acesse modules/Todo/Tasks/Controllers/Tasks.php. E cole o código abaixo.

<?php

namespace Modules\Todo\Tasks\Controllers;

use CodeIgniter\Entity\Entity;
use Modules\Todo\Main\Controllers\BaseController;
use Modules\Todo\Tasks\Models\TaskModel;
use Modules\Todo\Entities\Task;

class Tasks extends BaseController
{
    protected $taskModel;

    public function __construct()
    {
        array_push($this->helpers, 'custom', 'form', 'dates');
        $this->taskModel = new taskModel();
        $this->data['validation'] = \Config\Services::validation();
    }

    public function index()
    {
        return view('Modules\Todo\Tasks\Views\index', $this->data);
    }

    public function all()
    {
        $this->data['tasks'] = $this->taskModel->paginate();
        $this->data['pager'] = $this->taskModel->pager;
        return view('Modules\Todo\Tasks\Views\all', $this->data);
    }

    public function save()
    {
        if ($this->request->is('post')) {

            $post = $this->request->getPost();

            unset($post['btn_salvar']);

            if ($this->data['validation']->run($post, 'tasks_save')) {

                if ((int) $post['id'] > 0) {
                    $task = $this->getByIdOr404($post['id']);
                    $task->fill($post);

                    if ($task->hasChanged() === false) {
                        return redirect()->to('todo/tasks/new')
                            ->with('message', lang('FlashMessages.sem_alteracao'));
                    }
                }

                $taskNew = new Task($post);                

                /*
                echo '<pre>';
                print_r($taskNew);
                exit;
                */
                if ($this->taskModel->save($taskNew)) {
                     //$lastId = $this->taskModel->getInsertID();
                    // app/Language/pt-BR/FlashMessages.php
                    return redirect()->to('todo/tasks/new')->with('message', lang('FlashMessages.sucesso'));
                }

                /*
                var_dump($post, $task->hasChanged());
                die();
                */
            }

            $this->lastTasks();
            return view('Modules\Todo\Tasks\Views\new', $this->data);
        }
    }

    public function new()
    {
        $this->lastTasks();
        return view('Modules\Todo\Tasks\Views\new', $this->data);
    }

    public function edit($id)
    {
        $id = (int) $id;
        //$this->taskModel->where('id',1)->first(1);
        //string(78) "SELECT * FROM `tasks` WHERE `id` = 1 AND `tasks`.`deleted_at` IS NULL LIMIT 1"

        $this->data['results'] = $this->taskModel->find($id);

        //$this->taskModel->getLastQuery()->getQuery()
        //string(77) "SELECT * FROM `tasks` WHERE `tasks`.`deleted_at` IS NULL AND `tasks`.`id` = 1"

        $this->lastTasks();
        return view('Modules\Todo\Tasks\Views\new', $this->data);
    }

    public function view($id)
    {
        $id = (int) $id;
        $this->data['task'] = $this->getByIdOr404($id);
        return view('Modules\Todo\Tasks\Views\view', $this->data);
    }

    public function delete($id)
    {
        $id = (int) $id;
        $this->getByIdOr404($id);
        // app/Language/pt-BR/FlashMessages.php
        $msg = lang('FlashMessages.deleted_ok');

        if (!$this->taskModel
            ->where(['id' => $id])
            ->delete()) {
            // app/Language/pt-BR/FlashMessages.php
            $msg = lang('FlashMessages.deleted_error');
        }

        return redirect()->to('todo/tasks/new')->with('message', $msg);
    }

    public function close($id)
    {
        $id = (int) $id;
        $this->getByIdOr404($id);
        // app/Language/pt-BR/FlashMessages.php
        $msg = lang('FlashMessages.sucesso');

        if (!$this->taskModel->update($id, ['status' => 1])) {
            // app/Language/pt-BR/FlashMessages.php
            $msg = lang('FlashMessages.close_error');
        }

        return redirect()->to('todo/tasks/new')->with('message', $msg);
    }

    private function lastTasks()
    {
        $this->data['tasks'] = $this->taskModel
            ->orderBy('id')
            ->limit(10)
            ->findAll();
    }

    /**
     * Recupera task por id
     * @param integer $id
     * @return Exception|object
     */
    private function getByIdOr404(int $id = null)
    {

        if (!$id || !$task = $this->taskModel->withDeleted(true)->find($id)) {
            throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound("A tarefa $id não foi encontrada.");
        }

        return $task;
    }
}

A View compartilhada _tasks_table

Acesse modules/Todo/Main/Views/_tasks_table.php. E cole o código abaixo.

<div class="row">
    <div class="col-12">
        <?php if ($tasks) : ?>
            <hr>
            Últimas tarefas...
            <div class="table-responsive">
                <table class="table table-striped">
                    <thead class="bg-light">
                        <tr>
                            <th>
                                id
                            </th>
                            <th>
                                nome
                            </th>
                            <th>
                                Prazo final
                            </th>
                            <th>
                                Prioridade
                            </th>
                            <th class="text-center">
                                status
                            </th>
                            <th class="text-center">
                                ações
                            </th>
                        </tr>
                    </thead>
                    <tbody>
                        <?php foreach ($tasks as $task) : ?>
                            <tr>
                                <td><?= $task->id; ?></td>
                                <td><?= $task->name; ?></td>
                                <td><?= isset($task->deadline) ? dataDBtoBRL($task->deadline) : '---'; ?></td>
                                <td><?= isset($task->priority) ? priority($task->priority) : ''; ?></td>
                                <td class="text-center"><?= isset($task->status) ? status($task->status) : '---'; ?></td>
                                <td class="text-center bg-light text-nowrap">
                                    <?= anchor(site_url('todo/tasks/delete/' . $task->id . ''), 'Excluir', ['class' => 'btn btn-outline-danger btn-sm', 'onClick' => "return confirm('Deseja mesmo excluir este arquivo?');"]); ?>
                                    <?= anchor(site_url('todo/tasks/edit/' . $task->id . ''), 'Editar', ['class' => 'btn btn-outline-info btn-sm']); ?>
                                    <?= anchor(site_url('todo/tasks/view/' . $task->id . ''), 'detalhes', ['class' => 'btn btn-outline-secondary btn-sm']); ?>
                                    <?= anchor(site_url('todo/tasks/close/' . $task->id . ''), 'Finalizar', ['class' => 'btn btn-outline-success btn-sm']); ?>

                                </td>
                            </tr>
                        <?php endforeach; ?>
                    </tbody>
                </table>
            </div>
        <?php endif; ?>
    </div>
</div>

As 4 views de tasks (index.php, all.php, new.php e view.php).

Códigos abaixo

modules/Todo/Tasks/Views/all.php

 

<?= $this->extend('Modules\Todo\Main\Views\internal_template'); ?>

<?= $this->section('css'); ?>
<?= $this->endSection(); ?>

<?= $this->section('content'); ?>
<h1>
   Todas as tarefas...
</h1>

<?= $pager->links('default') ?>

<div class="text-right">
   <small> Páginas: <?= $pager->getPageCount(); ?> - registros <?= $pager->getTotal(); ?>
   </small>
</div>

<?php echo $this->include('Modules\Todo\Main\Views\_tasks_table'); ?>

<?= $this->endSection(); ?>

<?= $this->section('scripts'); ?>
<?= $this->endSection(); ?>

 

modules/Todo/Tasks/Views/index.php

<?= $this->extend('Modules\Todo\Main\Views\internal_template'); ?>

<?= $this->section('css'); ?>
<?= $this->endSection(); ?>

<?= $this->section('content'); ?>
<h1>
   Lista de tarefas
</h1>
tarefas...
<?= $this->endSection(); ?>

<?= $this->section('scripts'); ?>
<?= $this->endSection(); ?>

modules/Todo/Tasks/Views/new.php

<?= $this->extend('Modules\Todo\Main\Views\internal_template'); ?>

<?= $this->section('css'); ?>
<?= $this->endSection(); ?>

<?= $this->section('content'); ?>
<h1>
    Nova Tarefa
</h1>
<?php
/*
echo '<pre>';
var_dump($results->id);
die();
*/
?>
<?php echo form_open(site_url('todo/tasks/save'), ['id' => 'form', 'autocomplete' => 'off'], ['id' => $results->id ?? set_value('id')]) ?>
<div class="row">
    <div class="col-10">
        <?= form_label(renderLabel($validation->getError('name'), '*Nome')); ?>
        <?= form_input([
            'class' => 'form-control form-control-sm ' . toggleCSSValidOrInvalid($validation->getError('name')),
            'placeholder' => 'Informe nome',
            'type' => 'text',
            'name' => 'name',
            'id' => 'name',
            // 'required' => 'required',
            'value' => $results->name ?? set_value('name')
        ]);
        ?>
    </div>

    <div class="col-2">
        <?= form_label(renderLabel($validation->getError('hash'), '*Auto')); ?>
        <?= form_input([
            'class' => 'form-control form-control-sm ' . toggleCSSValidOrInvalid($validation->getError('hash')),
            'placeholder' => 'Informe nome',
            'type' => 'text',
            'name' => 'hash',
            'id' => 'hash',
            // 'required' => 'required',
            'value' => $results->hash ?? set_value('hash')
        ]);
        ?>
    </div>

    <div class="col-12">
        <?= form_label(renderLabel($validation->getError('description'), '*Descrição')); ?>
        <?= form_textarea([
            'class' => 'form-control form-control-sm' . toggleCSSValidOrInvalid($validation->getError('description')),
            'name' => 'description',
            'placeholder' => 'Descrição da tarefa',
            'id' => 'description',
            // 'required' => 'required',
            'rows' => 4,
            'value' => $results->description ?? set_value('description')
        ]);
        ?>
    </div>

    <div class="col-12 col-sm-6">
        <?= form_label(renderLabel($validation->getError('deadline'), '*Prazo final')); ?>
        <?= form_input([
            'class' => 'form-control form-control-sm' . toggleCSSValidOrInvalid($validation->getError('deadline')),
            'name' => 'deadline',
            'type' => 'date',
            'placeholder' => 'Prazo final',
            'id' => 'deadline',
            // 'required' => 'required',            
            'value' => $results->deadline ?? set_value('deadline')
        ]);
        ?>
    </div>

    <div class="col-12 col-sm-6 ">
        <?= form_label(renderLabel($validation->getError('priority'), '*Prioridade')); ?>
        <?= form_dropdown('priority', [
            '' => '(Selecione)',
            1 => 'Urgente',
            2 => 'Importante',
            3 => 'Media',
            4 => 'Baixa',
        ], $results->priority ??  set_value('priority'), ['id' => 'priority', 'class' => 'form-select form-select-sm' . toggleCSSValidOrInvalid($validation->getError('priority'))]); ?>
    </div>



    <div class="col-12 col-sm-10">
        <?= form_submit('btn_salvar', (isset($results->id) && $results->id > 0) ? 'Atualizar' : 'Salvar', ['class' => 'btn btn-success mt-2 w-100 btn-sm']); ?>
    </div>
    <div class="col-12 col-sm-2">
        <a href="<?= site_url('todo/tasks/new'); ?>" class="btn w-100 btn-sm border mt-2">Nova</a>
    </div>

</div>
<?= form_close() ?>

<div class="row">
    <div class="col-12">
        <?php if ($tasks) : ?>
            <hr>
            Últimas tarefas...
            <div class="table-responsive">
                <table class="table table-striped">
                    <thead class="bg-light">
                        <tr>
                            <th>
                                id
                            </th>
                            <th>
                                nome
                            </th>
                            <th>
                                Prazo final
                            </th>
                            <th>
                                Prioridade
                            </th>
                            <th class="text-center">
                                status
                            </th>
                            <th class="text-center">
                                ações
                            </th>
                        </tr>
                    </thead>
                    <tbody>
                        <?php foreach ($tasks as $task) : ?>
                            <tr>
                                <td><?= $task->id; ?></td>
                                <td><?= $task->name; ?></td>
                                <td><?= isset($task->deadline) ? dataDBtoBRL($task->deadline) : '---'; ?></td>
                                <td><?= isset($task->priority) ? priority($task->priority) : ''; ?></td>
                                <td class="text-center"><?= isset($task->status) ? status($task->status) : '---'; ?></td>
                                <td class="text-center bg-light text-nowrap">
                                    <?= anchor(site_url('todo/tasks/delete/' . $task->id . ''), 'Excluir', ['class' => 'btn btn-outline-danger btn-sm', 'onClick' => "return confirm('Deseja mesmo excluir este arquivo?');"]); ?>
                                    <?= anchor(site_url('todo/tasks/edit/' . $task->id . ''), 'Editar', ['class' => 'btn btn-outline-info btn-sm']); ?>
                                    <?= anchor(site_url('todo/tasks/view/' . $task->id . ''), 'detalhes', ['class' => 'btn btn-outline-secondary btn-sm']); ?>
                                    <?= anchor(site_url('todo/tasks/close/' . $task->id . ''), 'Finalizar', ['class' => 'btn btn-outline-success btn-sm']); ?>

                                </td>
                            </tr>
                        <?php endforeach; ?>
                    </tbody>
                </table>
            </div>
        <?php endif; ?>
    </div>
</div>
<?= $this->endSection(); ?>
<?= $this->section('scripts'); ?>
<?= $this->endSection(); ?>

modules/Todo/Tasks/Views/view.php

<?= $this->extend('Modules\Todo\Main\Views\internal_template'); ?>

<?= $this->section('css'); ?>
<?= $this->endSection(); ?>

<?= $this->section('content'); ?>
<div class="row">
    <div class="col-12">

        <h1>
            Código: <?php echo $task->id; ?>
        </h1>
        <div class="card">
            <div class="card-header">
                Detalhes da tarefa
            </div>
            <div class="card-body">
                <h5 class="card-title"><?php echo $task->name; ?></h5>
                <p class="card-text">
                    <?php echo $task->description; ?>
                </p>
                <div class="text-end">
                    <?= anchor(site_url('todo/tasks/edit/' . $task->id . ''), 'Editar', ['class' => 'btn btn-outline-info btn-sm']); ?>
                    <?= anchor(site_url('todo/tasks/delete/' . $task->id . ''), 'Excluir', ['class' => 'btn btn-outline-danger btn-sm', 'onClick' => "return confirm('Deseja mesmo excluir este arquivo?');"]); ?>

                </div>
            </div>
        </div>
    </div>
</div>
<?= $this->endSection(); ?>

<?= $this->section('scripts'); ?>
<?= $this->endSection(); ?>

Entidade Task

modules/Todo/Entities/Task.php

<?php

namespace Modules\Todo\Entities;

use CodeIgniter\Entity\Entity;

class Task extends Entity
{
    protected $dates = ['created_at', 'updated_at', 'deleted_at'];

}

Model taskModel

modules/Todo/Tasks/Models/TaskModel.php

<?php

namespace Modules\Todo\Tasks\Models;

use CodeIgniter\Model;
use Modules\Todo\Entities\Task;

class TaskModel extends Model
{
    protected $table            = 'tasks';
    protected $primaryKey       = 'id';
    protected $returnType       = Task::class; // 'array' ou 'object';
    protected $useSoftDeletes   = true;
    protected $protectFields    = true;
    protected $allowedFields    = [
        'name',
        'description',
        'hash',
        'deadline',
        'priority',
        'status'
    ];

    // Dates
    protected $useTimestamps = false;

    // Validation    
    protected $skipValidation       = false;

    // Callbacks
    protected $allowCallbacks = true;
}

Validações

Tradução das mensagens de erro. Precisa baixar o pacote pt-BR para a versão do Codeigniter 4 

app/Language/pt-BR/Validation.php

<?php

/**
 * This file is part of the CodeIgniter 4 framework.
 *
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

// Validation language settings
return [
	// Core Messages
	'noRuleSets'      => 'Nenhum conjunto de regras especificado na configuração de Validação.',
	'ruleNotFound'    => '{0} não é uma regra válida.',
	'groupNotFound'   => '{0} não é um grupo de regras de validação.',
	'groupNotArray'   => 'O grupo de regras {0} deve ser um array.',
	'invalidTemplate' => '{0} não é um template de Validation válido.',

	// Rule Messages
	'alpha'                 => 'O campo {field} pode conter apenas caracteres alfabéticos.',
	'alpha_dash'            => 'O campo {field} pode conter apenas caracteres alfa-numéricos, sublinhados, e traços.',
	'alpha_numeric'         => 'O campo {field} pode conter apenas caracteres alfa-numéricos.',
	'alpha_numeric_punct'   => 'O campo {field} pode conter apenas caracteres alfa-numéricos, espaços, e  ~ ! # $ % & * - _ + = | : . caracteres.',
	'alpha_numeric_space'   => 'O campo {field} pode conter apenas caracteres alfa-numéricos e espaços.',
	'alpha_space'           => 'O campo {field} pode conter apenas caracteres alfabéticos e espaços.',
	'decimal'               => 'O campo {field} deve conter um número decimal.',
	'differs'               => 'O campo {field} deve ser diferente do campo {param}.',
	'equals'                => 'O campo {field} deve ser exatamente: {param}.',
	'exact_length'          => 'O campo {field} deve conter exatamente {param} caracteres no tamanho.',
	'greater_than'          => 'O campo {field} deve conter um número maior que {param}.',
	'greater_than_equal_to' => 'O campo {field} deve conter um número maior ou igual a {param}.',
	'hex'                   => 'O campo {field} pode conter apenas caracteres hexadecimais.',
	'in_list'               => 'O campo {field} deve ser um desses: {param}.',
	'integer'               => 'O campo {field} deve conter um número inteiro.',
	'is_natural'            => 'O campo {field} deve conter apenas dígitos.',
	'is_natural_no_zero'    => 'O campo {field} deve conter apenas dígitos e deve ser maior que zero.',
	'is_not_unique'         => 'O campo {field} deve conter um valor já existente no banco de dados.',
	'is_unique'             => 'O campo {field} deve conter um valor único.',
	'less_than'             => 'O campo {field} deve conter um número menor que {param}.',
	'less_than_equal_to'    => 'O campo {field} deve conter um número menor ou igual a {param}.',
	'matches'               => 'O campo {field} não é igual ao campo {param}.',
	'max_length'            => 'O campo {field} não pode exceder {param} caracteres no tamanho.',
	'min_length'            => 'O campo {field} deve conter pelo menos {param} caracteres no tamanho.',
	'not_equals'            => 'O campo {field} não pode ser: {param}.',
	'not_in_list'           => 'O campo {field} não deve ser um desses: {param}.',
	'numeric'               => 'O campo {field} deve conter apenas números.',
	'regex_match'           => 'O campo {field} não está no formato correto.',
	'required'              => 'Campo obrigatório*', //'O campo {field} é requerido.',
	'required_with'         => 'O campo {field} é requerido quando {param} está presente.',
	'required_without'      => 'O campo {field} é requerido quando {param} não está presente.',
	'string'                => 'O campo {field} deve ser uma string válida.',
	'timezone'              => 'O campo {field} deve ser uma timezone válida.',
	'valid_base64'          => 'O campo {field} deve ser uma string base64 válida.',
	'valid_email'           => 'O campo {field} deve conter um endereço de e-mail válido.',
	'valid_emails'          => 'O campo {field} deve conter todos os endereços de e-mails válidos.',
	'valid_ip'              => 'O campo {field} deve conter um IP válido.',
	'valid_url'             => 'O campo {field} deve conter uma URL válida.',
	'valid_date'            => 'O campo {field} deve conter uma data válida.',
	'isValidDateAndFuture'  => 'Data inválida',

	// Credit Cards
	'valid_cc_num' => '{field} não parece ser um número de cartão de crédito válido.',

	// Files
	'uploaded' => '{field} não é um arquivo de upload válido.',
	'max_size' => '{field} é um arquivo muito grande.',
	'is_image' => '{field} não é um arquivo de imagem válida do upload.',
	'mime_in'  => '{field} não tem um tipo mime válido.',
	'ext_in'   => '{field} não tem uma extensão de arquivo válida.',
	'max_dims' => '{field} não é uma imagem, ou ela é muito larga ou muito grande.',
];

Validações comuns de tarefas

app/Config/Validation.php

<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Validation\StrictRules\CreditCardRules;
use CodeIgniter\Validation\StrictRules\FileRules;
use CodeIgniter\Validation\StrictRules\FormatRules;
use CodeIgniter\Validation\StrictRules\Rules;

class Validation extends BaseConfig
{
    // --------------------------------------------------------------------
    // Setup
    // --------------------------------------------------------------------

    /**
     * Stores the classes that contain the
     * rules that are available.
     *
     * @var string[]
     */
    public array $ruleSets = [
        \Modules\Todo\Config\MyRules::class,
        Rules::class,
        FormatRules::class,
        FileRules::class,
        CreditCardRules::class,
    ];

    /**
     * Specifies the views that are used to display the
     * errors.
     *
     * @var array<string, string>
     */
    public array $templates = [
        'list'   => 'CodeIgniter\Validation\Views\list',
        'single' => 'CodeIgniter\Validation\Views\single',
    ];

    // --------------------------------------------------------------------
    // Rules
    
    public $tasks_save = [
        'name' => 'required',
        'description' => 'required',
        'deadline'=> 'required|isValidDateAndFuture',
        'priority' => 'required|is_natural_no_zero'
    ];

    // --------------------------------------------------------------------
}

Regras customizadas

modules/Todo/Config/MyRules.php. Precisa criar o arquivo MyRules.php

<?php

namespace Modules\Todo\Config;

class MyRules
{
    function isValidDateAndFuture($date)
    {
        // Obtém a data de hoje (sem levar em conta os minutos)
        $today = new \DateTime('today');

        // Tenta criar um objeto DateTime a partir da data fornecida
        $inputDate = \DateTime::createFromFormat('Y-m-d', $date);

        // Verifica se a data foi criada corretamente e se é válida
        if (!$inputDate || $inputDate->format('Y-m-d') !== $date) {
            return false; // A data é inválida
        }

        // Compara a data de hoje com a data fornecida (apenas o dia é considerado)
        if ($inputDate < $today) {
            return false; // A data não é maior que hoje
        }

        return true; // A data é válida e maior que hoje
    }
   
}

Criamos um template padrão estilo #bootstrap para customizar a paginação me modules/Todo/Main/Views/Pagers/default.php

<?php

use CodeIgniter\Pager\PagerRenderer;

/**
 * @var PagerRenderer $pager
 */
$pager->setSurroundCount(3);
?>

<nav aria-label="<?= lang('Pager.pageNavigation') ?>" class="text-end">
    <ul class="pagination mt-2 justify-content-end">
        <?php if ($pager->hasPrevious()) : ?>
            <li class="page-item">
                <a class="page-link" href="<?= $pager->getFirst() ?>" aria-label="<?= lang('Pager.first') ?>">
                    <span aria-hidden="true"><?= lang('Pager.first') ?></span>
                </a>
            </li>
            <li class="page-item">
                <a class="page-link" href="<?= $pager->getPreviousPage() ?>" aria-label="<?= lang('Pager.previous') ?>">
                    <span aria-hidden="true"><?= lang('Pager.previous') ?></span>
                </a>
            </li>
        <?php endif ?>

        <?php foreach ($pager->links() as $link) : ?>
            <li <?= $link['active'] ? 'class="disabled page-item active fw-bolder "' : '' ?>>
                <a class="page-link" href="<?= $link['uri'] ?>">
                    <?= $link['title'] ?>
                </a>
            </li>
        <?php endforeach ?>
        <?php
        /*
    $pager->getNext() por -> getNextPage
    $pager->getPrevious() por -> getPreviousPage
    */
        ?>
        <?php if ($pager->hasNext()) : ?>
            <li class="page-item">
                <a class="page-link" href="<?= $pager->getNextPage() ?>" aria-label="<?= lang('Pager.next') ?>">
                    <span aria-hidden="true"><?= lang('Pager.next') ?></span>
                </a>
            </li>
            <li class="page-item">
                <a class="page-link" href="<?= $pager->getLast() ?>" aria-label="<?= lang('Pager.last') ?>">
                    <span aria-hidden="true"><?= lang('Pager.last') ?></span>
                </a>
            </li>
        <?php endif ?>
    </ul>
</nav>

Helpers para nos ajudar com as Views.

app/Helpers/alert_helper.php. Este Helper nos ajusa com os #alerts do #Boostrap.

<?php
function renderAlerts($arr)
{
    $return = '';

    if ($arr !== null) {
        $return .= '<div class="alert alert-' . $arr['class'] . ' alert-dismissible fade show">';
        $return .= '<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>';
        $return .=  $arr['text'] . '</div>';
        return $return;
    }
}

app/Helpers/custom_helper.php. este Helper auxila na customização de elementos como status e priodade.

<?php

/**
 * Render do label para exibir o erro
 * @param string string
 * @return string
 */
function renderLabel($erro, $field)
{
    return (empty($erro) ? '' . $field . '' : '<small class="text-danger">' . $erro . '</small>');
}

/**
 * classe do CSS is-valid ou is-invalid
 * @param string string
 * @return string
 */
function toggleCSSValidOrInvalid($erro)
{
    return (empty($erro) ? '' : ' is-invalid ');
}

function status($status)
{
    $st = [];

    switch ($status) {
        case 0:
            $st[0] = 'warning';
            $st[1] = 'Pendente';
            break;
        case 1:
            $st[0] = 'success';
            $st[1] = 'Finalizada';
            break;
        case 2:
            $st[0] = 'danger';
            $st[1] = 'Atrasada';
            break;
        case 3:
            $st[0] = 'danger';
            $st[1] = 'Cancelada';
            break;
        case 4:
            $st[0] = 'info';
            $st[1] = 'Indefinido';
            break;
    }
    return '<span class="d-block text-center text-' . $st[0] . '">' . $st[1] . '</span>';
}

function priority($priority)
{
    $st = [];

    switch ($priority) {

        case 1:
            $st[0] = 'danger';
            $st[1] = 'Urgente';
            break;
        case 2:
            $st[0] = 'danger';
            $st[1] = 'Importante';
            break;
        case 3:
            $st[0] = 'warning';
            $st[1] = 'Media';
            break;
        case 4:
            $st[0] = 'info';
            $st[1] = 'baixa';
            break;
    }
    return '<span class="badge text-bg-' . $st[0] . ' d-block text-center">' . $st[1] . '</span>';
}

app/Helpers/dates_helper.php. Um Helper para lidar com datas.

<?php
//app/Helpers/dates_helper.php
function dataDBtoBRL($str)
{
    if (strlen($str) >= 8 && trim($str) != "0000-00-00") {
        $dataArray = explode("-", $str);
        return $dataArray[2] . '/' . $dataArray[1] . '/' . $dataArray[0]; // 2016-01-30
    }

    return ""; // vazio

}

Ajustes no  modules/Todo/Dashboard/Controllers/Dashboard.php

<?php

namespace Modules\Todo\Dashboard\Controllers;

use Modules\Todo\Main\Controllers\BaseController;
use Modules\Todo\Tasks\Models\TaskModel;

class Dashboard extends BaseController
{
    protected $taskModel;

    public function __construct()
    {
        array_push($this->helpers,'text','dates','custom');
        $this->taskModel = new taskModel();       
    }

    public function index()
    {
        // $this->getUser('id')
        
        $this->data['tasks'] = $this->taskModel
        ->orderBy('id')
        ->limit(20)
        ->findAll();

        return view('Modules\Todo\Dashboard\Views\index',$this->data);
    }
}

modules/Todo/Dashboard/Views/index.php

<?= $this->extend('Modules\Todo\Main\Views\internal_template'); ?>

<?= $this->section('css'); ?>
<?= $this->endSection(); ?>

<?= $this->section('content'); ?>
<h1>
    Dashboard
</h1>
últimas tarefas...

<?php echo $this->include('Modules\Todo\Main\Views\_tasks_table'); ?>
<?= $this->endSection(); ?>

<?= $this->section('scripts'); ?>
<?= $this->endSection(); ?>

Com isso já podemos ver alguns registros da tabela de dados tasks.

Dump de tasks.

-- phpMyAdmin SQL Dump
-- version 4.9.5deb2
-- https://www.phpmyadmin.net/
--
-- Host: localhost:3306
-- Tempo de geração: 24-Jul-2023 às 16:47
-- Versão do servidor: 5.7.34
-- versão do PHP: 7.4.33

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = "+00:00";


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;

--
-- Banco de dados: `todo`
--

-- --------------------------------------------------------

--
-- Estrutura da tabela `tasks`
--

CREATE TABLE `tasks` (
  `id` int(11) NOT NULL,
  `name` varchar(255) NOT NULL,
  `description` text,
  `hash` varchar(50) DEFAULT NULL,
  `deadline` date DEFAULT NULL,
  `priority` tinyint(4) DEFAULT NULL,
  `status` tinyint(4) DEFAULT '0',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `deleted_at` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Extraindo dados da tabela `tasks`
--

INSERT INTO `tasks` (`id`, `name`, `description`, `hash`, `deadline`, `priority`, `status`, `created_at`, `updated_at`, `deleted_at`) VALUES
(1, 'aaa 2 2 ', 'aaa2', '123', NULL, NULL, 0, '2023-07-24 12:08:19', '2023-07-24 17:11:37', '2023-07-24 17:11:37'),
(2, 'Teste 001', 'Teste 001', '123', '2023-07-25', 4, 1, '2023-07-24 12:09:04', '2023-07-24 18:57:18', NULL),
(3, 'Tarefa --', 'aaa', '123', '2023-08-01', 1, 1, '2023-07-24 12:20:31', '2023-07-24 19:04:51', NULL),
(4, 'Verificar servidor', 'Verificar servidor', '123', '2023-08-04', 3, 0, '2023-07-24 12:29:50', '2023-07-24 18:45:49', NULL),
(5, 'hfghfgh', 'fghfg', '', NULL, NULL, 0, '2023-07-24 12:29:55', '2023-07-24 16:41:27', NULL),
(6, 'fghfgh', 'gfhfg', '', NULL, NULL, 0, '2023-07-24 12:30:10', '2023-07-24 16:41:30', NULL),
(7, 'sdfsd', 'fsdfsd', '', NULL, NULL, 0, '2023-07-24 12:33:09', '2023-07-24 16:41:33', NULL),
(8, 'ghgf', 'hgfhfg', '', NULL, NULL, 0, '2023-07-24 13:21:31', '2023-07-24 16:41:36', NULL),
(9, 'Porque usamos?', 'É um fato conhecido de todos que um leitor se distrairá com o conteúdo de texto legível de uma página quando estiver examinando sua diagramação. A vantagem de usar Lorem Ipsum é que ele tem uma distribuição normal de letras, ao contrário de \"Conteúdo aqui, conteúdo aqui\", fazendo com que ele tenha uma aparência similar a de um texto legível. Muitos softwares de publicação e editores de páginas na internet agora usam Lorem Ipsum como texto-modelo padrão, e uma rápida busca por \'lorem ipsum\' mostra vários websites ainda em sua fase de construção. Várias versões novas surgiram ao longo dos anos, eventualmente por acidente, e às vezes de propósito (injetando humor, e coisas do gênero).', '', NULL, NULL, 0, '2023-07-24 14:32:53', '2023-07-24 15:11:40', NULL),
(10, '222', '333', '', NULL, NULL, 0, '2023-07-24 14:38:40', '2023-07-24 14:38:40', NULL),
(11, 'aaa2 a', 'aaa', '123', NULL, NULL, 0, '2023-07-24 14:39:16', '2023-07-24 14:39:16', NULL),
(12, 'O que é Lorem Ipsum?', 'Lorem Ipsum é simplesmente uma simulação de texto da indústria tipográfica e de impressos, e vem sendo utilizado desde o século XVI, quando um impressor desconhecido pegou uma bandeja de tipos e os embaralhou para fazer um livro de modelos de tipos. Lorem Ipsum sobreviveu não só a cinco séculos, como também ao salto para a editoração eletrônica, permanecendo essencialmente inalterado. Se popularizou na década de 60, quando a Letraset lançou decalques contendo passagens de Lorem Ipsum, e mais recentemente quando passou a ser integrado a softwares de editoração eletrônica como Aldus PageMaker.', '', NULL, NULL, 0, '2023-07-24 15:05:29', '2023-07-24 15:05:29', NULL),
(13, 'Tarefa teste 001', 'Tarefa teste 001', '', NULL, NULL, 0, '2023-07-24 17:24:49', '2023-07-24 17:24:49', NULL);

--
-- Índices para tabelas despejadas
--

--
-- Índices para tabela `tasks`
--
ALTER TABLE `tasks`
  ADD PRIMARY KEY (`id`);

--
-- AUTO_INCREMENT de tabelas despejadas
--

--
-- AUTO_INCREMENT de tabela `tasks`
--
ALTER TABLE `tasks`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=14;
COMMIT;

/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

 

Você pode obter alguma ajuda ou solicitar o código fonte pelo email: contato@codesnippets.dev.br ou no Twiter @BrCodeSnippets