4-CRUD de Tareas en Laravel (Sencillo y Práctico)-blade-plantilla

 

 Tareas en Laravel con Estructura Blade Completa

Voy a recrear el CRUD utilizando una plantilla base para mostrar mejor cómo funcionan @extends@section y @yield en Blade.

1. Estructura de Plantillas

Primero, crearemos una estructura de plantillas más organizada:

Plantilla Base (resources/views/layouts/app.blade.php)

php
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@yield('title', 'CRUD de Tareas')</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    @stack('styles')
</head>
<body>
    <!-- Header -->
    <header class="bg-primary text-white py-3">
        <div class="container">
            <div class="d-flex justify-content-between align-items-center">
                <h1>@yield('header', 'Gestión de Tareas')</h1>
                @yield('header-actions')
            </div>
        </div>
    </header>

    <!-- Main Content -->
    <main class="container py-4">
        @yield('content')
    </main>

    <!-- Footer -->
    <footer class="bg-light py-3 mt-4">
        <div class="container text-center">
            <p class="mb-0">CRUD de Tareas &copy; {{ date('Y') }}</p>
        </div>
    </footer>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
    @stack('scripts')
</body>
</html>

2. Vistas Específicas

Vista Index (resources/views/tasks/index.blade.php)

php
@extends('layouts.app')

@section('title', 'Lista de Tareas')

@section('header-actions')
    <a href="{{ route('tasks.create') }}" class="btn btn-light">Nueva Tarea</a>
@endsection

@section('content')
    @if(session('success'))
        <div class="alert alert-success alert-dismissible fade show">
            {{ session('success') }}
            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
        </div>
    @endif

    <div class="card">
        <div class="card-body">
            @if($tasks->isEmpty())
                <div class="text-center py-4">
                    <h4>No hay tareas registradas</h4>
                    <a href="{{ route('tasks.create') }}" class="btn btn-primary mt-2">Crear Primera Tarea</a>
                </div>
            @else
                <div class="table-responsive">
                    <table class="table table-hover">
                        <thead>
                            <tr>
                                <th>#</th>
                                <th>Nombre</th>
                                <th>Acciones</th>
                            </tr>
                        </thead>
                        <tbody>
                            @foreach($tasks as $task)
                            <tr>
                                <td>{{ $task->id }}</td>
                                <td>{{ $task->name }}</td>
                                <td>
                                    <div class="d-flex gap-2">
                                        <a href="{{ route('tasks.edit', $task->id) }}" class="btn btn-sm btn-warning">Editar</a>
                                        <form action="{{ route('tasks.destroy', $task->id) }}" method="POST">
                                            @csrf
                                            @method('DELETE')
                                            <button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('¿Estás seguro?')">Eliminar</button>
                                        </form>
                                    </div>
                                </td>
                            </tr>
                            @endforeach
                        </tbody>
                    </table>
                </div>
            @endif
        </div>
    </div>
@endsection

Vista Create (resources/views/tasks/create.blade.php)

php
@extends('layouts.app')

@section('title', 'Crear Nueva Tarea')

@section('content')
    <div class="card">
        <div class="card-header">
            <h5 class="card-title">Nueva Tarea</h5>
        </div>
        <div class="card-body">
            <form action="{{ route('tasks.store') }}" method="POST">
                @csrf
                <div class="mb-3">
                    <label for="name" class="form-label">Nombre de la Tarea</label>
                    <input type="text" class="form-control @error('name') is-invalid @enderror" id="name" name="name" value="{{ old('name') }}" required>
                    @error('name')
                        <div class="invalid-feedback">{{ $message }}</div>
                    @enderror
                </div>
                <div class="d-flex justify-content-end gap-2">
                    <a href="{{ route('tasks.index') }}" class="btn btn-secondary">Cancelar</a>
                    <button type="submit" class="btn btn-primary">Guardar</button>
                </div>
            </form>
        </div>
    </div>
@endsection

Vista Edit (resources/views/tasks/edit.blade.php)

