Creacion de index de ventas y mejora de vistas de catalogo, productos, y mejoras de ruteo a diferentes paginas, entre otros.
This commit is contained in:
@@ -16,6 +16,7 @@ class ProductosController extends Controller
|
||||
{
|
||||
// Recuperamos lo que el usuario escribió en el buscador (si escribió algo)
|
||||
$query = $request->input('search');
|
||||
$status = $request->input('stock_status');
|
||||
|
||||
// Construimos la consulta
|
||||
$products = Product::query()
|
||||
@@ -24,6 +25,24 @@ class ProductosController extends Controller
|
||||
return $q->where('name', 'like', "%{$query}%")
|
||||
->orWhere('sku', 'like', "%{$query}%");
|
||||
})
|
||||
->when($status, function ($q) use ($status) {
|
||||
if ($status === 'low') {
|
||||
// Rojo: Menor o igual a la alerta
|
||||
return $q->whereColumn('stock_quantity', '<', 'min_stock_alert')
|
||||
->where('type', '!=', 'service'); // Ignoramos servicios
|
||||
}
|
||||
elseif ($status === 'medium') {
|
||||
// Amarillo: Mayor a alerta PERO menor o igual a alerta + 2 (margen pequeño)
|
||||
return $q->whereColumn('stock_quantity', '>=', 'min_stock_alert')
|
||||
->whereRaw('stock_quantity <= (min_stock_alert + 1)') // Ajusta este "+ 5" según tu criterio de "amarillo"
|
||||
->where('type', '!=', 'service');
|
||||
}
|
||||
elseif ($status === 'ok') {
|
||||
// Verde: Stock saludable
|
||||
return $q->whereRaw('stock_quantity > (min_stock_alert + 1)')
|
||||
->where('type', '!=', 'service');
|
||||
}
|
||||
})
|
||||
->orderBy('stock_quantity', 'asc') // Ordenamos primero los que tienen poco stock (Alerta visual)
|
||||
->paginate(10) // Paginamos de a 10
|
||||
->withQueryString(); // Mantiene el filtro de búsqueda al cambiar de página
|
||||
|
||||
@@ -7,17 +7,25 @@ use App\Models\Product;
|
||||
use App\Models\Sale;
|
||||
use App\Models\SaleDetail;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
//
|
||||
use App\Models\Client;
|
||||
|
||||
class SaleController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$sales = Sale::with('client')
|
||||
->orderBy('created_at', 'desc')
|
||||
->paginate(15);
|
||||
|
||||
return view('sales.index', compact('sales'));
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
// Buscamos productos que tengan stock mayor a 0 para mostrar en el selector
|
||||
$products = Product::where('stock_quantity', '>', 0)->get();
|
||||
|
||||
$clients = \App\Models\Client::orderBy('name')->get();
|
||||
$clients = Client::orderBy('name')->get();
|
||||
|
||||
return view('sales.create', compact('products', 'clients'));
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
@foreach ($products as $prod)
|
||||
<div class="group p-4 border border-neutral-800 rounded-xl hover:border-neon-lime hover:-translate-y-1 transition-all duration-300">
|
||||
{{-- Imagen si existe --}}
|
||||
@if (!empty($prod->img))
|
||||
<img src="{{ asset('storage/' . $prod->img) }}" alt="{{ $prod->name }}"
|
||||
@if (!empty($prod->image_path))
|
||||
<img src="{{ asset('storage/' . $prod->image_path) }}" alt="{{ $prod->name }}"
|
||||
class="w-full h-40 object-cover rounded mb-4">
|
||||
@else
|
||||
<div class="w-full border border-neutral-800 h-40 bg-stone-900 flex items-center justify-center rounded mb-4">
|
||||
@@ -46,5 +46,9 @@
|
||||
@else
|
||||
<p class="text-center text-gray-600 mt-10">No hay productos disponibles.</p>
|
||||
@endif
|
||||
|
||||
<div class="mt-3">
|
||||
{{ $products->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</x-layout>
|
||||
|
||||
@@ -2,7 +2,12 @@
|
||||
<div class="max-w-7xl flex flex-wrap items-center justify-between mx-auto p-4">
|
||||
|
||||
<!-- Logo -->
|
||||
<a href="{{ url('/') }}" class="flex items-center space-x-3 rtl:space-x-reverse group">
|
||||
@auth
|
||||
<a href="{{ route('dashboard') }}" class="flex items-center space-x-3 rtl:space-x-reverse group">
|
||||
@endauth
|
||||
@guest
|
||||
<a href="{{ url('/') }}" class="flex items-center space-x-3 rtl:space-x-reverse group">
|
||||
@endguest
|
||||
<div class="h-10 w-10 bg-neutral-800 rounded-full flex items-center justify-center border-2 border-transparent group-hover:border-neon-lime transition-all duration-300">
|
||||
<svg class="w-6 h-6 text-white group-hover:text-neon-lime transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>
|
||||
</div>
|
||||
@@ -43,7 +48,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ route('sales.create') }}"
|
||||
<a href="{{ route('sales.index') }}"
|
||||
class="block py-2 px-3 md:p-0 transition-colors {{ request()->is('sales*') ? 'text-neon-lime border-b-2 border-neon-lime' : 'text-white hover:text-neon-lime' }}">
|
||||
Ventas
|
||||
</a>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<x-section-header
|
||||
subtitle="Panel de Control"
|
||||
title="¡Bienvenido, "
|
||||
highlight="Jose!"
|
||||
:highlight="auth()->user()->name . '!'"
|
||||
/>
|
||||
|
||||
<!-- Grid de Tarjetas -->
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
<div class="w-full flex flex-col md:flex-row justify-between items-center gap-4 mb-6">
|
||||
|
||||
<!-- Buscador -->
|
||||
<form action="{{ route('productos.index') }}" method="GET" class="w-full md:w-1/2">
|
||||
<div class="relative">
|
||||
<form action="{{ route('productos.index') }}" method="GET" class="w-full lg:w-2/3 flex flex-col sm:flex-row gap-3">
|
||||
<div class="relative w-full sm:w-2/3">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none">
|
||||
<svg class="w-4 h-4 text-gray-500" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"/>
|
||||
@@ -20,6 +20,17 @@
|
||||
class="block w-full p-3 ps-10 text-sm text-white border border-neutral-700 rounded-lg bg-neutral-800 focus:ring-neon-lime focus:border-neon-lime placeholder-gray-500"
|
||||
placeholder="Buscar por nombre, SKU o marca...">
|
||||
</div>
|
||||
<!-- Filtro de Estado de Stock -->
|
||||
<div class="w-full sm:w-1/3">
|
||||
<select name="stock_status" onchange="this.form.submit()"
|
||||
class="block w-full p-3 text-sm text-white border border-neutral-700 rounded-lg bg-neutral-800 focus:ring-neon-lime focus:border-neon-lime cursor-pointer">
|
||||
<option value="">Todos</option>
|
||||
<option value="low" {{ request('stock_status') == 'low' ? 'selected' : '' }} class="text-red-300">Stock en Alerta</option>
|
||||
<option value="medium" {{ request('stock_status') == 'medium' ? 'selected' : '' }} class="text-yellow-300">Stock Bajo</option>
|
||||
<option value="ok" {{ request('stock_status') == 'ok' ? 'selected' : '' }} class="text-green-300">Stock Normal</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<!-- Botón Nuevo -->
|
||||
@@ -27,7 +38,6 @@
|
||||
+ Nuevo Producto
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Tabla de Productos -->
|
||||
<div class="relative overflow-x-auto shadow-md sm:rounded-lg border border-neutral-800 w-full">
|
||||
<table class="w-full text-sm text-left rtl:text-right text-gray-400">
|
||||
@@ -58,8 +68,10 @@
|
||||
<td class="px-6 py-4 text-center">
|
||||
@if($product->type === 'service')
|
||||
<span class="text-gray-600">-</span>
|
||||
@elseif($product->stock_quantity <= $product->min_stock_alert)
|
||||
@elseif($product->stock_quantity < $product->min_stock_alert)
|
||||
<x-ui.badge color="red">{{ $product->stock_quantity }}</x-ui.badge>
|
||||
@elseif($product->stock_quantity <= $product->min_stock_alert+1)
|
||||
<x-ui.badge color="yellow">{{ $product->stock_quantity }}</x-ui.badge>
|
||||
@else
|
||||
<x-ui.badge color="green">{{ $product->stock_quantity }}</x-ui.badge>
|
||||
@endif
|
||||
@@ -78,10 +90,8 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Paginación -->
|
||||
<div class="mt-4 w-full">
|
||||
{{ $products->links() }}
|
||||
</div>
|
||||
|
||||
</x-layout>
|
||||
@@ -10,27 +10,27 @@
|
||||
<dd class="text-lg font-semibold">{{$product->name}}</dd>
|
||||
</div>
|
||||
<div class="flex flex-col pt-3">
|
||||
<dt class="mb-1 text-gray-500 md:text-lg dark:text-gray-400">Modelo</dt>
|
||||
<dt class="mb-1 text-gray-500 md:text-lg dark:text-gray-400">SKU</dt>
|
||||
<dd class="text-lg font-semibold">{{$product->sku}}</dd>
|
||||
</div>
|
||||
<div class="flex flex-col pt-3">
|
||||
<dt class="mb-1 text-gray-500 md:text-lg dark:text-gray-400">Color</dt>
|
||||
<dt class="mb-1 text-gray-500 md:text-lg dark:text-gray-400">Descripcion</dt>
|
||||
<dd class="text-lg font-semibold">{{$product->description}}</dd>
|
||||
</div>
|
||||
<div class="flex flex-col pt-3">
|
||||
<dt class="mb-1 text-gray-500 md:text-lg dark:text-gray-400">Rodado</dt>
|
||||
<dt class="mb-1 text-gray-500 md:text-lg dark:text-gray-400">Costo</dt>
|
||||
<dd class="text-lg font-semibold">{{$product->cost}}</dd>
|
||||
</div>
|
||||
<div class="flex flex-col pt-3">
|
||||
<dt class="mb-1 text-gray-500 md:text-lg dark:text-gray-400">Tipo</dt>
|
||||
<dt class="mb-1 text-gray-500 md:text-lg dark:text-gray-400">Precio</dt>
|
||||
<dd class="text-lg font-semibold">{{$product->price}}</dd>
|
||||
</div>
|
||||
<div class="flex flex-col pt-3">
|
||||
<dt class="mb-1 text-gray-500 md:text-lg dark:text-gray-400">Descripcion</dt>
|
||||
<dt class="mb-1 text-gray-500 md:text-lg dark:text-gray-400">Cantidad en Stock</dt>
|
||||
<dd class="text-lg font-semibold">{{$product->stock_quantity}}</dd>
|
||||
</div>
|
||||
<div class="flex flex-col pt-3">
|
||||
<dt class="mb-1 text-gray-500 md:text-lg dark:text-gray-400">Precio</dt>
|
||||
<dt class="mb-1 text-gray-500 md:text-lg dark:text-gray-400">Tipo</dt>
|
||||
<dd class="text-lg font-semibold">{{$product->type}}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
@@ -1,12 +1,84 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Gwssssssssssssssss</h1>
|
||||
</body>
|
||||
</html>
|
||||
<x-layout title="Historial de Ventas">
|
||||
|
||||
<x-section-header subtitle="Finanzas" title="Historial de " highlight="Ventas" />
|
||||
|
||||
<!-- Barra de Herramientas -->
|
||||
<div class="w-full flex justify-between items-center mb-6">
|
||||
<!-- Buscador simple (opcional visualmente, funcionalidad futura) -->
|
||||
<div class="relative w-1/3">
|
||||
<input type="text" placeholder="Buscar por N° Venta o Cliente..." class="bg-neutral-800 border border-neutral-700 text-white text-sm rounded-lg focus:ring-neon-lime focus:border-neon-lime block w-full p-2.5">
|
||||
</div>
|
||||
|
||||
<!-- Botón Nueva Venta -->
|
||||
<a href="{{ route('sales.create') }}" class="px-5 py-2.5 bg-neon-lime text-neutral-900 font-bold rounded-lg hover:bg-[#b3e600] transition-colors shadow-lg shadow-neon-lime/20 flex items-center gap-2">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path></svg>
|
||||
Registrar Venta
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Tabla de Ventas -->
|
||||
<div class="relative overflow-x-auto shadow-md sm:rounded-lg border border-neutral-800 bg-panel-bg">
|
||||
<table class="w-full text-sm text-left text-gray-400">
|
||||
<thead class="text-xs text-gray-300 uppercase bg-neutral-800 border-b border-neutral-700">
|
||||
<tr>
|
||||
<th scope="col" class="px-6 py-3"># Ref</th>
|
||||
<th scope="col" class="px-6 py-3">Fecha</th>
|
||||
<th scope="col" class="px-6 py-3">Cliente</th>
|
||||
<th scope="col" class="px-6 py-3">Método Pago</th>
|
||||
<th scope="col" class="px-6 py-3 text-right">Total</th>
|
||||
<th scope="col" class="px-6 py-3 text-center">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($sales as $sale)
|
||||
<tr class="bg-transparent border-b border-neutral-800 hover:bg-neutral-800/50 transition-colors">
|
||||
<td class="px-6 py-4 font-mono text-neon-lime font-bold">
|
||||
#{{ str_pad($sale->id, 5, '0', STR_PAD_LEFT) }}
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="text-white">{{ $sale->created_at->format('d/m/Y') }}</div>
|
||||
<div class="text-xs text-gray-600">{{ $sale->created_at->format('H:i') }} hs</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
@if($sale->client)
|
||||
<div class="text-white font-medium">{{ $sale->client->name }}</div>
|
||||
<div class="text-xs text-gray-500">Cli. Registrado</div>
|
||||
@else
|
||||
<span class="text-gray-500 italic">Consumidor Final</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<span class="bg-neutral-700 text-gray-300 text-xs font-medium px-2.5 py-0.5 rounded border border-neutral-600">
|
||||
{{ $sale->payment_method }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-right font-bold text-white text-base">
|
||||
${{ number_format($sale->total, 2, ',', '.') }}
|
||||
</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
<button class="text-blue-400 hover:text-blue-300 font-medium text-sm transition-colors">
|
||||
Ver Detalle
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="px-6 py-12 text-center">
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<svg class="w-12 h-12 text-gray-600 mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>
|
||||
<p class="text-gray-400 text-lg">No hay ventas registradas aún.</p>
|
||||
<p class="text-gray-600 text-sm">Realiza tu primera venta desde el botón verde.</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Paginación -->
|
||||
<div class="mt-4 w-full flex justify-center">
|
||||
{{ $sales->links() }}
|
||||
</div>
|
||||
|
||||
</x-layout>
|
||||
+7
-3
@@ -45,9 +45,13 @@ Route::middleware(['auth'])->group(function () {
|
||||
Route::resource('clients', ClientController::class);
|
||||
Route::get('/checker', [ProductosController::class, 'checker'])->name('productos.checker');
|
||||
|
||||
//Route::get('/sales', [SaleController::class, 'index'])->name('sales.index');
|
||||
Route::get('/sales/create', [SaleController::class, 'create'])->name('sales.create');
|
||||
Route::get('/sales/store', [SaleController::class, 'store'])->name('sales.store');
|
||||
Route::controller(SaleController::class)->prefix('sales')->name('sales.')->group(function () {
|
||||
Route::get('/', 'index')->name('index'); // Historial (Nueva)
|
||||
Route::get('/create', 'create')->name('create'); // Formulario (Existente)
|
||||
Route::post('/', 'store')->name('store'); // Guardar (CORREGIDO a POST)
|
||||
Route::get('/{sale}', 'show')->name('show'); // Ver detalle de una venta (Opcional futura)
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user