Merge branch 'bryam' of https://github.com/BryamE/ProyectoLauck into bryam

This commit is contained in:
Bryam Cesar
2026-01-20 18:02:10 -03:00
70 changed files with 4443 additions and 803 deletions
@@ -0,0 +1,41 @@
<?php
namespace App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
class CatalogoController extends Controller
{
/**
* Muestra los registros de ventas con buscador y paginación.
*/
public function index(Request $request)
{
// Consulta base
$query = Product::query();
// Lógica del Buscador: Si recibimos algo en el input "search"
if ($request->has('search')) {
$searchTerm = $request->input('search');
$query->where(function($q) use ($searchTerm) {
$q->where('name', 'like', "%{$searchTerm}%") // Buscar por nombre
->orWhere('sku', 'like', "%{$searchTerm}%"); // O por código SKU
});
}
$query->whereIn('type', ['bike', 'accessory']);// Filtros para el público
$query->where('stock_quantity', '>', 0); // Solo mostrar si tiene stock
// Resultados paginados
$products = $query->paginate(12)->withQueryString(); // withQueryString mantiene la búsqueda al cambiar de página
// Devuelve la vista
return view('catalogo.index', compact('products'));
}
public function show(Product $product)
{
return view('catalogo.show', compact('product'));
}
}
+109
View File
@@ -0,0 +1,109 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Client;
class ClientController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request)
{
$query = $request->input('search');
$clients = Client::query()
->when($query, function ($q) use ($query) {
// Si hay búsqueda, filtra por nombre o SKU
return $q->where('name', 'like', "%{$query}%");
})
->orderBy('name', 'asc')
//->orderBy('create_at', 'asc')
->paginate(10) // Paginamos de a 10
->withQueryString(); // Mantiene el filtro de búsqueda al cambiar de página
return view('clients.index', compact('clients'));
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view('clients.create');
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'phone' => 'nullable|string|max:50',
'email' => 'nullable|email|max:255|unique:clients,email',
'address' => 'nullable|string|max:255',
]);
// 1. Guardamos el cliente en una variable para tener su ID
$client = Client::create($validated);
// 2. Verificamos el origen
if ($request->input('origin') === 'sales') {
// Si vino de ventas, volvemos a ventas
// Y pasamos el ID del nuevo cliente para auto-seleccionarlo
return redirect()->route('sales.create', ['new_client_id' => $client->id])
->with('success', 'Cliente creado. Ya puedes seleccionarlo.');
}
// 3. Si no, comportamiento normal (volver al index de clientes)
return redirect()->route('clients.index')
->with('success', 'Cliente creado correctamente.');
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
//
}
public function edit(Client $client)
{
// Reutilizamos la vista de create, o creamos una edit.blade.php similar
return view('clients.edit', compact('client'));
}
public function update(Request $request, Client $client)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'phone' => 'nullable|string|max:50',
'email' => 'nullable|email|max:255|unique:clients,email,' . $client->id, // Ignorar email propio
'address' => 'nullable|string|max:255',
]);
$client->update($validated);
return redirect()->route('clients.index')->with('success', 'Cliente actualizado correctamente.');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Client $client)
{
try {
$client->delete();
return redirect()->route('clients.index')->with('success', 'Cliente eliminado correctamente.');
} catch (\Illuminate\Database\QueryException $e) {
return back()->with('error', 'No se puede eliminar el cliente porque tiene ventas registradas.');
}
}
}
+14 -2
View File
@@ -3,11 +3,23 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\Product;
class HomeController extends Controller class HomeController extends Controller
{ {
public function __invoke() // * Controlador con un unico metodo se usa __invoke public function __invoke(Request $request)
{ {
return view('welcome'); // Si tuvieras un campo 'sales_count', podrías usar ->orderByDesc('sales_count')
$destacados = Product::where('type', 'bike')
->latest() // Las más nuevas
->take(5)
->get();
// Si no hay bicis, traemos cualquier cosa para que no se rompa
if ($destacados->isEmpty()) {
$destacados = Product::take(5)->get();
}
return view('welcome', compact('destacados'));
} }
} }
+148 -41
View File
@@ -4,62 +4,169 @@ namespace App\Http\Controllers;
use App\Models\Product; use App\Models\Product;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Validation\Rule; // Necesario para validar unicidad al editar
use Illuminate\Support\Facades\Storage; // <--- IMPORTANTE: Agregar esto arriba
class ProductosController extends Controller class ProductosController extends Controller
{ {
public function index(){ /**
$products = Product::orderBy('id','desc')->paginate(); * Muestra la lista de productos con buscador y paginación.
*/
public function index(Request $request)
{
// 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()
->when($query, function ($q) use ($query) {
// Si hay búsqueda, filtra por nombre o SKU
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
return view('productos.index', compact('products')); return view('productos.index', compact('products'));
} }
public function create(){ /**
* Muestra el formulario de creación.
*/
public function create()
{
return view('productos.create'); return view('productos.create');
} }
public function show($id){ /**
$producto = Product::find($id); * Guarda el producto nuevo en la base de datos.
return view('productos.show', compact('producto')); */
} public function store(Request $request)
{
public function edit($id){ // 1. Validamos los datos con las nuevas columnas
$producto = Product::find($id); $validated = $request->validate([
return view('productos.edit', compact('producto')); 'name' => 'required|string|max:255',
} 'sku' => 'nullable|string|unique:products,sku|max:50', // SKU único
'description' => 'nullable|string',
public function store(Request $request){ 'price' => 'required|numeric|min:0',
$request->validate([ 'cost' => 'nullable|numeric|min:0', // Costo opcional
'nombre' => 'required', 'stock_quantity' => 'required|integer|min:0',
'marca' => 'required', 'min_stock_alert' => 'required|integer|min:0',
'modelo' => 'required', 'type' => 'required|in:bike,accessory,service', // Solo permite estos 3 valores
'descripcion' => 'required', 'serial_number' => 'nullable|string|max:100',
'rodado' => 'required', 'image' => 'nullable|image|mimes:jpeg,png,jpg,webp|max:2048',
'color' => 'required',
'tipo' => 'required',
'precio' => 'required'
]); ]);
Product::create($request->all());
return redirect(route('productos.index')); // 2. Si no viene SKU, generamos uno automático (Opcional pero útil)
if (empty($validated['sku'])) {
$validated['sku'] = 'GEN-' . strtoupper(uniqid());
} }
public function update(Request $request,Product $producto){ // 3. Validacion de imagenes
$request->validate([ if ($request->hasFile('image')) {
'nombre' => 'required', // Guarda el archivo en storage/app/public/products y devuelve la ruta
'marca' => 'required', $path = $request->file('image')->store('products', 'public');
'modelo' => 'required', $validated['image_path'] = $path;
'descripcion' => 'required', }
'rodado' => 'required',
'color' => 'required', unset($validated['image']);
'tipo' => 'required',
'precio' => 'required' // Para no romper la logica del supplier
$validated['suppliers_id'] = 1;
// 3. Creamos el producto
Product::create($validated);
// 4. Redireccionamos con mensaje de éxito (Necesitas el componente Alert en el layout)
return redirect()->route('productos.index')
->with('success', 'Producto creado correctamente.');
}
/**
* Muestra el detalle de un producto.
* Usamos Route Model Binding: Laravel busca el ID solo.
*/
public function show(Product $product)
{
return view('productos.show', compact('product'));
}
/**
* Muestra el formulario de edición.
*/
public function edit(Product $product)
{
return view('productos.edit', compact('product'));
}
/**
* Actualiza el producto existente.
*/
public function update(Request $request, Product $product)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
// Validamos que el SKU sea único PERO ignoramos el ID de este producto actual
'sku' => ['nullable', 'string', Rule::unique('products')->ignore($product->id)],
'description' => 'nullable|string',
'price' => 'required|numeric|min:0',
'cost' => 'nullable|numeric|min:0',
'stock_quantity' => 'required|integer|min:0',
'min_stock_alert' => 'required|integer|min:0',
'type' => 'required|in:bike,accessory,service',
'serial_number' => 'nullable|string|max:100',
'image' => 'nullable|image|mimes:jpeg,png,jpg,webp|max:2048',
]); ]);
$producto->update($request->all());
return redirect(route('productos.show',$producto)); // 2. Manejo de imagen al actualizar
if ($request->hasFile('image')) {
// Borrar la imagen anterior
if ($product->image_path) {
Storage::disk('public')->delete($product->image_path);
} }
public function destroy($id){ // Guardar la nueva
$producto = Product::find($id); $path = $request->file('image')->store('products', 'public');
$producto->delete(); $validated['image_path'] = $path;
return redirect(route('productos.index')); }
unset($validated['image']);
// Para no romper la logica del supplier
$validated['suppliers_id'] = 1;
$product->update($validated);
return redirect()->route('productos.index')
->with('success', 'Producto actualizado exitosamente.');
}
/**
* Elimina el producto.
*/
public function destroy(Product $product)
{
$product->delete();
return redirect()->route('productos.index')
->with('success', 'Producto eliminado.');
} }
} }
+1 -1
View File
@@ -13,7 +13,7 @@ class RegisterController extends Controller
{ {
$userData = $request->validate([ $userData = $request->validate([
'name' => ['required', 'string'], 'name' => ['required', 'string'],
'email' => ['required', 'email'], 'email' => ['required', 'email', 'unique:users,email'],
'password' => ['required', 'confirmed'] 'password' => ['required', 'confirmed']
]); ]);
+100
View File
@@ -0,0 +1,100 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Product;
use App\Models\Client;
use App\Models\Sale;
use App\Models\SaleDetail;
use Illuminate\Support\Facades\DB;
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()
{
// Traemos productos con stock y clientes para los selectores
$products = Product::where('stock_quantity', '>', 0)->get();
$clients = Client::orderBy('name')->get();
return view('sales.create', compact('products', 'clients'));
}
public function store(Request $request)
{
// Validación estricta
$request->validate([
'client_id' => 'nullable|exists:clients,id',
'payment_method' => 'required|string',
'items' => 'required|array',
'items.*.product_id' => 'required|exists:products,id',
'items.*.quantity' => 'required|integer|min:1',
]);
try {
// Variable para guardar el objeto venta y usarlo fuera del closure
$sale = DB::transaction(function () use ($request) {
$totalSale = 0;
foreach ($request->items as $item) {
$product = Product::find($item['product_id']);
$totalSale += $product->price * $item['quantity'];
if ($product->stock_quantity < $item['quantity']) {
throw new \Exception("No hay suficiente stock de " . $product->name);
}
}
//cabecera
$newSale = Sale::create([
//'user_id' => auth()->id(), //empleado logueado
'client_id' => $request->client_id,
'total' => $totalSale,
'payment_method' => $request->payment_method,
]);
// 3. Guardar Detalle y Restar Stock
foreach ($request->items as $itemData) {
$product = Product::findOrFail($itemData['product_id']);
SaleDetail::create([
'sale_id' => $newSale->id,
'product_id' => $product->id,
'quantity' => $itemData['quantity'],
'price' => $product->price,
]);
$product->decrement('stock_quantity', $itemData['quantity']);
}
return $newSale; // Retornamos la venta creada fuera de la transacción
});
// 4. REDIRECCIÓN AL DETALLE
return redirect()->route('sales.show', $sale)->with('success', '¡Venta registrada exitosamente!');
} catch (\Exception $e) {
return back()->with('error', $e->getMessage())->withInput();
}
}
/**
* Muestra el detalle de una venta específica.
*/
public function show(Sale $sale)
{
// Cargamos la venta con el cliente y los detalles
$sale->load(['client', 'details.product']);
return view('sales.show', compact('sale'));
}
}
+30
View File
@@ -0,0 +1,30 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Appointment extends Model
{
use HasFactory;
protected $fillable = [
'client_id',
'scheduled_at',
'bike_model',
'problem_description',
'status',
'notes'
];
protected $casts = [
'scheduled_at' => 'datetime', // Laravel lo convierte a objeto Carbon automáticamente
];
// Relación: Un turno pertenece a un cliente
public function client()
{
return $this->belongsTo(Client::class);
}
}
+26
View File
@@ -0,0 +1,26 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Client extends Model
{
use HasFactory;
protected $guarded = [];
protected $fillable = ['name', 'phone', 'email', 'address'];
// Un usuario (cliente) realiza muchas compras (ventas)
public function sales()
{
return $this->hasMany(Sale::class);
}
// Relación: Un cliente tiene muchos turnos
public function appointments()
{
return $this->hasMany(Appointment::class);
}
}
+29 -10
View File
@@ -8,14 +8,33 @@ use Illuminate\Database\Eloquent\Model;
class Product extends Model class Product extends Model
{ {
use HasFactory; use HasFactory;
protected $fillable = [ protected $guarded = [];
'nombre',
'marca',
'modelo', public function hasLowStock(): bool
'rodado', {
'color', return $this->stock_quantity <= $this->min_stock_alert;
'tipo', }
'descripcion',
'precio' public function supplier()
]; {
return $this->belongsTo(Supplier::class);
}
// Opción 1: Relación directa con los detalles (Renglones de ticket)
// Útil para saber cantidad total vendida: $product->saleDetails->sum('quantity')
public function saleDetails()
{
return $this->hasMany(SaleDetail::class);
}
// Opción 2 (Pro Tip): Relación directa con las Ventas a través de los detalles
// Útil para saber EN QUÉ fechas se vendió: $product->sales
public function sales()
{
return $this->belongsToMany(Sale::class, 'sale_details');
}
} }
/**$user->sales: Te da la lista de todas las compras de ese usuario.
$product->saleDetails->count(): Te dice cuántas veces aparece ese producto en tickets.**/
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Sale extends Model
{
//use HasFactory;
// Permitimos asignación masiva para poder guardar rápido
protected $guarded = [];
// Relación 1: Una venta la realiza un Usuario (User)
public function user()
{
return $this->belongsTo(User::class);
}
// Relación 2: Una venta pertenece a un Cliente (User)
public function client()
{
return $this->belongsTo(Client::class);
}
// Relación 3: Una venta tiene muchos items o detalles
public function details()
{
return $this->hasMany(SaleDetail::class);
}
}
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class SaleDetail extends Model
{
//use HasFactory;
protected $guarded = [];
// este detalle pertenece a una Venta específica
public function sale()
{
return $this->belongsTo(Sale::class);
}
// este detalle corresponde a un Producto
public function product()
{
return $this->belongsTo(Product::class);
}
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Supplier extends Model
{
protected $fillable = [ 'name', 'phone', 'email' ];
public function products()
{
return $this->hasMany(Product::class);
}
}
-40
View File
@@ -1,40 +0,0 @@
<?php
namespace App\View\Components;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class Alert2 extends Component
{
public $class;
//* Create a new component instance.
public function __construct($type = 'dark')
{
switch ($type) {
case 'info':
$class = 'text-blue-800 bg-blue-50 dark:text-blue-400';
break;
case 'danger':
$class = 'text-red-800 bg-red-50 dark:text-red-400';
break;
case 'success':
$class = 'text-green-800 bg-green-50 dark:text-green-400';
break;
case 'warning':
$class = 'text-yellow-800 bg-yellow-50 dark:text-yellow-300';
break;
default:
$class = 'text-gray-800 bg-gray-50 dark:text-gray-300';
break;
}
$this->class = $class;
}
//* Get the view / contents that represent the component.
public function render(): View|Closure|string
{
return view('components.alert2');
}
}
+32 -8
View File
@@ -16,15 +16,39 @@ class ProductFactory extends Factory
*/ */
public function definition(): array public function definition(): array
{ {
$brands = ['Shimano', 'Venzo', 'Trek', 'Specialized', 'Maxxis', 'Sram', 'Raleigh'];
$bikeModels = ['Loki', 'Marlin 5', 'Chisel', 'Talon 3', 'Aspect 950'];
$accessories = ['Guantes Grip', 'Calco Reflectora', 'Coderas', 'Rodilleras', 'Casco MTB', 'Luz Delantera USB', 'Cubierta Kevlar'];
// PARA DESPUES
$spareParts = ['Manubrio', 'Pedales Aluminio', 'Cámara 29"', 'Rayos x50', 'Disco de Freno', 'Cable de Freno', 'Asiento Goma', 'Cadena 9v'];
$type = $this->faker->randomElement(['bike', 'accessory', 'service']);
// Generar nombre según el tipo
if ($type === 'bike') {
$name = $this->faker->randomElement($brands) . ' ' . $this->faker->randomElement($bikeModels);
} elseif ($type === 'accessory') {
$name = $this->faker->randomElement($accessories) . ' ' . $this->faker->randomElement(['Pro', 'Basic', 'Comp', 'Elite']);
} else {
$name = $this->faker->randomElement(['Service General', 'Ajuste Cambios', 'Centrado de Rueda', 'Lavado y Engrase']);
}
// Lógica de Precios
$price = $this->faker->numberBetween(5000, 800000);
$cost = $price * $this->faker->randomFloat(2, 0.5, 0.7);
return [ return [
'nombre' => $this->faker->sentence(3), 'name' => $name,
'marca'=> $this->faker->randomElement(['Cube','Giant','Megamo','KTM','MMR','Liv','Ghost','Lapierre']), 'sku' => strtoupper($this->faker->bothify('???-#####')),
'modelo'=> $this->faker->bothify('????-####'), 'description' => $this->faker->sentence(10),
'rodado'=> $this->faker->numberBetween(12,30), 'price' => $price,
'color'=> $this->faker->safeColorName(), 'cost' => $cost,
'tipo'=> $this->faker->word(), 'stock_quantity' => $type === 'service' ? 0 : $this->faker->numberBetween(0, 50),
'descripcion'=> $this->faker->text(), 'min_stock_alert' => $this->faker->numberBetween(2, 10),
'precio'=> $this->faker->randomFloat(2,100,200) 'type' => $type,
// Nro de serie si es bicicleta
'serial_number' => $type === 'bike' ? strtoupper($this->faker->bothify('##??##??')) : null,
'suppliers_id'=> 1
]; ];
} }
} }
@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->enum('role', ['admin', 'employee'])->default('employee')->after('email');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('role');
});
}
};
@@ -11,16 +11,12 @@ return new class extends Migration
*/ */
public function up(): void public function up(): void
{ {
Schema::create('products', function (Blueprint $table) { Schema::create('clients', function (Blueprint $table) {
$table->id(); $table->id();
$table->string('nombre'); $table->string('name');
$table->string('marca'); $table->string('phone')->nullable(); // Clave para WhatsApp
$table->string('modelo'); $table->string('email')->nullable();
$table->string('rodado'); $table->text('address')->nullable();
$table->string('color');
$table->string('tipo');
$table->text('descripcion');
$table->float('precio');
$table->timestamps(); $table->timestamps();
}); });
} }
@@ -30,6 +26,6 @@ return new class extends Migration
*/ */
public function down(): void public function down(): void
{ {
Schema::dropIfExists('products'); Schema::dropIfExists('clients');
} }
}; };
@@ -0,0 +1,39 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('appointments', function (Blueprint $table) {
$table->id();
// Si se borra cliente, se borran sus turnos
$table->foreignId('client_id')->constrained()->onDelete('cascade');
$table->dateTime('scheduled_at'); // Fecha y hora del turno
$table->string('bike_model');
$table->text('problem_description'); // Ej: "Hace ruido la caja"
$table->enum('status', ['pending', 'confirmed', 'in_progress', 'ready', 'delivered'])
->default('pending');
$table->text('notes')->nullable(); // Notas internas del mecánico
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('appointments');
}
};
@@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('suppliers', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('phone');
$table->string('email');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('suppliers');
}
};
@@ -0,0 +1,41 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('sku')->unique()->nullable(); // Código de barras o interno
$table->text('description')->nullable();
$table->decimal('price', 10, 2); // Precio venta
$table->decimal('cost', 10, 2)->nullable(); // Costo (solo admin)
$table->integer('stock_quantity')->default(0);
$table->integer('min_stock_alert'); // Alerta
$table->enum('type', ['bike', 'accessory', 'service']);
$table->string('serial_number')->nullable(); // Solo para bicis
$table->foreignId('suppliers_id')->constrained()->default(1);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('products');
}
};
@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('sales', function (Blueprint $table) {
$table->id();
//$table->foreignId('user_id')->constrained();
// nullable para que no sea obligatrio
$table->foreignId('client_id')->nullable()->constrained();
$table->decimal('total', 10, 2);
$table->string('payment_method');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('sales');
}
};
@@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('sale_details', function (Blueprint $table) {
$table->id();
$table->foreignId('sale_id')->constrained()->onDelete('cascade');
$table->foreignId('product_id')->constrained();
$table->integer('quantity');
$table->decimal('price', 10, 2);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('sale_details');
}
};
@@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('products', function (Blueprint $table) {
$table->string('image_path')->nullable()->after('type');
});
}
public function down(): void
{
Schema::table('products', function (Blueprint $table) {
$table->dropColumn('image_path');
});
}
};
+65 -11
View File
@@ -2,26 +2,80 @@
namespace Database\Seeders; namespace Database\Seeders;
use App\Models\Product;
use App\Models\User;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
use App\Models\User;
use App\Models\Product;
use App\Models\Client;
use App\Models\Supplier;
use App\Models\Appointment;
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
class DatabaseSeeder extends Seeder class DatabaseSeeder extends Seeder
{ {
/**
* Seed the application's database.
*/
public function run(): void public function run(): void
{ {
User::factory(10)->create(); //Crear el Super Admin
User::factory()->create([ User::factory()->create([
'name' => 'admin', 'name' => 'Jose Admin',
'email' => 'admin@laravel.com', 'email' => 'admin@lauck.com',
'password' => 'admin' 'password' => bcrypt('password'), // Cambiar en producción
'role' => 'admin',
]); ]);
//Crear un Empleado de prueba
User::factory()->create([
'name' => 'Empleado Test',
'email' => 'taller@lauck.com',
'password' => bcrypt('password'),
'role' => 'employee',
]);
Supplier::create([
'name' => 'Cámara 29 Válvula Auto',
'phone' => '3434567890',
'email' => 'suplier@suplier.com'
]);
//Crear Productos
Product::create([
'name' => 'Cámara 29 Válvula Auto',
'sku' => 'CAM-29-A',
'price' => 5000,
'cost' => 2500,
'stock_quantity' => 20,
'min_stock_alert' => 5,
'type' => 'accessory',
'suppliers_id' => 1
]);
Product::create([
'name' => 'Venzo Loki Evo 29',
'sku' => 'BIC-VEN-001',
'price' => 450000,
'cost' => 300000,
'stock_quantity' => 2,
'min_stock_alert' => 1,
'type' => 'bike',
'serial_number' => 'VZ998877',
'suppliers_id' => 1
]);
// Generar 10 productos aleatorios más
Product::factory(50)->create(); Product::factory(50)->create();
// Clientes y Turnos
$client = Client::create([
'name' => 'Juan Perez',
'phone' => '1122334455',
'email' => 'juan@gmail.com'
]);
Appointment::create([
'client_id' => $client->id,
'scheduled_at' => now()->addDays(1)->setHour(10)->setMinute(0),
'bike_model' => 'Trek Marlin 5',
'problem_description' => 'Service completo y ajuste de cambios',
'status' => 'pending'
]);
} }
} }
+1499 -263
View File
File diff suppressed because it is too large Load Diff
+9 -2
View File
@@ -7,11 +7,18 @@
"dev": "vite" "dev": "vite"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/vite": "^4.0.0", "@tailwindcss/vite": "^4.1.18",
"autoprefixer": "^10.4.22",
"axios": "^1.8.2", "axios": "^1.8.2",
"concurrently": "^9.0.1", "concurrently": "^9.0.1",
"laravel-vite-plugin": "^2.0.0", "laravel-vite-plugin": "^2.0.0",
"tailwindcss": "^4.0.0", "postcss": "^8.5.6",
"tailwindcss": "^4.1.18",
"vite": "^7.0.4" "vite": "^7.0.4"
},
"dependencies": {
"@tailwindcss/postcss": "^4.1.18",
"jquery": "^3.7.1",
"select2": "^4.1.0-rc.0"
} }
} }
+6
View File
@@ -0,0 +1,6 @@
export default {
plugins: {
'@tailwindcss/postcss': {},
autoprefixer: {},
},
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

+42 -7
View File
@@ -1,17 +1,52 @@
@import 'tailwindcss'; /* 1. Importar fuente Montserrat */
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;800&display=swap');
/* 2. Importar Tailwind (v4) */
@import "tailwindcss";
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php'; @source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
@source '../../storage/framework/views/*.php'; @source '../../storage/framework/views/*.php';
@source '../**/*.blade.php'; @source '../**/*.blade.php';
@source '../**/*.js'; @source '../**/*.js';
/* 3. Configuración del Tema (NUEVO EN v4) */
@theme { @theme {
--font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', /* Definimos el color personalizado 'dark-bg' */
'Segoe UI Symbol', 'Noto Color Emoji'; --color-dark-bg: #121212;
/* TRADUCCIÓN: fontFamily: { sans: [...] } */
--font-sans: 'Montserrat', sans-serif;
/* TRADUCCIÓN: colors: { ... }
La sintaxis es: --color-nombre-del-color: valor;
*/
--color-neon-lime: #ccff00;
--color-dark-bg: #1a1a1a;
--color-panel-bg: #242424;
} }
@keyframes gradientMove { /* 4. Patrón de fondo estilo "Técnico" */
0%{ background-position: 0% 50%;} .bg-grid-pattern {
50%{ background-position: 100% 50%;} /* CAMBIO: Usamos rgba(255,255,255, 0.1) para que las líneas sean claras sobre fondo oscuro */
100%{ background-position: 0% 50%;} background-image: linear-gradient(to right, rgba(255, 255, 255, 0.05) 1px, transparent 1px),
linear-gradient(to bottom, rgba(255, 255, 255, 0.05) 1px, transparent 1px);
background-size: 40px 40px;
background-position: center;
}
/* 5. Personalización del Scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #1a1a1a;
}
::-webkit-scrollbar-thumb {
background: #444;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #ccff00; /* Verde Neón */
} }
+12
View File
@@ -1 +1,13 @@
import './bootstrap'; import './bootstrap';
// 1. Importar jQuery
import jQuery from 'jquery';
// 2. Hacerlo global (IMPORTANTE para que funcione $(document).ready en Blade)
window.$ = window.jQuery = jQuery;
// 3. Importar Select2
import select2 from 'select2';
select2(); // Inicializar el plugin
// 4. Importar los estilos de Select2 (Opcional aquí, o en CSS)
import 'select2/dist/css/select2.css';
+7
View File
@@ -2,3 +2,10 @@ import axios from 'axios';
window.axios = axios; window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
/**
* Configuración de jQuery global (window.$)
*/
import jQuery from 'jquery';
window.$ = jQuery;
window.jQuery = jQuery;
-6
View File
@@ -1,6 +0,0 @@
<x-app>
</x-app>
+54
View File
@@ -0,0 +1,54 @@
<x-layout title="Catalogo - Lauck">
<!-- Header -->
<x-section-header subtitle="Inicio" title="Catalogo de " highlight="Productos"/>
<div class="w-full max-w-5xl">
@if ($products->count() > 0)
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6">
@foreach ($products as $product)
<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($product->image_path))
<img src="{{ asset('storage/' . $product->image_path) }}" alt="{{ $product->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">
<span class="text-neon-lime text-md uppercase font-bold">Sin imagen</span>
</div>
@endif
{{-- Nombre --}}
<h2 class="text-lg font-semibold text-white">{{ $product->name }}</h2>
{{-- Descripción corta --}}
<div class="h-10 overflow-y-auto [scrollbar-width:none]">
@if (!empty($product->description))
<p class="text-sm text-gray-400 mt-2">{{ $product->description }}</p>
@endif
</div>
{{-- Precio --}}
@if (!empty($product->price))
<p class="text-white group-hover:text-neon-lime font-bold mt-3 transition-colors">${{ number_format($product->price, 2, ',', '.') }}</p>
@endif
{{-- Botón --}}
<a href="{{ route('catalogo.show', $product) }}"
class="mt-4 block w-full bg-neon-lime/80 uppercase text-black font-semibold py-2 rounded-lg text-center shadow-md shadow-neon-lime/10 hover:bg-neutral-900/70 border border-transparent hover:text-neon-lime hover:shadow-neon-lime/20 hover:border-neutral-800 transition-all">
Ver +
</a>
</div>
@endforeach
</div>
@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>
+105
View File
@@ -0,0 +1,105 @@
<x-layout title="{{ $product->name }} - Lauck">
<div class="container mx-auto px-4 py-8">
<!-- Botón Volver -->
<a href="{{ route('catalogo.index') }}" class="inline-flex items-center text-gray-400 hover:text-white mb-6 transition-colors">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
Volver al Catálogo
</a>
<div class="bg-panel-bg rounded-xl shadow-2xl overflow-hidden border border-neutral-800">
<div class="grid grid-cols-1 md:grid-cols-2">
<!-- COLUMNA IZQUIERDA: IMAGEN (El cuadro verde de tu ejemplo) -->
<div class="bg-neutral-800 relative h-96 md:h-auto min-h-[500px] flex items-center justify-center py-4 group">
@if($product->image_path)
<img src="{{ asset('storage/' . $product->image_path) }}"
alt="{{ $product->name }}"
class="w-full h-full object-contain max-h-[500px] transform drop-shadow-lg">
@else
<div class="text-neutral-700 flex flex-col items-center">
<svg class="w-48 h-48" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="0.5" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path></svg>
<span class="mt-4 text-gray-500 text-sm uppercase tracking-widest">Sin Imagen</span>
</div>
@endif
<!-- Badge de Tipo Flotante -->
<div class="absolute top-6 left-6">
@if($product->type == 'bike')
<span class="bg-black/50 backdrop-blur text-white px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wide border border-white/10">Bicicleta</span>
@else
<span class="bg-black/50 backdrop-blur text-white px-3 py-1 rounded-full text-xs font-bold uppercase tracking-wide border border-white/10">Accesorio</span>
@endif
</div>
</div>
<!-- COLUMNA DERECHA: INFO (El cuadro azul de tu ejemplo) -->
<div class="p-8 md:p-12 flex flex-col justify-center bg-panel-bg">
<!-- Encabezado -->
<div class="mb-6 border-b border-neutral-800 pb-6">
<p class="text-neon-lime text-sm font-bold uppercase tracking-widest mb-2">SKU: {{ $product->sku ?? 'N/A' }}</p>
<h1 class="text-4xl font-black text-white italic mb-2 leading-tight">{{ $product->name }}</h1>
<!-- Stock Status -->
<div class="flex items-center gap-2 mt-4">
@if($product->stock_quantity > 5)
<span class="w-3 h-3 bg-green-500 rounded-full animate-pulse"></span>
<span class="text-green-400 text-sm font-bold">En Stock ({{ $product->stock_quantity }} unid.)</span>
@elseif($product->stock_quantity > 0)
<span class="w-3 h-3 bg-yellow-500 rounded-full animate-pulse"></span>
<span class="text-yellow-400 text-sm font-bold">¡Últimas Unidades! ({{ $product->stock_quantity }})</span>
@else
<span class="w-3 h-3 bg-red-500 rounded-full"></span>
<span class="text-red-400 text-sm font-bold">Agotado</span>
@endif
</div>
</div>
<!-- Precio -->
<div class="mb-8">
<span class="block text-gray-400 text-xs uppercase mb-1">Precio Contado</span>
<div class="flex items-baseline gap-2">
<span class="text-5xl font-black text-white">${{ number_format($product->price, 0, ',', '.') }}</span>
<span class="text-gray-500 font-medium">ARG</span>
</div>
<p class="text-gray-500 text-xs mt-2">* Consultar financiación en el local.</p>
</div>
<!-- Descripción -->
<div class="mb-8">
<h3 class="text-white font-bold uppercase text-sm mb-3">Descripción</h3>
<p class="text-gray-400 leading-relaxed">
{{ $product->description ?? 'Sin descripción detallada disponible para este producto. Por favor, acérquese al local para más información.' }}
</p>
</div>
<!-- Botones de Acción -->
<div class="mt-auto grid grid-cols-1 sm:grid-cols-2 gap-4">
<button class="bg-neon-lime text-neutral-900 font-black py-4 px-6 rounded-lg hover:bg-[#b3e600] transition-all uppercase tracking-wide shadow-lg shadow-neon-lime/20 flex items-center justify-center gap-2 transform hover:-translate-y-1">
<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="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z"></path></svg>
Consultar
</button>
<a href="https://wa.me/?text=Hola,%20me%20interesa%20el%20producto:%20{{ $product->name }}" target="_blank" class="bg-neutral-800 text-white font-bold py-4 px-6 rounded-lg hover:bg-neutral-700 border border-neutral-700 transition-all uppercase tracking-wide flex items-center justify-center gap-2">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.008-.57-.008-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413Z"/></svg>
WhatsApp
</a>
</div>
</div>
</div>
</div>
</div>
</x-layout>
{{-- <a href="{{ route('productos.edit', $product ) }}" class="w-full sm:w-auto px-5 py-2 rounded-full text-white font-semibold bg-blue-600 hover:bg-blue-700">Editar</a>
<form action="{{route('productos.destroy',$product)}}" method="post">
@csrf
@method('DELETE')
<button type="submit" class="w-full sm:w-auto px-5 py-2 rounded-full text-white font-semibold bg-red-600 hover:bg-red-700">
Borrar
</button>
</form> --}}
+49
View File
@@ -0,0 +1,49 @@
<x-layout title="Lauck - Nuevo Cliente">
<x-section-header subtitle="Gestión de Clientes" title="Nuevo " highlight="Cliente" />
<div class="w-full max-w-4xl mx-auto bg-panel-bg border border-neutral-800 rounded-xl p-8 shadow-lg">
<form action="{{ route('clients.store') }}" method="POST">
@csrf
<input type="hidden" name="origin" value="{{ request('origin') }}">
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
<div class="md:col-span-4">
<x-forms.label for="name" value="Nombre Completo" />
<x-forms.input id="name" name="name" type="text" :value="old('name')" required autofocus placeholder="Ej: Sergio Lauck" :error="$errors->first('name')" />
</div>
<div class="md:col-span-2">
<x-forms.label for="phone" value="Teléfono / WhatsApp" />
<x-forms.input id="phone" name="phone" type="text" :value="old('phone')" placeholder="Ej: 343 154..." :error="$errors->first('phone')" />
</div>
<div class="md:col-span-2">
<x-forms.label for="email" value="Correo Electrónico" />
<x-forms.input id="email" name="email" type="email" :value="old('email')" placeholder="cliente@ejemplo.com" :error="$errors->first('email')" />
</div>
<div class="md:col-span-4">
<x-forms.label for="address" value="Dirección / Domicilio" />
<textarea id="address" name="address" rows="3" class="bg-neutral-800 border-neutral-700 text-white text-sm rounded-lg focus:ring-neon-lime focus:border-neon-lime block w-full p-2.5 placeholder-gray-500" placeholder="Calle, número, piso...">{{ old('address') }}</textarea>
@error('address')
<span class="text-red-500 text-xs mt-1">{{ $message }}</span>
@enderror
</div>
</div>
<div class="flex items-center justify-between md:justify-end space-x-4 border-t border-neutral-800 pt-6">
<a href="{{ route('clients.index') }}" class="text-gray-400 hover:text-white font-medium text-sm transition-colors">Cancelar</a>
<button type="submit" class="px-6 py-2.5 bg-neon-lime text-neutral-900 font-bold rounded-lg hover:bg-[#b3e600] transition-colors shadow-lg shadow-neon-lime/20">
Guardar Cliente
</button>
</div>
</form>
</div>
</x-layout>
+79
View File
@@ -0,0 +1,79 @@
<x-layout title="Lauck - Editar Cliente">
<x-section-header subtitle="Gestión de Clientes" title="Editar " highlight="Cliente" />
<div class="w-full max-w-4xl mx-auto bg-panel-bg border border-neutral-800 rounded-xl p-8 shadow-lg">
<form action="{{ route('clients.update', $client) }}" method="POST">
@csrf
@method('PUT') <div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
<div class="md:col-span-4">
<x-forms.label for="name" value="Nombre Completo" />
<x-forms.input
id="name"
name="name"
type="text"
:value="old('name', $client->name)"
required
autofocus
placeholder="Ej: Sergio Lauck"
:error="$errors->first('name')"
/>
</div>
<div class="md:col-span-2">
<x-forms.label for="phone" value="Teléfono / WhatsApp" />
<x-forms.input
id="phone"
name="phone"
type="text"
:value="old('phone', $client->phone)"
placeholder="Ej: 343 154..."
:error="$errors->first('phone')"
/>
</div>
<div class="md:col-span-2">
<x-forms.label for="email" value="Correo Electrónico" />
<x-forms.input
id="email"
name="email"
type="email"
:value="old('email', $client->email)"
placeholder="cliente@ejemplo.com"
:error="$errors->first('email')"
/>
</div>
<div class="md:col-span-4">
<x-forms.label for="address" value="Dirección / Domicilio" />
<textarea
id="address"
name="address"
rows="3"
class="bg-neutral-800 border-neutral-700 text-white text-sm rounded-lg focus:ring-neon-lime focus:border-neon-lime block w-full p-2.5 placeholder-gray-500"
placeholder="Calle, número, piso..."
>{{ old('address', $client->address) }}</textarea>
@error('address')
<span class="text-red-500 text-xs mt-1">{{ $message }}</span>
@enderror
</div>
</div>
<div class="flex items-center justify-between md:justify-end space-x-4 border-t border-neutral-800 pt-6">
<a href="{{ route('clients.index') }}" class="text-gray-400 hover:text-white font-medium text-sm transition-colors">
Cancelar
</a>
<button type="submit" class="px-6 py-2.5 bg-neon-lime text-neutral-900 font-bold rounded-lg hover:bg-[#b3e600] transition-colors shadow-lg shadow-neon-lime/20">
Actualizar Cliente
</button>
</div>
</form>
</div>
</x-layout>
+105
View File
@@ -0,0 +1,105 @@
<x-layout title="Lauck - Clientes">
<x-section-header subtitle="Gestión de Clientes" title="Cartera de " highlight="Clientes" />
@if(session('success'))
<div class="bg-green-900/50 border border-green-500 text-green-300 px-4 py-3 rounded-lg mb-6 shadow-sm">
{{ session('success') }}
</div>
@endif
<div class="w-full flex flex-col md:flex-row justify-between items-center gap-4 mb-6">
<form action="{{ route('clients.index') }}" method="GET" class="w-full md:w-1/2">
<div class="relative">
<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"/>
</svg>
</div>
<input type="text" name="search" value="{{ request('search') }}"
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, teléfono o email...">
</div>
</form>
<a href="{{ route('clients.create') }}" class="w-full md:w-auto text-center px-5 py-3 text-sm font-bold text-neutral-900 bg-neon-lime rounded-lg hover:bg-[#b3e600] transition-colors uppercase tracking-wide shadow-lg shadow-neon-lime/20">
+ Nuevo Cliente
</a>
</div>
<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">
<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">Cliente / Email</th>
<th scope="col" class="px-6 py-3">Teléfono / WhatsApp</th>
<th scope="col" class="px-6 py-3">Dirección</th>
<th scope="col" class="px-6 py-3 text-right">Acciones</th>
</tr>
</thead>
<tbody>
@forelse($clients as $client)
<tr class="bg-neutral-900/50 border-b border-neutral-800 hover:bg-neutral-800 transition-colors group">
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-base font-bold text-white">{{ $client->name }}</div>
<div class="text-xs text-gray-500 font-mono">
{{ $client->email ?? 'Sin email registrado' }}
</div>
</td>
<td class="px-6 py-4">
@if($client->phone)
<div class="flex items-center gap-2">
<span class="font-mono text-gray-300">{{ $client->phone }}</span>
<span class="text-green-500 text-xs bg-green-900/30 px-1.5 py-0.5 rounded">WA</span>
</div>
@else
<span class="text-gray-600 italic">No registrado</span>
@endif
</td>
<td class="px-6 py-4">
<span class="text-gray-400 truncate max-w-xs block" title="{{ $client->address }}">
{{ $client->address ?? '-' }}
</span>
</td>
<td class="px-6 py-4 text-right flex items-center justify-end gap-3">
<a href="{{ route('clients.edit', $client) }}" class="font-medium text-blue-400 hover:text-blue-300 hover:underline transition-colors">
Editar
</a>
<form action="{{ route('clients.destroy', $client) }}" method="POST" class="inline-block" onsubmit="return confirm('¿Estás seguro de que deseas eliminar este cliente? Esta acción no se puede deshacer.');">
@csrf
@method('DELETE')
<button type="submit" class="font-medium text-red-500 hover:text-red-400 hover:underline transition-colors cursor-pointer bg-transparent border-0 p-0">
Eliminar
</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="px-6 py-10 text-center text-gray-500">
<div class="flex flex-col items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 mb-2 opacity-50" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
<p>No se encontraron clientes.</p>
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="mt-4 w-full">
{{ $clients->links() }}
</div>
</x-layout>
@@ -1,25 +0,0 @@
@props(['type' => 'dark'])
@php
switch ($type) {
case 'info':
$class = 'text-blue-800 bg-blue-50 dark:text-blue-400';
break;
case 'danger':
$class = 'text-red-800 bg-red-50 dark:text-red-400';
break;
case 'success':
$class = 'text-green-800 bg-green-50 dark:text-green-400';
break;
case 'warning':
$class = 'text-yellow-800 bg-yellow-50 dark:text-yellow-300';
break;
default:
$class = 'text-gray-800 bg-gray-50 dark:text-gray-300';
break;
}
@endphp
<div {{$attributes->merge(['class'=>'p-4 my-4 mx-auto text-md max-w-[80%] rounded-lg dark:bg-gray-800 '.$class])}} role="alert">
<span class="font-semibold">{{$title}}</span> {{$content}}
</div>
@@ -1,3 +0,0 @@
<div {{$attributes->merge(['class'=>'p-4 mb-4 text-sm rounded-lg dark:bg-gray-800 '.$class])}} role="alert">
<span class="font-medium">{{$title}}</span> {{$slot}}
</div>
+89 -69
View File
@@ -1,103 +1,123 @@
@props(['auth' => false]) @props(['auth' => false])
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="es">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<title>{{ $title ?? 'Lauck - Home' }}</title> <title>{{ $title ?? 'Lauck - Home' }}</title>
<!-- Importamos la fuente Montserrat para el estilo deportivo -->
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600;800&display=swap" rel="stylesheet">
<!-- Script de Tailwind para que funcione en este archivo sin compilar -->
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: {
sans: ['Montserrat', 'sans-serif'],
},
colors: {
// Definimos los colores personalizados de la marca
'neon-lime': '#ccff00',
'dark-bg': '#1a1a1a',
'panel-bg': '#242424',
}
}
}
}
</script>
<style>
/* Efecto sutil de grilla de fondo para estilo técnico */
.bg-grid-pattern {
background-image: linear-gradient(to right, #333 1px, transparent 1px),
linear-gradient(to bottom, #333 1px, transparent 1px);
background-size: 40px 40px;
background-position: center;
}
</style>
</head> </head>
<body class="bg-neutral-900 relative pb-50 min-h-screen"> <body class="bg-dark-bg text-gray-300 font-sans antialiased min-h-screen flex flex-col relative overflow-x-hidden">
<nav class="bg-white border-gray-200 dark:bg-gray-900">
<div class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4"> <!-- Fondo decorativo técnico -->
<a href="https://flowbite.com/" class="flex items-center space-x-3 rtl:space-x-reverse"> <div class="absolute inset-0 bg-grid-pattern opacity-[0.07] z-0 pointer-events-none"></div>
<img src="https://flowbite.com/docs/images/logo.svg" class="h-8" alt="Flowbite Logo" />
<span <!-- NAVBAR -->
class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">{{ $navTitle ?? 'Lauck Home' }}</span> <nav class="bg-neutral-900/90 backdrop-blur-md border-b border-white/10 fixed w-full z-50 sticky top-0">
<div class="max-w-7xl flex flex-wrap items-center justify-between mx-auto p-4">
<!-- Logo -->
<a href="/" class="flex items-center space-x-3 rtl:space-x-reverse group">
<!-- Placeholder de logo con efecto hover -->
<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>
<span class="self-center text-2xl font-black whitespace-nowrap text-white italic tracking-wide group-hover:text-neon-lime transition-colors duration-300">
LAUCK<span class="text-neon-lime font-light not-italic">SYS</span>
</span>
</a> </a>
<button data-collapse-toggle="navbar-default" type="button"
class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600" <!-- Mobile Menu Button -->
aria-controls="navbar-default" aria-expanded="false"> <button data-collapse-toggle="navbar-default" type="button" class="inline-flex items-center p-2 w-10 h-10 justify-center text-gray-400 rounded-lg md:hidden hover:bg-neutral-800 focus:outline-none focus:ring-2 focus:ring-neon-lime" aria-controls="navbar-default" aria-expanded="false">
<span class="sr-only">Open main menu</span> <span class="sr-only">Abrir menú</span>
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" <svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 17 14">
viewBox="0 0 17 14"> <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 1h15M1 7h15M1 13h15"/>
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M1 1h15M1 7h15M1 13h15" />
</svg> </svg>
</button> </button>
<!-- Links -->
<div class="hidden w-full md:block md:w-auto" id="navbar-default"> <div class="hidden w-full md:block md:w-auto" id="navbar-default">
@if ($auth) <ul class="font-bold flex flex-col p-4 md:p-0 mt-4 border border-neutral-700 rounded-lg bg-neutral-800 md:flex-row md:space-x-8 rtl:space-x-reverse md:mt-0 md:border-0 md:bg-transparent">
<ul
class="font-medium flex flex-col p-4 md:p-0 mt-4 border border-gray-100 rounded-lg bg-gray-50 md:flex-row md:space-x-8 rtl:space-x-reverse md:mt-0 md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700">
<li> <li>
<a href="{{ route('dashboard') }}" <a href="http://127.0.0.1:8000/dashboard" class="block py-2 px-3 text-neon-lime border-b-2 border-neon-lime md:p-0" aria-current="page">Dashboard</a>
class="block py-2 px-3 text-white bg-blue-700 rounded-sm md:bg-transparent md:text-blue-700 md:p-0 dark:text-white md:dark:text-blue-500">Home</a>
</li> </li>
<li> <li>
<a href="{{ route('productos.index') }}" <a href="http://127.0.0.1:8000/productos" class="block py-2 px-3 text-white hover:text-neon-lime md:hover:bg-transparent md:border-0 md:p-0 transition-colors">Stock</a>
class="block py-2 px-3 text-gray-900 rounded-sm hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Productos</a>
</li> </li>
<li> <li>
<a href="#" <a href="#" class="block py-2 px-3 text-white hover:text-neon-lime md:hover:bg-transparent md:border-0 md:p-0 transition-colors">Taller</a>
class="block py-2 px-3 text-gray-900 rounded-sm hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Services</a>
</li> </li>
<li> <li>
<a href="#" <a href="#" class="block py-2 px-3 text-white hover:text-neon-lime md:hover:bg-transparent md:border-0 md:p-0 transition-colors">Ventas</a>
class="block py-2 px-3 text-gray-900 rounded-sm hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Pricing</a>
</li>
<li>
<a href="#"
class="block py-2 px-3 text-gray-900 rounded-sm hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Contact</a>
</li> </li>
</ul> </ul>
@else
<ul
class="font-medium flex flex-col p-4 md:p-0 mt-4 border border-gray-100 rounded-lg bg-gray-50 md:flex-row md:space-x-8 rtl:space-x-reverse md:mt-0 md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700">
<li>
<a href="{{ route('register') }}"
class="block py-2 px-3 dark:hover:bg-gray-700 dark:hover:text-white text-gray-900 rounded-sm hover:bg-gray-100 dark:text-white md:px-4 md:py-2 md:rounded-md md:text-white md:font-semibold md:bg-stone-500 md:hover:scale-105 transition">Registrate</a>
</li>
<li>
<a href="{{route('login')}}"
class="block py-2 px-3 dark:hover:bg-gray-700 dark:hover:text-white text-gray-900 rounded-sm hover:bg-gray-100 dark:text-white md:px-4 md:py-2 md:rounded-md md:text-white md:font-semibold md:bg-stone-500 md:hover:scale-105 transition">Iniciar Sesion</a>
</li>
</ul>
@endif
</div> </div>
</div> </div>
</nav> </nav>
<!-- MAIN CONTENT -->
{{ $slot }} {{ $slot }}
<footer class="bg-white rounded-lg shadow-sm dark:bg-gray-900 absolute bottom-0 start-0 end-0">
<div class="w-full max-w-screen-xl mx-auto p-4 md:py-8"> <!-- FOOTER -->
<footer class="bg-neutral-900 border-t border-white/5 mt-auto z-10">
<div class="w-full max-w-7xl mx-auto p-8">
<div class="sm:flex sm:items-center sm:justify-between"> <div class="sm:flex sm:items-center sm:justify-between">
<a href="https://flowbite.com/" class="flex items-center mb-4 sm:mb-0 space-x-3 rtl:space-x-reverse">
<img src="https://flowbite.com/docs/images/logo.svg" class="h-8" alt="Flowbite Logo" /> <a href="../" class="flex items-center mb-4 sm:mb-0 space-x-3 rtl:space-x-reverse grayscale opacity-70 hover:grayscale-0 hover:opacity-100 transition-all">
<span class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">Flowbite</span> <!-- Icono simple footer -->
<svg class="h-8 text-neon-lime" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2L2 7l10 5 10-5-10-5zm0 9l2.5-1.25L12 8.5l-2.5 1.25L12 11zm0 2.5l-5-2.5-5 2.5L12 22l10-8.5-5-2.5-5 2.5z"/></svg>
<span class="self-center text-xl font-bold whitespace-nowrap text-white">Cicles Lauck</span>
</a> </a>
<ul
class="flex flex-wrap items-center mb-6 text-sm font-medium text-gray-500 sm:mb-0 dark:text-gray-400"> <ul class="flex flex-wrap items-center mb-6 text-sm font-medium text-gray-500 sm:mb-0">
<li> <li><a href="#" class="hover:text-neon-lime hover:underline me-4 md:me-6 transition-colors">Soporte</a></li>
<a href="#" class="hover:underline me-4 md:me-6">About</a> <li><a href="#" class="hover:text-neon-lime hover:underline me-4 md:me-6 transition-colors">Privacidad</a></li>
</li> <li><a href="#" class="hover:text-neon-lime hover:underline transition-colors">Contacto</a></li>
<li>
<a href="#" class="hover:underline me-4 md:me-6">Privacy Policy</a>
</li>
<li>
<a href="#" class="hover:underline me-4 md:me-6">Licensing</a>
</li>
<li>
<a href="#" class="hover:underline">Contact</a>
</li>
</ul> </ul>
</div> </div>
<hr class="my-6 border-gray-200 sm:mx-auto dark:border-gray-700 lg:my-8" /> <hr class="my-6 border-neutral-800 sm:mx-auto lg:my-8">
<span class="block text-sm text-gray-500 sm:text-center dark:text-gray-400">© 2023 <a <span class="block text-sm text-gray-600 sm:text-center">© 2024 <a href="../" class="hover:text-neon-lime transition-colors">Lauck Systems™</a>. All Rights Reserved.</span>
href="https://flowbite.com/" class="hover:underline">Flowbite™</a>. All Rights Reserved.</span>
</div> </div>
</footer> </footer>
</body>
<!-- Script para funcionalidad del navbar móvil (reemplazo simple de Flowbite) -->
<script>
const btn = document.querySelector('[data-collapse-toggle="navbar-default"]');
const menu = document.getElementById('navbar-default');
btn.addEventListener('click', () => {
menu.classList.toggle('hidden');
});
</script>
</body>
</html> </html>
@@ -0,0 +1,16 @@
<footer class="bg-neutral-900 border-t border-white/5 mt-auto z-10 w-full">
<div class="w-full max-w-7xl mx-auto p-8">
<div class="sm:flex sm:items-center sm:justify-between">
<a href="{{ url('/') }}" class="flex items-center mb-4 sm:mb-0 space-x-3 rtl:space-x-reverse grayscale opacity-70 hover:grayscale-0 hover:opacity-100 transition-all">
<svg class="h-8 text-neon-lime" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2L2 7l10 5 10-5-10-5zm0 9l2.5-1.25L12 8.5l-2.5 1.25L12 11zm0 2.5l-5-2.5-5 2.5L12 22l10-8.5-5-2.5-5 2.5z"/></svg>
<span class="self-center text-xl font-bold whitespace-nowrap text-white">Cicles Lauck</span>
</a>
<ul class="flex flex-wrap items-center mb-6 text-sm font-medium text-gray-500 sm:mb-0">
<li><a href="#" class="hover:text-neon-lime hover:underline me-4 md:me-6 transition-colors">Soporte</a></li>
<li><a href="#" class="hover:text-neon-lime hover:underline transition-colors">Contacto</a></li>
</ul>
</div>
<hr class="my-6 border-neutral-800 sm:mx-auto lg:my-8">
<span class="block text-sm text-gray-600 sm:text-center">© {{ date('Y') }} <a href="#" class="hover:text-neon-lime transition-colors">Lauck Systems™</a>.</span>
</div>
</footer>
@@ -0,0 +1,7 @@
@props(['disabled' => false, 'error' => null])
<input {{ $disabled ? 'disabled' : ''}} {!! $attributes->merge(['class' => 'bg-neutral-800 border-neutral-700 text-white text-sm rounded-lg focus:ring-neon-lime focus:border-neon-lime block w-full p-2.5 placeholder-gray-500 transition-colors ' . ($error ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : '')]) !!}>
@if($error)
<p class="mt-1 text-xs text-red-400">{{ $error }}</p>
@endif
@@ -0,0 +1,5 @@
@props(['value'])
<label {!! $attributes->merge(['class' => 'block mb-2 text-xs font-bold text-gray-400 uppercase tracking-wider']) !!}>
{{$value ?? $slot}}
</label>
@@ -0,0 +1,39 @@
@props(['disabled' => false, 'error' => null, 'options' => [], 'placeholder' => 'Seleccionar...'])
<!-- Contenedor relativo para posicionar la flecha personalizada si quisiéramos (opcional) -->
<div class="relative">
<select {{ $disabled ? 'disabled' : '' }} {!! $attributes->merge(['class' => '
appearance-none bg-transparent border-0 border-b-2 border-neutral-700 text-white text-sm
py-2.5 px-0 w-full focus:outline-none focus:ring-0 focus:border-neon-lime peer cursor-pointer
transition-colors' . ($error ? 'border-red-500 focus:border-red-500' : '')
]) !!}>
@if($placeholder)
<option value="" disabled selected class="bg-neutral-800 text-gray-500">{{ $placeholder }}</option>
@endif
{{ $slot }}
</select>
<!-- Flecha personalizada (SVG) posicionada a la derecha -->
<div class="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none">
<svg class="w-4 h-4 text-gray-500 peer-focus: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="M19 9l-7 7-7-7"></path>
</svg>
</div>
<!-- Mensaje de error -->
@if($error)
<p class="mt-1 text-xs text-red-400">{{ $error }}</p>
@endif
</div>
{{--
NOTA DE ESTILO:
- `appearance-none`: Quita el estilo feo por defecto del navegador.
- `bg-transparent`: Fondo transparente para que se vea el color de fondo de tu web.
- `border-b-2`: Borde solo abajo (estilo línea).
- `focus:ring-0`: Quita el anillo azul de Chrome al hacer click.
- `peer`: Permite que el icono de la flecha cambie de color cuando el select tiene foco.
--}}
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ $title ?? 'Lauck Dashboard' }}</title>
<!-- VITE: Esto carga tu Tailwind compilado y JS -->
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="bg-dark-bg text-gray-300 font-sans antialiased min-h-screen flex flex-col relative overflow-x-hidden">
<!-- Fondo grilla global -->
<div class="inset-0 bg-grid-pattern opacity-[0.15] z-0 pointer-events-none fixed"></div>
<!-- Navbar Component -->
<x-navbar />
<!-- Contenido Principal -->
<main class="grow flex flex-col items-center justify-start pt-10 pb-20 px-4 w-full max-w-7xl mx-auto z-10">
{{ $slot }}
</main>
<!-- Footer Component -->
<x-footer />
<!-- Scripts globales -->
<script>
// Lógica del menú móvil
const btn = document.querySelector('[data-collapse-toggle="navbar-default"]');
const menu = document.getElementById('navbar-default');
if(btn && menu) {
btn.addEventListener('click', () => menu.classList.toggle('hidden'));
}
</script>
@stack('scripts')
</body>
</html>
@@ -0,0 +1,94 @@
<nav class="bg-neutral-900/90 backdrop-blur-md border-b border-white/10 w-full z-50 sticky top-0">
<div class="max-w-7xl flex flex-wrap items-center justify-between mx-auto p-4">
<!-- Logo -->
@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>
<span class="self-center text-2xl font-black whitespace-nowrap text-white italic tracking-wide group-hover:text-neon-lime transition-colors duration-300">
LAUCK<span class="text-neon-lime font-light not-italic">SYS</span>
</span>
</a>
<!-- Mobile Menu Button -->
<button data-collapse-toggle="navbar-default" type="button" class="inline-flex items-center p-2 w-10 h-10 justify-center text-gray-400 rounded-lg md:hidden hover:bg-neutral-800 focus:outline-none focus:ring-2 focus:ring-neon-lime" aria-controls="navbar-default" aria-expanded="false">
<span class="sr-only">Abrir menú</span>
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 17 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 1h15M1 7h15M1 13h15"/>
</svg>
</button>
<!-- Links -->
<div class="hidden w-full md:block md:w-auto" id="navbar-default">
<ul class="font-bold flex flex-col items-center p-4 md:p-0 mt-4 border border-neutral-700 rounded-lg bg-neutral-800 md:flex-row md:space-x-8 rtl:space-x-reverse md:mt-0 md:border-0 md:bg-transparent">
@auth
<li>
<a href="{{ url('/dashboard') }}"
class="block py-2 px-3 md:p-0 transition-colors {{ request()->is('dashboard') ? 'text-neon-lime border-b-2 border-neon-lime' : 'text-white hover:text-neon-lime' }}">
Dashboard
</a>
</li>
<li>
<a href="{{ url('/productos') }}"
class="block py-2 px-3 md:p-0 transition-colors {{ request()->is('productos*') ? 'text-neon-lime border-b-2 border-neon-lime' : 'text-white hover:text-neon-lime' }}">
Stock
</a>
</li>
<li>
<a href="#"
class="block py-2 px-3 md:p-0 transition-colors {{ request()->is('mantenimiento*') ? 'text-neon-lime border-b-2 border-neon-lime' : 'text-white hover:text-neon-lime' }}">
Taller
</a>
</li>
<li>
<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>
</li>
@endauth
{{-- LÓGICA DE VISITANTE (GUEST) --}}
@guest
<li>
<a href="{{ url('/catalogo') }}"
class="block py-2 px-3 md:p-0 transition-colors {{ request()->is('catalogo*') ? 'text-neon-lime border-b-2 border-neon-lime' : 'text-white hover:text-neon-lime' }}">
Catalogo
</a>
</li>
<!-- Buscador Integrado -->
<li class="w-full md:w-auto my-2 md:my-0">
<form action="{{ route('catalogo.index') }}" method="GET">
<div class="relative">
<input type="text" name="search"
class="block w-full md:w-48 p-2 ps-3 text-xs text-white border border-neutral-600 rounded-full bg-neutral-800/50 focus:ring-neon-lime focus:border-neon-lime placeholder-gray-500 transition-all focus:w-full md:focus:w-64"
placeholder="Buscar productos...">
<button type="submit" class="absolute top-0 end-0 p-2 text-sm font-medium h-full text-white rounded-e-lg hover:text-neon-lime">
<svg class="w-4 h-4" 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"/>
</svg>
</button>
</div>
</form>
</li>
<!-- Botón Login Resaltado -->
<li>
<a href="{{ route('login') }}"
class="block py-2 px-5 text-center text-neutral-900 bg-neon-lime font-black rounded hover:bg-[#b3e600] transition-colors uppercase tracking-wider text-xs shadow-lg shadow-neon-lime/20 hover:shadow-neon-lime/40 transform hover:-translate-y-0.5 duration-200">
Ingresar
</a>
</li>
@endguest
</ul>
</div>
</div>
</nav>
@@ -0,0 +1,11 @@
@props(['subtitle', 'title', 'highlight' => null])
<div class="w-full text-left mb-10 border-l-4 border-neon-lime pl-6 py-2">
<h2 class="text-neutral-400 text-sm font-bold uppercase tracking-widest mb-1">{{ $subtitle }}</h2>
<h1 class="font-black text-4xl md:text-5xl text-white uppercase italic">
{{ $title }}
@if($highlight)
<span class="text-transparent bg-clip-text bg-gradient-to-r from-white to-gray-500 pe-2">{{ $highlight }}</span>
@endif
</h1>
</div>
@@ -0,0 +1,11 @@
@if (session('success'))
<div class="p-4 mb-4 text-sm text-green-400 rounded-lg bg-neutral-800 border border-green-800/50" role="alert">
<span class="font-bold">¡Éxito!</span> {{ session('success') }}
</div>
@endif
@if (session('error'))
<div class="p-4 mb-4 text-sm text-red-400 rounded-lg bg-neutral-800 border border-red-800/50" role="alert">
<span class="font-bold">Error:</span> {{ session('error') }}
</div>
@endif
@@ -0,0 +1,16 @@
@props(['color' => 'gray'])
@php
$colors = [
'gray' => 'bg-gray-700 text-gray-300',
'red' => 'bg-red-900/50 text-red-300 border border-red-800',
'green' => 'bg-green-900/50 text-green-300 border border-green-800',
'yellow' => 'bg-yellow-900/50 text-yellow-300 border border-yellow-800',
'neon' => 'bg-[#ccff00]/10 text-[#ccff00] border border-[#ccff00]/50',
];
$classes = $colors[$color] ?? $colors['gray'];
@endphp
<span class="{{ $classes }} text-xs font-medium me-2 px-2.5 py-0.5 rounded border">
{{ $slot }}
</span>
@@ -0,0 +1,15 @@
@props(['href' => '#', 'title', 'description', 'linkText' => 'Ver detalles'])
<a href="{{ $href }}" class="group relative block p-6 bg-panel-bg border border-neutral-800 rounded-xl hover:border-neon-lime hover:-translate-y-1 transition-all duration-300 shadow-lg hover:shadow-neon-lime/20 h-full">
<!-- slot para SVG -->
<div class="absolute top-0 right-0 p-4 opacity-10 group-hover:opacity-100 transition-opacity">
{{ $slot }}
</div>
<h3 class="mt-4 text-xl font-bold text-white group-hover:text-neon-lime transition-colors">{{ $title }}</h3>
<p class="mt-2 text-sm text-gray-500">{{ $description }}</p>
<div class="mt-6 inline-flex items-center text-xs font-bold text-neon-lime uppercase tracking-wider">
{{ $linkText }} <span class="ml-2 group-hover:translate-x-1 transition-transform"></span>
</div>
</a>
@@ -0,0 +1,141 @@
@props(['items'])
<div class="relative w-full overflow-hidden rounded-xl border border-neutral-800 bg-panel-bg shadow-xl group" id="lauck-carousel">
<!-- Título Flotante (Opcional) -->
<div class="absolute top-4 left-6 z-10 bg-black/50 backdrop-blur-sm px-3 py-1 rounded border border-neon-lime/30">
<span class="text-xs font-bold text-neon-lime uppercase tracking-widest">Destacados</span>
</div>
<!-- Contenedor de Slides (Track) -->
<div class="flex transition-transform duration-500 ease-in-out h-[400px]" id="carousel-track">
@forelse($items as $item)
<div class="w-full flex-shrink-0 flex flex-col md:flex-row h-full relative">
<!-- Imagen / Visual (Izquierda o Fondo) -->
<div class="w-full md:w-1/2 bg-neutral-800 flex items-center justify-center relative overflow-hidden">
<!-- Decoración de fondo -->
<div class="absolute inset-0 bg-grid-pattern opacity-10"></div>
<!-- Icono Gigante (Placeholder de Bici) -->
<div class="text-neutral-700 transform group-hover:scale-110 transition-transform duration-700">
@if($item->image_path)
<img src="{{ asset('storage/' . $item->image_path) }}" alt="{{ $item->name }}" class="w-full h-full object-cover">
@else
<!-- Placeholder si no tiene foto -->
<div class="w-full h-full flex items-center justify-center bg-neutral-800">
<svg class="w-48 h-48" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="0.5" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path></svg>
</div>
@endif
</div>
<!-- Precio Flotante -->
<div class="absolute bottom-4 left-4 bg-neon-lime text-neutral-900 font-black px-4 py-2 rounded-lg text-xl shadow-lg shadow-neon-lime/20">
${{ number_format($item->price, 0, ',', '.') }}
</div>
</div>
<!-- Info (Derecha) -->
<div class="w-full md:w-1/2 p-8 md:p-12 flex flex-col justify-center bg-gradient-to-br from-panel-bg to-neutral-900">
<h3 class="text-sm font-bold text-gray-400 uppercase tracking-widest mb-2">{{ $item->sku }}</h3>
<h2 class="text-3xl md:text-4xl font-black text-white italic mb-4 leading-tight">
{{ $item->name }}
</h2>
<p class="text-gray-400 text-sm md:text-base mb-8 line-clamp-3">
{{ $item->description ?? 'Una bicicleta diseñada para el máximo rendimiento en todo terreno. Consultar especificaciones técnicas en el catálogo.' }}
</p>
<div class="flex gap-4">
<a href="{{ route('catalogo.show', $item) }}" class="px-6 py-3 bg-white text-neutral-900 font-bold uppercase tracking-wider rounded hover:bg-gray-200 transition-colors">
Ver Detalles
</a>
</div>
</div>
</div>
@empty
<!-- Slide de Fallback por si no hay datos -->
<div class="w-full flex-shrink-0 flex items-center justify-center h-full bg-neutral-800 text-gray-500">
<p>No hay productos destacados disponibles.</p>
</div>
@endforelse
</div>
<!-- Controles (Flechas) -->
<button id="prevBtn" class="absolute top-1/2 left-4 -translate-y-1/2 bg-black/30 hover:bg-neon-lime hover:text-neutral-900 text-white p-3 rounded-full backdrop-blur-sm transition-all border border-white/10 z-20">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>
</button>
<button id="nextBtn" class="absolute top-1/2 right-4 -translate-y-1/2 bg-black/30 hover:bg-neon-lime hover:text-neutral-900 text-white p-3 rounded-full backdrop-blur-sm transition-all border border-white/10 z-20">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</button>
<!-- Indicadores (Puntos) -->
<div class="absolute bottom-4 left-1/2 transform -translate-x-1/2 flex space-x-2 z-20">
@foreach($items as $index => $item)
<button class="carousel-dot w-3 h-3 rounded-full bg-white/20 hover:bg-neon-lime transition-all" data-index="{{ $index }}"></button>
@endforeach
</div>
</div>
<script type="module">
$(document).ready(function() {
const $track = $('#carousel-track');
const $slides = $track.children();
const slideCount = $slides.length;
let currentIndex = 0;
let autoPlayInterval;
function updateCarousel() {
const translateX = -(currentIndex * 100);
$track.css('transform', `translateX(${translateX}%)`);
// Actualizar puntos
$('.carousel-dot').removeClass('bg-neon-lime scale-125').addClass('bg-white/20');
$(`.carousel-dot[data-index="${currentIndex}"]`).addClass('bg-neon-lime scale-125').removeClass('bg-white/20');
}
function nextSlide() {
currentIndex = (currentIndex + 1) % slideCount;
updateCarousel();
}
function prevSlide() {
currentIndex = (currentIndex - 1 + slideCount) % slideCount;
updateCarousel();
}
// Event Listeners
$('#nextBtn').click(function() {
nextSlide();
resetAutoPlay();
});
$('#prevBtn').click(function() {
prevSlide();
resetAutoPlay();
});
$('.carousel-dot').click(function() {
currentIndex = $(this).data('index');
updateCarousel();
resetAutoPlay();
});
// AutoPlay (cada 5 segundos)
function startAutoPlay() {
autoPlayInterval = setInterval(nextSlide, 5000);
}
function resetAutoPlay() {
clearInterval(autoPlayInterval);
startAutoPlay();
}
// Iniciar
if(slideCount > 0) {
updateCarousel();
startAutoPlay();
}
});
</script>
@@ -0,0 +1,31 @@
@props(['href' => '#', 'title', 'description', 'price', 'image' => null])
<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 ($image)
<img src="{{ asset('storage/' . $image) }}" alt="{{ $title }}"
class="w-full h-40 object-cover rounded mb-4">
@else
<div class="w-full h-40 bg-lime-500 flex items-center justify-center rounded mb-4">
<span class="text-neutral-900 text-md uppercase font-bold">Sin imagen</span>
</div>
@endif
{{-- Nombre --}}
<h2 class="text-lg font-semibold text-white">{{ $title }}</h2>
{{-- Descripción corta --}}
<div class="h-10 overflow-y-auto [scrollbar-width:none]">
@if (!empty($description))
<p class="text-sm text-gray-400 mt-2">{{ $description }}</p>
@endif
</div>
{{-- Precio --}}
<p class="text-white group-hover:text-neon-lime font-bold mt-3 transition-colors">${{ number_format($price, 2, ',', '.') }}</p>
{{-- Botón --}}
<a href="{{ $href }}" class="mt-4 block w-full bg-blue-500 text-white py-2 rounded-lg text-center hover:bg-blue-600">
Ver producto
</a>
</div>
+46 -3
View File
@@ -1,6 +1,49 @@
<x-layout title="Dashboard - Lauck">
<H1>BIENVENIDO {{Auth::user()->name}} </H1> <!-- Header -->
<form action="{{route('logout')}}" method="post"> <x-section-header
subtitle="Panel de Control"
title="¡Bienvenido, "
:highlight="auth()->user()->name . '!'"
/>
<!-- Grid de Tarjetas -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 w-full mb-12">
<!-- Tarjeta Catálogo -->
<x-ui.card href="{{ url('/catalogo') }}" title="Catálogo" description="Administrar bicicletas y repuestos." linkText="Ir al stock">
<svg class="w-12 h-12 text-neon-lime" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z" /></svg>
</x-ui.card>
<!-- Tarjeta Taller -->
<x-ui.card href="#" title="Taller" description="Gestión de services y reparaciones." linkText="Ver agenda">
<svg class="w-12 h-12 text-neon-lime" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
</x-ui.card>
<!-- Tarjeta Clientes -->
<x-ui.card href="{{ url('/clients') }}" title="Clientes" description="Base de datos de ciclistas." linkText="Buscar usuario">
<svg class="w-12 h-12 text-neon-lime" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" /></svg>
</x-ui.card>
<!-- Estadísticas (Sin template) -->
<div class="p-6 bg-gradient-to-br from-neutral-800 to-neutral-900 border border-neutral-700 rounded-xl flex flex-col justify-center items-center text-center">
<span class="text-4xl font-black text-white">85%</span>
<span class="text-xs uppercase tracking-widest text-gray-500 mt-1">Eficiencia Taller</span>
<div class="w-full bg-gray-700 rounded-full h-1.5 mt-4">
<div class="bg-neon-lime h-1.5 rounded-full" style="width: 85%"></div>
</div>
</div>
</div>
<!-- Botón de Logout -->
<div class="w-full border-t border-neutral-800 pt-8 flex justify-end">
<form action="{{ route('logout') }}" method="post" class="flex items-center gap-4">
@csrf @csrf
<button type="submit">Logout</button> <span class="text-sm text-gray-500">¿Sesión finalizada?</span>
<button class="font-bold text-sm text-red-500 border border-red-900/50 bg-red-900/10 py-2 px-6 rounded-md hover:bg-red-600 hover:text-white hover:border-red-600 transition-all duration-300 uppercase tracking-wide" type="submit">
Cerrar Sesión
</button>
</form> </form>
</div>
</x-layout>
-17
View File
@@ -1,17 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<title>@yield('title','Lauck - Home')</title>
</head>
<body>
<header>Cabeza</header>
@yield('main')
<footer>Pies</footer>
</body>
</html>
+25 -31
View File
@@ -1,48 +1,45 @@
<!doctype html> <x-layout title="Login - Lauck">
<html> <div class="flex flex-col items-center space-y-6 w-full max-w-lg p-6">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bicicletería Lauck - Inicio de sesión</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gradient-to-br from-blue-500 to-blue-700 flex items-center justify-center min-h-screen px-4">
<div class="flex flex-col items-center space-y-6 w-full max-w-lg">
<!-- Título --> <!-- Título -->
<h1 class="text-4xl font-bold text-white text-center drop-shadow-lg"> Bicicletería Lauck </h1> <h1 class="text-4xl font-bold text-white text-center drop-shadow-lg" > Bicicletería<span class="text-neon-lime"> Lauck</span> </h1>
<!-- Formulario --> <!--
<div class="bg-white p-8 rounded-xl shadow-lg w-full max-w-md"> <x-section-header
subtitle="Inicio de sesion"
title="Bicicletería "
highlight="Lauck"
/>
-->
<div class="bg-zinc-900 p-8 rounded-xl shadow-lg w-full max-w-md border-2 border-gray-500">
<form action="{{ route('login.attempt') }}" method="POST" class="space-y-5"> <form action="{{ route('login.attempt') }}" method="POST" class="space-y-5">
@csrf @csrf
{{-- No muestra los errores, ver --}} {{-- No muestra los errores, ver --}}
{{-- Bloque de errores de validación y autenticación --}}
@if ($errors->any()) @if ($errors->any())
<div> <div class="bg-red-900 border border-red-700 text-white p-3 rounded-md">
<ul> {{-- Muestra el primer mensaje de error sin lista --}}
@foreach ($errors->all() as $error) <p class="text-sm font-medium">
<li>{{ $error }}</li> {{ $errors->first() }}
@endforeach </p>
</ul>
</div> </div>
@endif @endif
<div> <div>
<label class="block text-sm font-medium text-gray-700 mb-1">Correo electrónico</label> <label class="block text-sm font-bold text-gray-600 mb-1">Correo electrónico</label>
<input type="email" name="email" required <input type="email" name="email" required
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-400"> class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-lime-400 text-gray-600">
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-gray-700 mb-1">Contraseña</label> <label class="block text-sm font-bold text-gray-600 mb-1">Contraseña</label>
<input type="password" name="password" required <input type="password" name="password" required
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-400"> class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-lime-400 text-gray-600">
</div> </div>
<div> <div>
<input type="submit" name="submit" value="Iniciar sesión" <input type="submit" name="submit" value="Iniciar sesión"
class="w-full bg-blue-600 text-white py-2 rounded-md hover:bg-blue-700 transition-colors"> class="w-full bg-lime-400 font-bold text-white py-2 rounded-md hover:bg-lime-500 transition-colors">
</div> </div>
<p class="text-center text-sm text-gray-600"> <p class="text-center text-sm text-gray-600">
@@ -51,7 +48,4 @@
</form> </form>
</div> </div>
</div> </div>
</x-layout>
</body>
</html>
+69 -48
View File
@@ -1,61 +1,82 @@
<x-appc> <x-layout title="Lauck - Nuevo Producto">
<x-slot name="title">Lauck - Agregar</x-slot>
<x-slot name="navTitle">Agregar - Producto</x-slot>
@if ($errors->any()) <x-section-header subtitle="Inventario" title="Nuevo " highlight="Producto" />
<x-alert type="danger">
<x-slot name="title">Error: </x-slot> <div class="w-full max-w-4xl mx-auto bg-panel-bg border border-neutral-800 rounded-xl p-8 shadow-lg">
<x-slot name="content">Todos los campos son obligatorios.</x-slot>
</x-alert> <form action="{{ route('productos.store') }}" method="POST" enctype="multipart/form-data">
@endif
<div class="w-full h-fit flex flex-col justify-start items-center gap-3 p-18">
<form class="w-lg mx-auto" action="{{route('productos.store')}}" method="POST">
@csrf @csrf
<div class="relative z-0 w-full mb-5 group">
<input type="text" value="{{old('nombre')}}" name="nombre" id="nombre" class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer" placeholder=" "/> <!-- Grid Layout -->
<label for="nombre" class="peer-focus:font-medium absolute text-sm text-gray-300 dark:text-gray-300 duration-300 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:start-0 rtl:peer-focus:translate-x-1/4 rtl:peer-focus:left-auto peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-6">Titulo</label> <div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
<!-- Nombre (Ocupa 2 columnas) -->
<div class="md:col-span-4">
<x-forms.label for="name" value="Nombre del Producto" />
<x-forms.input id="name" name="name" type="text" :value="old('name')" required autofocus placeholder="Ej: Cámara 29 Válvula Auto" :error="$errors->first('name')" />
</div> </div>
<div class="relative z-0 w-full mb-5 group">
<input type="text" value="{{old('marca')}}" name="marca" id="marca" class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer" placeholder=" "/> <!-- SKU -->
<label for="marca" class="peer-focus:font-medium absolute text-sm text-gray-300 dark:text-gray-300 duration-300 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:start-0 rtl:peer-focus:translate-x-1/4 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-6">Marca</label> <div class="md:col-span-2">
<x-forms.label for="sku" value="Código SKU (Opcional)" />
<x-forms.input id="sku" name="sku" type="text" :value="old('sku')" placeholder="Dejar vacío para generar auto" :error="$errors->first('sku')" />
</div> </div>
<div class="relative z-0 w-full mb-5 group">
<input type="text" value="{{old('modelo')}}" name="modelo" id="modelo" class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer" placeholder=" "/> <!-- Tipo -->
<label for="modelo" class="peer-focus:font-medium absolute text-sm text-gray-300 dark:text-gray-300 duration-300 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:start-0 rtl:peer-focus:translate-x-1/4 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-6">Modelo</label> <div class="md:col-span-2">
<x-forms.label for="type" value="Tipo de Producto" />
<select id="type" name="type" class="bg-neutral-800 border-neutral-700 text-white text-sm rounded-lg focus:ring-neon-lime focus:border-neon-lime block w-full p-2.5">
<option value="accessory" {{ old('type') == 'accessory' ? 'selected' : '' }}>Accesorio / Repuesto</option>
<option value="bike" {{ old('type') == 'bike' ? 'selected' : '' }}>Bicicleta</option>
<option value="service" {{ old('type') == 'service' ? 'selected' : '' }}>Servicio / Mano de Obra</option>
</select>
</div> </div>
<div class="relative z-0 w-full mb-5 group">
<textarea type="text" name="descripcion" id="descripcion" class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer" placeholder=" ">{{old('descripcion')}}</textarea> <!-- Precios -->
<label for="descripcion" class="peer-focus:font-medium absolute text-sm text-gray-300 dark:text-gray-300 duration-300 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:start-0 rtl:peer-focus:translate-x-1/4 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-6">Descripcion</label> <div class="">
<x-forms.label for="price" value="Precio Venta ($)" />
<x-forms.input id="price" name="price" type="number" step="0.01" :value="old('price')" required :error="$errors->first('price')" />
</div> </div>
<div class="grid md:grid-cols-2 md:gap-6">
<div class="relative z-0 w-full mb-5 group"> <div class="">
<input type="number" value="{{old('rodado')}}" name="rodado" id="rodado" class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer" placeholder=" "/> <x-forms.label for="cost" value="Costo ($) - Solo Admin" />
<label for="rodado" class="peer-focus:font-medium absolute text-sm text-gray-300 dark:text-gray-300 duration-300 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:start-0 rtl:peer-focus:translate-x-1/4 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-6">Rodado</label> <x-forms.input id="cost" name="cost" type="number" step="0.01" :value="old('cost')" :error="$errors->first('cost')" />
</div> </div>
<div class="relative z-0 w-full mb-5 group">
<input type="text" value="{{old('color')}}" name="color" id="color" class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer" placeholder=" "/> <!-- Stock -->
<label for="color" class="peer-focus:font-medium absolute text-sm text-gray-300 dark:text-gray-300 duration-300 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:start-0 rtl:peer-focus:translate-x-1/4 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-6">Color</label> <div class="">
<x-forms.label for="stock_quantity" value="Cantidad Inicial" />
<x-forms.input id="stock_quantity" name="stock_quantity" type="number" :value="old('stock_quantity', 0)" required :error="$errors->first('stock_quantity')" />
</div>
<div class="">
<x-forms.label for="min_stock_alert" value="Alerta de Stock Mínimo" />
<x-forms.input id="min_stock_alert" name="min_stock_alert" type="number" :value="old('min_stock_alert', 5)" required />
</div>
<!-- Descripción (2 columnas) -->
<div class="md:col-span-4">
<x-forms.label for="description" value="Descripción / Notas" />
<textarea id="description" name="description" rows="3" class="bg-neutral-800 border-neutral-700 text-white text-sm rounded-lg focus:ring-neon-lime focus:border-neon-lime block w-full p-2.5 placeholder-gray-500">{{ old('description') }}</textarea>
</div>
<div class="md:col-span-2">
<x-forms.label for="image" value="Imagen del Producto" />
<x-forms.input type="file" name="image" class="block w-full text-sm text-gray-400 file:mr-4 file:py-2 file:px-4 file:rounded-full
file:border-0 file:text-sm file:font-semibold file:bg-neon-lime file:text-neutral-900 hover:file:bg-[#b3e600]
" :error="$errors->first('image')"/>
{{-- @error('image') <span class="text-red-500 text-xs">{{ $message }}</span> @enderror --}}
</div> </div>
</div> </div>
<div class="grid md:grid-cols-2 md:gap-6">
<div class="relative z-0 w-full mb-5 group"> <!-- Botones Acción -->
<input type="text" value="{{old('tipo')}}" name="tipo" id="tipo" class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer" placeholder=" "/> <div class="flex items-center justify-between md:justify-end space-x-4 border-t border-neutral-800 pt-6">
<label for="tipo" class="peer-focus:font-medium absolute text-sm text-gray-300 dark:text-gray-300 duration-300 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:start-0 rtl:peer-focus:translate-x-1/4 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-6">Tipo</label> <a href="{{ route('productos.index') }}" class="text-gray-400 hover:text-white font-medium text-sm transition-colors">Cancelar</a>
</div> <button type="submit" class="px-6 py-2.5 bg-neon-lime text-neutral-900 font-bold rounded-lg hover:bg-[#b3e600] transition-colors shadow-lg shadow-neon-lime/20">
<div class="relative z-0 w-full mb-5 group"> Guardar
<input type="number" value="{{old('precio')}}" name="precio" id="precio" class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer" placeholder=" " step="0.01"/>
<label for="precio" class="peer-focus:font-medium absolute text-sm text-gray-300 dark:text-gray-300 duration-300 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:start-0 rtl:peer-focus:translate-x-1/4 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-6">Precio (Ej. $9999)</label>
</div>
</div>
<div class="flex w-full gap-5 mt-5">
<button type="submit" class="w-full sm:w-auto px-4 py-2 rounded-full text-white font-semibold bg-blue-700 hover:bg-blue-800">
Agregar
</button> </button>
<a href="{{route('productos.index')}}" class="w-full sm:w-auto px-5 py-2 rounded-full text-white font-semibold bg-stone-500">
Volver
</a>
</div> </div>
</form> </form>
</div> </div>
</x-layout>
</x-appc>
+75 -13
View File
@@ -1,44 +1,44 @@
<x-appc> {{-- <x-appc>
<x-slot name="title">Lauck - Editar</x-slot> <x-slot name="title">Lauck - Editar</x-slot>
<x-slot name="navTitle">Editar - {{$producto->nombre}}</x-slot> <x-slot name="navTitle">Editar - {{$product->nombre}}</x-slot>
<div class="w-full h-fit flex flex-col justify-start items-center gap-3 p-18"> <div class="w-full h-fit flex flex-col justify-start items-center gap-3 p-18">
<form class="w-lg mx-auto" action="{{route('productos.update',$producto)}}" method="POST"> <form class="w-lg mx-auto" action="{{route('productos.update',$product)}}" method="POST">
@csrf @csrf
@method('PUT') @method('PUT')
<div class="relative z-0 w-full mb-5 group"> <div class="relative z-0 w-full mb-5 group">
<input type="text" value="{{$producto->nombre}}" name="nombre" id="nombre" class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer"/> <input type="text" value="{{$product->nombre}}" name="nombre" id="nombre" class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer"/>
<label for="nombre" class="peer-focus:font-medium absolute text-sm text-gray-300 dark:text-gray-300 duration-300 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:start-0 rtl:peer-focus:translate-x-1/4 rtl:peer-focus:left-auto peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-6">Titulo</label> <label for="nombre" class="peer-focus:font-medium absolute text-sm text-gray-300 dark:text-gray-300 duration-300 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:start-0 rtl:peer-focus:translate-x-1/4 rtl:peer-focus:left-auto peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-6">Titulo</label>
</div> </div>
<div class="relative z-0 w-full mb-5 group"> <div class="relative z-0 w-full mb-5 group">
<input type="text" value="{{$producto->marca}}" name="marca" id="marca" class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer"/> <input type="text" value="{{$product->marca}}" name="marca" id="marca" class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer"/>
<label for="marca" class="peer-focus:font-medium absolute text-sm text-gray-300 dark:text-gray-300 duration-300 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:start-0 rtl:peer-focus:translate-x-1/4 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-6">Marca</label> <label for="marca" class="peer-focus:font-medium absolute text-sm text-gray-300 dark:text-gray-300 duration-300 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:start-0 rtl:peer-focus:translate-x-1/4 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-6">Marca</label>
</div> </div>
<div class="relative z-0 w-full mb-5 group"> <div class="relative z-0 w-full mb-5 group">
<input type="text" value="{{$producto->modelo}}" name="modelo" id="modelo" class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer"/> <input type="text" value="{{$product->modelo}}" name="modelo" id="modelo" class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer"/>
<label for="modelo" class="peer-focus:font-medium absolute text-sm text-gray-300 dark:text-gray-300 duration-300 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:start-0 rtl:peer-focus:translate-x-1/4 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-6">Modelo</label> <label for="modelo" class="peer-focus:font-medium absolute text-sm text-gray-300 dark:text-gray-300 duration-300 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:start-0 rtl:peer-focus:translate-x-1/4 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-6">Modelo</label>
</div> </div>
<div class="relative z-0 w-full mb-5 group"> <div class="relative z-0 w-full mb-5 group">
<textarea type="text" name="descripcion" id="descripcion" class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer">{{$producto->descripcion}}</textarea> <textarea type="text" name="descripcion" id="descripcion" class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer">{{$product->descripcion}}</textarea>
<label for="descripcion" class="peer-focus:font-medium absolute text-sm text-gray-300 dark:text-gray-300 duration-300 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:start-0 rtl:peer-focus:translate-x-1/4 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-6">Descripcion</label> <label for="descripcion" class="peer-focus:font-medium absolute text-sm text-gray-300 dark:text-gray-300 duration-300 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:start-0 rtl:peer-focus:translate-x-1/4 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-6">Descripcion</label>
</div> </div>
<div class="grid md:grid-cols-2 md:gap-6"> <div class="grid md:grid-cols-2 md:gap-6">
<div class="relative z-0 w-full mb-5 group"> <div class="relative z-0 w-full mb-5 group">
<input type="number" value="{{$producto->rodado}}" name="rodado" id="rodado" class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer"/> <input type="number" value="{{$product->rodado}}" name="rodado" id="rodado" class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer"/>
<label for="rodado" class="peer-focus:font-medium absolute text-sm text-gray-300 dark:text-gray-300 duration-300 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:start-0 rtl:peer-focus:translate-x-1/4 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-6">Rodado</label> <label for="rodado" class="peer-focus:font-medium absolute text-sm text-gray-300 dark:text-gray-300 duration-300 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:start-0 rtl:peer-focus:translate-x-1/4 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-6">Rodado</label>
</div> </div>
<div class="relative z-0 w-full mb-5 group"> <div class="relative z-0 w-full mb-5 group">
<input type="text" value="{{$producto->color}}" name="color" id="color"class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer"/> <input type="text" value="{{$product->color}}" name="color" id="color"class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer"/>
<label for="color" class="peer-focus:font-medium absolute text-sm text-gray-300 dark:text-gray-300 duration-300 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:start-0 rtl:peer-focus:translate-x-1/4 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-6">Color</label> <label for="color" class="peer-focus:font-medium absolute text-sm text-gray-300 dark:text-gray-300 duration-300 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:start-0 rtl:peer-focus:translate-x-1/4 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-6">Color</label>
</div> </div>
</div> </div>
<div class="grid md:grid-cols-2 md:gap-6"> <div class="grid md:grid-cols-2 md:gap-6">
<div class="relative z-0 w-full mb-5 group"> <div class="relative z-0 w-full mb-5 group">
<input type="text" value="{{$producto->tipo}}" name="tipo" id="tipo" class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer"/> <input type="text" value="{{$product->tipo}}" name="tipo" id="tipo" class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer"/>
<label for="tipo" class="peer-focus:font-medium absolute text-sm text-gray-300 dark:text-gray-300 duration-300 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:start-0 rtl:peer-focus:translate-x-1/4 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-6">Tipo</label> <label for="tipo" class="peer-focus:font-medium absolute text-sm text-gray-300 dark:text-gray-300 duration-300 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:start-0 rtl:peer-focus:translate-x-1/4 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-6">Tipo</label>
</div> </div>
<div class="relative z-0 w-full mb-5 group"> <div class="relative z-0 w-full mb-5 group">
<input type="number" value={{$producto->precio}} name="precio" id="precio" class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer" step="0.01"/> <input type="number" value={{$product->precio}} name="precio" id="precio" class="block py-2.5 px-0 w-full text-sm text-gray-300 bg-transparent border-0 border-b-2 border-gray-300 appearance-none dark:text-white dark:border-gray-600 dark:focus:border-blue-500 focus:outline-none focus:ring-0 focus:border-blue-600 peer" step="0.01"/>
<label for="precio" class="peer-focus:font-medium absolute text-sm text-gray-300 dark:text-gray-300 duration-300 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:start-0 rtl:peer-focus:translate-x-1/4 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-6">Precio (Ej. $9999)</label> <label for="precio" class="peer-focus:font-medium absolute text-sm text-gray-300 dark:text-gray-300 duration-300 transform -translate-y-6 scale-75 top-3 -z-10 origin-[0] peer-focus:start-0 rtl:peer-focus:translate-x-1/4 peer-focus:text-blue-600 peer-focus:dark:text-blue-500 peer-placeholder-shown:scale-100 peer-placeholder-shown:translate-y-0 peer-focus:scale-75 peer-focus:-translate-y-6">Precio (Ej. $9999)</label>
</div> </div>
</div> </div>
@@ -46,11 +46,73 @@
<button type="submit" class="w-full sm:w-auto px-4 py-2 rounded-full text-white font-semibold bg-blue-700 hover:bg-blue-800 dark:bg-blue-700 dark:hover:bg-blue-800"> <button type="submit" class="w-full sm:w-auto px-4 py-2 rounded-full text-white font-semibold bg-blue-700 hover:bg-blue-800 dark:bg-blue-700 dark:hover:bg-blue-800">
Editar Editar
</button> </button>
<a href="{{route('productos.show',$producto)}}" class="w-full sm:w-auto px-5 py-2 rounded-full text-white font-semibold bg-stone-500"> <a href="{{route('productos.show',$product)}}" class="w-full sm:w-auto px-5 py-2 rounded-full text-white font-semibold bg-stone-500">
Volver Volver
</a> </a>
</div> </div>
</form> </form>
</div> </div>
</x-appc> </x-appc> --}}
<x-layout title="Lauck - Editar Producto">
<x-section-header subtitle="Inventario" title="Edicion de " highlight="Producto" />
<div class="w-full max-w-4xl mx-auto bg-panel-bg border border-neutral-800 rounded-xl p-8 shadow-lg">
<form action="{{route('productos.update',$product)}}" method="POST">
@csrf
@method('PUT')
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
<!-- Nombre -->
<div class="md:col-span-4">
<x-forms.label for="name" value="Nombre del Producto" />
<x-forms.input id="name" name="name" type="text" value="{{$product->name}}" required autofocus placeholder="Ej: Cámara 29 Válvula Auto" :error="$errors->first('name')" />
</div>
<!-- SKU -->
<div class="md:col-span-2">
<x-forms.label for="sku" value="Código SKU (Opcional)" />
<x-forms.input id="sku" name="sku" type="text" value="{{$product->sku}}" placeholder="Dejar vacío para generar auto" :error="$errors->first('sku')" />
</div>
<!-- Tipo -->
<div class="md:col-span-2">
<x-forms.label for="type" value="Tipo de Producto" />
<select id="type" name="type" class="bg-neutral-800 border-neutral-700 text-white text-sm rounded-lg focus:ring-neon-lime focus:border-neon-lime block w-full p-2.5">
<option value="accessory" {{ $product->type == 'accessory' ? 'selected' : '' }}>Accesorio / Repuesto</option>
<option value="bike" {{ $product->type == 'bike' ? 'selected' : '' }}>Bicicleta</option>
<option value="service" {{ $product->type == 'service' ? 'selected' : '' }}>Servicio / Mano de Obra</option>
</select>
</div>
<!-- Precios -->
<div class="">
<x-forms.label for="price" value="Precio Venta ($)" />
<x-forms.input id="price" name="price" type="number" step="0.01" value="{{$product->price}}" required :error="$errors->first('price')" />
</div>
<div class="">
<x-forms.label for="cost" value="Costo ($) - Solo Admin" />
<x-forms.input id="cost" name="cost" type="number" step="0.01" value="{{$product->cost}}" :error="$errors->first('cost')" />
</div>
<!-- Stock -->
<div class="">
<x-forms.label for="stock_quantity" value="Cantidad Inicial" />
<x-forms.input id="stock_quantity" name="stock_quantity" type="number" value="{{$product->stock_quantity}}" required :error="$errors->first('stock_quantity')" />
</div>
<div class="">
<x-forms.label for="min_stock_alert" value="Alerta de Stock Mínimo" />
<x-forms.input id="min_stock_alert" name="min_stock_alert" type="number" value="{{$product->min_stock_alert}}" required />
</div>
<!-- Descripción (2 columnas) -->
<div class="md:col-span-4">
<x-forms.label for="description" value="Descripción / Notas" />
<textarea id="description" name="description" rows="3" class="bg-neutral-800 border-neutral-700 text-white text-sm rounded-lg focus:ring-neon-lime focus:border-neon-lime block w-full p-2.5 placeholder-gray-500">{{$product->description}}</textarea>
</div>
</div>
<!-- Botones Acción -->
<div class="flex items-center justify-between md:justify-end space-x-4 border-t border-neutral-800 pt-6">
<a href="{{ route('productos.index') }}" class="text-gray-400 hover:text-white font-medium text-sm transition-colors">Cancelar</a>
<button type="submit" class="px-6 py-2.5 bg-neon-lime text-neutral-900 font-bold rounded-lg hover:bg-[#b3e600] transition-colors shadow-lg shadow-neon-lime/20">
Guardar
</button>
</div>
</form>
</div>
</x-layout>
+84 -31
View File
@@ -1,44 +1,97 @@
<x-appc> <x-layout title="Lauck - Stock">
<x-slot name="title">Lauck - Home - Productos</x-slot>
<x-slot name="navTitle">Productos</x-slot>
<div class="w-6xl mx-auto flex flex-col justify-start items-stretch gap-5 my-5"> <x-section-header subtitle="Gestión de Inventario" title="Listado de " highlight="Productos" />
<a href="{{route('productos.create')}}" class="w-fit mx-auto px-4 py-2 rounded-full text-white font-semibold bg-stone-500 hover:scale-105 transition">
Agregar Producto <!-- Mensajes de feedback -->
<x-ui.alert />
<!-- Barra de Herramientas (Buscador + Botón Crear) -->
<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 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"/>
</svg>
</div>
<input type="text" name="search" value="{{ request('search') }}"
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 -->
<a href="{{ route('productos.create') }}" class="w-full md:w-auto text-center px-5 py-3 text-sm font-bold text-neutral-900 bg-neon-lime rounded-lg hover:bg-[#b3e600] transition-colors uppercase tracking-wide">
+ Nuevo Producto
</a> </a>
<div class="overflow-x-auto rounded-lg shadow-xl"> </div>
<table class="w-full min-w-xl text-sm text-left rtl:text-right text-neutral-500 dark:text-neutral-400"> <!-- Tabla de Productos -->
<thead class="text-xs text-neutral-700 uppercase bg-neutral-50 dark:bg-neutral-700 dark:text-neutral-400"> <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">
<thead class="text-xs text-gray-300 uppercase bg-neutral-800 border-b border-neutral-700">
<tr> <tr>
<th scope="col" class="px-6 py-3">Nombre</th> <th scope="col" class="px-6 py-3">Producto / SKU</th>
<th scope="col" class="px-6 py-3">Marca</th>
<th scope="col" class="px-6 py-3">Modelo</th>
<th scope="col" class="px-6 py-3">Rodado</th>
<th scope="col" class="px-6 py-3">Color</th>
<th scope="col" class="px-6 py-3">Tipo</th> <th scope="col" class="px-6 py-3">Tipo</th>
<th scope="col" class="px-6 py-3">Precio</th> <th scope="col" class="px-6 py-3">Precio</th>
<th scope="col" class="px-6 py-3 text-center">Stock</th>
<th scope="col" class="px-6 py-3 text-right">Acciones</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@foreach ($products as $prod) @forelse($products as $product)
<tr class="bg-white border-b dark:bg-neutral-800 dark:border-neutral-700 border-neutral-200"> <tr class="bg-neutral-900/50 border-b border-neutral-800 hover:bg-neutral-800 transition-colors group">
<th scope="row" <td class="px-6 py-4 font-medium text-white whitespace-nowrap">
class="px-6 py-4 font-medium text-neutral-900 whitespace-nowrap dark:text-white"> <div class="text-base font-bold">{{ $product->name }}</div>
<a href="{{route('productos.show',$prod)}}">{{ $prod->nombre }}</a> <div class="text-xs text-gray-500 font-mono">{{ $product->sku }}</div>
</th> </td>
<td class="px-6 py-4">{{ $prod->marca }}</td> <td class="px-6 py-4">
<td class="px-6 py-4">{{ $prod->modelo }}</td> @if($product->type === 'bike') <x-ui.badge color="neon">Bicicleta</x-ui.badge>
<td class="px-6 py-4">{{ $prod->rodado }}</td> @elseif($product->type === 'accessory') <x-ui.badge color="gray">Accesorio</x-ui.badge>
<td class="px-6 py-4">{{ $prod->color }}</td> @else <x-ui.badge color="yellow">Servicio</x-ui.badge> @endif
<td class="px-6 py-4">{{ $prod->tipo }}</td> </td>
<td class="px-6 py-4">{{ $prod->precio }}</td> <td class="px-6 py-4 font-mono text-white">
${{ number_format($product->price, 2) }}
</td>
<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)
<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
</td>
<td class="px-6 py-4 text-right">
<a href="{{ route('productos.edit', $product) }}" class="font-medium text-blue-400 hover:underline mr-3">Editar</a>
</td>
</tr> </tr>
@endforeach @empty
<tr>
<td colspan="5" class="px-6 py-10 text-center text-gray-500">
No se encontraron productos.
</td>
</tr>
@endforelse
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Paginación -->
{{$products->links()}} <div class="mt-4 w-full">
{{ $products->links() }}
</div> </div>
</x-layout>
</x-appc>
+24 -30
View File
@@ -1,42 +1,43 @@
<x-appc> <x-layout title="Lauck - Ver Producto">
<x-slot name="title">Lauck - Vista - Producto</x-slot>
<x-slot name="navTitle">Producto - {{$producto->nombre}}</x-slot> <!-- Header -->
<x-section-header subtitle="Producto" title=" " highlight="{{$product->name}}"/>
<div class="w-full min-h-7/12 mx-auto px-4 my-5 flex flex-col gap-5 items-center justify-center"> <div class="w-full min-h-7/12 mx-auto px-4 my-5 flex flex-col gap-5 items-center justify-center">
<dl class="w-lg text-gray-900 dark:text-white *:border-b *:border-gray-200 *:dark:border-gray-400"> <dl class="w-lg text-gray-900 dark:text-white *:border-b *:border-gray-200 *:dark:border-gray-400">
<div class="flex flex-col pt-3"> <div class="flex flex-col pt-3">
<dt class="mb-1 text-gray-500 md:text-lg dark:text-gray-400">Marca</dt> <dt class="mb-1 text-gray-500 md:text-lg dark:text-gray-400">Marca</dt>
<dd class="text-lg font-semibold">{{$producto->marca}}</dd> <dd class="text-lg font-semibold">{{$product->name}}</dd>
</div> </div>
<div class="flex flex-col pt-3"> <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">{{$producto->modelo}}</dd> <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>
<dd class="text-lg font-semibold">{{$producto->color}}</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>
<dd class="text-lg font-semibold">{{$producto->rodado}}</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>
<dd class="text-lg font-semibold">{{$producto->tipo}}</dd>
</div> </div>
<div class="flex flex-col pt-3"> <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">Descripcion</dt>
<dd class="text-lg font-semibold">{{$producto->descripcion}}</dd> <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">Costo</dt>
<dd class="text-lg font-semibold">{{$product->cost}}</dd>
</div> </div>
<div class="flex flex-col pt-3"> <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">Precio</dt>
<dd class="text-lg font-semibold">{{$producto->precio}}</dd> <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">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">Tipo</dt>
<dd class="text-lg font-semibold">{{$product->type}}</dd>
</div> </div>
</dl> </dl>
<div class="flex gap-5"> <div class="flex gap-5">
<a href="{{route('productos.index')}}" class="w-full sm:w-auto px-5 py-2 rounded-full text-white font-semibold bg-stone-500 hover:bg-stone-600">Volver</a> <a href="{{route('productos.index')}}" class="w-full sm:w-auto px-5 py-2 rounded-full text-white font-semibold bg-stone-500 hover:bg-stone-600">Volver</a>
<a href="{{route('productos.edit',$producto)}}" class="w-full sm:w-auto px-5 py-2 rounded-full text-white font-semibold bg-blue-600 hover:bg-blue-700">Editar</a> <a href="{{ route('productos.edit', $product ) }}" class="w-full sm:w-auto px-5 py-2 rounded-full text-white font-semibold bg-blue-600 hover:bg-blue-700">Editar</a>
<form action="{{route('productos.destroy',$producto)}}" method="post"> <form action="{{route('productos.destroy',$product)}}" method="post">
@csrf @csrf
@method('DELETE') @method('DELETE')
<button type="submit" class="w-full sm:w-auto px-5 py-2 rounded-full text-white font-semibold bg-red-600 hover:bg-red-700"> <button type="submit" class="w-full sm:w-auto px-5 py-2 rounded-full text-white font-semibold bg-red-600 hover:bg-red-700">
@@ -46,11 +47,4 @@
</div> </div>
</div> </div>
</x-layout>
</x-appc>
{{-- componente alerta --}}
{{-- <x-alert type="success">
<x-slot name="title">Jose!</x-slot>
<x-slot name="content">Lorem ipsum dolor sit amet consectetur adipisicing elit. Libero, vel omnis</x-slot>
</x-alert> --}}
@@ -0,0 +1,12 @@
<x-appc>
<div class="max-w-6xl mx-auto px-4 py-8 text-white">
<a href="{{ route('catalogo') }}">Volver a catalogo</a>
<h1>Titulo: {{ $producto->nombre }}</h1>
<p>
<b>Categoria:</b> {{ $producto->Categoria }}
</p>
<p>
{{ $producto->content }}
</p>
</div>
</x-appc>
+9
View File
@@ -0,0 +1,9 @@
<x-layout>
<div>
<x-forms.label class="block text-sm font-bold text-gray-600 mb-1">Contraseña</x-forms.label>
<input type="password" name="password" required
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-lime-400 text-gray-600">
</div>
</x-layout>
+29 -26
View File
@@ -1,48 +1,52 @@
<!doctype html> <x-layout title="Registrarse - Lauck">
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bicicletería Lauck - Registro</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gradient-to-br from-blue-500 to-blue-700 flex items-center justify-center min-h-screen px-4">
<div class="flex flex-col items-center space-y-6 w-full max-w-lg"> <div class="flex flex-col items-center space-y-6 w-full max-w-lg">
<!-- Título --> <!-- Título -->
<h1 class="text-4xl font-bold text-white text-center drop-shadow-lg"> <h1 class="text-4xl font-bold text-white text-center drop-shadow-lg" > Bicicletería<span class="text-neon-lime"> Lauck</span> </h1>
Bicicletería Lauck
</h1>
<!-- Formulario --> <!-- Formulario -->
<div class="bg-white p-8 rounded-xl shadow-lg w-full max-w-md"> <div class="bg-zinc-900 p-8 rounded-xl shadow-lg w-full max-w-md border-2 border-gray-500">
<h2 class="text-2xl font-bold mb-2 text-center">Registrarse</h2> <h2 class="text-2xl font-bold mb-2 text-center">Registrarse</h2>
<p class="mb-6 text-center text-gray-600">Por favor completá el formulario para crear una cuenta.</p> <p class="mb-6 text-center text-gray-500">Por favor completá el formulario para crear una cuenta.</p>
<form action="{{route('register.store')}}" method="post" class="space-y-5"> <form action="{{ route('register.store') }}" method="post" class="space-y-5">
@csrf @csrf
@if ($errors->any())
<div> <div>
<label class="block text-sm font-medium text-gray-700 mb-1">Nombre completo</label> <ul>
<input type="text" name="name" required class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-400"> @foreach ($errors->all() as $error)
<li class="text-red-600">{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div>
<label class="block text-sm font-bold text-gray-600 mb-1">Nombre completo</label>
<input type="text" name="name" required
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-400">
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-gray-700 mb-1">Correo electrónico</label> <label class="block text-sm font-bold text-gray-600 mb-1">Correo electrónico</label>
<input type="email" name="email" required class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-400"> <input type="email" name="email" required
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-400">
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-gray-700 mb-1">Contraseña</label> <label class="block text-sm font-bold text-gray-600 mb-1">Contraseña</label>
<input type="password" name="password" required class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-400"> <input type="password" name="password" required
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-400">
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-gray-700 mb-1">Confirmar contraseña</label> <label class="block text-sm font-bold text-gray-600 mb-1">Confirmar contraseña</label>
<input type="password" name="password_confirmation" required class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-400"> <input type="password" name="password_confirmation" required
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-400">
</div> </div>
<div> <div>
<input type="submit" name="submit" value="Registrarse" class="w-full bg-blue-600 text-white py-2 rounded-md hover:bg-blue-700 transition-colors"> <input type="submit" name="submit" value="Registrarse"
class="w-full bg-lime-500 font-bold text-white py-2 rounded-md hover:bg-lime-600 transition-colors">
</div> </div>
<p class="text-center text-sm text-gray-600"> <p class="text-center text-sm text-gray-600">
@@ -52,5 +56,4 @@
</div> </div>
</div> </div>
</body> </x-layout>
</html>
+91
View File
@@ -0,0 +1,91 @@
<x-layout title="Nuevo Producto">
<x-section-header subtitle="Ventas" title="Registro de " :highlight="'ventas ' . ' '" />
<!-- Mensajes de feedback -->
<x-ui.alert />
<!-- Barra de Herramientas (Buscador + Botón Crear) -->
<div class="w-full flex flex-col md:flex-row justify-between items-center gap-4 mb-6">
<!-- Buscador -->
<form action="{{ url('sales') }}" method="GET" class="flex flex-row w-full max-w-3xl gap-4">
<div class="relative 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"/>
</svg>
</div>
<input type="text" name="search" value="{{ request('search') }}"
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>
<div class="w-1/3">
<x-forms.select id="type" name="type" :error="$errors->first('type')" placeholder="Filtro...">
<option value="bike" class="bg-neutral-800">Bicicleta</option>
<option value="accessory" class="bg-neutral-800">Accesorio</option>
<option value="service" class="bg-neutral-800">Servicio</option>
</x-forms.select>
</div>
</form>
<!-- Botón Nuevo -->
<a href="{{ route('productos.create') }}" class="w-full md:w-auto text-center px-5 py-3 text-sm font-bold text-neutral-900 bg-neon-lime rounded-lg hover:bg-[#b3e600] transition-colors uppercase tracking-wide">
+ Registrar Venta
</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">
<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">ID Venta</th>
<th scope="col" class="px-6 py-3">Descripcion</th>
<th scope="col" class="px-6 py-3">Cant. Productos</th>
<th scope="col" class="px-6 py-3 text-center">Total Venta</th>
<th scope="col" class="px-6 py-3 text-right">Acciones</th>
</tr>
</thead>
<tbody>
@forelse($sales as $product)
<tr class="bg-neutral-900/50 border-b border-neutral-800 hover:bg-neutral-800 transition-colors group">
<td class="px-6 py-4 font-medium text-white whitespace-nowrap">
<div class="text-gray-500 font-mono">{{ $product->sku }}</div>
</td>
<td class="px-6 py-4 font-medium text-white whitespace-nowrap">
<div class="text-base font-bold">{{ $product->name }}</div>
</td>
<td class="px-6 py-4 font-mono text-white">
{{ $product->stock_quantity }}
</td>
<td class="px-6 py-4 font-mono text-white text-center">
${{ number_format(($product->price * 2), 2) }}
{{-- @if($product->type === 'service')
<span class="text-gray-600">-</span>
@elseif($product->stock_quantity <= $product->min_stock_alert)
<x-ui.badge color="red">{{ $product->stock_quantity }}</x-ui.badge>
@else
<x-ui.badge color="green">{{ $product->stock_quantity }}</x-ui.badge>
@endif --}}
</td>
<td class="px-6 py-4 text-right">
<a href="{{ route('productos.edit', $product) }}" class="font-medium text-blue-400 hover:underline mr-3">Editar</a>
</td>
</tr>
@empty
<tr>
<td colspan="5" class="px-6 py-10 text-center text-gray-500">
No se encontraron productos.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<!-- Paginación -->
<div class="mt-4 w-full">
{{ $sales->links() }}
</div>
</x-layout>
+281
View File
@@ -0,0 +1,281 @@
<x-layout title="Lauck - Nueva Venta">
@push('styles')
<style>
/* Select2 adaptado al Tema 'Lauck' (Neutral + Neon Lime) */
.select2-container--default .select2-selection--single {
background-color: #262626 !important; /* neutral-800 */
border-color: #404040 !important; /* neutral-700 */
color: white !important;
height: 42px;
display: flex;
align-items: center;
border-radius: 0.5rem; /* rounded-lg */
}
.select2-container--default .select2-selection--single .select2-selection__rendered {
color: white !important;
line-height: 42px;
padding-left: 12px;
}
.select2-dropdown {
background-color: #262626 !important; /* neutral-800 */
border-color: #404040 !important; /* neutral-700 */
color: white !important;
}
/* Color de resalto al pasar el mouse u opción seleccionada */
.select2-container--default .select2-results__option--highlighted.select2-results__option--selectable {
background-color: #ccff00 !important; /* neon-lime */
color: #171717 !important; /* neutral-900 */
font-weight: bold;
}
/* Ajuste del placeholder */
.select2-container--default .select2-selection--single .select2-selection__placeholder {
color: #9ca3af !important; /* gray-400 */
}
</style>
@endpush
<x-section-header subtitle="Punto de Venta" title="Nueva " highlight="Venta" />
<div class="w-full max-w-5xl mx-auto bg-panel-bg border border-neutral-800 rounded-xl p-8 shadow-lg">
<x-ui.alert />
@if($errors->any())
<div class="bg-red-900/50 border border-red-500 text-red-300 px-4 py-3 rounded-lg mb-6 shadow-sm">
<ul class="list-disc list-inside">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('sales.store') }}" method="POST">
@csrf
<div class="grid grid-cols-1 md:grid-cols-12 gap-6 mb-8 border-b border-neutral-800 pb-8">
<div class="md:col-span-10">
<x-forms.label for="client_id" value="Cliente" />
<div class="relative">
<select name="client_id" class="w-full select2-basic">
<option value="">-- Consumidor Final (Anónimo) --</option>
@foreach($clients as $client)
<option value="{{ $client->id }}">{{ $client->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="md:col-span-2 flex items-end">
<a href="{{ route('clients.create', ['origin' => 'sales']) }}" target="_blank" class="bg-indigo-100 text-indigo-700 hover:bg-indigo-200 px-4 py-2 rounded-md font-bold flex items-center border border-indigo-200" title="Crear nuevo cliente">
+
</a>
</div>
</div>
<div class="mb-8">
<div class="flex justify-between items-end mb-4">
<x-forms.label value="Detalle de Productos" />
<div class="text-sm text-gray-400">
Total Items: <span class="text-white font-mono" id="item-count">1</span>
</div>
</div>
<div id="items-container" class="space-y-3">
<div class="item-row grid grid-cols-1 md:grid-cols-12 gap-4 items-start bg-neutral-800/50 p-4 rounded-xl border border-neutral-800">
<div class="md:col-span-6">
<label class="block text-gray-400 text-xs mb-1.5 uppercase tracking-wider font-semibold">Producto</label>
<select name="items[0][product_id]" class="w-full select2-product product-select">
<option value="" data-price="0" selected disabled>Buscar producto...</option>
@foreach($products as $product)
<option value="{{ $product->id }}" data-price="{{ $product->price }}">
{{ $product->sku }} - {{ $product->name }} (Stock: {{ $product->stock_quantity }})
</option>
@endforeach
</select>
</div>
<div class="md:col-span-2">
<label class="block text-gray-400 text-xs mb-1.5 uppercase tracking-wider font-semibold">Cant.</label>
<input type="number" name="items[0][quantity]" value="1" min="1"
class="quantity-input bg-neutral-800 border-neutral-700 text-white text-sm rounded-lg focus:ring-neon-lime focus:border-neon-lime block w-full p-2.5 h-[42px]">
</div>
<div class="md:col-span-3 text-right flex flex-col justify-center h-[42px] mt-6">
<span class="text-xs text-gray-500">Subtotal</span>
<span class="row-total text-lg font-bold text-neon-lime">$0.00</span>
</div>
<div class="md:col-span-1 flex items-end justify-end h-[42px] mt-6">
<button type="button" class="btn-remove text-neutral-500 hover:text-red-500 transition-colors p-2" title="Quitar item">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</div>
</div>
</div>
<div class="mt-4">
<button type="button" id="btn-add-row" class="inline-flex items-center text-neon-lime hover:text-[#b3e600] font-bold text-sm transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" clip-rule="evenodd" />
</svg>
Agregar otro producto
</button>
</div>
<div class="mt-5 flex justify-end items-center border-t border-neutral-700 pt-6">
<div class="text-right">
<span class="block text-gray-400 text-sm mb-1">Monto Total a Pagar</span>
<span id="grand-total" class="text-3xl font-bold text-white">$0.00</span>
</div>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 items-end border-t border-neutral-800 pt-6">
<div>
<x-forms.label for="payment_method" value="Método de Pago" />
<select name="payment_method" class="bg-neutral-800 border-neutral-700 text-white text-sm rounded-lg focus:ring-neon-lime focus:border-neon-lime block w-full p-2.5">
<option value="Efectivo">Efectivo</option>
<option value="Tarjeta de Débito">Tarjeta de Débito</option>
<option value="Tarjeta de Crédito">Tarjeta de Crédito</option>
<option value="Transferencia">Transferencia</option>
</select>
</div>
<div class="flex justify-end gap-4">
<a href="{{ route('sales.create') }}" class="px-6 py-2.5 text-gray-400 hover:text-white font-medium text-sm transition-colors flex items-center">
Cancelar
</a>
<button type="submit" class="px-8 py-2.5 bg-neon-lime text-neutral-900 font-bold rounded-lg hover:bg-[#b3e600] transition-colors shadow-lg shadow-neon-lime/20 transform hover:scale-105">
Confirmar Venta
</button>
</div>
</div>
</form>
</div>
<script type="module">
let rowCount = 0;
$(document).ready(function() {
initSelect2();
actualizarBloqueos(); // Bloqueo inicial
// --- AGREGAR FILA ---
$('#btn-add-row').click(function() {
rowCount++;
const container = $('#items-container');
const firstRow = container.find('.item-row').first();
$('.select2-product').select2('destroy'); // Destruir
const newRow = firstRow.clone(); // Clonar
initSelect2(); // Reactivar originales
// Limpiar nueva fila
newRow.find('input').val(1);
newRow.find('.row-total').text('$0.00');
// Actualizar atributos
newRow.find('select').attr('name', `items[${rowCount}][product_id]`).val('');
newRow.find('input').attr('name', `items[${rowCount}][quantity]`);
// Desbloquear opciones en la nueva fila (vital)
newRow.find('option').prop('disabled', false);
container.append(newRow);
initSelect2(); // Activar nueva
actualizarBloqueos(); // Recalcular bloqueos
actualizarContadorItems();
});
// --- ELIMINAR FILA ---
$(document).on('click', '.btn-remove', function() {
if ($('.item-row').length > 1) {
$(this).closest('.item-row').remove();
recalcularTodo();
actualizarBloqueos();
actualizarContadorItems();
} else {
alert("Debe haber al menos un producto.");
}
});
// --- EVENTOS ---
$(document).on('select2:select input', '.product-select, .quantity-input', function() {
let row = $(this).closest('.item-row');
calcularFila(row);
recalcularTodo();
if ($(this).hasClass('product-select')) {
actualizarBloqueos();
}
});
});
// --- FUNCIONES ---
function initSelect2() {
$('.select2-basic').select2({ width: '100%' });
$('.select2-product').select2({
width: '100%',
placeholder: "Buscar producto...",
language: { noResults: () => "Sin resultados" }
});
}
function calcularFila(row) {
let select = row.find('.product-select');
let input = row.find('.quantity-input');
let option = select.find(':selected');
let price = parseFloat(option.data('price')) || 0;
let qty = parseInt(input.val()) || 0;
let subtotal = price * qty;
row.find('.row-total').text('$' + subtotal.toFixed(2));
}
function recalcularTodo() {
let grandTotal = 0;
$('.item-row').each(function() {
let row = $(this);
let price = parseFloat(row.find('.product-select').find(':selected').data('price')) || 0;
let qty = parseInt(row.find('.quantity-input').val()) || 0;
grandTotal += (price * qty);
});
$('#grand-total').text('$' + grandTotal.toFixed(2));
}
function actualizarContadorItems() {
$('#item-count').text($('.item-row').length);
}
function actualizarBloqueos() {
let seleccionados = [];
$('.product-select').each(function() {
let valor = $(this).val();
if (valor) seleccionados.push(valor);
});
$('.product-select').each(function() {
let selectActual = $(this);
let miValor = selectActual.val();
selectActual.find('option').each(function() {
let opcion = $(this);
let valorOpcion = opcion.val();
if (!valorOpcion) return;
if (seleccionados.includes(valorOpcion) && valorOpcion != miValor) {
opcion.prop('disabled', true);
} else {
opcion.prop('disabled', false);
}
});
});
}
</script>
</x-layout>
+42
View File
@@ -0,0 +1,42 @@
<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>
</tbody>
</table>
</div>
<!-- Paginación -->
<div class="mt-4 w-full flex justify-center">
</div>
</x-layout>
+149
View File
@@ -0,0 +1,149 @@
<x-layout title="Detalle de Venta #{{ $sale->id }}">
<!-- Mensajes de Alerta -->
<x-ui.alert />
<!-- Encabezado de Navegación -->
<div class="w-full flex justify-between items-center mb-8 print:hidden">
<a href="{{ route('sales.index') }}" class="text-gray-400 hover:text-white flex items-center gap-2 transition-colors">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
Volver al Historial
</a>
<button onclick="window.print()" class="px-5 py-2.5 bg-neutral-800 text-neon-lime border border-neon-lime/30 font-bold rounded-lg hover:bg-neon-lime hover:text-neutral-900 transition-colors 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="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z"></path></svg>
Imprimir / Guardar PDF
</button>
</div>
<!-- Contenedor "Hoja" de la Factura -->
<div class="max-w-3xl mx-auto bg-white text-neutral-900 rounded-xl shadow-2xl overflow-hidden print:shadow-none print:w-full print:max-w-none">
<!-- Cabecera Factura -->
<div class="p-8 border-b-2 border-gray-100 flex justify-between items-start bg-gray-50 print:bg-white">
<div>
<!-- Logo Simulado -->
<div class="flex items-center gap-2 mb-4">
<div class="h-8 w-8 bg-neutral-900 rounded-full flex items-center justify-center text-neon-lime">
<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="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>
</div>
<span class="text-2xl font-black uppercase tracking-wide text-neutral-900">
CICLES<span class="text-lime-800"> LAUCK</span>
</span>
</div>
<p class="text-sm text-gray-500">
Av. Francisco Ramírez 1389<br>
Paraná, Entre Ríos<br>
Tel: 343 422-0103
</p>
</div>
<div class="text-right">
<h2 class="text-sm font-bold text-gray-400 uppercase tracking-widest mb-1">Comprobante de Venta</h2>
<div class="text-4xl font-mono font-bold text-neutral-900">#{{ str_pad($sale->id, 5, '0', STR_PAD_LEFT) }}</div>
<div class="mt-2 text-sm text-gray-600">
Fecha: <strong>{{ $sale->created_at->format('d/m/Y') }}</strong><br>
Hora: {{ $sale->created_at->format('H:i') }} hs
</div>
</div>
</div>
<!-- Datos del Cliente -->
<div class="p-8 grid grid-cols-2 gap-8">
<div>
<h3 class="text-xs font-bold text-gray-400 uppercase mb-2">Facturado A:</h3>
@if($sale->client)
<p class="font-bold text-lg">{{ $sale->client->name }}</p>
<p class="text-gray-600 text-sm">{{ $sale->client->phone }}</p>
<p class="text-gray-600 text-sm">{{ $sale->client->email }}</p>
@if($sale->client->address)
<p class="text-gray-600 text-sm mt-1">{{ $sale->client->address }}</p>
@endif
@else
<p class="font-bold text-lg text-gray-500 italic">Consumidor Final</p>
<p class="text-gray-400 text-sm">Venta anónima de mostrador</p>
@endif
</div>
<div class="text-right">
<h3 class="text-xs font-bold text-gray-400 uppercase mb-2">Método de Pago:</h3>
<span class="inline-block bg-gray-100 text-gray-800 text-sm font-bold px-3 py-1 rounded border border-gray-200">
{{ $sale->payment_method }}
</span>
</div>
</div>
<!-- Tabla de Detalles -->
<div class="px-8 pb-8">
<table class="w-full text-sm text-left">
<thead class="bg-gray-100 text-gray-600 uppercase font-bold text-xs">
<tr>
<th class="px-4 py-3 rounded-l-lg">Producto</th>
<th class="px-4 py-3 text-right">Precio Unit.</th>
<th class="px-4 py-3 text-center">Cant.</th>
<th class="px-4 py-3 text-right rounded-r-lg">Subtotal</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
@foreach($sale->details as $detail)
<tr>
<td class="px-4 py-4">
<div class="font-bold text-gray-900">{{ $detail->product->name }}</div>
<div class="text-xs text-gray-500 font-mono">{{ $detail->product->sku }}</div>
</td>
<td class="px-4 py-4 text-right font-mono text-gray-600">
${{ number_format($detail->price, 2, ',', '.') }}
</td>
<td class="px-4 py-4 text-center font-bold text-gray-900">
{{ $detail->quantity }}
</td>
<td class="px-4 py-4 text-right font-mono font-bold text-gray-900">
${{ number_format($detail->price * $detail->quantity, 2, ',', '.') }}
</td>
</tr>
@endforeach
</tbody>
<tfoot class="border-t-2 border-gray-100">
<tr>
<td colspan="3" class="px-4 pt-6 text-right text-gray-500 uppercase font-bold text-sm">Total a Pagar</td>
<td class="px-4 pt-6 text-right text-3xl font-black text-gray-900">
${{ number_format($sale->total, 2, ',', '.') }}
</td>
</tr>
</tfoot>
</table>
</div>
<!-- Footer Factura -->
<div class="bg-gray-50 p-6 text-center border-t border-gray-100 print:bg-white">
<p class="text-sm text-gray-500 font-medium">¡Gracias por tu compra!</p>
<p class="text-xs text-gray-400 mt-1">Documento no valido como factura.</p>
</div>
</div>
<!-- Estilos específicos para impresión -->
<style>
@media print {
body * {
visibility: hidden;
}
.max-w-3xl, .max-w-3xl * {
visibility: visible;
}
.max-w-3xl {
position: absolute;
left: 0;
top: 0;
width: 100%;
margin: 0;
box-shadow: none;
}
/* Ocultar elementos del layout */
nav, footer, header {
display: none !important;
}
}
</style>
</x-layout>
+9 -4
View File
@@ -1,5 +1,10 @@
<x-appc> <x-layout title="Home - Lauck">
<x-slot name="title">Lauck - Inicio</x-slot> <x-section-header subtitle="Inicio" title="Bicicleteria " highlight="Lauck"/>
<x-slot name="navTitle">Bienvenido</x-slot>
</x-appc>
<!-- CARROUSEL DESTACADO -->
<div class="w-full mb-12">
<!-- datos del controlador -->
<x-ui.carrousel :items="$destacados" />
</div>
</x-layout>
+18 -20
View File
@@ -1,6 +1,9 @@
<?php <?php
use App\Http\Controllers\AuthController; use App\Http\Controllers\AuthController;
use App\Http\Controllers\CatalogoController;
use App\Http\Controllers\SaleController;
use App\Http\Controllers\ClientController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use App\Http\Controllers\HomeController; use App\Http\Controllers\HomeController;
use App\Http\Controllers\LoginController; use App\Http\Controllers\LoginController;
@@ -12,17 +15,14 @@ use Illuminate\Support\Facades\Session;
Route::get('/',HomeController::class); Route::get('/',HomeController::class);
Route::get('login', function(){ // Route::get('/catalogo', [CatalogoController::class,'catalogo'])->name('catalogo');
return view('login'); Route::resource('catalogo', CatalogoController::class)->only(['index', 'show'])->parameters(['catalogo' => 'product']);
})->name('login');
Route::post('login', LoginController::class) Route::get('login', function(){ return view('login'); })->name('login');
->middleware('throttle:5,1') Route::post('login', LoginController::class)->middleware('throttle:5,1')->name('login.attempt');
->name('login.attempt');
Route::view('dashboard', 'dashboard') Route::view('register', 'register')->name('register');
->middleware('auth') Route::post('register', RegisterController::class)->name('register.store');
->name('dashboard');
Route::post('logout', function(){ Route::post('logout', function(){
Auth::guard('web')->logout(); Auth::guard('web')->logout();
@@ -33,17 +33,15 @@ Route::post('logout', function(){
return redirect('/'); return redirect('/');
})->name('logout'); })->name('logout');
Route::view('register', 'register')->name('register');
Route::post('register', RegisterController::class)->name('register.store');
Route::middleware(['auth'])->group(function () { Route::middleware(['auth'])->group(function () {
Route::get('/productos',[ProductosController::class,'index'])->name('productos.index'); Route::view('dashboard', 'dashboard')->name('dashboard');
Route::get('/productos/create',[ProductosController::class,'create'])->name('productos.create'); Route::resource('clients', ClientController::class);
Route::get('/productos/{id}', [ProductosController::class,'show'])->name('productos.show'); Route::resource('productos', ProductosController::class)->parameters([
Route::get('/productos/{id}/edit', [ProductosController::class,'edit'])->name('productos.edit'); 'productos' => 'product'
Route::post('/productos',[ProductosController::class,'store'])->name('productos.store'); ]);
Route::put('/productos/{id}',[ProductosController::class,'update'])->name('productos.update'); Route::resource('sales', SaleController::class)->only(['index', 'create', 'store', 'show']);
Route::delete('/productos/{id}', [ProductosController::class,'destroy'])->name('productos.destroy');
}); });
Route::view('catalogo','catalogo')->name('catalogo');
Route::get('/productos/{id}/vistaUsuario', [ProductosController::class,'vistaUsuario'])->name('productos.vistaUsuario');
+22
View File
@@ -0,0 +1,22 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./resources/**/*.blade.php",
"./resources/**/*.js",
"./resources/**/*.vue",
],
theme: {
extend: {
fontFamily: {
sans: ['Montserrat', 'sans-serif'], // Define Montserrat como fuente principal
},
colors: {
// Tus colores personalizados
'neon-lime': '#ccff00',
'dark-bg': '#1a1a1a',
'panel-bg': '#242424',
}
},
},
plugins: [],
}