php
@extends('layouts.app')

@section('title', 'Editar Tarea')

@section('content')
    <div class="card">
        <div class="card-header">
            <h5 class="card-title">Editar Tarea #{{ $task->id }}</h5>
        </div>
        <div class="card-body">
            <form action="{{ route('tasks.update', $task->id) }}" method="POST">
                @csrf
                @method('PUT')
                <div class="mb-3">
                    <label for="name" class="form-label">Nombre de la Tarea</label>
                    <input type="text" class="form-control @error('name') is-invalid @enderror" id="name" name="name" value="{{ old('name', $task->name) }}" required>
                    @error('name')
                        <div class="invalid-feedback">{{ $message }}</div>
                    @enderror
                </div>
                <div class="d-flex justify-content-end gap-2">
                    <a href="{{ route('tasks.index') }}" class="btn btn-secondary">Cancelar</a>
                    <button type="submit" class="btn btn-primary">Actualizar</button>
                </div>
            </form>
        </div>
    </div>
@endsection

3. Actualización del Controlador

Actualiza app/Http/Controllers/TaskController.php para incluir todos los métodos:

php
<?php

namespace App\Http\Controllers;

use App\Models\Task;
use Illuminate\Http\Request;

class TaskController extends Controller
{
    public function index()
    {
        $tasks = Task::latest()->paginate(10);
        return view('tasks.index', compact('tasks'));
    }

    public function create()
    {
        return view('tasks.create');
    }

    public function store(Request $request)
    {
        $validated = $request->validate([
            'name' => 'required|string|max:255',
        ]);

        Task::create($validated);

        return redirect()->route('tasks.index')
            ->with('success', 'Tarea creada exitosamente');
    }

    public function edit(Task $task)
    {
        return view('tasks.edit', compact('task'));
    }

    public function update(Request $request, Task $task)
    {
        $validated = $request->validate([
            'name' => 'required|string|max:255',
        ]);

        $task->update($validated);

        return redirect()->route('tasks.index')
            ->with('success', 'Tarea actualizada exitosamente');
    }

    public function destroy(Task $task)
    {
        $task->delete();

        return redirect()->route('tasks.index')
            ->with('success', 'Tarea eliminada exitosamente');
    }
}

4. Rutas

Actualiza routes/web.php:

php
<?php

use App\Http\Controllers\TaskController;
use Illuminate\Support\Facades\Route;

Route::get('/', function () {
    return redirect()->route('tasks.index');
});

Route::resource('tasks', TaskController::class);

Explicación de la Estructura Blade

  1. @extends('layouts.app'): Indica que la vista hereda de la plantilla base.

  2. @section('nombre') y @endsection: Define una sección que será inyectada en la plantilla base.

  3. @yield('nombre'): En la plantilla base, marca el lugar donde se insertará el contenido de la sección.

  4. @stack('nombre'): Permite agregar contenido (como CSS o JS) que será renderizado en ese lugar.

  5. @include('partials.alert') (no usado aquí pero útil): Para incluir sub-vistas.

  6. @error('campo'): Directiva de Blade para mostrar errores de validación.

Beneficios de esta estructura:

  1. Organización: Separación clara entre layout y contenido.

  2. Reutilización: El layout base se usa en todas las vistas.

  3. Mantenibilidad: Cambios en el diseño general solo requieren modificar el layout.

  4. Flexibilidad: Cada vista puede sobrescribir solo las secciones necesarias.

  5. Claridad: El código es más legible y fácil de entender.

Este CRUD ahora incluye:

  • Diseño profesional con Bootstrap

  • Paginación

  • Validación de formularios

  • Mensajes de confirmación

  • Confirmación antes de eliminar

  • Manejo de errores

  • Estructura Blade bien organizad

Comentarios

Entradas más populares de este blog

0-Sistema de Tareas

13-CRUD en Laravel para Web y API con Rutas Normales (No Resource)

10-Introducción a Blade en Laravel con un Ejemplo Práctico