Merge branch 'bryam' of https://github.com/BryamE/ProyectoLauck into bryam
This commit is contained in:
@@ -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'));
|
||||
}
|
||||
}
|
||||
@@ -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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,23 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Product;
|
||||
|
||||
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'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,62 +4,169 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Product;
|
||||
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
|
||||
{
|
||||
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'));
|
||||
}
|
||||
|
||||
public function create(){
|
||||
/**
|
||||
* Muestra el formulario de creación.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
return view('productos.create');
|
||||
}
|
||||
|
||||
public function show($id){
|
||||
$producto = Product::find($id);
|
||||
return view('productos.show', compact('producto'));
|
||||
}
|
||||
|
||||
public function edit($id){
|
||||
$producto = Product::find($id);
|
||||
return view('productos.edit', compact('producto'));
|
||||
}
|
||||
|
||||
public function store(Request $request){
|
||||
$request->validate([
|
||||
'nombre' => 'required',
|
||||
'marca' => 'required',
|
||||
'modelo' => 'required',
|
||||
'descripcion' => 'required',
|
||||
'rodado' => 'required',
|
||||
'color' => 'required',
|
||||
'tipo' => 'required',
|
||||
'precio' => 'required'
|
||||
/**
|
||||
* Guarda el producto nuevo en la base de datos.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
// 1. Validamos los datos con las nuevas columnas
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'sku' => 'nullable|string|unique:products,sku|max:50', // SKU único
|
||||
'description' => 'nullable|string',
|
||||
'price' => 'required|numeric|min:0',
|
||||
'cost' => 'nullable|numeric|min:0', // Costo opcional
|
||||
'stock_quantity' => 'required|integer|min:0',
|
||||
'min_stock_alert' => 'required|integer|min:0',
|
||||
'type' => 'required|in:bike,accessory,service', // Solo permite estos 3 valores
|
||||
'serial_number' => 'nullable|string|max:100',
|
||||
'image' => 'nullable|image|mimes:jpeg,png,jpg,webp|max:2048',
|
||||
]);
|
||||
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){
|
||||
$request->validate([
|
||||
'nombre' => 'required',
|
||||
'marca' => 'required',
|
||||
'modelo' => 'required',
|
||||
'descripcion' => 'required',
|
||||
'rodado' => 'required',
|
||||
'color' => 'required',
|
||||
'tipo' => 'required',
|
||||
'precio' => 'required'
|
||||
// 3. Validacion de imagenes
|
||||
if ($request->hasFile('image')) {
|
||||
// Guarda el archivo en storage/app/public/products y devuelve la ruta
|
||||
$path = $request->file('image')->store('products', 'public');
|
||||
$validated['image_path'] = $path;
|
||||
}
|
||||
|
||||
unset($validated['image']);
|
||||
|
||||
// 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){
|
||||
$producto = Product::find($id);
|
||||
$producto->delete();
|
||||
return redirect(route('productos.index'));
|
||||
// Guardar la nueva
|
||||
$path = $request->file('image')->store('products', 'public');
|
||||
$validated['image_path'] = $path;
|
||||
}
|
||||
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.');
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ class RegisterController extends Controller
|
||||
{
|
||||
$userData = $request->validate([
|
||||
'name' => ['required', 'string'],
|
||||
'email' => ['required', 'email'],
|
||||
'email' => ['required', 'email', 'unique:users,email'],
|
||||
'password' => ['required', 'confirmed']
|
||||
]);
|
||||
|
||||
|
||||
@@ -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'));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -8,14 +8,33 @@ use Illuminate\Database\Eloquent\Model;
|
||||
class Product extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
protected $fillable = [
|
||||
'nombre',
|
||||
'marca',
|
||||
'modelo',
|
||||
'rodado',
|
||||
'color',
|
||||
'tipo',
|
||||
'descripcion',
|
||||
'precio'
|
||||
];
|
||||
protected $guarded = [];
|
||||
|
||||
|
||||
public function hasLowStock(): bool
|
||||
{
|
||||
return $this->stock_quantity <= $this->min_stock_alert;
|
||||
}
|
||||
|
||||
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.**/
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -16,15 +16,39 @@ class ProductFactory extends Factory
|
||||
*/
|
||||
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 [
|
||||
'nombre' => $this->faker->sentence(3),
|
||||
'marca'=> $this->faker->randomElement(['Cube','Giant','Megamo','KTM','MMR','Liv','Ghost','Lapierre']),
|
||||
'modelo'=> $this->faker->bothify('????-####'),
|
||||
'rodado'=> $this->faker->numberBetween(12,30),
|
||||
'color'=> $this->faker->safeColorName(),
|
||||
'tipo'=> $this->faker->word(),
|
||||
'descripcion'=> $this->faker->text(),
|
||||
'precio'=> $this->faker->randomFloat(2,100,200)
|
||||
'name' => $name,
|
||||
'sku' => strtoupper($this->faker->bothify('???-#####')),
|
||||
'description' => $this->faker->sentence(10),
|
||||
'price' => $price,
|
||||
'cost' => $cost,
|
||||
'stock_quantity' => $type === 'service' ? 0 : $this->faker->numberBetween(0, 50),
|
||||
'min_stock_alert' => $this->faker->numberBetween(2, 10),
|
||||
'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');
|
||||
});
|
||||
}
|
||||
};
|
||||
+6
-10
@@ -11,16 +11,12 @@ return new class extends Migration
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('products', function (Blueprint $table) {
|
||||
Schema::create('clients', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('nombre');
|
||||
$table->string('marca');
|
||||
$table->string('modelo');
|
||||
$table->string('rodado');
|
||||
$table->string('color');
|
||||
$table->string('tipo');
|
||||
$table->text('descripcion');
|
||||
$table->float('precio');
|
||||
$table->string('name');
|
||||
$table->string('phone')->nullable(); // Clave para WhatsApp
|
||||
$table->string('email')->nullable();
|
||||
$table->text('address')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
@@ -30,6 +26,6 @@ return new class extends Migration
|
||||
*/
|
||||
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');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -2,26 +2,80 @@
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\Product;
|
||||
use App\Models\User;
|
||||
// use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
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
|
||||
{
|
||||
/**
|
||||
* Seed the application's database.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
User::factory(10)->create();
|
||||
|
||||
//Crear el Super Admin
|
||||
User::factory()->create([
|
||||
'name' => 'admin',
|
||||
'email' => 'admin@laravel.com',
|
||||
'password' => 'admin'
|
||||
'name' => 'Jose Admin',
|
||||
'email' => 'admin@lauck.com',
|
||||
'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();
|
||||
|
||||
// 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'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+1499
-263
File diff suppressed because it is too large
Load Diff
+9
-2
@@ -7,11 +7,18 @@
|
||||
"dev": "vite"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"autoprefixer": "^10.4.22",
|
||||
"axios": "^1.8.2",
|
||||
"concurrently": "^9.0.1",
|
||||
"laravel-vite-plugin": "^2.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"postcss": "^8.5.6",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"vite": "^7.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/postcss": "^4.1.18",
|
||||
"jquery": "^3.7.1",
|
||||
"select2": "^4.1.0-rc.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 '../../storage/framework/views/*.php';
|
||||
@source '../**/*.blade.php';
|
||||
@source '../**/*.js';
|
||||
|
||||
/* 3. Configuración del Tema (NUEVO EN v4) */
|
||||
@theme {
|
||||
--font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||
'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
/* Definimos el color personalizado 'dark-bg' */
|
||||
--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 {
|
||||
0%{ background-position: 0% 50%;}
|
||||
50%{ background-position: 100% 50%;}
|
||||
100%{ background-position: 0% 50%;}
|
||||
/* 4. Patrón de fondo estilo "Técnico" */
|
||||
.bg-grid-pattern {
|
||||
/* CAMBIO: Usamos rgba(255,255,255, 0.1) para que las líneas sean claras sobre fondo oscuro */
|
||||
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 */
|
||||
}
|
||||
@@ -1 +1,13 @@
|
||||
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';
|
||||
|
||||
Vendored
+7
@@ -2,3 +2,10 @@ import axios from 'axios';
|
||||
window.axios = axios;
|
||||
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
|
||||
/**
|
||||
* Configuración de jQuery global (window.$)
|
||||
*/
|
||||
import jQuery from 'jquery';
|
||||
window.$ = jQuery;
|
||||
window.jQuery = jQuery;
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<x-app>
|
||||
|
||||
|
||||
|
||||
|
||||
</x-app>
|
||||
@@ -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>
|
||||
@@ -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> --}}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1,103 +1,123 @@
|
||||
@props(['auth' => false])
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<html lang="es">
|
||||
<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>{{ $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>
|
||||
|
||||
<body class="bg-neutral-900 relative pb-50 min-h-screen">
|
||||
<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">
|
||||
<a href="https://flowbite.com/" class="flex items-center space-x-3 rtl:space-x-reverse">
|
||||
<img src="https://flowbite.com/docs/images/logo.svg" class="h-8" alt="Flowbite Logo" />
|
||||
<span
|
||||
class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">{{ $navTitle ?? 'Lauck Home' }}</span>
|
||||
<body class="bg-dark-bg text-gray-300 font-sans antialiased min-h-screen flex flex-col relative overflow-x-hidden">
|
||||
|
||||
<!-- Fondo decorativo técnico -->
|
||||
<div class="absolute inset-0 bg-grid-pattern opacity-[0.07] z-0 pointer-events-none"></div>
|
||||
|
||||
<!-- NAVBAR -->
|
||||
<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>
|
||||
<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"
|
||||
aria-controls="navbar-default" aria-expanded="false">
|
||||
<span class="sr-only">Open main menu</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" />
|
||||
|
||||
<!-- 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">
|
||||
@if ($auth)
|
||||
<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">
|
||||
<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">
|
||||
<li>
|
||||
<a href="{{ route('dashboard') }}"
|
||||
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>
|
||||
<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>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ route('productos.index') }}"
|
||||
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>
|
||||
<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>
|
||||
</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">Services</a>
|
||||
<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>
|
||||
</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">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>
|
||||
<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>
|
||||
</li>
|
||||
</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>
|
||||
</nav>
|
||||
<!-- MAIN CONTENT -->
|
||||
{{ $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">
|
||||
<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" />
|
||||
<span class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">Flowbite</span>
|
||||
|
||||
<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">
|
||||
<!-- 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>
|
||||
<ul
|
||||
class="flex flex-wrap items-center mb-6 text-sm font-medium text-gray-500 sm:mb-0 dark:text-gray-400">
|
||||
<li>
|
||||
<a href="#" class="hover:underline me-4 md:me-6">About</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 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 me-4 md:me-6 transition-colors">Privacidad</a></li>
|
||||
<li><a href="#" class="hover:text-neon-lime hover:underline transition-colors">Contacto</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<hr class="my-6 border-gray-200 sm:mx-auto dark:border-gray-700 lg:my-8" />
|
||||
<span class="block text-sm text-gray-500 sm:text-center dark:text-gray-400">© 2023 <a
|
||||
href="https://flowbite.com/" class="hover:underline">Flowbite™</a>. All Rights Reserved.</span>
|
||||
<hr class="my-6 border-neutral-800 sm:mx-auto lg:my-8">
|
||||
<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>
|
||||
</div>
|
||||
</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>
|
||||
@@ -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>
|
||||
@@ -1,6 +1,49 @@
|
||||
<x-layout title="Dashboard - Lauck">
|
||||
|
||||
<H1>BIENVENIDO {{Auth::user()->name}} </H1>
|
||||
<form action="{{route('logout')}}" method="post">
|
||||
<!-- Header -->
|
||||
<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
|
||||
<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>
|
||||
</div>
|
||||
|
||||
</x-layout>
|
||||
@@ -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>
|
||||
@@ -1,48 +1,45 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<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">
|
||||
<x-layout title="Login - Lauck">
|
||||
<div class="flex flex-col items-center space-y-6 w-full max-w-lg p-6">
|
||||
<!-- 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">
|
||||
@csrf
|
||||
{{-- No muestra los errores, ver --}}
|
||||
{{-- No muestra los errores, ver --}}
|
||||
{{-- Bloque de errores de validación y autenticación --}}
|
||||
@if ($errors->any())
|
||||
<div>
|
||||
<ul>
|
||||
@foreach ($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
<div class="bg-red-900 border border-red-700 text-white p-3 rounded-md">
|
||||
{{-- Muestra el primer mensaje de error sin lista --}}
|
||||
<p class="text-sm font-medium">
|
||||
{{ $errors->first() }}
|
||||
</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<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">
|
||||
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>
|
||||
<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">
|
||||
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>
|
||||
<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>
|
||||
|
||||
<p class="text-center text-sm text-gray-600">
|
||||
@@ -51,7 +48,4 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</x-layout>
|
||||
@@ -1,61 +1,82 @@
|
||||
<x-appc>
|
||||
<x-slot name="title">Lauck - Agregar</x-slot>
|
||||
<x-slot name="navTitle">Agregar - Producto</x-slot>
|
||||
<x-layout title="Lauck - Nuevo Producto">
|
||||
|
||||
@if ($errors->any())
|
||||
<x-alert type="danger">
|
||||
<x-slot name="title">Error: </x-slot>
|
||||
<x-slot name="content">Todos los campos son obligatorios.</x-slot>
|
||||
</x-alert>
|
||||
@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">
|
||||
<x-section-header subtitle="Inventario" title="Nuevo " 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.store') }}" method="POST" enctype="multipart/form-data">
|
||||
@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=" "/>
|
||||
<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>
|
||||
|
||||
<!-- Grid Layout -->
|
||||
<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 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=" "/>
|
||||
<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>
|
||||
|
||||
<!-- 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="old('sku')" placeholder="Dejar vacío para generar auto" :error="$errors->first('sku')" />
|
||||
</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=" "/>
|
||||
<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>
|
||||
|
||||
<!-- 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" {{ 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 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>
|
||||
<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>
|
||||
|
||||
<!-- Precios -->
|
||||
<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 class="grid md:grid-cols-2 md:gap-6">
|
||||
<div class="relative z-0 w-full mb-5 group">
|
||||
<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=" "/>
|
||||
<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 class="">
|
||||
<x-forms.label for="cost" value="Costo ($) - Solo Admin" />
|
||||
<x-forms.input id="cost" name="cost" type="number" step="0.01" :value="old('cost')" :error="$errors->first('cost')" />
|
||||
</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=" "/>
|
||||
<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>
|
||||
|
||||
<!-- Stock -->
|
||||
<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 class="grid md:grid-cols-2 md:gap-6">
|
||||
<div class="relative z-0 w-full mb-5 group">
|
||||
<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=" "/>
|
||||
<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 class="relative z-0 w-full mb-5 group">
|
||||
<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
|
||||
|
||||
<!-- 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>
|
||||
<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>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</x-appc>
|
||||
</x-layout>
|
||||
@@ -1,44 +1,44 @@
|
||||
<x-appc>
|
||||
{{-- <x-appc>
|
||||
<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">
|
||||
<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
|
||||
@method('PUT')
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</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">{{$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>
|
||||
</div>
|
||||
<div class="grid md:grid-cols-2 md:gap-6">
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid md:grid-cols-2 md:gap-6">
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</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">
|
||||
Editar
|
||||
</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
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</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>
|
||||
@@ -1,44 +1,97 @@
|
||||
<x-appc>
|
||||
<x-slot name="title">Lauck - Home - Productos</x-slot>
|
||||
<x-slot name="navTitle">Productos</x-slot>
|
||||
<x-layout title="Lauck - Stock">
|
||||
|
||||
<div class="w-6xl mx-auto flex flex-col justify-start items-stretch gap-5 my-5">
|
||||
<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
|
||||
<x-section-header subtitle="Gestión de Inventario" title="Listado de " highlight="Productos" />
|
||||
|
||||
<!-- 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>
|
||||
<div class="overflow-x-auto rounded-lg shadow-xl">
|
||||
<table class="w-full min-w-xl text-sm text-left rtl:text-right text-neutral-500 dark:text-neutral-400">
|
||||
<thead class="text-xs text-neutral-700 uppercase bg-neutral-50 dark:bg-neutral-700 dark:text-neutral-400">
|
||||
</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">Nombre</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">Producto / SKU</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 text-center">Stock</th>
|
||||
<th scope="col" class="px-6 py-3 text-right">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($products as $prod)
|
||||
<tr class="bg-white border-b dark:bg-neutral-800 dark:border-neutral-700 border-neutral-200">
|
||||
<th scope="row"
|
||||
class="px-6 py-4 font-medium text-neutral-900 whitespace-nowrap dark:text-white">
|
||||
<a href="{{route('productos.show',$prod)}}">{{ $prod->nombre }}</a>
|
||||
</th>
|
||||
<td class="px-6 py-4">{{ $prod->marca }}</td>
|
||||
<td class="px-6 py-4">{{ $prod->modelo }}</td>
|
||||
<td class="px-6 py-4">{{ $prod->rodado }}</td>
|
||||
<td class="px-6 py-4">{{ $prod->color }}</td>
|
||||
<td class="px-6 py-4">{{ $prod->tipo }}</td>
|
||||
<td class="px-6 py-4">{{ $prod->precio }}</td>
|
||||
@forelse($products 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-base font-bold">{{ $product->name }}</div>
|
||||
<div class="text-xs text-gray-500 font-mono">{{ $product->sku }}</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
@if($product->type === 'bike') <x-ui.badge color="neon">Bicicleta</x-ui.badge>
|
||||
@elseif($product->type === 'accessory') <x-ui.badge color="gray">Accesorio</x-ui.badge>
|
||||
@else <x-ui.badge color="yellow">Servicio</x-ui.badge> @endif
|
||||
</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>
|
||||
@endforeach
|
||||
@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>
|
||||
|
||||
{{$products->links()}}
|
||||
<!-- Paginación -->
|
||||
<div class="mt-4 w-full">
|
||||
{{ $products->links() }}
|
||||
</div>
|
||||
|
||||
</x-appc>
|
||||
</x-layout>
|
||||
@@ -1,42 +1,43 @@
|
||||
<x-appc>
|
||||
<x-slot name="title">Lauck - Vista - Producto</x-slot>
|
||||
<x-slot name="navTitle">Producto - {{$producto->nombre}}</x-slot>
|
||||
<x-layout title="Lauck - Ver Producto">
|
||||
|
||||
<!-- 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">
|
||||
<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">
|
||||
<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 class="flex flex-col pt-3">
|
||||
<dt class="mb-1 text-gray-500 md:text-lg dark:text-gray-400">Modelo</dt>
|
||||
<dd class="text-lg font-semibold">{{$producto->modelo}}</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>
|
||||
<dt class="mb-1 text-gray-500 md:text-lg dark:text-gray-400">SKU</dt>
|
||||
<dd class="text-lg font-semibold">{{$product->sku}}</dd>
|
||||
</div>
|
||||
<div class="flex flex-col pt-3">
|
||||
<dt class="mb-1 text-gray-500 md:text-lg dark:text-gray-400">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 class="flex flex-col pt-3">
|
||||
<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>
|
||||
</dl>
|
||||
<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.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>
|
||||
<form action="{{route('productos.destroy',$producto)}}" method="post">
|
||||
<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">
|
||||
@@ -46,11 +47,4 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</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> --}}
|
||||
</x-layout>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1,48 +1,52 @@
|
||||
<!doctype html>
|
||||
<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">
|
||||
<x-layout title="Registrarse - Lauck">
|
||||
|
||||
<div class="flex flex-col items-center space-y-6 w-full max-w-lg">
|
||||
<!-- 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">
|
||||
<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>
|
||||
<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
|
||||
@if ($errors->any())
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 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">
|
||||
<ul>
|
||||
@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>
|
||||
<label class="block text-sm font-medium text-gray-700 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">
|
||||
<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">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 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">
|
||||
<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">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 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">
|
||||
<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">
|
||||
</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>
|
||||
|
||||
<p class="text-center text-sm text-gray-600">
|
||||
@@ -52,5 +56,4 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</x-layout>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -1,5 +1,10 @@
|
||||
<x-appc>
|
||||
<x-slot name="title">Lauck - Inicio</x-slot>
|
||||
<x-slot name="navTitle">Bienvenido</x-slot>
|
||||
</x-appc>
|
||||
<x-layout title="Home - Lauck">
|
||||
<x-section-header subtitle="Inicio" title="Bicicleteria " highlight="Lauck"/>
|
||||
|
||||
<!-- CARROUSEL DESTACADO -->
|
||||
<div class="w-full mb-12">
|
||||
<!-- datos del controlador -->
|
||||
<x-ui.carrousel :items="$destacados" />
|
||||
</div>
|
||||
|
||||
</x-layout>
|
||||
|
||||
+18
-20
@@ -1,6 +1,9 @@
|
||||
<?php
|
||||
|
||||
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 App\Http\Controllers\HomeController;
|
||||
use App\Http\Controllers\LoginController;
|
||||
@@ -12,17 +15,14 @@ use Illuminate\Support\Facades\Session;
|
||||
|
||||
Route::get('/',HomeController::class);
|
||||
|
||||
Route::get('login', function(){
|
||||
return view('login');
|
||||
})->name('login');
|
||||
// Route::get('/catalogo', [CatalogoController::class,'catalogo'])->name('catalogo');
|
||||
Route::resource('catalogo', CatalogoController::class)->only(['index', 'show'])->parameters(['catalogo' => 'product']);
|
||||
|
||||
Route::post('login', LoginController::class)
|
||||
->middleware('throttle:5,1')
|
||||
->name('login.attempt');
|
||||
Route::get('login', function(){ return view('login'); })->name('login');
|
||||
Route::post('login', LoginController::class)->middleware('throttle:5,1')->name('login.attempt');
|
||||
|
||||
Route::view('dashboard', 'dashboard')
|
||||
->middleware('auth')
|
||||
->name('dashboard');
|
||||
Route::view('register', 'register')->name('register');
|
||||
Route::post('register', RegisterController::class)->name('register.store');
|
||||
|
||||
Route::post('logout', function(){
|
||||
Auth::guard('web')->logout();
|
||||
@@ -33,17 +33,15 @@ Route::post('logout', function(){
|
||||
return redirect('/');
|
||||
})->name('logout');
|
||||
|
||||
Route::view('register', 'register')->name('register');
|
||||
Route::post('register', RegisterController::class)->name('register.store');
|
||||
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::get('/productos',[ProductosController::class,'index'])->name('productos.index');
|
||||
Route::get('/productos/create',[ProductosController::class,'create'])->name('productos.create');
|
||||
Route::get('/productos/{id}', [ProductosController::class,'show'])->name('productos.show');
|
||||
Route::get('/productos/{id}/edit', [ProductosController::class,'edit'])->name('productos.edit');
|
||||
Route::post('/productos',[ProductosController::class,'store'])->name('productos.store');
|
||||
Route::put('/productos/{id}',[ProductosController::class,'update'])->name('productos.update');
|
||||
Route::delete('/productos/{id}', [ProductosController::class,'destroy'])->name('productos.destroy');
|
||||
Route::view('dashboard', 'dashboard')->name('dashboard');
|
||||
Route::resource('clients', ClientController::class);
|
||||
Route::resource('productos', ProductosController::class)->parameters([
|
||||
'productos' => 'product'
|
||||
]);
|
||||
Route::resource('sales', SaleController::class)->only(['index', 'create', 'store', 'show']);
|
||||
|
||||
});
|
||||
|
||||
Route::view('catalogo','catalogo')->name('catalogo');
|
||||
|
||||
Route::get('/productos/{id}/vistaUsuario', [ProductosController::class,'vistaUsuario'])->name('productos.vistaUsuario');
|
||||
@@ -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: [],
|
||||
}
|
||||
Reference in New Issue
Block a user