Add rank management feature with CRUD and UI
Introduces a new rank management system including the Rank model, migrations for the ranks table and user IP address, Livewire components for managing and displaying ranks, and related Blade views. Updates navigation and routes to include the new 'Gestión Rangos' section, and enhances the ShareCodeComponent with user data. Also adds a reusable input field component for forms.
This commit is contained in:
@@ -42,6 +42,10 @@ class MenuController extends Controller
|
||||
{
|
||||
return view('menu/tarifas');
|
||||
}
|
||||
public function showgestionrangos()
|
||||
{
|
||||
return view('menu/gestion-rangos');
|
||||
}
|
||||
public function showclientes()
|
||||
{
|
||||
return view('menu/clientes');
|
||||
|
||||
@@ -7,6 +7,13 @@ use Livewire\Component;
|
||||
|
||||
class ShareCodeComponent extends Component
|
||||
{
|
||||
public $user;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->user = Auth::user();
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$referrals = Auth::user()->children()
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use App\Models\Rank;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
|
||||
class ShowGestionRangos extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
public $ranks;
|
||||
public $nombre, $cashback, $bono_equipo, $tope_bono_equipo;
|
||||
public $compra_personal_min, $directos_activos_min, $niveles_min, $volumen_equipo_min, $volumen_por_nivel_min;
|
||||
public $rank_id;
|
||||
|
||||
public $logo;
|
||||
public $logo_url;
|
||||
|
||||
public $modal = false;
|
||||
|
||||
protected $rules = [
|
||||
'nombre' => 'required|string|max:100',
|
||||
'cashback' => 'required|numeric|min:0',
|
||||
'bono_equipo' => 'required|numeric|min:0',
|
||||
'tope_bono_equipo' => 'required|numeric|min:0',
|
||||
'compra_personal_min' => 'required|numeric|min:0',
|
||||
'directos_activos_min' => 'required|integer|min:0',
|
||||
'niveles_min' => 'required|integer|min:0',
|
||||
'volumen_equipo_min' => 'required|numeric|min:0',
|
||||
'volumen_por_nivel_min' => 'required|numeric|min:0',
|
||||
'logo' => 'nullable|image|max:2048',
|
||||
];
|
||||
|
||||
protected $messages = [
|
||||
'nombre.required' => 'El nombre del rango es obligatorio.',
|
||||
'nombre.string' => 'El nombre debe ser una cadena de texto válida.',
|
||||
'nombre.max' => 'El nombre no puede tener más de 100 caracteres.',
|
||||
|
||||
'cashback.required' => 'El porcentaje de cashback es obligatorio.',
|
||||
'cashback.numeric' => 'El cashback debe ser un valor numérico.',
|
||||
'cashback.min' => 'El cashback no puede ser menor a 0.',
|
||||
|
||||
'bono_equipo.required' => 'El porcentaje de bono de equipo es obligatorio.',
|
||||
'bono_equipo.numeric' => 'El bono de equipo debe ser un valor numérico.',
|
||||
'bono_equipo.min' => 'El bono de equipo no puede ser menor a 0.',
|
||||
|
||||
'tope_bono_equipo.required' => 'El tope del bono de equipo es obligatorio.',
|
||||
'tope_bono_equipo.numeric' => 'El tope del bono de equipo debe ser un valor numérico.',
|
||||
'tope_bono_equipo.min' => 'El tope del bono de equipo no puede ser menor a 0.',
|
||||
|
||||
'compra_personal_min.required' => 'La compra personal mínima es obligatoria.',
|
||||
'compra_personal_min.numeric' => 'La compra personal mínima debe ser un número.',
|
||||
'compra_personal_min.min' => 'La compra personal mínima no puede ser menor a 0.',
|
||||
|
||||
'directos_activos_min.required' => 'El número de directos activos mínimos es obligatorio.',
|
||||
'directos_activos_min.integer' => 'Los directos activos deben ser un número entero.',
|
||||
'directos_activos_min.min' => 'Los directos activos no pueden ser menores a 0.',
|
||||
|
||||
'niveles_min.required' => 'El número de niveles mínimos es obligatorio.',
|
||||
'niveles_min.integer' => 'Los niveles mínimos deben ser un número entero.',
|
||||
'niveles_min.min' => 'Los niveles mínimos no pueden ser menores a 0.',
|
||||
|
||||
'volumen_equipo_min.required' => 'El volumen mínimo de equipo es obligatorio.',
|
||||
'volumen_equipo_min.numeric' => 'El volumen mínimo de equipo debe ser un número.',
|
||||
'volumen_equipo_min.min' => 'El volumen mínimo de equipo no puede ser menor a 0.',
|
||||
|
||||
'volumen_por_nivel_min.required' => 'El volumen por nivel mínimo es obligatorio.',
|
||||
'volumen_por_nivel_min.numeric' => 'El volumen por nivel mínimo debe ser un número.',
|
||||
'volumen_por_nivel_min.min' => 'El volumen por nivel mínimo no puede ser menor a 0.',
|
||||
|
||||
'logo.image' => 'El archivo del logo debe ser una imagen válida (JPG, PNG, etc.).',
|
||||
'logo.max' => 'El logo no puede superar los 2 MB.',
|
||||
];
|
||||
|
||||
public function render()
|
||||
{
|
||||
$this->ranks = Rank::orderBy('volumen_equipo_min')->get();
|
||||
|
||||
return view('livewire.show-gestion-rangos');
|
||||
}
|
||||
|
||||
public function crear()
|
||||
{
|
||||
$this->resetExcept('ranks');
|
||||
$this->modal = true;
|
||||
}
|
||||
|
||||
public function editar($id)
|
||||
{
|
||||
$rank = Rank::findOrFail($id);
|
||||
$this->fill($rank->toArray());
|
||||
$this->rank_id = $rank->id;
|
||||
$this->modal = true;
|
||||
}
|
||||
|
||||
public function guardar()
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
$rutaLogo = null;
|
||||
|
||||
if ($this->logo) {
|
||||
$rutaLogo = $this->logo->store('photos');
|
||||
}
|
||||
|
||||
Rank::updateOrCreate(
|
||||
['id' => $this->rank_id],
|
||||
[
|
||||
'nombre' => $this->nombre,
|
||||
'cashback' => $this->cashback,
|
||||
'bono_equipo' => $this->bono_equipo,
|
||||
'tope_bono_equipo' => $this->tope_bono_equipo,
|
||||
'compra_personal_min' => $this->compra_personal_min,
|
||||
'directos_activos_min' => $this->directos_activos_min,
|
||||
'niveles_min' => $this->niveles_min,
|
||||
'volumen_equipo_min' => $this->volumen_equipo_min,
|
||||
'volumen_por_nivel_min' => $this->volumen_por_nivel_min,
|
||||
'logo' => $rutaLogo ?? $this->logo_url,
|
||||
]
|
||||
);
|
||||
|
||||
$this->modal = false;
|
||||
$this->resetExcept('ranks');
|
||||
}
|
||||
|
||||
public function eliminar($id)
|
||||
{
|
||||
Rank::findOrFail($id)->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class ShowRangos extends Component
|
||||
{
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.show-rangos');
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class ShowSavingIp extends Component
|
||||
{
|
||||
public $ip_address;
|
||||
public $ip_address;
|
||||
|
||||
protected $listeners = ['setIpAddress'];
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Rank extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'nombre',
|
||||
'cashback',
|
||||
'bono_equipo',
|
||||
'tope_bono_equipo',
|
||||
'compra_personal_min',
|
||||
'directos_activos_min',
|
||||
'niveles_min',
|
||||
'volumen_equipo_min',
|
||||
'volumen_por_nivel_min',
|
||||
'logo'
|
||||
];
|
||||
|
||||
public function users()
|
||||
{
|
||||
return $this->hasMany(User::class, 'rank_id');
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -23,11 +23,11 @@ class User extends Authenticatable
|
||||
'rol_id',
|
||||
'subvendedor_id',
|
||||
'subvendedor',
|
||||
'ip_address',
|
||||
|
||||
'referral_code',
|
||||
'parent_id',
|
||||
|
||||
|
||||
'rank_id',
|
||||
];
|
||||
|
||||
@@ -126,10 +126,10 @@ class User extends Authenticatable
|
||||
return $this->hasMany(User::class, 'parent_id')->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
// public function rank()
|
||||
// {
|
||||
// return $this->belongsTo(Rank::class);
|
||||
// }
|
||||
public function rank()
|
||||
{
|
||||
return $this->belongsTo(Rank::class, 'rank_id');
|
||||
}
|
||||
|
||||
protected static function boot()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class InputField extends Component
|
||||
{
|
||||
public $label;
|
||||
public $model;
|
||||
public $type;
|
||||
public $step;
|
||||
|
||||
public function __construct($label, $model, $type = 'text', $step = null)
|
||||
{
|
||||
$this->label = $label;
|
||||
$this->model = $model;
|
||||
$this->type = $type;
|
||||
$this->step = $step;
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('components.input-field');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->string('ip_address')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('ip_address');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::create('ranks', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
||||
$table->string('nombre'); // Bronce, Plata, Oro, Diamante
|
||||
|
||||
$table->string('logo')->nullable();
|
||||
|
||||
$table->decimal('cashback', 5, 2)->default(0); // % de cashback personal
|
||||
$table->decimal('bono_equipo', 5, 2)->default(0); // % de bono sobre ventas del equipo
|
||||
$table->integer('tope_bono_equipo')->default(0); // límite máximo del bono
|
||||
|
||||
// Requisitos
|
||||
$table->integer('compra_personal_min')->default(0);
|
||||
$table->integer('directos_activos_min')->default(0);
|
||||
$table->integer('niveles_min')->default(0);
|
||||
$table->integer('volumen_equipo_min')->default(0);
|
||||
|
||||
// Valor mínimo por nivel (ej: cada nivel >= 300.000)
|
||||
$table->integer('volumen_por_nivel_min')->default(0);
|
||||
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('ranks');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
@props(['label', 'model', 'type' => 'text', 'step' => null])
|
||||
|
||||
<div class="flex flex-col space-y-1">
|
||||
<label class="block text-sm font-medium text-gray-700">{{ $label }}</label>
|
||||
|
||||
<input
|
||||
type="{{ $type }}"
|
||||
@if($step) step="{{ $step }}" @endif
|
||||
wire:model="{{ $model }}"
|
||||
class="border-gray-300 rounded-lg focus:ring-indigo-500 focus:border-indigo-500 w-full text-gray-800"
|
||||
/>
|
||||
|
||||
@error($model)
|
||||
<span class="text-xs text-red-500">{{ $message }}</span>
|
||||
@enderror
|
||||
</div>
|
||||
@@ -182,6 +182,12 @@
|
||||
</a>
|
||||
|
||||
@if (auth()->user()->rol->nombre == 'super')
|
||||
<a href="{{ route('gestion-rangos') }}" class="flex items-center gap-2 py-2 px-3 rounded text-white text-left {{ request()->routeIs('gestion-rangos') ? 'bg-white/20' : 'hover:bg-white/10' }}">
|
||||
<svg class="w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14" />
|
||||
</svg>
|
||||
<span>Gestión Rangos</span>
|
||||
</a>
|
||||
<a href="{{ route('tarifas') }}" class="flex items-center gap-2 py-2 px-3 rounded text-white text-left {{ request()->routeIs('tarifas') ? 'bg-white/20' : 'hover:bg-white/10' }}">
|
||||
<svg class="w-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14" />
|
||||
|
||||
@@ -1,3 +1,88 @@
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 my-12">
|
||||
<div class="flex items-center justify-center gap-4">
|
||||
<div>
|
||||
<img src="https://cdn.imgbin.com/11/2/10/imgbin-metal-bronze-copper-platinum-bronzing-wRAqc4xeeYJw5RmknN6xuYZND.jpg"
|
||||
alt="Rango Diamante" class="w-24 h-24 rounded-full object-cover">
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center justify-center gap-1 text-center">
|
||||
<p class="text-lg">Rango actual</p>
|
||||
<p class="text-3xl font-semibold">Bronce</p>
|
||||
<div class="w-48 bg-gray-700 rounded-full h-2 mt-1">
|
||||
<div class="bg-blue-500 h-2 rounded-full transition-all duration-1000 ease-linear"
|
||||
style="width: {{ (4 / 5) * 100 }}%">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="py-4 text-center px-4 text-sm">Te faltan $120,000 para llegar a Plata</p>
|
||||
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<!-- Tarjeta 1 -->
|
||||
<div class="flex flex-col items-center justify-center p-6 rounded-xl shadow-md transition-transform duration-300">
|
||||
<div class="flex gap-2 items-start">
|
||||
<svg class="w-10 pt-1" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M2.25 3h1.386c.51 0 .955.343 1.087.835l.383 1.437M7.5 14.25a3 3 0 0 0-3 3h15.75m-12.75-3h11.218c1.121-2.3 2.1-4.684 2.924-7.138a60.114 60.114 0 0 0-16.536-1.84M7.5 14.25 5.106 5.272M6 20.25a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Zm12.75 0a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z" />
|
||||
</svg>
|
||||
<p class="text-sm">Compra personal</p>
|
||||
</div>
|
||||
<p class="text-lg font-semibold mt-2">$70,000</p>
|
||||
</div>
|
||||
|
||||
<!-- Tarjeta 2 -->
|
||||
<div class="flex flex-col items-center justify-center p-6 rounded-xl shadow-md transition-transform duration-300">
|
||||
<div class="flex gap-2 items-start">
|
||||
<svg class="w-10 pt-1" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 18.75a60.07 60.07 0 0 1 15.797 2.101c.727.198 1.453-.342 1.453-1.096V18.75M3.75 4.5v.75A.75.75 0 0 1 3 6h-.75m0 0v-.375c0-.621.504-1.125 1.125-1.125H20.25M2.25 6v9m18-10.5v.75c0 .414.336.75.75.75h.75m-1.5-1.5h.375c.621 0 1.125.504 1.125 1.125v9.75c0 .621-.504 1.125-1.125 1.125h-.375m1.5-1.5H21a.75.75 0 0 0-.75.75v.75m0 0H3.75m0 0h-.375a1.125 1.125 0 0 1-1.125-1.125V15m1.5 1.5v-.75A.75.75 0 0 0 3 15h-.75M15 10.5a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm3 0h.008v.008H18V10.5Zm-12 0h.008v.008H6V10.5Z" />
|
||||
</svg>
|
||||
|
||||
<p class="text-sm">Cashback actual</p>
|
||||
</div>
|
||||
<p class="text-lg font-semibold mt-2">$25,000</p>
|
||||
</div>
|
||||
|
||||
<!-- Tarjeta 3 -->
|
||||
<div class="flex flex-col items-center justify-center p-6 rounded-xl shadow-md transition-transform duration-300">
|
||||
<div class="flex gap-2 items-start">
|
||||
<svg class="w-10 pt-1" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z" />
|
||||
</svg>
|
||||
|
||||
<p class="text-sm">Volumen de equipo</p>
|
||||
</div>
|
||||
<p class="text-lg font-semibold mt-2">$1,250,000</p>
|
||||
</div>
|
||||
|
||||
<!-- Tarjeta 4 -->
|
||||
<div class="flex flex-col items-center justify-center p-6 rounded-xl shadow-md transition-transform duration-300">
|
||||
<div class="flex gap-2 items-start">
|
||||
<svg class="w-10 pt-1" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<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>
|
||||
<p class="text-sm">Directos activos</p>
|
||||
</div>
|
||||
<p class="text-lg font-semibold mt-2">4</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 my-12">
|
||||
<div>
|
||||
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
<div
|
||||
x-data="{
|
||||
modal : @entangle('modal'),
|
||||
}"
|
||||
class="p-6 bg-gray-50 min-h-screen">
|
||||
<div class="grid grid-cols-1 text-center sm:flex justify-between my-2 items-center text-sm">
|
||||
<div class="grid grid-cols-1 sm:flex items-center gap-4">
|
||||
<div class="flex text-sm">
|
||||
<x-icon-arrow-right />
|
||||
<h1>Rangos</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4">
|
||||
<button wire:click="crear" class="justify-center gap-2 flex px-4 py-2 rounded-lg cursor-pointer text-white bg-indigo-600 hover:bg-indigo-700 mb-3 md:mb-0">
|
||||
Nuevo Rango
|
||||
<svg class="w-3" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<path d="M480,224H288V32c0-17.673-14.327-32-32-32s-32,14.327-32,32v192H32c-17.673,0-32,14.327-32,32s14.327,32,32,32h192v192 c0,17.673,14.327,32,32,32s32-14.327,32-32V288h192c17.673,0,32-14.327,32-32S497.673,224,480,224z" fill="#ffffff" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<x-primary-table>
|
||||
<x-slot name="thead">
|
||||
<tr class="text-left">
|
||||
<th class="px-4 py-3">Logo</th>
|
||||
<th class="px-4 py-3">Nombre</th>
|
||||
<th class="px-4 py-3">Cashback</th>
|
||||
<th class="px-4 py-3">Bono Equipo</th>
|
||||
<th class="px-4 py-3">Tope</th>
|
||||
<th class="px-4 py-3">Compra Mínima</th>
|
||||
<th class="px-4 py-3">Directos</th>
|
||||
<th class="px-4 py-3">Niveles</th>
|
||||
<th class="px-4 py-3">Volumen</th>
|
||||
<th class="px-4 py-3">Por Nivel</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</x-slot>
|
||||
<x-slot name="tbody">
|
||||
@forelse ($ranks as $rank)
|
||||
<tr class="border-b hover:bg-gray-50 transition text-left" x-data="{ openOption: false }">
|
||||
<td class="px-4 py-3">
|
||||
@if($rank->logo)
|
||||
<img src="{{ asset('storage/' . $rank->logo) }}" alt="{{ $rank->nombre }}" class="h-10 w-10 rounded-full object-cover">
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-3">{{ $rank->cashback }}%</td>
|
||||
<td class="px-4 py-3">{{ $rank->cashback }}%</td>
|
||||
<td class="px-4 py-3">{{ $rank->bono_equipo }}%</td>
|
||||
<td class="px-4 py-3">${{ number_format($rank->tope_bono_equipo) }}</td>
|
||||
<td class="px-4 py-3">${{ number_format($rank->compra_personal_min) }}</td>
|
||||
<td class="px-4 py-3">{{ $rank->directos_activos_min }}</td>
|
||||
<td class="px-4 py-3">{{ $rank->niveles_min }}</td>
|
||||
<td class="px-4 py-3">${{ number_format($rank->volumen_equipo_min) }}</td>
|
||||
<td class="px-4 py-3">${{ number_format($rank->volumen_por_nivel_min) }}</td>
|
||||
<td>
|
||||
<button x-on:click="openOption=!openOption">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 cursor-pointer">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM12.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM18.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div x-cloak x-show="openOption" @click.away="openOption = false" class="sm:absolute bg-white text-center w-[8rem] rounded-[1rem] border-b border-gray-200 shadow-lg right-10">
|
||||
<button x-on:click="openOption=false" wire:click="editar({{ $rank->id }})" class="text-gray-500 w-full py-1 hover:bg-gray-200 border-b border-gray-300 hover:rounded-t-[1rem]">Editar</button>
|
||||
<button x-on:click="openOption=false" wire:click="eliminar({{ $rank->id }})" class="text-white w-full py-1 bg-red-400 rounded-b-[1rem] hover:bg-red-600 hover:rounded-b-[1rem]">Eliminar</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="10" class="text-center py-6 text-gray-500">No hay rangos registrados aún.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</x-slot>
|
||||
<x-slot name="tlink">
|
||||
</x-slot>
|
||||
</x-primary-table>
|
||||
|
||||
{{-- Modal --}}
|
||||
<div x-cloak x-show="modal" x-transition:enter="transition duration-350"
|
||||
x-transition:enter-start="opacity-0 scale-100" x-transition:enter-end="opacity-100 scale-100"
|
||||
x-transition:leave="transition duration-350" x-transition:leave-start="opacity-100 scale-100"
|
||||
x-transition:leave-end="opacity-0 scale-100" class="fixed inset-0 z-50 backdrop-blur-sm bg-black/20">
|
||||
<div class="grid h-full place-items-center overflow-y-auto ">
|
||||
|
||||
<div class="p-6 bg-white rounded-xl">
|
||||
<h2 class="text-lg font-bold mb-4">{{ $rank_id ? 'Editar Rango' : 'Nuevo Rango' }}</h2>
|
||||
|
||||
<form wire:submit.prevent="guardar" class="grid grid-cols-2 gap-4">
|
||||
<div class="col-span-2">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">Logo del Rango</label>
|
||||
<input type="file" wire:model="logo"
|
||||
class="block w-full text-sm text-gray-700 border border-gray-300 rounded-lg cursor-pointer focus:ring-indigo-500 focus:border-indigo-500" />
|
||||
@error('logo') <span class="text-xs text-red-500">{{ $message }}</span> @enderror
|
||||
|
||||
{{-- Preview --}}
|
||||
@if ($logo)
|
||||
<div class="mt-3">
|
||||
<img src="{{ $logo->temporaryUrl() }}" class="h-20 rounded-lg shadow-md" accept="image/*">
|
||||
</div>
|
||||
@elseif ($logo_url)
|
||||
<div class="mt-3">
|
||||
<img src="{{ asset('storage/' . $logo_url) }}" class="h-20 rounded-lg shadow-md" accept="image/*">
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<x-input-field label="Nombre" model="nombre" />
|
||||
<x-input-field label="Cashback (%)" model="cashback" type="number" step="0.01" />
|
||||
<x-input-field label="Bono de Equipo (%)" model="bono_equipo" type="number" step="0.01" />
|
||||
<x-input-field label="Tope del Bono" model="tope_bono_equipo" type="number" />
|
||||
<x-input-field label="Compra Personal Mínima" model="compra_personal_min" type="number" />
|
||||
<x-input-field label="Directos Activos Mínimos" model="directos_activos_min" type="number" />
|
||||
<x-input-field label="Niveles Mínimos" model="niveles_min" type="number" />
|
||||
<x-input-field label="Volumen de Equipo Mínimo" model="volumen_equipo_min" type="number" />
|
||||
<x-input-field label="Volumen por Nivel" model="volumen_por_nivel_min" type="number" />
|
||||
</form>
|
||||
|
||||
<div class="flex justify-end mt-6 space-x-3">
|
||||
<button wire:click="$set('modal', false)" type="button" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-4 py-2 rounded-lg">
|
||||
Cancelar
|
||||
</button>
|
||||
<button wire:click="guardar" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg">
|
||||
Guardar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
<div>
|
||||
{{-- The whole world belongs to you. --}}
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
<x-app-layout>
|
||||
<livewire:show-gestion-rangos />
|
||||
</x-app-layout>
|
||||
@@ -71,6 +71,7 @@ Route::middleware(["auth", "solo_usuario_administrador"])->group(function () {
|
||||
Route::any('/log-historial', [MenuController::class, 'showLogHistorial'])->name('logHistorial');
|
||||
Route::get('/utilidades', [MenuController::class, 'showutilidad'])->name('utilidades');
|
||||
Route::get('/mantenimiento', [MenuController::class, 'mantenimiento'])->name('mantenimiento');
|
||||
Route::get('/rank-management', [MenuController::class, 'showgestionrangos'])->name('gestion-rangos');
|
||||
});
|
||||
|
||||
Route::post('/registrar-accion-compra', [MenuController::class, 'click_compra'])->name('click_compra');
|
||||
|
||||
Reference in New Issue
Block a user