Add empleado, borrado del seeder, cambios esteticos y middleware.

This commit is contained in:
2026-06-10 15:57:51 -03:00
parent d0d61f7985
commit 2454ee3a23
20 changed files with 1039 additions and 200 deletions
@@ -0,0 +1,88 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules;
class EmployeeController extends Controller
{
/**
* Listar empleados
*/
public function index()
{
$employees = User::where('role', 'employee')->orderBy('name')->get();
return view('admin.employees.index', compact('employees'));
}
/**
* Crear un nuevo empleado
*/
public function store(Request $request)
{
$request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
$user = new User();
$user->name = $request->name;
$user->email = $request->email;
$user->password = Hash::make($request->password);
$user->role = 'employee';
$user->save();
return redirect()->route('admin.employees.index')->with('success', 'Empleado creado correctamente.');
}
/**
* Eliminar un empleado
*/
public function destroy(User $user)
{
// Evitar que se elimine a sí mismo o a otro administrador (seguridad adicional)
if ($user->role !== 'employee') {
return redirect()->route('admin.employees.index')->with('error', 'Solo se pueden eliminar empleados.');
}
if ($user->id === auth()->id()) {
return redirect()->route('admin.employees.index')->with('error', 'No puedes eliminar tu propia cuenta.');
}
$user->delete();
return redirect()->route('admin.employees.index')->with('success', 'Empleado eliminado correctamente.');
}
/**
* Actualizar datos de un empleado
*/
public function update(Request $request, User $user)
{
if ($user->role !== 'employee') {
return redirect()->route('admin.employees.index')->with('error', 'Solo se pueden editar empleados.');
}
$request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email,' . $user->id],
'password' => ['nullable', 'string', 'min:8', 'confirmed'],
]);
$user->name = $request->name;
$user->email = $request->email;
if ($request->filled('password')) {
$user->password = Hash::make($request->password);
}
$user->save();
return redirect()->route('admin.employees.index')->with('success', 'Empleado actualizado correctamente.');
}
}
+8 -2
View File
@@ -19,8 +19,14 @@ class AgendaController extends Controller
'title' => $appointment->scheduled_at->format('H:i') . ' - ' . $appointment->client->name,
'start' => $appointment->scheduled_at->format('Y-m-d\TH:i:s'),
'extendedProps' => [
'description' => $appointment->problem_description,
'status' => $appointment->status,
'client_name' => $appointment->client->name,
'client_phone' => $appointment->contact_phone,
'bike_model' => $appointment->bike_model,
'description' => $appointment->problem_description,
'parts_needed' => $appointment->parts_needed,
'estimated_cost'=> $appointment->estimated_cost,
'status' => $appointment->status,
'notes' => $appointment->notes,
],
];
}
+14 -2
View File
@@ -7,6 +7,13 @@ use App\Models\Client;
class ClientController extends Controller
{
private function checkAdmin()
{
if (auth()->check() && auth()->user()->role !== 'admin') {
abort(403, 'Solo los administradores pueden realizar esta acción.');
}
}
/**
* Display a listing of the resource.
*/
@@ -31,6 +38,7 @@ class ClientController extends Controller
*/
public function create()
{
$this->checkAdmin();
return view('clients.create');
}
@@ -39,6 +47,7 @@ class ClientController extends Controller
*/
public function store(Request $request)
{
$this->checkAdmin();
$validated = $request->validate([
'name' => ['required', 'string', 'max:255', 'not_regex:/[0-9]/'],
'phone' => 'nullable|string|max:50',
@@ -76,19 +85,21 @@ class ClientController extends Controller
/**
* Display the specified resource.
*/
public function show(string $id)
public function show(Client $client)
{
//
return view('clients.show', compact('client'));
}
public function edit(Client $client)
{
$this->checkAdmin();
// 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)
{
$this->checkAdmin();
$validated = $request->validate([
'name' => ['required', 'string', 'max:255', 'not_regex:/[0-9]/'],
'phone' => 'nullable|string|max:50',
@@ -109,6 +120,7 @@ class ClientController extends Controller
*/
public function destroy(Client $client)
{
$this->checkAdmin();
try {
$client->delete();
return redirect()->route('clients.index')->with('success', 'Cliente eliminado correctamente.');
@@ -8,6 +8,13 @@ use App\Models\Supplier;
class ExpenseController extends Controller
{
private function checkAdmin()
{
if (auth()->check() && auth()->user()->role !== 'admin') {
abort(403, 'Solo los administradores pueden realizar esta acción.');
}
}
public function index(Request $request)
{
$query = $request->input('search');
@@ -33,12 +40,14 @@ class ExpenseController extends Controller
public function create()
{
$this->checkAdmin();
$suppliers = Supplier::orderBy('name')->get();
return view('expenses.create', compact('suppliers'));
}
public function store(Request $request)
{
$this->checkAdmin();
$validated = $request->validate([
'supplier_id' => 'nullable|exists:suppliers,id',
'description' => 'required|string|max:255',
@@ -66,12 +75,14 @@ class ExpenseController extends Controller
public function edit(Expense $expense)
{
$this->checkAdmin();
$suppliers = Supplier::orderBy('name')->get();
return view('expenses.edit', compact('expense', 'suppliers'));
}
public function update(Request $request, Expense $expense)
{
$this->checkAdmin();
$validated = $request->validate([
'supplier_id' => 'nullable|exists:suppliers,id',
'description' => 'required|string|max:255',
@@ -90,6 +101,7 @@ class ExpenseController extends Controller
public function destroy(Expense $expense)
{
$this->checkAdmin();
$expense->delete();
return redirect()->route('expenses.index')->with('success', 'Gasto eliminado correctamente.');
}
@@ -11,6 +11,13 @@ use Illuminate\Support\Facades\Storage;
class ProductosController extends Controller
{
private function checkAdmin()
{
if (auth()->check() && auth()->user()->role !== 'admin') {
abort(403, 'Solo los administradores pueden realizar esta acción.');
}
}
/*public function index(Request $request)
{
$query = $request->input('search');
@@ -55,6 +62,7 @@ class ProductosController extends Controller
public function create()
{
$this->checkAdmin();
$suppliers = Supplier::orderBy('name')->get();
$tags = Tag::orderBy('name')->get();
return view('productos.create', compact('suppliers', 'tags'));
@@ -62,6 +70,7 @@ class ProductosController extends Controller
public function store(Request $request)
{
$this->checkAdmin();
$validated = $request->validate([
'name' => 'required|string|max:255',
'price' => 'required|numeric|min:0',
@@ -124,6 +133,7 @@ class ProductosController extends Controller
public function edit(Product $product)
{
$this->checkAdmin();
$suppliers = Supplier::orderBy('name')->get();
$tags = Tag::orderBy('name')->get();
return view('productos.edit', compact('product', 'suppliers', 'tags'));
@@ -131,6 +141,7 @@ class ProductosController extends Controller
public function update(Request $request, Product $product)
{
$this->checkAdmin();
$validated = $request->validate([
'name' => 'required|string|max:255',
'type' => 'required|in:bike,accessory,clothing,spare,service,children,skate,rollers,other',
@@ -194,6 +205,7 @@ class ProductosController extends Controller
public function destroy(Product $product)
{
$this->checkAdmin();
if ($product->image_path) {
Storage::disk('public')->delete($product->image_path);
}
@@ -4,6 +4,13 @@ use Illuminate\Http\Request;
use App\Models\Supplier;
class SupplierController extends Controller
{
private function checkAdmin()
{
if (auth()->check() && auth()->user()->role !== 'admin') {
abort(403, 'Solo los administradores pueden realizar esta acción.');
}
}
/**
* grilla de proveedores con buscador
*/
@@ -27,12 +34,14 @@ class SupplierController extends Controller
*/
public function create()
{
$this->checkAdmin();
return view('suppliers.create');
}
public function store(Request $request)
{
$this->checkAdmin();
$validated = $request->validate([
'name' => 'required|string|max:255',
'phone' => 'nullable|string|max:50',
@@ -56,11 +65,13 @@ class SupplierController extends Controller
public function edit(Supplier $supplier)
{
$this->checkAdmin();
return view('suppliers.edit', compact('supplier'));
}
public function update(Request $request, Supplier $supplier)
{
$this->checkAdmin();
$validated = $request->validate([
'name' => 'required|string|max:255',
'phone' => 'nullable|string|max:50',
@@ -79,6 +90,7 @@ class SupplierController extends Controller
*/
public function destroy(Supplier $supplier)
{
$this->checkAdmin();
try {
$supplier->delete();
return redirect()->route('suppliers.index')
-9
View File
@@ -21,14 +21,5 @@ class DatabaseSeeder extends Seeder
'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',
]);
}
}
+55 -37
View File
@@ -45,43 +45,53 @@ document.addEventListener('DOMContentLoaded', function () {
window.location.href = `/agenda/dia/${fecha}`
},
*/
eventClick: function (info) {
const evento = info.event;
const modal = document.getElementById("eventModal");
if (!modal) return;
const evento = info.event
modal.classList.remove("hidden");
document.body.style.overflow = "hidden";
const modal = document.getElementById("eventModal")
if (!modal) return
// Llenar campos dinámicos
document.getElementById("modalClientName").innerText = evento.extendedProps.client_name ?? 'Sin asignar';
document.getElementById("modalBikeModel").innerText = evento.extendedProps.bike_model ?? 'Sin modelo';
modal.classList.remove("hidden")
const statusMap = {
pending: 'Pendiente',
confirmed: 'Confirmado',
in_progress: 'En progreso',
ready: 'Listo',
delivered: 'Entregado'
}
const estado = statusMap[evento.extendedProps.status] ?? 'desconocido'
document.getElementById("modalTitle").innerText =
evento.extendedProps.description + " - " + estado
const fecha = evento.start
const hora = fecha.toLocaleTimeString("es-AR", {
hour: "2-digit",
minute: "2-digit",
// Formatear Fecha y Hora
const fecha = evento.start;
const dateStr = fecha.toLocaleDateString("es-AR", {
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric'
});
const timeStr = fecha.toLocaleTimeString("es-AR", {
hour: '2-digit',
minute: '2-digit',
hour12: false
})
});
const formattedDateTime = dateStr.charAt(0).toUpperCase() + dateStr.slice(1) + " a las " + timeStr;
document.getElementById("modalDateTime").innerText = formattedDateTime;
document.getElementById("modalDate").innerText =
"Hora: " + hora
const url = window.appointmentShowTemplate.replace(':id', evento.id);
document.getElementById("viewEventBtn").href = url;
const url = window.appointmentShowTemplate.replace(':id', evento.id)
document.getElementById("viewEventBtn").href = url
// Mapear Badge de Estado y clases correspondientes (estilo pastel idéntico a pendiente)
const statusMap = {
pending: { label: 'Pendiente', classes: ['bg-red-100', 'text-red-800', 'border-red-200', 'dark:bg-red-900/30', 'dark:text-red-400', 'dark:border-red-800'] },
confirmed: { label: 'Confirmado', classes: ['bg-blue-100', 'text-blue-800', 'border-blue-200', 'dark:bg-blue-900/30', 'dark:text-blue-400', 'dark:border-blue-800'] },
in_progress: { label: 'En progreso', classes: ['bg-orange-100', 'text-orange-800', 'border-orange-200', 'dark:bg-orange-950/30', 'dark:text-orange-400', 'dark:border-orange-800'] },
ready: { label: 'Listo', classes: ['bg-green-100', 'text-green-800', 'border-green-200', 'dark:bg-green-900/30', 'dark:text-green-450', 'dark:border-green-800'] },
delivered: { label: 'Entregado', classes: ['bg-neutral-100', 'text-neutral-800', 'border-neutral-200', 'dark:bg-neutral-800', 'dark:text-neutral-400', 'dark:border-neutral-700'] }
};
const statusObj = statusMap[evento.extendedProps.status] || { label: 'Desconocido', classes: ['bg-neutral-100', 'text-neutral-800', 'border-neutral-200'] };
const badge = document.getElementById("modalStatusBadge");
if (badge) {
badge.innerText = statusObj.label;
badge.className = "px-2.5 py-0.5 text-xs font-black rounded-full uppercase tracking-wider border";
statusObj.classes.forEach(c => badge.classList.add(c));
}
},
events: window.appointments ?? [],
@@ -109,14 +119,22 @@ document.addEventListener('DOMContentLoaded', function () {
calendar.render()
const closeBtn = document.getElementById("closeModal")
// Manejo del cierre del modal con múltiples triggers (Cerrar, Botón X y Click afuera)
const closeModalElements = [
document.getElementById("closeModal"),
document.getElementById("closeModalCross"),
document.getElementById("eventModal")
]
if (closeBtn) {
closeBtn.addEventListener("click", function () {
document.getElementById("eventModal").classList.add("hidden")
document.body.style.overflow = "auto"
})
}
closeModalElements.forEach(el => {
if (el) {
el.addEventListener("click", function (e) {
if (el.id === "eventModal" && e.target !== el) return;
document.getElementById("eventModal").classList.add("hidden")
document.body.style.overflow = "auto"
})
}
})
})
@@ -0,0 +1,323 @@
<x-layout title="Empleados - Lauck">
<x-section-header subtitle="Personal" title="Administración de " highlight="Empleados" />
<div class="w-full max-w-5xl mx-auto space-y-6">
<!-- Formulario de Registro de Empleado -->
<div class="bg-gray-200 dark:bg-panel-bg border border-neutral-400 dark:border-neutral-800 rounded-xl p-6 shadow-lg">
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-4">Registrar Nuevo Empleado</h3>
<form action="{{ route('admin.employees.store') }}" method="POST" class="space-y-4">
@csrf
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<!-- Nombre -->
<div>
<x-forms.label for="name" value="Nombre Completo" />
<x-forms.input id="name" name="name" type="text" required placeholder="Ej: Juan Pérez" :value="old('name')" :error="$errors->first('name')" />
</div>
<!-- Correo -->
<div>
<x-forms.label for="email" value="Correo Electrónico" />
<x-forms.input id="email" name="email" type="email" required placeholder="empleado@lauck.com" :value="old('email')" :error="$errors->first('email')" />
</div>
<!-- Botón Registrar (Desktop: abajo-derecha de la primera fila, o alineado. Pongámoslo al final) -->
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- Contraseña -->
<div>
<x-forms.label for="password" value="Contraseña (Mín. 8 caracteres)" />
<div class="relative">
<x-forms.input id="password" name="password" type="password" required placeholder="••••••••" :error="$errors->first('password')" class="pr-10" />
<button type="button" id="togglePassword" class="absolute right-3 top-[11px] text-neutral-500 hover:text-black dark:hover:text-white transition-colors">
{{-- Ojo abierto --}}
<svg id="iconShow" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.477 0 8.268 2.943 9.542 7-1.274 4.057-5.065 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
{{-- Ojo cerrado --}}
<svg id="iconHide" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.477 0-8.268-2.943-9.542-7a9.956 9.956 0 012.293-3.95M6.938 6.938A9.956 9.956 0 0112 5c4.477 0 8.268 2.943 9.542 7a9.97 9.97 0 01-1.88 3.118M3 3l18 18" />
</svg>
</button>
</div>
</div>
<!-- Confirmar Contraseña -->
<div>
<x-forms.label for="password_confirmation" value="Confirmar Contraseña" />
<div class="relative">
<x-forms.input id="password_confirmation" name="password_confirmation" type="password" required placeholder="••••••••" class="pr-10" />
<button type="button" id="togglePasswordConf" class="absolute right-3 top-[11px] text-neutral-500 hover:text-black dark:hover:text-white transition-colors">
{{-- Ojo abierto --}}
<svg id="iconShowConf" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.477 0 8.268 2.943 9.542 7-1.274 4.057-5.065 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
{{-- Ojo cerrado --}}
<svg id="iconHideConf" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.477 0-8.268-2.943-9.542-7a9.956 9.956 0 012.293-3.95M6.938 6.938A9.956 9.956 0 0112 5c4.477 0 8.268 2.943 9.542 7a9.97 9.97 0 01-1.88 3.118M3 3l18 18" />
</svg>
</button>
</div>
</div>
</div>
<div class="flex justify-end pt-2">
<button type="submit" class="w-full md:w-auto px-6 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 uppercase text-xs tracking-wider">
Registrar Empleado
</button>
</div>
</form>
</div>
<!-- Tabla de Empleados -->
<div class="bg-gray-200 dark:bg-panel-bg border border-neutral-400 dark:border-neutral-800 rounded-xl shadow-lg overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full text-sm text-left text-neutral-900 dark:text-white">
<thead class="text-xs text-neutral-800 dark:text-gray-300 uppercase bg-gray-400 dark:bg-neutral-800 border-b border-gray-300 dark:border-neutral-700">
<tr>
<th scope="col" class="px-6 py-3">Nombre</th>
<th scope="col" class="px-6 py-3">Correo Electrónico</th>
<th scope="col" class="px-6 py-3">Fecha de Registro</th>
<th scope="col" class="px-6 py-3 text-right">Acciones</th>
</tr>
</thead>
<tbody>
@forelse($employees as $employee)
<tr class="bg-gray-200/50 dark:bg-neutral-900/50 border-b border-gray-400 dark:border-neutral-800 hover:bg-gray-400 hover:dark:bg-neutral-800 transition-colors group">
<td class="px-6 py-4 font-bold text-neutral-900 dark:text-white">
{{ $employee->name }}
</td>
<td class="px-6 py-4 font-mono text-neutral-600 dark:text-gray-400">
{{ $employee->email }}
</td>
<td class="px-6 py-4 text-neutral-700 dark:text-gray-300">
{{ $employee->created_at->format('d/m/Y H:i') }}
</td>
<td class="px-6 py-4 text-right">
<div class="flex items-center justify-end gap-2">
<!-- Editar -->
<button onclick="openEditModal({{ $employee->id }}, '{{ addslashes($employee->name) }}', '{{ addslashes($employee->email) }}')" title="Editar Empleado" 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>
</button>
<!-- Eliminar -->
<form action="{{ route('admin.employees.destroy', $employee) }}" method="POST" class="delete-form inline">
@csrf
@method('DELETE')
<button title="Eliminar Empleado" type="submit" class="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">
<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="M5 7h14m-9 3v8m4-8v8M10 3h4a1 1 0 0 1 1 1v3H9V4a1 1 0 0 1 1-1ZM6 7h12v13a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7Z"/>
</svg>
</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="px-6 py-10 text-center text-gray-500 dark:text-gray-400 font-semibold">
No hay empleados registrados actualmente.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
<!-- Modal de Edición de Empleado -->
<div id="editEmployeeModal"
class="hidden fixed inset-0 z-50 flex items-center justify-center p-4 bg-neutral-950/60 backdrop-blur-sm transition-all duration-300">
<div class="relative p-6 rounded-2xl w-full max-w-md shadow-2xl border transition-all transform scale-100
bg-white border-neutral-200 text-neutral-900
dark:bg-neutral-900 dark:border-neutral-800 dark:text-white">
<!-- Close button (X) -->
<button type="button" onclick="closeEditModal()" class="absolute top-4 right-4 text-neutral-400 hover:text-neutral-600 dark:hover:text-neutral-350 transition-colors p-1.5 rounded-lg hover:bg-neutral-100 dark:hover:bg-neutral-800">
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
<!-- Header -->
<div class="mb-5 pb-3 border-b border-neutral-200 dark:border-neutral-800 pr-8">
<h3 class="text-lg font-black text-neutral-900 dark:text-white flex items-center gap-2">
<svg class="w-5 h-5 text-neutral-500 dark:text-neutral-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.83 21.75a0.75 0.75 0 01-0.343.203l-4.852 1.213a0.075 0.075 0 01-0.09-.09l1.213-4.852a0.75 0.75 0 01.203-.343L16.862 4.487zm0 0L19.5 7.125" />
</svg>
Editar Empleado
</h3>
</div>
<form id="editEmployeeForm" method="POST" class="space-y-4">
@csrf
@method('PUT')
<!-- Nombre -->
<div>
<x-forms.label for="edit_name" value="Nombre Completo" />
<x-forms.input id="edit_name" name="name" type="text" required placeholder="Ej: Juan Pérez" />
</div>
<!-- Correo -->
<div>
<x-forms.label for="edit_email" value="Correo Electrónico" />
<x-forms.input id="edit_email" name="email" type="email" required placeholder="empleado@lauck.com" />
</div>
<!-- Contraseña opcional -->
<div>
<x-forms.label for="edit_password" value="Nueva Contraseña (Opcional)" />
<div class="relative">
<x-forms.input id="edit_password" name="password" type="password" placeholder="Dejar vacío para no cambiar" class="pr-10" />
<button type="button" id="toggleEditPassword" class="absolute right-3 top-[11px] text-neutral-500 hover:text-black dark:hover:text-white transition-colors">
{{-- Ojo abierto --}}
<svg id="iconEditShow" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.477 0 8.268 2.943 9.542 7-1.274 4.057-5.065 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
{{-- Ojo cerrado --}}
<svg id="iconEditHide" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.477 0-8.268-2.943-9.542-7a9.956 9.956 0 012.293-3.95M6.938 6.938A9.956 9.956 0 0112 5c4.477 0 8.268 2.943 9.542 7a9.97 9.97 0 01-1.88 3.118M3 3l18 18" />
</svg>
</button>
</div>
</div>
<!-- Confirmar Contraseña opcional -->
<div>
<x-forms.label for="edit_password_confirmation" value="Confirmar Nueva Contraseña" />
<div class="relative">
<x-forms.input id="edit_password_confirmation" name="password_confirmation" type="password" placeholder="Dejar vacío para no cambiar" class="pr-10" />
<button type="button" id="toggleEditPasswordConf" class="absolute right-3 top-[11px] text-neutral-500 hover:text-black dark:hover:text-white transition-colors">
{{-- Ojo abierto --}}
<svg id="iconEditShowConf" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.477 0 8.268 2.943 9.542 7-1.274 4.057-5.065 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
{{-- Ojo cerrado --}}
<svg id="iconEditHideConf" xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.477 0-8.268-2.943-9.542-7a9.956 9.956 0 012.293-3.95M6.938 6.938A9.956 9.956 0 0112 5c4.477 0 8.268 2.943 9.542 7a9.97 9.97 0 01-1.88 3.118M3 3l18 18" />
</svg>
</button>
</div>
</div>
<!-- Acciones -->
<div class="flex justify-end gap-2 pt-3 border-t border-neutral-100 dark:border-neutral-800">
<button type="button" onclick="closeEditModal()"
class="px-4 py-2 text-xs font-bold rounded-lg transition-colors bg-neutral-100 hover:bg-neutral-200 text-neutral-700 dark:bg-neutral-800 dark:hover:bg-neutral-700 dark:text-white">
Cancelar
</button>
<button type="submit"
class="px-4 py-2 text-xs font-bold rounded-lg transition-colors bg-neutral-950 hover:bg-neutral-900/80 text-neon-lime dark:bg-neon-lime dark:hover:bg-[#b3e600] dark:text-neutral-900">
Guardar Cambios
</button>
</div>
</form>
</div>
</div>
@push('scripts')
<script>
function openEditModal(id, name, email) {
const modal = document.getElementById('editEmployeeModal');
const form = document.getElementById('editEmployeeForm');
document.getElementById('edit_name').value = name;
document.getElementById('edit_email').value = email;
document.getElementById('edit_password').value = '';
document.getElementById('edit_password_confirmation').value = '';
form.action = `/admin/employees/${id}`;
modal.classList.remove('hidden');
document.body.style.overflow = 'hidden';
}
function closeEditModal() {
const modal = document.getElementById('editEmployeeModal');
modal.classList.add('hidden');
document.body.style.overflow = 'auto';
}
document.addEventListener('DOMContentLoaded', function() {
// Cierre por click en backdrop
const editModal = document.getElementById('editEmployeeModal');
if (editModal) {
editModal.addEventListener('click', function(e) {
if (e.target === editModal) {
closeEditModal();
}
});
}
// Toggle contraseña principal (Registro)
const togglePassword = document.getElementById('togglePassword');
const passwordInput = document.getElementById('password');
const iconShow = document.getElementById('iconShow');
const iconHide = document.getElementById('iconHide');
if (togglePassword && passwordInput) {
togglePassword.addEventListener('click', function() {
const isPassword = passwordInput.getAttribute('type') === 'password';
passwordInput.setAttribute('type', isPassword ? 'text' : 'password');
iconShow.classList.toggle('hidden', isPassword);
iconHide.classList.toggle('hidden', !isPassword);
});
}
// Toggle confirmar contraseña (Registro)
const togglePasswordConf = document.getElementById('togglePasswordConf');
const passwordConfInput = document.getElementById('password_confirmation');
const iconShowConf = document.getElementById('iconShowConf');
const iconHideConf = document.getElementById('iconHideConf');
if (togglePasswordConf && passwordConfInput) {
togglePasswordConf.addEventListener('click', function() {
const isPassword = passwordConfInput.getAttribute('type') === 'password';
passwordConfInput.setAttribute('type', isPassword ? 'text' : 'password');
iconShowConf.classList.toggle('hidden', isPassword);
iconHideConf.classList.toggle('hidden', !isPassword);
});
}
// Toggle contraseña (Edición)
const toggleEditPassword = document.getElementById('toggleEditPassword');
const editPasswordInput = document.getElementById('edit_password');
const iconEditShow = document.getElementById('iconEditShow');
const iconEditHide = document.getElementById('iconEditHide');
if (toggleEditPassword && editPasswordInput) {
toggleEditPassword.addEventListener('click', function() {
const isPassword = editPasswordInput.getAttribute('type') === 'password';
editPasswordInput.setAttribute('type', isPassword ? 'text' : 'password');
iconEditShow.classList.toggle('hidden', isPassword);
iconEditHide.classList.toggle('hidden', !isPassword);
});
}
// Toggle confirmar contraseña (Edición)
const toggleEditPasswordConf = document.getElementById('toggleEditPasswordConf');
const editPasswordConfInput = document.getElementById('edit_password_confirmation');
const iconEditShowConf = document.getElementById('iconEditShowConf');
const iconEditHideConf = document.getElementById('iconEditHideConf');
if (toggleEditPasswordConf && editPasswordConfInput) {
toggleEditPasswordConf.addEventListener('click', function() {
const isPassword = editPasswordConfInput.getAttribute('type') === 'password';
editPasswordConfInput.setAttribute('type', isPassword ? 'text' : 'password');
iconEditShowConf.classList.toggle('hidden', isPassword);
iconEditHideConf.classList.toggle('hidden', !isPassword);
});
}
});
</script>
@endpush
</x-layout>
+66 -11
View File
@@ -37,29 +37,84 @@
</x-layout>
<div id="eventModal"
class="hidden fixed inset-0 z-50 flex items-center justify-center">
class="hidden fixed inset-0 z-50 flex items-center justify-center p-4 bg-neutral-950/60 backdrop-blur-sm transition-all duration-300">
<div class="relative p-6 rounded-xl w-96 shadow-2xl border
<div class="relative p-6 rounded-2xl w-full max-w-sm shadow-2xl border transition-all transform scale-100
bg-white border-neutral-200 text-neutral-900
dark:bg-neutral-900 dark:border-neutral-700 dark:text-white">
dark:bg-neutral-900 dark:border-neutral-800 dark:text-white">
<h2 id="modalTitle" class="text-xl font-bold mb-2 text-neutral-900 dark:text-white"></h2>
<!-- Close button (X) -->
<button id="closeModalCross" class="absolute top-4 right-4 text-neutral-400 hover:text-neutral-600 dark:hover:text-neutral-350 transition-colors p-1.5 rounded-lg hover:bg-neutral-100 dark:hover:bg-neutral-800">
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
<p id="modalDate" class="mb-4 text-neutral-500 dark:text-gray-300"></p>
<!-- Header -->
<div class="flex justify-between items-center mb-5 pb-3 border-b border-neutral-200 dark:border-neutral-800 pr-8">
<h3 class="text-base font-black text-neutral-900 dark:text-white flex items-center gap-2">
<svg class="w-5 h-5 text-neutral-500 dark:text-neutral-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5" />
</svg>
Detalle del Turno
</h3>
<span id="modalStatusBadge" class="px-2.5 py-0.5 text-xs font-black rounded-full uppercase tracking-wider border"></span>
</div>
<div class="flex justify-end gap-2">
<!-- Info Grid -->
<div class="space-y-4 mb-6">
<!-- Dueño -->
<div class="flex items-start gap-3">
<div class="p-2.5 bg-neutral-100 dark:bg-neutral-800 rounded-lg text-neutral-500 dark:text-neutral-400 shrink-0">
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z"/></svg>
</div>
<div>
<p class="text-[10px] text-neutral-400 dark:text-neutral-500 uppercase font-bold tracking-wider">Dueño</p>
<p id="modalClientName" class="font-extrabold text-sm text-neutral-800 dark:text-neutral-100"></p>
</div>
</div>
<!-- Modelo de Bicicleta -->
<div class="flex items-start gap-3">
<div class="p-2.5 bg-neutral-100 dark:bg-neutral-800 rounded-lg text-neutral-500 dark:text-neutral-400 shrink-0">
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9.568 3H5.25A2.25 2.25 0 0 0 3 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581a1.125 1.125 0 0 0 1.591 0l7.261-7.26a1.125 1.125 0 0 0 0-1.591L12.51 3.659A2.25 2.25 0 0 0 10.917 3H9.568Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M6 6h.008v.008H6V6Z"/></svg>
</div>
<div>
<p class="text-[10px] text-neutral-400 dark:text-neutral-500 uppercase font-bold tracking-wider">Modelo de Bicicleta</p>
<p id="modalBikeModel" class="font-bold text-sm text-neutral-800 dark:text-neutral-100"></p>
</div>
</div>
<!-- Día y Horario Pactado -->
<div class="flex items-start gap-3">
<div class="p-2.5 bg-neutral-100 dark:bg-neutral-800 rounded-lg text-neutral-500 dark:text-neutral-400 shrink-0">
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/></svg>
</div>
<div>
<p class="text-[10px] text-neutral-400 dark:text-neutral-500 uppercase font-bold tracking-wider">Día y Horario Pactado</p>
<p id="modalDateTime" class="font-bold text-sm text-neutral-800 dark:text-neutral-100"></p>
</div>
</div>
</div>
<!-- Acciones -->
<div class="flex justify-end gap-2 pt-3 border-t border-neutral-100 dark:border-neutral-800">
<button id="closeModal"
class="px-4 py-2 rounded-lg
class="px-4 py-2 text-xs font-bold rounded-lg transition-colors
bg-neutral-100 hover:bg-neutral-200 text-neutral-700
dark:bg-gray-600 dark:hover:bg-gray-500 dark:text-white">
dark:bg-neutral-800 dark:hover:bg-neutral-700 dark:text-white">
Cerrar
</button>
<a id="viewEventBtn"
class="px-4 py-2 font-bold rounded-lg
bg-neutral-900 hover:bg-neutral-700 text-neon-lime
class="px-4 py-2 text-xs font-bold rounded-lg transition-colors flex items-center gap-1.5
bg-neutral-950 hover:bg-neutral-900/80 text-neon-lime
dark:bg-neon-lime dark:hover:bg-[#b3e600] dark:text-neutral-900">
Ver turno
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
Ver Detalle Completo
</a>
</div>
</div>
+126 -80
View File
@@ -2,109 +2,155 @@
<x-section-header subtitle="Gestor de Turnos" title="Detalle del" highlight="Turno" />
<div class="flex justify-end mb-6">
<a href="{{ route('agenda') }}"
<div class="flex justify-end mb-6 gap-3">
<a href="{{ route('taller.index') }}"
class="px-5 py-2.5 font-bold rounded-lg uppercase tracking-wide text-xs flex items-center gap-2
bg-neutral-900 text-white hover:bg-neutral-700
dark:bg-neon-lime dark:text-neutral-900 dark:hover:bg-[#b3e600]">
dark:bg-neon-lime dark:text-neutral-900 dark:hover:bg-[#b3e600] transition-colors">
Volver a Taller
</a>
<a href="{{ route('agenda') }}"
class="px-5 py-2.5 font-bold rounded-lg uppercase tracking-wide text-xs flex items-center gap-2
bg-stone-200 dark:bg-neutral-800 text-neutral-800 dark:text-neutral-200 hover:bg-stone-300 dark:hover:bg-neutral-700 transition-colors">
Volver a Agenda
</a>
</div>
<div class="rounded-2xl shadow-lg p-8 space-y-6
bg-white border border-neutral-300
dark:bg-neutral-900/50 dark:border-neutral-700 dark:border-2">
@php
$statusMap = [
'pending' => [
'label' => 'Pendiente',
'classes' => 'bg-red-100 text-red-800 border-red-200 dark:bg-red-900/30 dark:text-red-400 dark:border-red-800'
],
'confirmed' => [
'label' => 'Confirmado',
'classes' => 'bg-blue-100 text-blue-800 border-blue-200 dark:bg-blue-900/30 dark:text-blue-400 dark:border-blue-800'
],
'in_progress' => [
'label' => 'En progreso',
'classes' => 'bg-orange-100 text-orange-800 border-orange-200 dark:bg-orange-950/30 dark:text-orange-400 dark:border-orange-800'
],
'ready' => [
'label' => 'Listo',
'classes' => 'bg-green-100 text-green-800 border-green-200 dark:bg-green-900/30 dark:text-green-450 dark:border-green-800'
],
'delivered' => [
'label' => 'Entregado',
'classes' => 'bg-neutral-100 text-neutral-800 border-neutral-200 dark:bg-neutral-850 dark:text-gray-400 dark:border-neutral-700'
]
];
@php
$statusMap = [
'pending' => 'Pendiente',
'confirmed' => 'Confirmado',
'in_progress' => 'En progreso',
'ready' => 'Listo',
'delivered' => 'Entregado',
];
$currentStatus = $statusMap[$appointment->status] ?? [
'label' => 'Desconocido',
'classes' => 'bg-neutral-100 text-neutral-800 border-neutral-200'
];
@endphp
$translatedStatus = $statusMap[$appointment->status] ?? 'Desconocido';
$colorMap = [
'pending' => '#ef4444',
'in_progress' => '#f59e0b',
'completed' => '#22c55e',
'ready' => '#22c55e',
'delivered' => '#22c55e',
];
$color = $colorMap[$appointment->status] ?? '#3b82f6';
@endphp
{{-- Estado --}}
<div class="text-center">
<span class="text-lg uppercase font-bold tracking-wide text-neutral-500 dark:text-gray-400">
Estado
<div class="max-w-2xl mx-auto bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-2xl shadow-xl p-8 space-y-6">
<!-- Header / Status -->
<div class="flex justify-between items-center pb-4 border-b border-neutral-200 dark:border-neutral-800">
<h3 class="text-lg font-black text-neutral-900 dark:text-white flex items-center gap-2">
<svg class="w-5 h-5 text-neutral-500 dark:text-neutral-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5" />
</svg>
Ficha del Turno #{{ $appointment->id }}
</h3>
<span class="px-2.5 py-0.5 text-xs font-black rounded-full uppercase tracking-wider border {{ $currentStatus['classes'] }}">
{{ $currentStatus['label'] }}
</span>
<div class="mt-2">
<span class="px-3 py-1 rounded-full text-lg font-semibold text-black"
style="background-color: {{ $color }}">
{{ $translatedStatus }}
</span>
</div>
</div>
{{-- Información principal --}}
<!-- Info Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<p class="text-lg uppercase font-bold text-neutral-500 dark:text-gray-400">Cliente</p>
<p class="text-lg font-semibold text-neutral-900 dark:text-white">
{{ $appointment->client->name }}
</p>
<!-- Cliente -->
<div class="flex items-start gap-3 md:col-span-2">
<div class="p-2.5 bg-neutral-100 dark:bg-neutral-800 rounded-lg text-neutral-500 dark:text-neutral-400 shrink-0">
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z"/></svg>
</div>
<div>
<p class="text-[10px] text-neutral-400 dark:text-neutral-500 uppercase font-bold tracking-wider">Cliente</p>
<p class="font-extrabold text-base text-neutral-800 dark:text-neutral-100">{{ $appointment->client->name }}</p>
</div>
</div>
<div>
<p class="text-lg uppercase font-bold text-neutral-500 dark:text-gray-400">Teléfono</p>
<p class="text-neutral-900 dark:text-white">
{{ $appointment->contact_phone ?? '-' }}
</p>
<!-- Teléfono -->
<div class="flex items-start gap-3">
<div class="p-2.5 bg-neutral-100 dark:bg-neutral-800 rounded-lg text-neutral-500 dark:text-neutral-400 shrink-0">
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 6.75c0 8.284 6.716 15 15 15h2.25a2.25 2.25 0 0 0 2.25-2.25v-1.372c0-.516-.351-.966-.852-1.091l-4.423-1.106c-.44-.11-.902.055-1.173.417l-.97 1.293c-.282.376-.769.542-1.21.387a12.035 12.035 0 0 1-7.108-7.108c-.145-.44.02-9.27.387-1.21l1.293-.97c.363-.271.527-.734.417-1.173L6.963 3.102a1.125 1.125 0 0 0-1.091-.852H4.5A2.25 2.25 0 0 0 2.25 4.5v2.25Z"/></svg>
</div>
<div>
<p class="text-[10px] text-neutral-400 dark:text-neutral-500 uppercase font-bold tracking-wider">Teléfono de contacto</p>
<p class="font-bold text-neutral-800 dark:text-neutral-100">{{ $appointment->contact_phone ?? 'Sin teléfono registrado' }}</p>
</div>
</div>
<div>
<p class="text-lg uppercase font-bold text-neutral-500 dark:text-gray-400">Fecha pactada de entrega</p>
<p class="text-neutral-900 dark:text-white">
{{ $appointment->scheduled_at->format('d/m/Y') }}
</p>
<!-- Bicicleta -->
<div class="flex items-start gap-3">
<div class="p-2.5 bg-neutral-100 dark:bg-neutral-800 rounded-lg text-neutral-500 dark:text-neutral-400 shrink-0">
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9.568 3H5.25A2.25 2.25 0 0 0 3 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581a1.125 1.125 0 0 0 1.591 0l7.261-7.26a1.125 1.125 0 0 0 0-1.591L12.51 3.659A2.25 2.25 0 0 0 10.917 3H9.568Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M6 6h.008v.008H6V6Z"/></svg>
</div>
<div>
<p class="text-[10px] text-neutral-400 dark:text-neutral-500 uppercase font-bold tracking-wider">Bicicleta</p>
<p class="font-bold text-neutral-800 dark:text-neutral-100">{{ $appointment->bike_model ?? 'Sin modelo' }}</p>
</div>
</div>
<div>
<p class="text-lg uppercase font-bold text-neutral-500 dark:text-gray-400">Hora</p>
<p class="text-neutral-900 dark:text-white">
{{ $appointment->scheduled_at->format('H:i') }}
</p>
<!-- Fecha y Hora de Entrega -->
<div class="flex items-start gap-3">
<div class="p-2.5 bg-neutral-100 dark:bg-neutral-800 rounded-lg text-neutral-500 dark:text-neutral-400 shrink-0">
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/></svg>
</div>
<div>
<p class="text-[10px] text-neutral-400 dark:text-neutral-500 uppercase font-bold tracking-wider">Fecha y Hora Pactada</p>
<p class="font-bold text-neutral-800 dark:text-neutral-100">
{{ ucfirst($appointment->scheduled_at->locale('es')->translatedFormat('l, d \d\e F \d\e Y')) }} a las {{ $appointment->scheduled_at->format('H:i') }}
</p>
</div>
</div>
<div class="md:col-span-2">
<p class="text-lg uppercase font-bold text-neutral-500 dark:text-gray-400">Modelo de Bicicleta</p>
<p class="text-neutral-900 dark:text-white">
{{ $appointment->bike_model ?? '-' }}
</p>
<!-- Presupuesto -->
<div class="flex items-start gap-3">
<div class="p-2.5 bg-neutral-100 dark:bg-neutral-800 rounded-lg text-neutral-500 dark:text-neutral-400 shrink-0">
<svg class="w-5 h-5 text-neutral-500 dark:text-neutral-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v12m-3 -2.818l0.879 0.659c1.171 0.879 3.07 0.879 4.242 0c1.172 -0.879 1.172 -2.303 0 -3.182C13.536 12.219 12.768 12 12 12c-0.725 0 -1.45 -0.22 -2.003 -0.659c-1.106 -0.879 -1.106 -2.303 0 -3.182s2.9 -0.879 4.006 0l0.415 0.33M21 12a9 9 0 1 1 -18 0a9 9 0 0 1 18 0Z" />
</svg>
</div>
<div>
<p class="text-[10px] text-neutral-400 dark:text-neutral-500 uppercase font-bold tracking-wider">Presupuesto Estimado</p>
<p class="font-black text-neutral-800 dark:text-neutral-100 font-mono">
{{ $appointment->estimated_cost ? '$' . number_format($appointment->estimated_cost, 0, ',', '.') : 'Sin presupuestar' }}
</p>
</div>
</div>
<div class="md:col-span-2">
<p class="text-lg uppercase font-bold text-neutral-500 dark:text-gray-400">Problema Reportado</p>
<p class="text-neutral-900 dark:text-white">
{{ $appointment->problem_description }}
</p>
</div>
<div class="md:col-span-2">
<p class="text-lg uppercase font-bold text-neutral-500 dark:text-gray-400">Notas</p>
<p class="text-neutral-900 dark:text-white">
{{ $appointment->notes ?? '-' }}
</p>
</div>
</div>
<!-- Descripción del problema -->
<div class="mt-6">
<p class="text-[10px] text-neutral-400 dark:text-neutral-500 uppercase font-bold tracking-wider mb-1.5">Descripción del problema</p>
<div class="p-4 bg-neutral-50 dark:bg-neutral-950/30 border border-neutral-100 dark:border-neutral-800 rounded-xl">
<p class="text-sm text-neutral-700 dark:text-neutral-300 leading-relaxed whitespace-pre-line">{{ $appointment->problem_description }}</p>
</div>
</div>
<!-- Repuestos necesarios (si existen) -->
@if($appointment->parts_needed)
<div class="mt-6">
<p class="text-[10px] text-neutral-400 dark:text-neutral-500 uppercase font-bold tracking-wider mb-1.5">Repuestos Necesarios</p>
<div class="p-4 bg-amber-50/50 dark:bg-amber-950/10 border border-amber-100/70 dark:border-amber-900/30 text-amber-800 dark:text-amber-300 rounded-xl">
<p class="text-sm leading-relaxed whitespace-pre-line">{{ $appointment->parts_needed }}</p>
</div>
</div>
@endif
<!-- Notas internas (si existen) -->
@if($appointment->notes)
<div class="mt-6">
<p class="text-[10px] text-neutral-400 dark:text-neutral-500 uppercase font-bold tracking-wider mb-1.5">Notas Internas</p>
<div class="p-4 bg-blue-50/50 dark:bg-blue-950/10 border border-blue-100/70 dark:border-blue-900/30 text-blue-800 dark:text-blue-300 rounded-xl">
<p class="text-sm leading-relaxed whitespace-pre-line">{{ $appointment->notes }}</p>
</div>
</div>
@endif
</div>
</x-layout>
+44 -11
View File
@@ -21,9 +21,15 @@
</div>
</form>
<a href="{{ route('clients.create') }}" class="w-full md:w-auto text-center px-5 py-3 text-sm font-bold text-neon-lime dark:text-neutral-900 bg-neutral-950 dark:bg-neon-lime rounded-lg hover:bg-neutral-900/80 hover:dark:bg-[#b3e600] transition-colors uppercase tracking-wide">
+ Nuevo Cliente
</a>
@if(auth()->user()->role === 'admin')
<a href="{{ route('clients.create') }}" class="w-full md:w-auto text-center px-5 py-3 text-sm font-bold text-neon-lime dark:text-neutral-900 bg-neutral-950 dark:bg-neon-lime rounded-lg hover:bg-neutral-900/80 hover:dark:bg-[#b3e600] transition-colors uppercase tracking-wide">
+ Nuevo Cliente
</a>
@else
<span title="Acceso denegado" class="w-full md:w-auto text-center px-5 py-3 text-sm font-bold text-gray-500 dark:text-neutral-500 bg-gray-300 dark:bg-neutral-800 border border-gray-400 dark:border-neutral-700 rounded-lg cursor-not-allowed pointer-events-none select-none uppercase tracking-wide">
+ Nuevo Cliente
</span>
@endif
</div>
<div class="relative overflow-x-auto shadow-md sm:rounded-lg border border-gray-400 dark:border-neutral-800 w-full">
@@ -61,20 +67,47 @@
</span>
</td>
<td class="px-6 py-4 text-right flex items-center justify-end gap-2">
<a href="{{ route('clients.edit', $client) }}" title="Editar" class="font-semibold p-2 text-blue-700 dark:text-blue-400 border-2 border-blue-700 rounded-lg hover:bg-blue-700 hover:text-white dark:hover:text-white transition-colors">
<!-- Ver Más -->
<a href="{{ route('clients.show', $client) }}" title="Ver Más" class="font-semibold p-2 text-green-700 dark:text-green-400 border-2 border-green-700 rounded-lg hover:bg-green-700 dark:hover:bg-green-600 dark:hover:border-green-600 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"/>
<path stroke="currentColor" stroke-width="2" d="M21 12c0 1.2-4.03 6-9 6s-9-4.8-9-6c0-1.2 4.03-6 9-6s9 4.8 9 6Z"/>
<path stroke="currentColor" stroke-width="2" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/>
</svg>
</a>
<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">
<!-- Editar -->
@if(auth()->user()->role === 'admin')
<a href="{{ route('clients.edit', $client) }}" 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>
@else
<span title="Acceso denegado" class="font-semibold p-2 text-gray-400 dark:text-neutral-500 border-2 border-gray-300 dark:border-neutral-700 rounded-lg bg-gray-100 dark:bg-neutral-800/30 cursor-not-allowed pointer-events-none select-none">
<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>
</span>
@endif
<!-- Eliminar -->
@if(auth()->user()->role === 'admin')
<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">
<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="M5 7h14m-9 3v8m4-8v8M10 3h4a1 1 0 0 1 1 1v3H9V4a1 1 0 0 1 1-1ZM6 7h12v13a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7Z"/>
</svg>
</button>
</form>
@else
<span title="Acceso denegado" class="p-2 font-bold dark:font-medium text-gray-400 dark:text-neutral-500 border-2 border-gray-300 dark:border-neutral-700 rounded-lg bg-gray-100 dark:bg-neutral-800/30 cursor-not-allowed pointer-events-none select-none">
<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="M5 7h14m-9 3v8m4-8v8M10 3h4a1 1 0 0 1 1 1v3H9V4a1 1 0 0 1 1-1ZM6 7h12v13a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7Z"/>
</svg>
</button>
</form>
</span>
@endif
</td>
</tr>
+61
View File
@@ -0,0 +1,61 @@
<x-layout title="Lauck - Detalle de Cliente">
<x-section-header subtitle="Clientes" title="Detalle de " highlight="Cliente" />
<div class="w-full max-w-4xl mx-auto bg-gray-200 dark:bg-panel-bg border border-neutral-400 dark:border-neutral-800 rounded-xl p-8 shadow-lg relative">
<div class="flex justify-between items-start mb-6">
<div>
<h3 class="text-2xl font-bold text-neutral-900 dark:text-white">{{ $client->name }}</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">Registrado el: {{ $client->created_at->format('d/m/Y H:i') }}</p>
</div>
<div>
<a href="{{ route('clients.index') }}" class="text-sm font-bold text-gray-500 hover:text-white transition-colors">Volver al listado</a>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 bg-gray-300 dark:bg-neutral-800 p-6 rounded-lg border border-gray-400 dark:border-neutral-700">
<div>
<p class="text-xs text-gray-500 uppercase tracking-wider mb-1">Nombre Completo</p>
<p class="text-lg font-bold text-neutral-900 dark:text-white">{{ $client->name }}</p>
</div>
<div>
<p class="text-xs text-gray-500 uppercase tracking-wider mb-1">Teléfono / WhatsApp</p>
@if($client->phone)
<p class="text-lg font-bold text-neutral-900 dark:text-white font-mono">{{ $client->phone }}</p>
@else
<p class="text-lg text-gray-500 italic">No registrado</p>
@endif
</div>
<div>
<p class="text-xs text-gray-500 uppercase tracking-wider mb-1">Correo Electrónico</p>
@if($client->email)
<p class="text-base font-semibold text-neutral-900 dark:text-white font-mono">{{ $client->email }}</p>
@else
<p class="text-base text-gray-500 italic">No registrado</p>
@endif
</div>
<div>
<p class="text-xs text-gray-500 uppercase tracking-wider mb-1">Dirección</p>
@if($client->address)
<p class="text-base font-semibold text-neutral-900 dark:text-white">{{ $client->address }}</p>
@else
<p class="text-base text-gray-500 italic">No registrada</p>
@endif
</div>
</div>
<div class="mt-8 flex justify-end gap-4">
@if(auth()->user()->role === 'admin')
<a href="{{ route('clients.edit', $client) }}" class="px-6 py-2.5 text-sm font-bold text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors">
Editar Cliente
</a>
@else
<span title="Acceso denegado" class="px-6 py-2.5 text-sm font-bold text-gray-400 bg-gray-300 dark:bg-neutral-800 border border-gray-400 dark:border-neutral-700 rounded-lg cursor-not-allowed pointer-events-none">
Editar Cliente
</span>
@endif
</div>
</div>
</x-layout>
@@ -80,6 +80,9 @@
<x-sidebar-link href="{{ route('admin.backups.index') }}" :active="request()->routeIs('admin.backups.*')" icon="database">
Respaldos
</x-sidebar-link>
<x-sidebar-link href="{{ route('admin.employees.index') }}" :active="request()->routeIs('admin.employees.*')" icon="users">
Empleados
</x-sidebar-link>
@endif
<div class="pt-4 pb-2">
@@ -20,12 +20,23 @@
<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-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
<div class="flex justify-between items-center mb-3">
@if($job->estimated_cost)
<div>
<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>
@else
<div></div>
@endif
<a href="{{ route('appointments.show', $job) }}" class="text-xs font-bold text-neon-lime dark:text-neutral-900 bg-neutral-950 dark:bg-neon-lime rounded px-2.5 py-1 hover:bg-neutral-900/80 hover:dark:bg-[#b3e600] transition-colors flex items-center gap-1">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
Ver
</a>
</div>
<!-- Controles de Estado -->
<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">
+43 -16
View File
@@ -16,7 +16,7 @@
</div>
<div class="w-full md:w-1/3">
<select name="category" onchange="this.form.submit()" class="block w-full p-3 text-sm text-black dark:text-white border border-neutral-400 dark:border-neutral-700 rounded-lg bg-neutral-200 dark:bg-neutral-800">
<select name="category" onchange="this.form.submit()" class="block w-full p-3 pr-10 text-sm text-black dark:text-white border border-neutral-400 dark:border-neutral-700 rounded-lg bg-neutral-200 dark:bg-neutral-800">
<option value="">Todas las Categorías</option>
<option value="Mercadería" {{ request('category') == 'Mercadería' ? 'selected' : '' }}>Mercadería</option>
<option value="Servicios" {{ request('category') == 'Servicios' ? 'selected' : '' }}>Servicios (Luz, Internet...)</option>
@@ -28,9 +28,15 @@
<button type="submit" class="hidden">Buscar</button>
</form>
<a href="{{ route('expenses.create') }}" class="w-full md:w-auto text-center px-5 py-3 text-sm font-bold text-neon-lime dark:text-neutral-900 bg-neutral-950 dark:bg-neon-lime rounded-lg hover:bg-neutral-900/80 hover:dark:bg-[#b3e600] transition-colors uppercase tracking-wide">
+ Nuevo Gasto
</a>
@if(auth()->user()->role === 'admin')
<a href="{{ route('expenses.create') }}" class="w-full md:w-auto text-center px-5 py-3 text-sm font-bold text-neon-lime dark:text-neutral-900 bg-neutral-950 dark:bg-neon-lime rounded-lg hover:bg-neutral-900/80 hover:dark:bg-[#b3e600] transition-colors uppercase tracking-wide">
+ Nuevo Gasto
</a>
@else
<span title="Acceso denegado" class="w-full md:w-auto text-center px-5 py-3 text-sm font-bold text-gray-500 dark:text-neutral-500 bg-gray-300 dark:bg-neutral-800 border border-gray-400 dark:border-neutral-700 rounded-lg cursor-not-allowed pointer-events-none select-none uppercase tracking-wide">
+ Nuevo Gasto
</span>
@endif
</div>
<div class="relative overflow-x-auto shadow-md sm:rounded-lg border border-gray-400 dark:border-neutral-800 w-full">
@@ -83,26 +89,47 @@
</div>
</td>
<td class="px-6 py-4 text-right flex items-center justify-end gap-3">
<a href="{{ route('expenses.show', $expense) }}" title="Ver Detalle" class="font-semibold p-2 text-gray-700 dark:text-gray-300 border-2 border-gray-500 rounded-lg hover:bg-gray-500 hover:text-white dark:hover:text-white transition-colors">
<!-- Ver Más -->
<a href="{{ route('expenses.show', $expense) }}" title="Ver Detalle" class="font-semibold p-2 text-green-700 dark:text-green-400 border-2 border-green-700 rounded-lg hover:bg-green-700 dark:hover:bg-green-600 dark:hover:border-green-600 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-width="2" d="M21 12c0 1.2-4.03 6-9 6s-9-4.8-9-6c0-1.2 4.03-6 9-6s9 4.8 9 6Z"/>
<path stroke="currentColor" stroke-width="2" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/>
</svg>
</a>
<a href="{{ route('expenses.edit', $expense) }}" title="Editar" class="font-semibold p-2 text-blue-700 dark:text-blue-400 border-2 border-blue-700 rounded-lg hover:bg-blue-700 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('expenses.destroy', $expense)}}" 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">
<!-- Editar -->
@if(auth()->user()->role === 'admin')
<a href="{{ route('expenses.edit', $expense) }}" 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>
@else
<span title="Acceso denegado" class="font-semibold p-2 text-gray-400 dark:text-neutral-500 border-2 border-gray-300 dark:border-neutral-700 rounded-lg bg-gray-100 dark:bg-neutral-800/30 cursor-not-allowed pointer-events-none select-none">
<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>
</span>
@endif
<!-- Eliminar -->
@if(auth()->user()->role === 'admin')
<form action="{{route('expenses.destroy', $expense)}}" 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">
<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="M5 7h14m-9 3v8m4-8v8M10 3h4a1 1 0 0 1 1 1v3H9V4a1 1 0 0 1 1-1ZM6 7h12v13a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7Z"/>
</svg>
</button>
</form>
@else
<span title="Acceso denegado" class="p-2 font-bold dark:font-medium text-gray-400 dark:text-neutral-500 border-2 border-gray-300 dark:border-neutral-700 rounded-lg bg-gray-100 dark:bg-neutral-800/30 cursor-not-allowed pointer-events-none select-none">
<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="M5 7h14m-9 3v8m4-8v8M10 3h4a1 1 0 0 1 1 1v3H9V4a1 1 0 0 1 1-1ZM6 7h12v13a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7Z"/>
</svg>
</button>
</form>
</span>
@endif
</td>
</tr>
@empty
+32 -12
View File
@@ -39,9 +39,15 @@
</div>
</div>
<a href="{{ route('productos.create') }}" class="w-full xl:w-auto text-center px-5 py-3 text-sm font-bold text-neon-lime dark:text-neutral-900 bg-neutral-950 dark:bg-neon-lime rounded-lg hover:bg-neutral-900/80 hover:dark:bg-[#b3e600] transition-colors uppercase tracking-wide">
+ Nuevo Producto
</a>
@if(auth()->user()->role === 'admin')
<a href="{{ route('productos.create') }}" class="w-full xl:w-auto text-center px-5 py-3 text-sm font-bold text-neon-lime dark:text-neutral-900 bg-neutral-950 dark:bg-neon-lime rounded-lg hover:bg-neutral-900/80 hover:dark:bg-[#b3e600] transition-colors uppercase tracking-wide">
+ Nuevo Producto
</a>
@else
<span title="Acceso denegado" class="w-full xl:w-auto text-center px-5 py-3 text-sm font-bold text-gray-500 dark:text-neutral-500 bg-gray-300 dark:bg-neutral-800 border border-gray-400 dark:border-neutral-700 rounded-lg cursor-not-allowed pointer-events-none select-none uppercase tracking-wide">
+ Nuevo Producto
</span>
@endif
</div>
<div class="relative overflow-x-auto shadow-md sm:rounded-lg border border-gray-400 dark:border-neutral-800 w-full mb-4">
@@ -112,16 +118,30 @@
<a href="{{ route('productos.show', $product) }}" title="Ver Mas" class="font-semibold p-2 text-green-700 dark:text-green-400 border-2 border-green-700 rounded-lg hover:bg-green-700 dark:hover:bg-green-600 dark:hover:border-green-600 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-width="2" d="M21 12c0 1.2-4.03 6-9 6s-9-4.8-9-6c0-1.2 4.03-6 9-6s9 4.8 9 6Z"/><path stroke="currentColor" stroke-width="2" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/></svg>
</a>
<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" 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">
@if(auth()->user()->role === 'admin')
<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>
@else
<span title="Acceso denegado" class="font-semibold p-2 text-gray-400 dark:text-neutral-500 border-2 border-gray-300 dark:border-neutral-700 rounded-lg bg-gray-100 dark:bg-neutral-800/30 cursor-not-allowed pointer-events-none select-none">
<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>
</span>
@endif
@if(auth()->user()->role === 'admin')
<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">
<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="M5 7h14m-9 3v8m4-8v8M10 3h4a1 1 0 0 1 1 1v3H9V4a1 1 0 0 1 1-1ZM6 7h12v13a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7Z"/></svg>
</button>
</form>
@else
<span title="Acceso denegado" class="p-2 font-bold dark:font-medium text-gray-400 dark:text-neutral-500 border-2 border-gray-300 dark:border-neutral-700 rounded-lg bg-gray-100 dark:bg-neutral-800/30 cursor-not-allowed pointer-events-none select-none">
<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="M5 7h14m-9 3v8m4-8v8M10 3h4a1 1 0 0 1 1 1v3H9V4a1 1 0 0 1 1-1ZM6 7h12v13a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7Z"/></svg>
</button>
</form>
</span>
@endif
</div>
</td>
</tr>
+49 -14
View File
@@ -20,9 +20,15 @@
</div>
</form>
<a href="{{ route('suppliers.create') }}" class="w-full md:w-auto text-center px-5 py-3 text-sm font-bold text-neon-lime dark:text-neutral-900 bg-neutral-950 dark:bg-neon-lime rounded-lg hover:bg-neutral-900/80 hover:dark:bg-[#b3e600] transition-colors uppercase tracking-wide">
+ Nuevo Proveedor
</a>
@if(auth()->user()->role === 'admin')
<a href="{{ route('suppliers.create') }}" class="w-full md:w-auto text-center px-5 py-3 text-sm font-bold text-neon-lime dark:text-neutral-900 bg-neutral-950 dark:bg-neon-lime rounded-lg hover:bg-neutral-900/80 hover:dark:bg-[#b3e600] transition-colors uppercase tracking-wide">
+ Nuevo Proveedor
</a>
@else
<span title="Acceso denegado" class="w-full md:w-auto text-center px-5 py-3 text-sm font-bold text-gray-500 dark:text-neutral-500 bg-gray-300 dark:bg-neutral-800 border border-gray-400 dark:border-neutral-700 rounded-lg cursor-not-allowed pointer-events-none select-none uppercase tracking-wide">
+ Nuevo Proveedor
</span>
@endif
</div>
<div class="relative overflow-x-auto shadow-md sm:rounded-lg border border-gray-400 dark:border-neutral-800 w-full">
@@ -60,25 +66,54 @@
</span>
</td>
<td class="px-6 py-4 text-right flex items-center justify-end gap-3">
<!-- Ver Más -->
<a href="{{ route('suppliers.show', $supplier) }}" title="Ver Más" class="font-semibold p-2 text-green-700 dark:text-green-400 border-2 border-green-700 rounded-lg hover:bg-green-700 dark:hover:bg-green-600 dark:hover:border-green-600 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-width="2" d="M21 12c0 1.2-4.03 6-9 6s-9-4.8-9-6c0-1.2 4.03-6 9-6s9 4.8 9 6Z"/>
<path stroke="currentColor" stroke-width="2" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/>
</svg>
</a>
<!-- Hacer Pedido -->
<a href="{{ route('suppliers.order', $supplier) }}" title="Hacer Pedido" class="font-semibold p-2 text-green-600 dark:text-green-400 border-2 border-green-600 rounded-lg hover:bg-green-600 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="currentColor" viewBox="0 0 24 24">
<path fill-rule="evenodd" d="M12 4a8 8 0 0 0-6.895 12.06l.569 1.167-1.076 2.872 3.018-1.045 1.18.528A8 8 0 1 0 12 4Zm5.1 10.3c-.28-.14-1.65-.815-1.905-.909-.255-.094-.44-.14-.625.14-.185.28-.72 1.054-.882 1.265-.162.21-.324.234-.604.094-.28-.14-1.178-.435-2.246-1.385-.83-.739-1.39-1.65-1.552-1.93-.162-.28-.017-.432.123-.57.126-.125.28-.328.42-.493.14-.165.187-.28.28-.468.093-.188.047-.35-.023-.491-.07-.14-.625-1.508-.857-2.064-.225-.544-.454-.47-.625-.478-.162-.008-.348-.01-.533-.01-.185 0-.485.07-.74.35-.255.28-.972.95-.972 2.316 0 1.366.995 2.687 1.133 2.874.138.188 1.956 2.986 4.74 4.153.662.277 1.18.442 1.583.565.665.204 1.27.175 1.745.106.531-.077 1.65-.674 1.882-1.325.232-.651.232-1.21.162-1.325-.07-.116-.255-.186-.535-.326Z" clip-rule="evenodd"/>
</svg>
</a>
<a href="{{ route('suppliers.edit', $supplier) }}" title="Editar" class="font-semibold p-2 text-blue-700 dark:text-blue-400 border-2 border-blue-700 rounded-lg hover:bg-blue-700 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('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">
<!-- Editar -->
@if(auth()->user()->role === 'admin')
<a href="{{ route('suppliers.edit', $supplier) }}" 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>
@else
<span title="Acceso denegado" class="font-semibold p-2 text-gray-400 dark:text-neutral-500 border-2 border-gray-300 dark:border-neutral-700 rounded-lg bg-gray-100 dark:bg-neutral-800/30 cursor-not-allowed pointer-events-none select-none">
<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>
</span>
@endif
<!-- Eliminar -->
@if(auth()->user()->role === 'admin')
<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">
<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="M5 7h14m-9 3v8m4-8v8M10 3h4a1 1 0 0 1 1 1v3H9V4a1 1 0 0 1 1-1ZM6 7h12v13a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7Z"/>
</svg>
</button>
</form>
@else
<span title="Acceso denegado" class="p-2 font-bold dark:font-medium text-gray-400 dark:text-neutral-500 border-2 border-gray-300 dark:border-neutral-700 rounded-lg bg-gray-100 dark:bg-neutral-800/30 cursor-not-allowed pointer-events-none select-none">
<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="M5 7h14m-9 3v8m4-8v8M10 3h4a1 1 0 0 1 1 1v3H9V4a1 1 0 0 1 1-1ZM6 7h12v13a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V7Z"/>
</svg>
</button>
</form>
</span>
@endif
</td>
</tr>
+67
View File
@@ -0,0 +1,67 @@
<x-layout title="Lauck - Detalle de Proveedor">
<x-section-header subtitle="Proveedores" title="Detalle de " highlight="Proveedor" />
<div class="w-full max-w-4xl mx-auto bg-gray-200 dark:bg-panel-bg border border-neutral-400 dark:border-neutral-800 rounded-xl p-8 shadow-lg relative">
<div class="flex justify-between items-start mb-6">
<div>
<h3 class="text-2xl font-bold text-neutral-900 dark:text-white">{{ $supplier->name }}</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">Registrado el: {{ $supplier->created_at->format('d/m/Y H:i') }}</p>
</div>
<div>
<a href="{{ route('suppliers.index') }}" class="text-sm font-bold text-gray-500 hover:text-white transition-colors">Volver al listado</a>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 bg-gray-300 dark:bg-neutral-800 p-6 rounded-lg border border-gray-400 dark:border-neutral-700">
<div>
<p class="text-xs text-gray-500 uppercase tracking-wider mb-1">Nombre / Razón Social</p>
<p class="text-lg font-bold text-neutral-900 dark:text-white">{{ $supplier->name }}</p>
</div>
<div>
<p class="text-xs text-gray-500 uppercase tracking-wider mb-1">Teléfono / WhatsApp</p>
@if($supplier->phone)
<p class="text-lg font-bold text-neutral-900 dark:text-white font-mono">{{ $supplier->phone }}</p>
@else
<p class="text-lg text-gray-500 italic">No registrado</p>
@endif
</div>
<div>
<p class="text-xs text-gray-500 uppercase tracking-wider mb-1">Correo Electrónico</p>
@if($supplier->email)
<p class="text-base font-semibold text-neutral-900 dark:text-white font-mono">{{ $supplier->email }}</p>
@else
<p class="text-base text-gray-500 italic">No registrado</p>
@endif
</div>
<div>
<p class="text-xs text-gray-500 uppercase tracking-wider mb-1">Dirección</p>
@if($supplier->address)
<p class="text-base font-semibold text-neutral-900 dark:text-white">{{ $supplier->address }}</p>
@else
<p class="text-base text-gray-500 italic">No registrada</p>
@endif
</div>
</div>
<div class="mt-8 flex justify-end gap-4">
@if(auth()->user()->role === 'admin')
<a href="{{ route('suppliers.order', $supplier) }}" class="px-6 py-2.5 text-sm font-bold text-white bg-green-600 hover:bg-green-700 rounded-lg transition-colors">
Hacer Pedido
</a>
<a href="{{ route('suppliers.edit', $supplier) }}" class="px-6 py-2.5 text-sm font-bold text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors">
Editar Proveedor
</a>
@else
<span title="Acceso denegado" class="px-6 py-2.5 text-sm font-bold text-gray-400 bg-gray-300 dark:bg-neutral-800 border border-gray-400 dark:border-neutral-700 rounded-lg cursor-not-allowed pointer-events-none">
Hacer Pedido
</span>
<span title="Acceso denegado" class="px-6 py-2.5 text-sm font-bold text-gray-400 bg-gray-300 dark:bg-neutral-800 border border-gray-400 dark:border-neutral-700 rounded-lg cursor-not-allowed pointer-events-none">
Editar Proveedor
</span>
@endif
</div>
</div>
</x-layout>
+7
View File
@@ -17,6 +17,7 @@ use App\Http\Controllers\PasswordResetLinkController;
use App\Http\Controllers\NewPasswordController;
use App\Http\Controllers\TagController;
use App\Http\Controllers\Admin\BackupController;
use App\Http\Controllers\Admin\EmployeeController;
use App\Models\Product;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
@@ -90,5 +91,11 @@ Route::middleware(['auth'])->group(function () {
Route::post('/backups/{filename}/restore', [BackupController::class, 'restore'])->name('backups.restore');
Route::get('/backups/{filename}/download', [BackupController::class, 'download'])->name('backups.download');
Route::delete('/backups/{filename}', [BackupController::class, 'destroy'])->name('backups.destroy');
// Gestión de Empleados
Route::get('/employees', [EmployeeController::class, 'index'])->name('employees.index');
Route::post('/employees', [EmployeeController::class, 'store'])->name('employees.store');
Route::put('/employees/{user}', [EmployeeController::class, 'update'])->name('employees.update');
Route::delete('/employees/{user}', [EmployeeController::class, 'destroy'])->name('employees.destroy');
});
});