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

This commit is contained in:
gianella
2026-05-27 15:42:51 -03:00
30 changed files with 826 additions and 160 deletions
+6 -2
View File
@@ -40,10 +40,12 @@ class ClientController extends Controller
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'name' => ['required', 'string', 'max:255', 'not_regex:/[0-9]/'],
'phone' => 'nullable|string|max:50',
'email' => 'nullable|email|max:255|unique:clients,email',
'address' => 'nullable|string|max:255',
], [
'name.not_regex' => 'El nombre del cliente no puede contener números.',
]);
// 1. Guardamos el cliente en una variable para tener su ID
@@ -88,10 +90,12 @@ class ClientController extends Controller
public function update(Request $request, Client $client)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'name' => ['required', 'string', 'max:255', 'not_regex:/[0-9]/'],
'phone' => 'nullable|string|max:50',
'email' => 'nullable|email|max:255|unique:clients,email,' . $client->id, // Ignorar email propio
'address' => 'nullable|string|max:255',
], [
'name.not_regex' => 'El nombre del cliente no puede contener números.',
]);
$client->update($validated);
@@ -0,0 +1,45 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Support\Str;
class NewPasswordController extends Controller
{
public function create(Request $request, $token)
{
return view('auth.reset-password', ['request' => $request, 'token' => $token]);
}
public function store(Request $request)
{
$request->validate([
'token' => 'required',
'email' => 'required|email',
'password' => 'required|min:8|confirmed',
]);
$status = Password::reset(
$request->only('email', 'password', 'password_confirmation', 'token'),
function ($user, $password) {
$user->forceFill([
'password' => Hash::make($password)
])->setRememberToken(Str::random(60));
$user->save();
event(new PasswordReset($user));
}
);
if ($status == Password::PASSWORD_RESET) {
return redirect()->route('login')->with('status', __($status));
}
return back()->withErrors(['email' => [__($status)]]);
}
}
@@ -0,0 +1,31 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
class PasswordResetLinkController extends Controller
{
public function create()
{
return view('auth.forgot-password');
}
public function store(Request $request)
{
$request->validate([
'email' => 'required|email',
]);
$status = Password::sendResetLink(
$request->only('email')
);
if ($status == Password::RESET_LINK_SENT) {
return back()->with('status', __($status));
}
return back()->withErrors(['email' => __($status)]);
}
}
+12
View File
@@ -6,6 +6,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use App\Notifications\ResetPasswordNotification;
class User extends Authenticatable
{
@@ -45,4 +46,15 @@ class User extends Authenticatable
'password' => 'hashed',
];
}
/**
* Send the password reset notification.
*
* @param string $token
* @return void
*/
public function sendPasswordResetNotification($token)
{
$this->notify(new ResetPasswordNotification($token));
}
}
@@ -0,0 +1,26 @@
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Auth\Notifications\ResetPassword as DefaultResetPassword;
class ResetPasswordNotification extends DefaultResetPassword
{
use Queueable;
public function toMail($notifiable)
{
$url = url(route('password.reset', [
'token' => $this->token,
'email' => $notifiable->getEmailForPasswordReset(),
], false));
return (new MailMessage)
->subject('Recuperación de Contraseña - Lauck')
->view('emails.reset-password', [
'url' => $url,
]);
}
}
@@ -0,0 +1,51 @@
<x-layout title="Recuperar Contraseña - Lauck">
<div class="w-full flex flex-col items-center justify-center pt-8">
<x-section-header
subtitle="Recuperación de cuenta"
title="Bicicletería "
highlight="Lauck"
/>
<div class="bg-gray-100 dark:bg-panel-bg p-8 rounded-xl shadow-lg w-full max-w-md border border-gray-300 dark:border-neutral-700">
<div class="mb-6 text-sm text-gray-600 dark:text-gray-400">
¿Olvidaste tu contraseña? No hay problema. Simplemente déjanos saber tu dirección de correo electrónico y te enviaremos un enlace para restablecer la contraseña que te permitirá elegir una nueva.
</div>
@if (session('status'))
<div class="mb-4 font-medium text-sm text-green-600 dark:text-neon-lime bg-green-100 dark:bg-green-900/30 p-3 rounded-lg border border-green-500/50">
{{ session('status') }}
</div>
@endif
<form method="POST" action="{{ route('password.email') }}" class="space-y-6">
@csrf
@if ($errors->any())
<div class="bg-red-100 dark:bg-red-900/50 border border-red-500 text-red-600 dark:text-red-300 p-3 rounded-lg shadow-sm">
<p class="text-sm font-medium">
{{ $errors->first() }}
</p>
</div>
@endif
<!-- Email Address -->
<div>
<label class="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Correo electrónico</label>
<input type="email" name="email" value="{{ old('email') }}" required autofocus
class="w-full px-4 py-2 bg-white dark:bg-neutral-900 border border-gray-300 dark:border-neutral-700 text-gray-900 dark:text-white rounded-lg focus:outline-none focus:ring-2 focus:ring-neon-lime focus:border-neon-lime transition-colors"/>
</div>
<div class="flex items-center justify-between mt-4">
<a class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors" href="{{ route('login') }}">
Volver a inicio de sesión
</a>
<button type="submit"
class="px-4 py-2 bg-neutral-900 dark:bg-neon-lime text-white dark:text-neutral-900 font-bold rounded-lg hover:bg-neutral-800 hover:dark:bg-[#b3e600] transition-colors shadow-lg dark:shadow-neon-lime/20">
Enviar enlace
</button>
</div>
</form>
</div>
</div>
</x-layout>
@@ -0,0 +1,56 @@
<x-layout title="Restablecer Contraseña - Lauck">
<div class="w-full flex flex-col items-center justify-center pt-8">
<x-section-header
subtitle="Nueva contraseña"
title="Bicicletería "
highlight="Lauck"
/>
<div class="bg-gray-100 dark:bg-panel-bg p-8 rounded-xl shadow-lg w-full max-w-md border border-gray-300 dark:border-neutral-700">
<form method="POST" action="{{ route('password.store') }}" class="space-y-6">
@csrf
<!-- Password Reset Token -->
<input type="hidden" name="token" value="{{ $token }}">
@if ($errors->any())
<div class="bg-red-100 dark:bg-red-900/50 border border-red-500 text-red-600 dark:text-red-300 p-3 rounded-lg shadow-sm">
<ul class="text-sm font-medium list-disc list-inside">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<!-- Email Address -->
<div>
<label class="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Correo electrónico</label>
<input type="email" name="email" value="{{ old('email', $request->email) }}" required autofocus
class="w-full px-4 py-2 bg-gray-200 dark:bg-neutral-800 border border-gray-300 dark:border-neutral-700 text-gray-700 dark:text-gray-400 rounded-lg focus:outline-none" readonly />
</div>
<!-- Password -->
<div>
<label class="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Nueva Contraseña</label>
<input type="password" name="password" required
class="w-full px-4 py-2 bg-white dark:bg-neutral-900 border border-gray-300 dark:border-neutral-700 text-gray-900 dark:text-white rounded-lg focus:outline-none focus:ring-2 focus:ring-neon-lime focus:border-neon-lime transition-colors"/>
</div>
<!-- Confirm Password -->
<div>
<label class="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Confirmar Contraseña</label>
<input type="password" name="password_confirmation" required
class="w-full px-4 py-2 bg-white dark:bg-neutral-900 border border-gray-300 dark:border-neutral-700 text-gray-900 dark:text-white rounded-lg focus:outline-none focus:ring-2 focus:ring-neon-lime focus:border-neon-lime transition-colors"/>
</div>
<div class="flex items-center justify-end mt-4">
<button type="submit"
class="w-full py-2.5 bg-neutral-900 dark:bg-neon-lime text-neon-lime dark:text-neutral-900 font-bold rounded-lg hover:bg-neutral-800 hover:dark:bg-[#b3e600] transition-colors shadow-lg dark:shadow-neon-lime/20 tracking-wide uppercase">
Restablecer Contraseña
</button>
</div>
</form>
</div>
</div>
</x-layout>
+1 -1
View File
@@ -102,7 +102,7 @@
</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">
<form action="{{route('productos.destroy',$product)}}" method="post" class="delete-form">
@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">
+75 -2
View File
@@ -23,9 +23,13 @@
<x-forms.input id="email" name="email" type="email" :value="old('email')" placeholder="cliente@ejemplo.com" :error="$errors->first('email')" />
</div>
<!-- Direccion -->
<div class="md:col-span-4">
<div class="md:col-span-4 relative">
<x-forms.label for="address" value="Dirección / Domicilio" />
<textarea id="address" name="address" rows="3" class="bg-gray-300 dark:bg-neutral-800 border border-neutral-400 dark:border-neutral-700 dark:text-white text-sm rounded-lg focus:ring-neon-lime focus:border-neon-lime block w-full p-2.5 placeholder-neutral-600 dark:placeholder-gray-400" placeholder="Calle, número, piso...">{{ old('address') }}</textarea>
<input type="text" id="address" name="address" value="{{ old('address') }}" class="bg-gray-300 dark:bg-neutral-800 border border-neutral-400 dark:border-neutral-700 dark:text-white text-sm rounded-lg focus:ring-neon-lime focus:border-neon-lime block w-full p-2.5 placeholder-neutral-600 dark:placeholder-gray-400" placeholder="Calle, número, piso..." autocomplete="off">
<!-- Sugerencias -->
<div id="address-suggestions" class="absolute z-50 w-full bg-white dark:bg-neutral-800 border border-gray-300 dark:border-neutral-700 rounded-b-lg shadow-xl max-h-60 overflow-y-auto hidden mt-1"></div>
@error('address')
<span class="text-red-500 text-xs mt-1">{{ $message }}</span>
@enderror
@@ -41,4 +45,73 @@
</form>
</div>
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
const addressInput = document.getElementById('address');
const suggestionsBox = document.getElementById('address-suggestions');
let timeout = null;
addressInput.addEventListener('input', function() {
clearTimeout(timeout);
const query = this.value.trim();
if (query.length < 3) {
suggestionsBox.classList.add('hidden');
suggestionsBox.innerHTML = '';
return;
}
// Esperar 500ms de inactividad antes de buscar (debounce)
timeout = setTimeout(() => {
// Coordenadas Centro de Paraná: -31.73197, -60.52897
const url = `https://photon.komoot.io/api/?q=${encodeURIComponent(query)}&limit=5&lat=-31.73197&lon=-60.52897`;
fetch(url)
.then(response => response.json())
.then(data => {
suggestionsBox.innerHTML = '';
if (data.features && data.features.length > 0) {
data.features.forEach(feature => {
const props = feature.properties;
// Construir dirección legible
// let addressParts = [];
// if (props.name) addressParts.push(props.name);
// if (props.street && props.street !== props.name) addressParts.push(props.street);
// if (props.housenumber && !addressParts.includes(props.housenumber)) addressParts.push(props.housenumber);
// if (props.city || props.town) addressParts.push(props.city || props.town);
const fullAddress = `${props.street || props.name} ${props.housenumber??''}${(props.city || props.town) ? ', '+(props.city || props.town) :''}`;
const div = document.createElement('div');
div.className = 'p-3 hover:bg-gray-100 dark:hover:bg-neutral-700 cursor-pointer text-sm text-gray-900 dark:text-white border-b border-gray-200 dark:border-neutral-700 last:border-0 transition-colors';
div.textContent = fullAddress;
div.addEventListener('click', function() {
addressInput.value = fullAddress;
suggestionsBox.classList.add('hidden');
});
suggestionsBox.appendChild(div);
});
suggestionsBox.classList.remove('hidden');
} else {
suggestionsBox.classList.add('hidden');
}
})
.catch(err => console.error('Error fetching address:', err));
}, 500);
});
// Ocultar si se hace clic fuera del input y de las sugerencias
document.addEventListener('click', function(e) {
if (!addressInput.contains(e.target) && !suggestionsBox.contains(e.target)) {
suggestionsBox.classList.add('hidden');
}
});
});
</script>
@endpush
</x-layout>
+74 -2
View File
@@ -23,10 +23,13 @@
<x-forms.input id="email" name="email" type="email" :value="old('email', $client->email)" placeholder="cliente@ejemplo.com" :error="$errors->first('email')" />
</div>
<!-- Direccion -->
<div class="md:col-span-4">
<div class="md:col-span-4 relative">
<x-forms.label for="address" value="Dirección / Domicilio" />
<textarea id="address" name="address" rows="3" class="bg-gray-300 dark:bg-neutral-800 border border-neutral-400 dark:border-neutral-700 dark: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>
<input type="text" id="address" name="address" value="{{ old('address', $client->address) }}" class="bg-gray-300 dark:bg-neutral-800 border border-neutral-400 dark:border-neutral-700 dark:text-white text-sm rounded-lg focus:ring-neon-lime focus:border-neon-lime block w-full p-2.5 placeholder-neutral-600 dark:placeholder-gray-400" placeholder="Calle, número, piso..." autocomplete="off">
<!-- Sugerencias -->
<div id="address-suggestions" class="absolute z-50 w-full bg-white dark:bg-neutral-800 border border-gray-300 dark:border-neutral-700 rounded-b-lg shadow-xl max-h-60 overflow-y-auto hidden mt-1"></div>
@error('address')
<span class="text-red-500 text-xs mt-1">{{ $message }}</span>
@enderror
@@ -42,4 +45,73 @@
</form>
</div>
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
const addressInput = document.getElementById('address');
const suggestionsBox = document.getElementById('address-suggestions');
let timeout = null;
addressInput.addEventListener('input', function() {
clearTimeout(timeout);
const query = this.value.trim();
if (query.length < 3) {
suggestionsBox.classList.add('hidden');
suggestionsBox.innerHTML = '';
return;
}
// Esperar 500ms de inactividad antes de buscar (debounce)
timeout = setTimeout(() => {
// Coordenadas Centro de Paraná: -31.73197, -60.52897
const url = `https://photon.komoot.io/api/?q=${encodeURIComponent(query)}&limit=5&lat=-31.73197&lon=-60.52897`;
fetch(url)
.then(response => response.json())
.then(data => {
suggestionsBox.innerHTML = '';
if (data.features && data.features.length > 0) {
data.features.forEach(feature => {
const props = feature.properties;
// Construir dirección legible
// let addressParts = [];
// if (props.name) addressParts.push(props.name);
// if (props.housenumber && !addressParts.includes(props.housenumber)) addressParts.push(props.housenumber);
// if (props.street && props.street !== props.name) addressParts.push(props.street);
// if (props.city || props.town) addressParts.push(props.city || props.town);
const fullAddress = `${props.street || props.name} ${props.housenumber??''}${(props.city || props.town) ? ', '+(props.city || props.town) :''}`;
const div = document.createElement('div');
div.className = 'p-3 hover:bg-gray-100 dark:hover:bg-neutral-700 cursor-pointer text-sm text-gray-900 dark:text-white border-b border-gray-200 dark:border-neutral-700 last:border-0 transition-colors';
div.textContent = fullAddress;
div.addEventListener('click', function() {
addressInput.value = fullAddress;
suggestionsBox.classList.add('hidden');
});
suggestionsBox.appendChild(div);
});
suggestionsBox.classList.remove('hidden');
} else {
suggestionsBox.classList.add('hidden');
}
})
.catch(err => console.error('Error fetching address:', err));
}, 500);
});
// Ocultar si se hace clic fuera del input y de las sugerencias
document.addEventListener('click', function(e) {
if (!addressInput.contains(e.target) && !suggestionsBox.contains(e.target)) {
suggestionsBox.classList.add('hidden');
}
});
});
</script>
@endpush
</x-layout>
+3 -7
View File
@@ -3,12 +3,8 @@
<x-section-header subtitle="Gestión de Clientes" title="Cartera de " highlight="Clientes" />
<!-- Mensajes de feedback -->
<x-ui.alert />
@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">
@@ -70,7 +66,7 @@
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m14.304 4.844 2.852 2.852M7 7H4a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h11a1 1 0 0 0 1-1v-4.5m2.409-9.91a2.017 2.017 0 0 1 0 2.853l-6.844 6.844L8 14l.713-3.565 6.844-6.844a2.015 2.015 0 0 1 2.852 0Z"/>
</svg>
</a>
<form action="{{route('clients.destroy',$client)}}" method="post">
<form action="{{route('clients.destroy',$client)}}" method="post" class="delete-form">
@csrf
@method('DELETE')
<button title="Eliminar" type="submit" class="w-full sm:w-auto p-2 font-bold dark:font-medium text-red-600 dark:text-red-400 border-2 border-red-600 rounded-lg hover:bg-red-600 hover:text-white dark:hover:text-white transition-colors">
+13 -14
View File
@@ -5,23 +5,19 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ csrf_token() }}">
<script type="module">
// Revisamos el almacenamiento local o las preferencias del sistema
if (localStorage.getItem('theme') === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
// Función global para cambiar el tema desde cualquier botón
window.toggleTheme = function() {
if (document.documentElement.classList.contains('dark')) {
document.documentElement.classList.remove('dark');
localStorage.setItem('theme', 'light');
} else {
// Revisamos únicamente las preferencias del sistema
function applySystemTheme() {
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.classList.add('dark');
localStorage.setItem('theme', 'dark');
} else {
document.documentElement.classList.remove('dark');
}
}
applySystemTheme();
// Escuchar cambios en la preferencia del sistema
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', applySystemTheme);
</script>
<title>{{ $title ?? 'Lauck Dashboard' }}</title>
@stack('styles')
@@ -46,6 +42,9 @@
<!-- Footer Component -->
<x-footer />
<x-ui.alert />
<x-ui.confirm-delete />
<!-- Scripts globales -->
<script>
// Lógica del menú móvil
+7 -6
View File
@@ -53,14 +53,15 @@
Ventas
</a>
</li>
<li>
<a href="{{ route('faq') }}"
class="block py-2 px-3 md:p-0 transition-colors {{ request()->routeIs('faq') ? 'text-lime-600 dark:text-neon-lime border-b-2 border-lime-600 dark:border-neon-lime' : 'dark:text-white hover:text-lime-600 hover:dark:text-neon-lime' }}">
Ayuda/FAQ
</a>
</li>
@endauth
<button onclick="toggleTheme()" class="p-2 text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white transition-colors rounded-full hover:bg-gray-200 dark:hover:bg-neutral-800">
<!-- Icono de Sol (Se muestra en modo oscuro) -->
<svg class="w-5 h-5 hidden dark:block" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"></path></svg>
<!-- Icono de Luna (Se muestra en modo claro) -->
<svg class="w-5 h-5 block dark:hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path></svg>
</button>
{{-- LÓGICA DE VISITANTE (GUEST) --}}
@guest
@@ -9,33 +9,33 @@
};
@endphp
<div class="bg-panel-bg p-4 rounded-lg border border-neutral-700 border-l-4 {{ $borderColor }} shadow-lg group hover:bg-neutral-800 transition-colors">
<div class="bg-white dark:bg-panel-bg p-4 rounded-lg border border-gray-300 dark:border-neutral-700 border-l-4 {{ $borderColor }} shadow-lg group hover:bg-gray-50 dark:hover:bg-neutral-800 transition-colors">
<!-- Encabezado Tarjeta -->
<div class="flex justify-between items-start mb-2">
<span class="text-xs font-bold text-gray-400">{{ $job->client->name }}</span>
<span class="text-xs font-mono text-gray-500">{{ $job->scheduled_at->format('d/m H:i') }}</span>
<span class="text-xs font-bold text-gray-800 dark:text-gray-300">{{ $job->client->name }}</span>
<span class="text-xs font-mono text-gray-600 dark:text-gray-400">{{ $job->scheduled_at->format('d/m H:i') }}</span>
</div>
<h4 class="text-white font-bold text-sm mb-1">{{ $job->bike_model }}</h4>
<p class="text-gray-400 text-xs line-clamp-2 mb-3">{{ $job->problem_description }}</p>
<h4 class="text-gray-900 dark:text-white font-black text-sm mb-1">{{ $job->bike_model }}</h4>
<p class="text-gray-700 dark:text-gray-300 text-xs line-clamp-2 mb-3">{{ $job->problem_description }}</p>
@if($job->estimated_cost)
<div class="text-right mb-3">
<span class="text-xs text-gray-500">Presupuesto:</span>
<span class="text-sm font-mono text-white font-bold">${{ number_format($job->estimated_cost, 0) }}</span>
<span class="text-xs text-gray-600 dark:text-gray-400">Presupuesto:</span>
<span class="text-sm font-mono text-gray-900 dark:text-white font-black">${{ number_format($job->estimated_cost, 0) }}</span>
</div>
@endif
<!-- Controles de Estado -->
<div class="flex justify-between items-center pt-2 border-t border-neutral-700 mt-2 opacity-100 md:opacity-50 group-hover:opacity-100 transition-opacity">
<div class="flex justify-between items-center pt-2 border-t border-gray-200 dark:border-neutral-700 mt-2 opacity-100 md:opacity-50 group-hover:opacity-100 transition-opacity">
<!-- Botón Mover Atrás -->
@if($job->status !== 'pending')
<form action="{{ route('taller.updateStatus', $job) }}" method="POST">
@csrf @method('PATCH')
<input type="hidden" name="status" value="{{ $job->status === 'ready' ? 'in_progress' : 'pending' }}">
<button class="text-gray-500 hover:text-white p-1" title="Volver al estado anterior">
<button class="text-gray-500 hover:text-gray-900 dark:hover:text-white p-1" title="Volver al estado anterior">
<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="M15 19l-7-7 7-7"></path></svg>
</button>
</form>
@@ -44,9 +44,9 @@
@endif
<!-- Enlace a WhatsApp -->
<a href="https://wa.me/{{ $job->contact_phone }}?text=Hola {{ $job->client->name }}, te escribimos de Lauck para avisarte sobre tu {{ $job->bike_model }}..." target="_blank" class="text-green-500 hover:text-green-400">
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24"><path d="M17.472 14.382c..."></path></svg> <!-- (Usa el mismo SVG de whatsapp de antes) -->
<span class="text-xs ml-1">Avisar</span>
<a href="https://wa.me/{{ $job->contact_phone }}?text=Hola {{ $job->client->name }}, te escribimos de Lauck para avisarte sobre tu {{ $job->bike_model }}..." target="_blank" class="flex flex-col items-center justify-center text-green-600 dark:text-green-400 hover:text-green-700 dark:hover:text-green-300 transition-colors group/wa">
<svg class="w-5 h-5 mb-0.5 group-hover/wa:scale-110 transition-transform" 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> <!-- (Usa el mismo SVG de whatsapp de antes) -->
<span class="text-[10px] font-bold tracking-wider uppercase">Avisar</span>
</a>
<!-- Botón Mover Adelante -->
@@ -54,7 +54,7 @@
<form action="{{ route('taller.updateStatus', $job) }}" method="POST">
@csrf @method('PATCH')
<input type="hidden" name="status" value="{{ $job->status === 'pending' ? 'in_progress' : 'ready' }}">
<button class="text-neon-lime hover:text-white p-1" title="Avanzar estado">
<button class="text-green-600 dark:text-neon-lime hover:text-gray-900 dark:hover:text-white p-1" title="Avanzar estado">
<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="M9 5l7 7-7 7"></path></svg>
</button>
</form>
@@ -63,7 +63,7 @@
<form action="{{ route('taller.updateStatus', $job) }}" method="POST">
@csrf @method('PATCH')
<input type="hidden" name="status" value="delivered">
<button class="text-xs bg-neon-lime text-black px-2 py-1 rounded font-bold hover:bg-white" title="Marcar como entregado">
<button class="text-xs bg-green-600 dark:bg-neon-lime text-white dark:text-black px-3 py-1.5 rounded-lg font-bold hover:bg-green-700 dark:hover:bg-white transition-colors" title="Marcar como entregado">
Entregar
</button>
</form>
+25 -8
View File
@@ -1,11 +1,28 @@
@if (session('success'))
<div class="p-4 mb-4 text-md text-black rounded-lg bg-green-500 border border-green-800/50" role="alert">
<span class="font-bold">¡Éxito!</span> {{ session('success') }}
@if (session('success') || session('error'))
<div id="toast-alert" class="fixed top-20 right-5 z-50 flex items-center w-full max-w-sm p-4 space-x-3 text-black rounded-lg shadow-xl transition-opacity duration-500 ease-in-out {{ session('success') ? 'bg-green-500 border border-green-800/50' : 'bg-red-500 border border-red-800/50' }}" role="alert">
<div class="ml-2 text-md font-normal">
<span class="font-bold">{{ session('success') ? '¡Éxito!' : 'Error:' }}</span>
{{ session('success') ?? session('error') }}
</div>
<button type="button" onclick="closeToastAlert()" class="ml-auto -mx-1.5 -my-1.5 bg-transparent text-black hover:text-gray-800 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 inline-flex h-8 w-8 justify-center items-center" aria-label="Close">
<span class="sr-only">Cerrar</span>
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
</svg>
</button>
</div>
@endif
@if (session('error'))
<div class="p-4 mb-4 text-md text-black rounded-lg bg-red-500 border border-red-800/50" role="alert">
<span class="font-bold">Error:</span> {{ session('error') }}
</div>
<script>
function closeToastAlert() {
const alertElement = document.getElementById('toast-alert');
if (alertElement) {
alertElement.classList.add('opacity-0');
setTimeout(() => alertElement.remove(), 500);
}
}
setTimeout(() => {
closeToastAlert();
}, 5000); // 5 seconds
</script>
@endif
@@ -0,0 +1,80 @@
<div id="delete-modal" tabindex="-1" class="hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-full max-h-full bg-black/50 backdrop-blur-sm transition-opacity duration-300 opacity-0">
<div class="relative p-4 w-full max-w-md max-h-full mx-auto mt-20 transform transition-all duration-300 scale-95 opacity-0" id="delete-modal-content">
<div class="relative bg-white rounded-xl shadow dark:bg-neutral-800 border border-gray-200 dark:border-neutral-700">
<button type="button" onclick="closeDeleteModal()" class="absolute top-3 end-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-neutral-700 dark:hover:text-white">
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
</svg>
<span class="sr-only">Cerrar modal</span>
</button>
<div class="p-4 md:p-5 text-center">
<svg class="mx-auto mb-4 text-red-500 w-12 h-12 dark:text-red-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="M10 11V6m0 8h.01M19 10a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>
</svg>
<h3 class="mb-5 text-lg font-normal text-gray-500 dark:text-gray-300">¿Estás seguro de que quieres eliminar este registro?</h3>
<p class="mb-5 text-sm font-normal text-gray-400 dark:text-gray-400">Esta acción no se puede deshacer.</p>
<button type="button" id="confirm-delete-btn" class="text-white bg-red-600 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 dark:focus:ring-red-800 font-medium rounded-lg text-sm inline-flex items-center px-5 py-2.5 text-center">
, eliminar
</button>
<button onclick="closeDeleteModal()" type="button" class="py-2.5 px-5 ms-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-neutral-800 dark:text-gray-400 dark:border-neutral-600 dark:hover:text-white dark:hover:bg-neutral-700">
Cancelar
</button>
</div>
</div>
</div>
</div>
<script>
let formToSubmit = null;
function openDeleteModal(event, form) {
event.preventDefault();
formToSubmit = form;
const modal = document.getElementById('delete-modal');
const content = document.getElementById('delete-modal-content');
modal.classList.remove('hidden');
modal.classList.add('flex');
// Trigger reflow
void modal.offsetWidth;
modal.classList.remove('opacity-0');
content.classList.remove('scale-95', 'opacity-0');
content.classList.add('scale-100', 'opacity-100');
}
function closeDeleteModal() {
const modal = document.getElementById('delete-modal');
const content = document.getElementById('delete-modal-content');
modal.classList.add('opacity-0');
content.classList.remove('scale-100', 'opacity-100');
content.classList.add('scale-95', 'opacity-0');
setTimeout(() => {
modal.classList.add('hidden');
modal.classList.remove('flex');
formToSubmit = null;
}, 300); // match transition duration
}
document.addEventListener('DOMContentLoaded', function() {
const btn = document.getElementById('confirm-delete-btn');
if (btn) {
btn.addEventListener('click', function() {
if (formToSubmit) {
formToSubmit.submit();
}
});
}
// Add event listener to all forms with 'delete-form' class
const deleteForms = document.querySelectorAll('.delete-form');
deleteForms.forEach(form => {
form.addEventListener('submit', function(e) {
openDeleteModal(e, this);
});
});
});
</script>
@@ -0,0 +1,71 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Recuperar Contraseña</title>
</head>
<body style="margin: 0; padding: 0; background-color: #1a1a1a; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; color: #ffffff;">
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color: #1a1a1a; width: 100%;">
<tr>
<td align="center" style="padding: 40px 20px;">
<table width="100%" max-width="600" cellpadding="0" cellspacing="0" border="0" style="max-width: 600px; width: 100%; background-color: #242424; border-radius: 12px; border: 1px solid #333333; overflow: hidden; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);">
<!-- Header -->
<tr>
<td align="center" style="padding: 40px 20px 20px 20px; border-bottom: 1px solid #333333;">
<h1 style="margin: 0; font-size: 24px; font-weight: 800; color: #ffffff;">
Bicicletería <span style="color: #b3e600;">Lauck</span>
</h1>
<p style="margin: 5px 0 0 0; font-size: 14px; color: #a0a0a0; text-transform: uppercase; letter-spacing: 1px;">Recuperación de cuenta</p>
</td>
</tr>
<!-- Body -->
<tr>
<td style="padding: 40px 30px;">
<p style="margin: 0 0 20px 0; font-size: 16px; line-height: 1.5; color: #e5e5e5;">
Hola,
</p>
<p style="margin: 0 0 30px 0; font-size: 16px; line-height: 1.5; color: #a0a0a0;">
Estás recibiendo este correo porque recibimos una solicitud de restablecimiento de contraseña para tu cuenta en <strong>Lauck</strong>.
</p>
<table width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td align="center">
<a href="{{ $url }}" style="display: inline-block; padding: 14px 30px; background-color: #b3e600; color: #1a1a1a; text-decoration: none; font-size: 16px; font-weight: bold; border-radius: 8px; text-transform: uppercase; letter-spacing: 0.5px;">
Restablecer Contraseña
</a>
</td>
</tr>
</table>
<p style="margin: 30px 0 0 0; font-size: 16px; line-height: 1.5; color: #a0a0a0;">
Este enlace para restablecer la contraseña caducará en 60 minutos.
</p>
<p style="margin: 20px 0 0 0; font-size: 16px; line-height: 1.5; color: #a0a0a0;">
Si no solicitaste un restablecimiento de contraseña, no es necesario realizar ninguna otra acción.
</p>
</td>
</tr>
<!-- Footer -->
<tr>
<td style="background-color: #1f1f1f; padding: 20px 30px; border-top: 1px solid #333333;">
<p style="margin: 0; font-size: 12px; line-height: 1.5; color: #666666; text-align: center;">
Si tienes problemas para hacer clic en el botón "Restablecer Contraseña", copia y pega la siguiente URL en tu navegador web:<br>
<a href="{{ $url }}" style="color: #b3e600; word-break: break-all; margin-top: 10px; display: inline-block;">{{ $url }}</a>
</p>
</td>
</tr>
</table>
<p style="margin: 20px 0 0 0; font-size: 12px; color: #666666; text-align: center;">
© {{ date('Y') }} Bicicletería Lauck. Todos los derechos reservados.
</p>
</td>
</tr>
</table>
</body>
</html>
+123
View File
@@ -0,0 +1,123 @@
<x-layout title="FAQ y Manual - Lauck">
<x-section-header subtitle="Soporte y Documentación" title="Preguntas " highlight="Frecuentes" />
<div class="w-full max-w-4xl mx-auto space-y-8">
<!-- Sección Manual de Usuario -->
<div class="bg-white dark:bg-neutral-900 border border-gray-200 dark:border-neutral-800 rounded-xl shadow-sm overflow-hidden">
<div class="p-6 border-b border-gray-200 dark:border-neutral-800 bg-gray-50 dark:bg-neutral-800/50">
<h2 class="text-xl font-bold text-gray-900 dark:text-white flex items-center gap-2">
<svg class="w-6 h-6 text-lime-600 dark:text-neon-lime" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
</svg>
Manual de Uso Rápido
</h2>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">Conceptos básicos para navegar por el sistema Lauck.</p>
</div>
<div class="p-6 space-y-6">
<!-- Punto 1 -->
<div class="flex gap-4">
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-lime-100 dark:bg-lime-900/30 flex items-center justify-center text-lime-600 dark:text-neon-lime font-bold">1</div>
<div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">Dashboard</h3>
<p class="text-gray-600 dark:text-gray-400 mt-1">El panel principal muestra un resumen de las métricas clave, accesos directos a tus módulos más importantes y un panorama general del negocio.</p>
</div>
</div>
<!-- Punto 2 -->
<div class="flex gap-4">
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-lime-100 dark:bg-lime-900/30 flex items-center justify-center text-lime-600 dark:text-neon-lime font-bold">2</div>
<div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">Stock y Productos</h3>
<p class="text-gray-600 dark:text-gray-400 mt-1">Registra bicicletas, accesorios y repuestos. Mantén las cantidades actualizadas para evitar quiebres de inventario mediante la gestión de tu stock.</p>
</div>
</div>
<!-- Punto 3 -->
<div class="flex gap-4">
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-lime-100 dark:bg-lime-900/30 flex items-center justify-center text-lime-600 dark:text-neon-lime font-bold">3</div>
<div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">Taller</h3>
<p class="text-gray-600 dark:text-gray-400 mt-1">Administra la agenda de reparaciones, asigna estados (pendiente, en proceso, listo) y mantén un registro de los servicios realizados a cada cliente.</p>
</div>
</div>
<!-- Punto 4 -->
<div class="flex gap-4">
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-lime-100 dark:bg-lime-900/30 flex items-center justify-center text-lime-600 dark:text-neon-lime font-bold">4</div>
<div>
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">Ventas y Clientes</h3>
<p class="text-gray-600 dark:text-gray-400 mt-1">Genera nuevas ventas, consulta el historial, y gestiona la base de datos de tus clientes para ofrecer un servicio más personalizado.</p>
</div>
</div>
</div>
</div>
<!-- Preguntas Frecuentes -->
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mt-10 mb-6 flex items-center gap-2">
<svg class="w-6 h-6 text-lime-600 dark:text-neon-lime" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Preguntas Frecuentes (FAQ)
</h2>
<div class="space-y-4">
<!-- FAQ 1 -->
<details class="group bg-white dark:bg-neutral-900 border border-gray-200 dark:border-neutral-800 rounded-lg shadow-sm [&_summary::-webkit-details-marker]:hidden">
<summary class="flex items-center justify-between p-5 cursor-pointer font-semibold text-gray-900 dark:text-white transition-colors hover:bg-gray-50 dark:hover:bg-neutral-800/50 rounded-lg">
<span>¿Cómo agrego un nuevo producto al stock?</span>
<span class="transition-transform duration-300 group-open:-rotate-180 text-gray-500">
<svg fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" viewBox="0 0 24 24" width="24"><path d="M6 9l6 6 6-6"></path></svg>
</span>
</summary>
<div class="px-5 pb-5 pt-2 text-gray-600 dark:text-gray-400 border-t border-gray-100 dark:border-neutral-800/50">
<p>Dirígete a la sección <strong>Stock</strong> desde el menú principal, haz clic en el botón superior derecho de agregar nuevo producto y completa el formulario con los detalles (SKU, nombre, precio, etc).</p>
</div>
</details>
<!-- FAQ 2 -->
<details class="group bg-white dark:bg-neutral-900 border border-gray-200 dark:border-neutral-800 rounded-lg shadow-sm [&_summary::-webkit-details-marker]:hidden">
<summary class="flex items-center justify-between p-5 cursor-pointer font-semibold text-gray-900 dark:text-white transition-colors hover:bg-gray-50 dark:hover:bg-neutral-800/50 rounded-lg">
<span>¿Puedo registrar una venta si el cliente no está registrado?</span>
<span class="transition-transform duration-300 group-open:-rotate-180 text-gray-500">
<svg fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" viewBox="0 0 24 24" width="24"><path d="M6 9l6 6 6-6"></path></svg>
</span>
</summary>
<div class="px-5 pb-5 pt-2 text-gray-600 dark:text-gray-400 border-t border-gray-100 dark:border-neutral-800/50">
<p>, al crear una nueva venta desde la sección <strong>Ventas</strong> puedes seleccionar un cliente genérico ("Consumidor Final") o bien ingresar los datos de un cliente nuevo en el mismo momento para que quede registrado en tu base de datos.</p>
</div>
</details>
<!-- FAQ 3 -->
<details class="group bg-white dark:bg-neutral-900 border border-gray-200 dark:border-neutral-800 rounded-lg shadow-sm [&_summary::-webkit-details-marker]:hidden">
<summary class="flex items-center justify-between p-5 cursor-pointer font-semibold text-gray-900 dark:text-white transition-colors hover:bg-gray-50 dark:hover:bg-neutral-800/50 rounded-lg">
<span>¿Qué pasa si olvido mi contraseña?</span>
<span class="transition-transform duration-300 group-open:-rotate-180 text-gray-500">
<svg fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" viewBox="0 0 24 24" width="24"><path d="M6 9l6 6-6-6"></path></svg>
</span>
</summary>
<div class="px-5 pb-5 pt-2 text-gray-600 dark:text-gray-400 border-t border-gray-100 dark:border-neutral-800/50">
<p>En la pantalla de inicio de sesión, haz clic en el enlace "¿Olvidaste tu contraseña?". Te enviaremos un correo electrónico con instrucciones para restablecerla de forma segura. Si el problema persiste, contacta al administrador del sistema.</p>
</div>
</details>
<!-- FAQ 4 -->
<details class="group bg-white dark:bg-neutral-900 border border-gray-200 dark:border-neutral-800 rounded-lg shadow-sm [&_summary::-webkit-details-marker]:hidden">
<summary class="flex items-center justify-between p-5 cursor-pointer font-semibold text-gray-900 dark:text-white transition-colors hover:bg-gray-50 dark:hover:bg-neutral-800/50 rounded-lg">
<span>¿Cómo cambiar entre modo claro y oscuro?</span>
<span class="transition-transform duration-300 group-open:-rotate-180 text-gray-500">
<svg fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" viewBox="0 0 24 24" width="24"><path d="M6 9l6 6 6-6"></path></svg>
</span>
</summary>
<div class="px-5 pb-5 pt-2 text-gray-600 dark:text-gray-400 border-t border-gray-100 dark:border-neutral-800/50">
<p>En la barra de navegación superior (Navbar), encontrarás un icono de un sol o una luna (dependiendo del modo actual en el que te encuentres). Haz clic sobre el ícono para alternar entre el tema claro y el oscuro. El sistema recordará tu preferencia para futuras sesiones.</p>
</div>
</details>
</div>
<div class="mt-8 p-6 bg-lime-50 dark:bg-lime-900/10 border border-lime-200 dark:border-lime-900/30 rounded-xl text-center shadow-sm">
<h3 class="text-lg font-bold text-lime-800 dark:text-neon-lime mb-2">¿Necesitas más ayuda?</h3>
<p class="text-sm text-lime-700 dark:text-gray-400 mb-5">Si tienes alguna otra duda o experimentas problemas técnicos, nuestro equipo de soporte está disponible para ayudarte.</p>
<a href="mailto:soporte@lauck.com" class="inline-flex items-center justify-center px-6 py-3 text-sm font-bold text-neutral-900 bg-lime-400 dark:bg-neon-lime rounded-lg hover:bg-lime-500 hover:dark:bg-[#b3e600] transition-colors shadow-lg shadow-lime-500/20 dark:shadow-neon-lime/20 transform hover:-translate-y-0.5 duration-200 uppercase tracking-wide">
Contactar a Soporte
</a>
</div>
</div>
</x-layout>
+41 -20
View File
@@ -1,44 +1,65 @@
<x-layout title="Login - Lauck">
<div class="justify-center">
<x-section-header
subtitle="Inicio de sesion"
title="Bicicletería "
highlight="Lauck"
/>
<div class="bg-panel-bg p-8 rounded-xl shadow-lg w-full max-w-md border border-neutral-700">
<form action="{{ route('login.attempt') }}" method="POST" class="space-y-5">
<div class="w-full flex flex-col items-center justify-center pt-8">
<x-section-header
subtitle="Inicio de sesión"
title="Bicicletería "
highlight="Lauck"
/>
<div class="bg-gray-100 dark:bg-panel-bg p-8 rounded-xl shadow-lg w-full max-w-md border border-gray-300 dark:border-neutral-700">
<form action="{{ route('login.attempt') }}" method="POST" class="space-y-6">
@csrf
{{-- Bloque de errores --}}
@if ($errors->any())
<div class="bg-red-900 border border-red-700 text-white p-3 rounded-md">
<div class="bg-red-100 dark:bg-red-900/50 border border-red-500 text-red-600 dark:text-red-300 p-3 rounded-lg shadow-sm">
<p class="text-sm font-medium">
{{ $errors->first() }}
</p>
</div>
@endif
@if (session('status'))
<div class="bg-green-100 dark:bg-green-900/50 border border-green-500 text-green-700 dark:text-green-300 p-3 rounded-lg shadow-sm">
<p class="text-sm font-medium">
{{ session('status') }}
</p>
</div>
@endif
<div>
<x-forms.label class="block text-sm font-bold text-gray-600 mb-1">Correo electrónico</x-forms.label>
<x-forms.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-lime-400 text-gray-600"/>
<label class="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Correo electrónico</label>
<input type="email" name="email" required autofocus
class="w-full px-4 py-2 bg-white dark:bg-neutral-900 border border-gray-300 dark:border-neutral-700 text-gray-900 dark:text-white rounded-lg focus:outline-none focus:ring-2 focus:ring-neon-lime focus:border-neon-lime transition-colors"/>
</div>
<div>
<x-forms.label class="block text-sm font-bold text-gray-600 mb-1">Contraseña</x-forms.label>
<x-forms.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"/>
<label class="block text-sm font-bold text-gray-700 dark:text-gray-300 mb-2">Contraseña</label>
<input type="password" name="password" required
class="w-full px-4 py-2 bg-white dark:bg-neutral-900 border border-gray-300 dark:border-neutral-700 text-gray-900 dark:text-white rounded-lg focus:outline-none focus:ring-2 focus:ring-neon-lime focus:border-neon-lime transition-colors"/>
<div class="flex justify-end mt-2">
<a href="{{ route('password.request') }}" class="text-xs font-medium text-green-600 dark:text-neon-lime hover:underline transition-colors">
¿Olvidaste tu contraseña?
</a>
</div>
</div>
<div>
<input type="submit" name="submit" value="Iniciar sesión"
class="w-full bg-lime-400 font-bold text-white py-2 rounded-md hover:bg-lime-500 transition-colors">
<button type="submit"
class="w-full bg-neutral-900 dark:bg-neon-lime text-neon-lime dark:text-neutral-900 font-bold py-2.5 rounded-lg hover:bg-neutral-800 hover:dark:bg-[#b3e600] transition-colors shadow-lg dark:shadow-neon-lime/20 uppercase tracking-wide">
Iniciar sesión
</button>
</div>
<p class="text-center text-sm text-gray-600">
¿No tienes una cuenta? <a href="register" class="text-blue-600 hover:underline">Regístrate acá</a>.
</p>
<div class="text-center mt-6 pt-4 border-t border-gray-300 dark:border-neutral-700">
<p class="text-sm text-gray-600 dark:text-gray-400">
¿No tienes una cuenta? <a href="{{ route('register') }}" class="font-bold text-gray-900 dark:text-white hover:text-green-600 dark:hover:text-neon-lime transition-colors">Regístrate acá</a>.
</p>
</div>
</form>
</div>
</div>
</x-layout>
+1 -5
View File
@@ -53,11 +53,7 @@
<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 -->
<div class="md:col-span-4">
<x-forms.label for="description" value="Descripción / Notas" />
<textarea id="description" name="description" rows="3" class="bg-gray-300 dark:bg-neutral-800 border border-neutral-400 dark:border-neutral-700 dark:text-white text-sm rounded-lg focus:ring-neon-lime focus:border-neon-lime block w-full p-2.5 placeholder-neutral-600 dark:placeholder-gray-400">{{ old('description') }}</textarea>
</div>
<!-- Imagen -->
<div class="md:col-span-2">
<x-forms.label for="image" value="Imagen del Producto" />
-5
View File
@@ -52,11 +52,6 @@
<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-gray-300 dark:bg-neutral-800 border border-neutral-400 dark:border-neutral-700 dark: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>
<!-- Imagen -->
<div class="md:col-span-2">
<x-forms.label for="image" value="Imagen del Producto" />
+2 -2
View File
@@ -2,7 +2,7 @@
<x-section-header subtitle="Gestión de Inventario" title="Listado de " highlight="Productos" />
<x-ui.alert />
<div class="w-full flex flex-col xl:flex-row justify-between items-start xl:items-center gap-4 mb-6">
@@ -114,7 +114,7 @@
<a href="{{ route('productos.edit', $product) }}" title="Editar" class="font-semibold p-2 text-blue-800 dark:text-blue-400 border-2 border-blue-800 rounded-lg hover:bg-blue-800 hover:text-white dark:hover:text-white transition-colors">
<svg class="size-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m14.304 4.844 2.852 2.852M7 7H4a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h11a1 1 0 0 0 1-1v-4.5m2.409-9.91a2.017 2.017 0 0 1 0 2.853l-6.844 6.844L8 14l.713-3.565 6.844-6.844a2.015 2.015 0 0 1 2.852 0Z"/></svg>
</a>
<form action="{{route('productos.destroy',$product)}}" method="post">
<form action="{{route('productos.destroy',$product)}}" method="post" class="delete-form">
@csrf
@method('DELETE')
<button title="Eliminar" type="submit" class="w-full sm:w-auto p-2 font-bold dark:font-medium text-red-600 dark:text-red-400 border-2 border-red-600 rounded-lg hover:bg-red-600 hover:text-white dark:hover:text-white transition-colors">
-7
View File
@@ -63,13 +63,6 @@
</div>
</div>
<div class="md:col-span-4">
<x-forms.label for="description" value="Descripción / Notas" />
<div class="bg-gray-300/50 dark:bg-neutral-800/50 border border-neutral-400/50 dark:border-neutral-700/50 text-gray-800 dark:text-gray-300 text-sm rounded-lg block w-full p-4 min-h-[80px] leading-relaxed">
{{ $product->description ?: 'No hay descripción cargada para este producto.' }}
</div>
</div>
<div class="md:col-span-4">
<x-forms.label for="image" value="Fotografía del Producto" />
<div class="mt-2">
+1 -1
View File
@@ -2,7 +2,7 @@
<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">
+1 -1
View File
@@ -85,7 +85,7 @@
<x-section-header subtitle="Punto de Venta" title="Nueva " highlight="Venta" />
<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">
+1 -1
View File
@@ -1,7 +1,7 @@
<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">
+3 -12
View File
@@ -2,17 +2,8 @@
<x-section-header subtitle="Gestión de Proveedores" title="Directorio de " highlight="Proveedores" />
<x-ui.alert />
@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
<!--@if(session('error'))
<div class="bg-red-900/50 border border-red-500 text-red-300 px-4 py-3 rounded-lg mb-6 shadow-sm">
{{ session('error') }}
</div>
@endif-->
<div class="w-full flex flex-col md:flex-row justify-between items-center gap-4 mb-6">
@@ -79,7 +70,7 @@
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m14.304 4.844 2.852 2.852M7 7H4a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h11a1 1 0 0 0 1-1v-4.5m2.409-9.91a2.017 2.017 0 0 1 0 2.853l-6.844 6.844L8 14l.713-3.565 6.844-6.844a2.015 2.015 0 0 1 2.852 0Z"/>
</svg>
</a>
<form action="{{route('suppliers.destroy', $supplier)}}" method="post">
<form action="{{route('suppliers.destroy', $supplier)}}" method="post" class="delete-form">
@csrf
@method('DELETE')
<button title="Eliminar" type="submit" class="w-full sm:w-auto p-2 font-bold dark:font-medium text-red-600 dark:text-red-400 border-2 border-red-600 rounded-lg hover:bg-red-600 hover:text-white dark:hover:text-white transition-colors">
+33 -33
View File
@@ -2,7 +2,7 @@
<x-section-header subtitle="Taller" title="Nueva " highlight="Orden de Reparación" />
<x-ui.alert />
@if($errors->any())
<div class="w-120 bg-red-900/50 border border-red-500 text-red-300 px-4 py-3 rounded-lg mb-6 shadow-sm">
@@ -14,19 +14,19 @@
</div>
@endif
<div class="max-w-5xl mx-auto bg-panel-bg border border-neutral-800 rounded-xl p-8 shadow-lg">
<div class="max-w-5xl mx-auto bg-white dark:bg-panel-bg border border-gray-300 dark:border-neutral-800 rounded-xl p-8 shadow-lg">
<form action="{{ route('taller.store') }}" method="POST" class="space-y-8" id="taller-form">
@csrf
<!-- 1. Datos del Cliente -->
<div class="border-b border-neutral-800 pb-6">
<h3 class="text-white font-bold uppercase tracking-wider text-sm mb-4 flex items-center gap-2">
<span class="bg-neutral-800 text-neon-lime px-2 py-0.5 rounded text-xs">1</span> Cliente
<div class="border-b border-gray-300 dark:border-neutral-800 pb-6">
<h3 class="text-gray-900 dark:text-white font-bold uppercase tracking-wider text-sm mb-4 flex items-center gap-2">
<span class="bg-gray-200 dark:bg-neutral-800 text-green-600 dark:text-neon-lime px-2 py-0.5 rounded text-xs">1</span> Cliente
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label class="block text-xs font-bold text-gray-400 uppercase mb-2">Propietario</label>
<select name="client_id" class="w-full bg-neutral-900 border border-neutral-700 text-white rounded-lg p-2.5 focus:ring-neon-lime focus:border-neon-lime">
<label class="block text-xs font-bold text-gray-600 dark:text-gray-400 uppercase mb-2">Propietario</label>
<select name="client_id" class="w-full bg-gray-50 dark:bg-neutral-900 border border-gray-300 dark:border-neutral-700 text-gray-900 dark:text-white rounded-lg p-2.5 focus:ring-neon-lime focus:border-neon-lime">
<option value="" disabled {{ !request('new_client_id') ? 'selected' : '' }}>-- Seleccione un propietario --</option>
@foreach($clients as $client)
@@ -38,41 +38,41 @@
<a href="{{ route('clients.create', ['origin' => 'taller']) }}" class="text-xs text-neon-lime hover:underline mt-1 inline-block">+ Nuevo Cliente</a>
</div>
<div>
<label class="block text-xs font-bold text-gray-400 uppercase mb-2">Teléfono Aviso</label>
<label class="block text-xs font-bold text-gray-600 dark:text-gray-400 uppercase mb-2">Teléfono Aviso</label>
<input type="text" name="contact_phone" value="{{ request('new_client_phone') }}" placeholder="Ingresar numero de telefono" class="w-full bg-neutral-900 border border-neutral-700 text-white rounded-lg p-2.5 focus:ring-neon-lime focus:border-neon-lime">
<input type="text" name="contact_phone" value="{{ request('new_client_phone') }}" placeholder="Ingresar numero de telefono" class="w-full bg-gray-50 dark:bg-neutral-900 border border-gray-300 dark:border-neutral-700 text-gray-900 dark:text-white rounded-lg p-2.5 focus:ring-neon-lime focus:border-neon-lime">
</div>
</div>
</div>
<!-- 2. Diagnóstico y Repuestos -->
<div class="border-b border-neutral-800 pb-6">
<h3 class="text-white font-bold uppercase tracking-wider text-sm mb-4 flex items-center gap-2">
<span class="bg-neutral-800 text-neon-lime px-2 py-0.5 rounded text-xs">2</span> Diagnóstico y Repuestos
<div class="border-b border-gray-300 dark:border-neutral-800 pb-6">
<h3 class="text-gray-900 dark:text-white font-bold uppercase tracking-wider text-sm mb-4 flex items-center gap-2">
<span class="bg-gray-200 dark:bg-neutral-800 text-green-600 dark:text-neon-lime px-2 py-0.5 rounded text-xs">2</span> Diagnóstico y Repuestos
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Columna Izquierda: Datos Bici -->
<div class="space-y-4">
<div>
<label class="block text-xs font-bold text-gray-400 uppercase mb-2">Modelo de Bici / Color</label>
<input type="text" name="bike_model" required placeholder="Ej: Venzo Loki Roja R29" class="w-full bg-neutral-900 border border-neutral-700 text-white rounded-lg p-2.5 focus:ring-neon-lime focus:border-neon-lime">
<label class="block text-xs font-bold text-gray-600 dark:text-gray-400 uppercase mb-2">Modelo de Bici / Color</label>
<input type="text" name="bike_model" required placeholder="Ej: Venzo Loki Roja R29" class="w-full bg-gray-50 dark:bg-neutral-900 border border-gray-300 dark:border-neutral-700 text-gray-900 dark:text-white rounded-lg p-2.5 focus:ring-neon-lime focus:border-neon-lime">
</div>
<div>
<label class="block text-xs font-bold text-gray-400 uppercase mb-2">Problema / Servicio</label>
<textarea name="problem_description" rows="3" required class="w-full bg-neutral-900 border border-neutral-700 text-white rounded-lg p-2.5 focus:ring-neon-lime focus:border-neon-lime" placeholder="Ej: Service completo, hace ruido la caja..."></textarea>
<label class="block text-xs font-bold text-gray-600 dark:text-gray-400 uppercase mb-2">Problema / Servicio</label>
<textarea name="problem_description" rows="3" required class="w-full bg-gray-50 dark:bg-neutral-900 border border-gray-300 dark:border-neutral-700 text-gray-900 dark:text-white rounded-lg p-2.5 focus:ring-neon-lime focus:border-neon-lime" placeholder="Ej: Service completo, hace ruido la caja..."></textarea>
</div>
</div>
<!-- Columna Derecha: Selector de Repuestos -->
<div class="bg-neutral-900/50 p-4 rounded-xl border border-neutral-800">
<label class="block text-xs font-bold text-neon-lime uppercase mb-2">Agregar Repuestos / Servicios</label>
<div class="bg-gray-100 dark:bg-neutral-900/50 p-4 rounded-xl border border-gray-300 dark:border-neutral-800">
<label class="block text-xs font-bold text-green-600 dark:text-neon-lime uppercase mb-2">Agregar Repuestos / Servicios</label>
<!-- Buscador -->
<div class="relative mb-3">
<input type="text" id="part-search" class="w-full bg-neutral-800 border border-neutral-700 text-white rounded-lg p-2 text-sm focus:ring-neon-lime focus:border-neon-lime" placeholder="Buscar repuesto..." autocomplete="off">
<input type="text" id="part-search" class="w-full bg-white dark:bg-neutral-800 border border-gray-300 dark:border-neutral-700 text-gray-900 dark:text-white rounded-lg p-2 text-sm focus:ring-neon-lime focus:border-neon-lime" placeholder="Buscar repuesto..." autocomplete="off">
<!-- Lista desplegable de sugerencias -->
<div id="suggestions-box" class="absolute left-0 right-0 z-50 bg-neutral-800 border border-neutral-600 rounded-b-lg shadow-xl max-h-40 overflow-y-auto hidden"></div>
<div id="suggestions-box" class="absolute left-0 right-0 z-50 bg-white dark:bg-neutral-800 border border-gray-300 dark:border-neutral-600 rounded-b-lg shadow-xl max-h-40 overflow-y-auto hidden"></div>
</div>
<!-- Lista Visual de Seleccionados -->
@@ -88,29 +88,29 @@
<!-- 3. Agenda y Costos -->
<div>
<h3 class="text-white font-bold uppercase tracking-wider text-sm mb-4 flex items-center gap-2">
<span class="bg-neutral-800 text-neon-lime px-2 py-0.5 rounded text-xs">3</span> Acuerdo
<h3 class="text-gray-900 dark:text-white font-bold uppercase tracking-wider text-sm mb-4 flex items-center gap-2">
<span class="bg-gray-200 dark:bg-neutral-800 text-green-600 dark:text-neon-lime px-2 py-0.5 rounded text-xs">3</span> Acuerdo
</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 items-end">
<div>
<label class="block text-xs font-bold text-gray-400 uppercase mb-2">Fecha Prometida / Retiro</label>
<input type="datetime-local" name="scheduled_at" required class="w-full bg-neutral-900 border border-neutral-700 text-white rounded-lg p-2.5 focus:ring-neon-lime focus:border-neon-lime">
<label class="block text-xs font-bold text-gray-600 dark:text-gray-400 uppercase mb-2">Fecha Prometida / Retiro</label>
<input type="datetime-local" name="scheduled_at" required class="w-full bg-gray-50 dark:bg-neutral-900 border border-gray-300 dark:border-neutral-700 text-gray-900 dark:text-white rounded-lg p-2.5 focus:ring-neon-lime focus:border-neon-lime">
</div>
<div class="bg-neutral-800 p-4 rounded-lg border border-neutral-700">
<label class="block text-xs font-bold text-gray-400 uppercase mb-1">Presupuesto Estimado</label>
<div class="bg-gray-100 dark:bg-neutral-800 p-4 rounded-lg border border-gray-300 dark:border-neutral-700">
<label class="block text-xs font-bold text-gray-600 dark:text-gray-400 uppercase mb-1">Presupuesto Estimado</label>
<div class="flex items-center gap-2">
<span class="text-gray-500">$</span>
<!-- Input de costo: Se autocalcula pero el usuario puede editarlo manualmente -->
<input type="number" id="estimated_cost" name="estimated_cost" step="0.01" class="w-full bg-transparent border-0 text-white text-2xl font-black focus:ring-0 p-0" placeholder="0.00">
<input type="number" id="estimated_cost" name="estimated_cost" step="0.01" class="w-full bg-transparent border-0 text-gray-900 dark:text-white text-2xl font-black focus:ring-0 p-0" placeholder="0.00">
</div>
<p class="text-[10px] text-gray-500 mt-1">* Calculado automáticamente según repuestos. Editable.</p>
</div>
</div>
</div>
<div class="flex justify-end pt-4 border-t border-neutral-800">
<a href="{{ route('taller.index') }}" class="mr-4 px-6 py-3 text-gray-400 hover:text-white font-medium">Cancelar</a>
<div class="flex justify-end pt-4 border-t border-gray-300 dark:border-neutral-800">
<a href="{{ route('taller.index') }}" class="mr-4 px-6 py-3 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white font-medium">Cancelar</a>
<button type="submit" class="px-8 py-3 bg-neon-lime text-neutral-900 font-bold rounded-lg hover:bg-[#b3e600] uppercase tracking-wide shadow-lg shadow-neon-lime/20">
Ingresar Bicicleta
</button>
@@ -154,10 +154,10 @@
if (matches.length > 0) {
matches.forEach(p => {
html += `
<div class="p-2 hover:bg-neutral-700 cursor-pointer text-xs text-white border-b border-neutral-700 last:border-0 suggestion-item flex justify-between items-center"
<div class="p-2 hover:bg-gray-100 dark:hover:bg-neutral-700 cursor-pointer text-xs text-gray-900 dark:text-white border-b border-gray-200 dark:border-neutral-700 last:border-0 suggestion-item flex justify-between items-center"
data-id="${p.id}" data-name="${p.name}" data-price="${p.price}">
<span>${p.name}</span>
<span class="text-neon-lime font-mono">$${p.price}</span>
<span class="text-green-600 dark:text-neon-lime font-mono">$${p.price}</span>
</div>`;
});
} else {
@@ -215,8 +215,8 @@
// Visual
$container.append(`
<div class="flex justify-between items-center bg-neutral-800 p-2 rounded border border-neutral-700 text-xs">
<div class="text-white">
<div class="flex justify-between items-center bg-gray-100 dark:bg-neutral-800 p-2 rounded border border-gray-300 dark:border-neutral-700 text-xs">
<div class="text-gray-900 dark:text-white">
${p.name}
<span class="text-gray-500 ml-1">($${p.price})</span>
</div>
+13 -17
View File
@@ -2,11 +2,7 @@
<x-section-header subtitle="Gestión" title="Tablero de " highlight="Taller" />
@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="flex w-full justify-end items-end mb-4 gap-x-2">
<a href="{{ route('agenda') }}"
@@ -23,10 +19,10 @@
<div class="flex flex-col lg:flex-row w-full gap-8 overflow-x-auto pb-4 h-[calc(100vh-250px)]">
<!-- COLUMNA 1 -->
<div class="flex-1 min-w-[300px] bg-neutral-900/50 border border-neutral-800 rounded-xl flex flex-col">
<div class="p-4 border-b border-neutral-800 bg-neutral-800/50 rounded-t-xl flex justify-between items-center">
<h3 class="font-bold text-gray-300 uppercase tracking-widest text-xs">🔴 Pendientes / A Revisar</h3>
<span class="bg-neutral-900 text-gray-400 text-xs px-2 py-1 rounded-full">{{ $pending->count() }}</span>
<div class="flex-1 min-w-[300px] bg-gray-50 dark:bg-neutral-900/50 border border-gray-300 dark:border-neutral-800 rounded-xl flex flex-col">
<div class="p-4 border-b border-gray-300 dark:border-neutral-800 bg-gray-200 dark:bg-neutral-800/50 rounded-t-xl flex justify-between items-center">
<h3 class="font-bold text-gray-800 dark:text-gray-300 uppercase tracking-widest text-xs">🔴 Pendientes / A Revisar</h3>
<span class="bg-gray-300 dark:bg-neutral-900 text-gray-700 dark:text-gray-400 text-xs px-2 py-1 rounded-full">{{ $pending->count() }}</span>
</div>
<div class="p-4 space-y-4 overflow-y-auto flex-1 custom-scrollbar">
@foreach($pending as $job)
@@ -36,10 +32,10 @@
</div>
<!-- COLUMNA 2 -->
<div class="flex-1 min-w-[300px] bg-neutral-900/50 border border-neutral-800 rounded-xl flex flex-col">
<div class="p-4 border-b border-neutral-800 bg-neutral-800/50 rounded-t-xl flex justify-between items-center">
<h3 class="font-bold text-yellow-500 uppercase tracking-widest text-xs">🟡 En Reparación</h3>
<span class="bg-neutral-900 text-gray-400 text-xs px-2 py-1 rounded-full">{{ $inProgress->count() }}</span>
<div class="flex-1 min-w-[300px] bg-gray-50 dark:bg-neutral-900/50 border border-gray-300 dark:border-neutral-800 rounded-xl flex flex-col">
<div class="p-4 border-b border-gray-300 dark:border-neutral-800 bg-gray-200 dark:bg-neutral-800/50 rounded-t-xl flex justify-between items-center">
<h3 class="font-bold text-yellow-600 dark:text-yellow-500 uppercase tracking-widest text-xs">🟡 En Reparación</h3>
<span class="bg-gray-300 dark:bg-neutral-900 text-gray-700 dark:text-gray-400 text-xs px-2 py-1 rounded-full">{{ $inProgress->count() }}</span>
</div>
<div class="p-4 space-y-4 overflow-y-auto flex-1 custom-scrollbar">
@foreach($inProgress as $job)
@@ -49,10 +45,10 @@
</div>
<!-- COLUMNA 3 -->
<div class="flex-1 min-w-[300px] bg-neutral-900/50 border border-neutral-800 rounded-xl flex flex-col">
<div class="p-4 border-b border-neutral-800 bg-neutral-800/50 rounded-t-xl flex justify-between items-center">
<h3 class="font-bold text-neon-lime uppercase tracking-widest text-xs">🟢 Listas para Retirar</h3>
<span class="bg-neutral-900 text-gray-400 text-xs px-2 py-1 rounded-full">{{ $ready->count() }}</span>
<div class="flex-1 min-w-[300px] bg-gray-50 dark:bg-neutral-900/50 border border-gray-300 dark:border-neutral-800 rounded-xl flex flex-col">
<div class="p-4 border-b border-gray-300 dark:border-neutral-800 bg-gray-200 dark:bg-neutral-800/50 rounded-t-xl flex justify-between items-center">
<h3 class="font-bold text-green-600 dark:text-neon-lime uppercase tracking-widest text-xs">🟢 Listas para Retirar</h3>
<span class="bg-gray-300 dark:bg-neutral-900 text-gray-700 dark:text-gray-400 text-xs px-2 py-1 rounded-full">{{ $ready->count() }}</span>
</div>
<div class="p-4 space-y-4 overflow-y-auto flex-1 custom-scrollbar">
@foreach($ready as $job)
+17
View File
@@ -13,6 +13,8 @@ use App\Http\Controllers\TallerController;
use App\Http\Controllers\AgendaController;
use App\Http\Controllers\AppointmentController;
use App\Http\Controllers\SupplierController;
use App\Http\Controllers\PasswordResetLinkController;
use App\Http\Controllers\NewPasswordController;
use App\Models\Product;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
@@ -25,6 +27,20 @@ Route::resource('catalogo', CatalogoController::class)->only(['index', 'show'])-
Route::get('login', function(){ return view('login'); })->name('login');
Route::post('login', LoginController::class)->middleware('throttle:5,1')->name('login.attempt');
Route::middleware('guest')->group(function () {
Route::get('forgot-password', [PasswordResetLinkController::class, 'create'])
->name('password.request');
Route::post('forgot-password', [PasswordResetLinkController::class, 'store'])
->name('password.email');
Route::get('reset-password/{token}', [NewPasswordController::class, 'create'])
->name('password.reset');
Route::post('reset-password', [NewPasswordController::class, 'store'])
->name('password.store');
});
Route::view('register', 'register')->name('register');
Route::post('register', RegisterController::class)->name('register.store');
@@ -56,4 +72,5 @@ Route::middleware(['auth'])->group(function () {
Route::get('/agenda', [AgendaController::class, 'index'])->name('agenda');
Route::get('/appointments/{appointment}', [AppointmentController::class, 'show'])
->name('appointments.show');
Route::view('/faq', 'faq')->name('faq');
});